-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathgpio.erl
532 lines (507 loc) · 23.4 KB
/
gpio.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
%
% This file is part of AtomVM.
%
% Copyright 2018-2023 Davide Bettio <[email protected]>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%
%%-----------------------------------------------------------------------------
%% @doc GPIO driver module
%%
%% This module provides functions for interacting with micro-controller GPIO
%% (General Purpose Input and Output) pins.
%%
%% Note: `-type pin()' used in this driver refers to a pin number on Espressif
%% chips and normal Raspberry Pi Pico pins, or a tuple {GPIO_BANK, PIN} for STM32
%% chips and the "extra" GPIOs available on the Pico-W.
%% @end
%%-----------------------------------------------------------------------------
-module(gpio).
-export([
start/0,
open/0,
read/2,
set_direction/3,
set_level/3,
set_int/3, set_int/4,
remove_int/2,
stop/0,
close/1
]).
-export([
init/1,
deinit/1,
set_pin_mode/2,
set_pin_pull/2,
hold_en/1,
hold_dis/1,
deep_sleep_hold_en/0,
deep_sleep_hold_dis/0,
digital_write/2,
digital_read/1,
attach_interrupt/2,
detach_interrupt/1
]).
-type gpio() :: pid().
%% This is the pid returned by `gpio:start/0'.
-type pin() :: non_neg_integer() | pin_tuple().
%% The pin definition for ESP32 and PR2040 is a non-negative integer. A tuple is used on the STM32 platform and for the extra "WL" pins on the Pico-W.
-type pin_tuple() :: {gpio_bank(), 0..15}.
%% A pin parameter on STM32 is a tuple consisting of a GPIO bank and pin number, also used on the Pico-W for the extra "WL" pins `0..2'.
-type gpio_bank() :: a | b | c | d | e | f | g | h | i | j | k | wl.
%% STM32 gpio banks vary by board, some only break out `a' thru `h'. The extra "WL" pins on Pico-W use bank `wl'.
-type direction() :: input | output | output_od | mode_config().
%% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain.
%% On the STM32 platform pull mode and output_speed must be set at the same time as direction. See @type mode_config()
-type mode_config() :: {direction(), pull()} | {output, pull(), output_speed()}.
%% Extended mode configuration options on STM32. Default pull() is `floating', default output_speed() is `mhz_2' if options are omitted.
-type pull() :: up | down | up_down | floating.
%% Internal resistor pull mode. STM32 does not support `up_down'.
-type output_speed() :: mhz_2 | mhz_25 | mhz_50 | mhz_100.
%% Output clock speed. Only available on STM32, default is `mhz_2'.
-type low_level() :: low | 0.
-type high_level() :: high | 1.
-type level() :: low_level() | high_level().
%% Valid pin levels can be atom or binary representation.
-type trigger() :: none | rising | falling | both | low | high.
%% Event type that will trigger a `gpio_interrupt'. STM32 only supports `rising', `falling', or `both'.
%%-----------------------------------------------------------------------------
%% @returns Pid | {error, Reason}
%% @doc Start the GPIO driver port
%%
%% Returns the pid of the active GPIO port driver, otherwise the GPIO
%% port driver will be stared and registered as `gpio'. The use of
%% `gpio:open/0' or `gpio:start/0' is required before using any functions
%% that require a GPIO pid as a parameter.
%%
%% Not currently available on rp2040 (Pico) port, use nif functions.
%% @end
%%-----------------------------------------------------------------------------
-spec start() -> gpio() | {error, Reason :: atom()} | error.
start() ->
case whereis(gpio) of
undefined ->
open();
GPIO ->
GPIO
end.
%%-----------------------------------------------------------------------------
%% @returns Pid | {error, Reason}
%% @doc Start the GPIO driver port
%%
%% The GPIO port driver will be stared and registered as `gpio'. If the
%% port has already been started through the `gpio:open/0' or
%% `gpio:start/0' the command will fail. The use of `gpio:open/0' or
%% `gpio:start/0' is required before using any functions that require a
%% GPIO pid as a parameter.
%%
%% Not currently available on rp2040 (Pico) port, use nif functions.
%% @end
%%-----------------------------------------------------------------------------
-spec open() -> gpio() | {error, Reason :: atom()} | error.
open() ->
open_port({spawn, "gpio"}, []).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from gpio:start/0
%% @returns ok | {error, Reason}
%% @doc Stop the GPIO interrupt port
%%
%% This function disables any interrupts that are set, stops
%% the listening port, and frees all of its resources.
%%
%% Not currently available on rp2040 (Pico) port, use nif functions.
%% @end
%%-----------------------------------------------------------------------------
-spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error.
close(GPIO) ->
port:call(GPIO, {close}).
%%-----------------------------------------------------------------------------
%% @returns ok | {error, Reason}
%% @doc Stop the GPIO interrupt port
%%
%% This function disables any interrupts that are set, stops
%% the listening port, and frees all of its resources.
%%
%% Not currently available on rp2040 (Pico) port, use nif functions.
%% @end
%%-----------------------------------------------------------------------------
-spec stop() -> ok | {error, Reason :: atom()} | error.
stop() ->
close(whereis(gpio)).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from gpio:start/0
%% @param Pin number of the pin to read
%% @returns high | low | {error, Reason}
%% @doc Read the digital state of a GPIO pin
%%
%% Read if an input pin state is `high' or `low'.
%% Warning: if the pin was not previously configured as an input using
%% `gpio:set_direction/3' it will always read as low.
%%
%% Not supported on rp2040 (Pico), use `gpio:digital_read/1' instead.
%% @end
%%-----------------------------------------------------------------------------
-spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error.
read(GPIO, Pin) ->
port:call(GPIO, {read, Pin}).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from `gpio:start/0'
%% @param Pin number of the pin to configure
%% @param Direction is `input', `output', or `output_od'
%% @returns ok | {error, Reason}
%% @doc Set the operational mode of a pin
%%
%% Pins can be used for input, output, or output with open drain.
%%
%% The STM32 platform has extended direction mode configuration options.
%% See @type mode_config() for details. All configuration must be set using
%% `set_direction/3', including pull() mode, unlike the ESP32 which has a
%% separate function (`set_pin_pull/2'). If you are configuring multiple pins
%% on the same GPIO `bank' with the same options the pins may be configured all
%% at the same time by giving a list of pin numbers in the pin tuple.
%%
%% Example to configure all of the leds on a Nucleo board:
%%
%% <pre>
%% gpio:set_direction({b, [0,7,14], output)
%% </pre>
%%
%% Not supported on rp2040 (Pico), use `gpio:set_pin_mode/2' instead.
%% @end
%%-----------------------------------------------------------------------------
-spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) ->
ok | {error, Reason :: atom()} | error.
set_direction(GPIO, Pin, Direction) ->
port:call(GPIO, {set_direction, Pin, Direction}).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from `gpio:start/0'
%% @param Pin number of the pin to write
%% @param Level the desired output level to set
%% @returns ok | {error, Reason}
%% @doc Set GPIO digital output level
%%
%% Set a pin to `high' (1) or `low' (0).
%%
%% The STM32 is capable of setting the state for any, or all of the output pins
%% on a single bank at the same time, this is done by passing a list of pins numbers
%% in the pin tuple.
%%
%% For example, setting all of the even numbered pins to a `high' state,
%% and all of the odd numbered pins to a `low' state can be accomplished in two lines:
%%
%% <pre>
%% gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
%% gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
%% </pre>
%%
%% To set the same state for all of the pins that have been previously configured as outputs
%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the
%% same bank that have been configured as inputs, so it is safe to use with mixed direction
%% modes on a bank.
%%
%% Not supported on rp2040 (Pico), use `gpio:digital_write/2' instead.
%% @end
%%-----------------------------------------------------------------------------
-spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) ->
ok | {error, Reason :: atom()} | error.
set_level(GPIO, Pin, Level) ->
port:call(GPIO, {set_level, Pin, Level}).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from `gpio:start/0'
%% @param Pin number of the pin to set the interrupt on
%% @param Trigger is the state that will trigger an interrupt
%% @returns ok | {error, Reason}
%% @doc Set a GPIO interrupt
%%
%% Available triggers are `none' (which is the same as disabling an
%% interrupt), `rising', `falling', `both' (rising or falling), `low',
%% and `high'. When the interrupt is triggered it will send a tuple:
%% `{gpio_interrupt, Pin}' to the process that set the interrupt. `Pin'
%% will be the number of the pin that triggered the interrupt.
%%
%% The STM32 port only supports `rising', `falling', or `both'.
%%
%% The rp2040 (Pico) port does not support gpio interrupts at this time.
%% @end
%%-----------------------------------------------------------------------------
-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger()) ->
ok | {error, Reason :: atom()} | error.
set_int(GPIO, Pin, Trigger) ->
port:call(GPIO, {set_int, Pin, Trigger}).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from `gpio:start/0'
%% @param Pin number of the pin to set the interrupt on
%% @param Trigger is the state that will trigger an interrupt
%% @param Pid is the process that will receive the interrupt message
%% @returns ok | {error, Reason}
%% @doc Set a GPIO interrupt
%%
%% Available triggers are `none' (which is the same as disabling an
%% interrupt), `rising', `falling', `both' (rising or falling), `low', and
%% `high'. When the interrupt is triggered it will send a tuple:
%% `{gpio_interrupt, Pin}'
%% to the process that set the interrupt. Pin will be the number
%% of the pin that triggered the interrupt.
%%
%% The STM32 port only supports `rising', `falling', or `both'.
%%
%% The rp2040 (Pico) port does not support gpio interrupts at this time.
%% @end
%%-----------------------------------------------------------------------------
-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger(), Pid :: pid()) ->
ok | {error, Reason :: atom()} | error.
set_int(GPIO, Pin, Trigger, Pid) ->
port:call(GPIO, {set_int, Pin, Trigger, Pid}).
%%-----------------------------------------------------------------------------
%% @param GPIO pid that was returned from `gpio:start/0'
%% @param Pin number of the pin to remove the interrupt
%% @returns ok | {error, Reason}
%% @doc Remove a GPIO interrupt
%%
%% Removes an interrupt from the specified pin.
%%
%% The rp2040 (Pico) port does not support gpio interrupts at this time.
%% @end
%%-----------------------------------------------------------------------------
-spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | {error, Reason :: atom()}.
remove_int(GPIO, Pin) ->
port:call(GPIO, {remove_int, Pin}).
%%-----------------------------------------------------------------------------
%% @param Pin number to initialize
%% @returns ok | raise(Error)
%% @doc Initialize a pin to be used as GPIO.
%% This is required on RP2040 and for some pins on ESP32.
%% @end
%%-----------------------------------------------------------------------------
-spec init(Pin :: pin()) -> ok.
init(_Pin) ->
ok.
%%-----------------------------------------------------------------------------
%% @param Pin number to deinitialize
%% @returns ok | raise(Error)
%% @doc Reset a pin back to the NULL function.
%% Currently only implemented for RP2040 (Pico).
%% @end
%%-----------------------------------------------------------------------------
-spec deinit(Pin :: pin()) -> ok.
deinit(_Pin) ->
ok.
%%-----------------------------------------------------------------------------
%% @param Pin number to set operational mode
%% @param Direction is `input', `output', or `output_od'
%% @returns ok | raise(Error)
%% @doc Set the operational mode of a pin
%%
%% Pins can be used for input, output, or output with open drain.
%%
%% The STM32 platform has extended direction mode configuration options.
%% See @type mode_config() for details. All configuration must be set using
%% `set_direction/3', including pull() mode, unlike the ESP32 which has a
%% separate function (`set_pin_pull/2'). If you are configuring multiple pins
%% on the same GPIO `bank' with the same options the pins may be configured all
%% at the same time by giving a list of pin numbers in the pin tuple.
%% Example to configure all of the leds on a Nucleo board:
%%
%% <pre>
%% gpio:set_direction({b, [0,7,14], output)
%% </pre>
%% @end
%%-----------------------------------------------------------------------------
-spec set_pin_mode(Pin :: pin(), Direction :: direction()) ->
ok.
set_pin_mode(_Pin, _Mode) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number to set internal resistor direction
%% @param Pull is the internal resistor state
%% @returns ok | raise(Error)
%% @doc Set the internal resistor of a pin
%%
%% Pins can be internally pulled `up', `down', `up_down' (pulled in
%% both directions), or left `floating'.
%%
%% This function is not supported on STM32, the internal resistor must
%% be configured when setting the direction mode, see `set_direction/3'
%% or `set_pin_mode/2'.
%% @end
%%-----------------------------------------------------------------------------
-spec set_pin_pull(Pin :: pin(), Pull :: pull()) -> ok.
set_pin_pull(_Pin, _Pull) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number of the pin to be held
%% @returns ok | raise(Error)
%% @doc Hold the state of a pin
%%
%% The gpio pad hold function works in both input and output modes,
%% but must be output-capable gpios.
%%
%% If pad hold enabled:
%% In output mode: the output level of the pad will be force locked
%% and can not be changed.
%% In input mode: the input value read will not change, regardless
%% the changes of input signal.
%%
%% The state of digital gpio cannot be held during Deep-sleep, and it
%% will resume the hold function when the chip wakes up from
%% Deep-sleep. If the digital gpio also needs to be held during
%% Deep-sleep `gpio:deep_sleep_hold_en' should also be called.
%%
%% This function is only supported on ESP32.
%% @end
%%-----------------------------------------------------------------------------
-spec hold_en(Pin :: pin()) -> ok.
hold_en(_Pin) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number of the pin to be released
%% @returns ok | raise(Error)
%% @doc Release a pin from a hold state.
%%
%% When the chip is woken up from Deep-sleep, the gpio will be set to
%% the default mode, so, the gpio will output the default level if
%% this function is called. If you don’t want the level changes, the
%% gpio should be configured to a known state before this function is
%% called. e.g. If you hold gpio18 high during Deep-sleep, after the
%% chip is woken up and `gpio:hold_dis' is called, gpio18 will output
%% low level(because gpio18 is input mode by default). If you don’t
%% want this behavior, you should configure gpio18 as output mode and
%% set it to hight level before calling `gpio:hold_dis'.
%%
%% This function is only supported on ESP32.
%% @end
%%-----------------------------------------------------------------------------
-spec hold_dis(Pin :: pin()) -> ok.
hold_dis(_Pin) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @returns ok
%% @doc Enable all hold functions to continue in deep sleep.
%%
%% The gpio pad hold function works in both input and output modes,
%% but must be output-capable gpios.
%%
%% When the chip is in Deep-sleep mode, all digital gpio will hold the
%% state before sleep, and when the chip is woken up, the status of
%% digital gpio will not be held. Note that the pad hold feature only
%% works when the chip is in Deep-sleep mode, when not in sleep mode,
%% the digital gpio state can be changed even you have called this
%% function.
%%
%% Power down or call `gpio_hold_dis' will disable this function,
%% otherwise, the digital gpio hold feature works as long as the chip
%% enters Deep-sleep.
%%
%% This function is only supported on ESP32.
%% @end
%%-----------------------------------------------------------------------------
-spec deep_sleep_hold_en() -> ok.
deep_sleep_hold_en() ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @returns ok
%% @doc Disable all gpio pad functions during Deep-sleep.
%%
%% This function is only supported on ESP32.
%% @end
%%-----------------------------------------------------------------------------
-spec deep_sleep_hold_dis() -> ok.
deep_sleep_hold_dis() ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number of the pin to write
%% @param Level the desired output level to set
%% @returns ok | raise(Error)
%% @doc Set GPIO digital output level
%%
%% Set a pin to `high' (1) or `low' (0).
%%
%% The STM32 is capable of setting the state for any, or all of the output pins
%% on a single bank at the same time, this is done by passing a list of pins numbers
%% in the pin tuple. For example, setting all of the even numbered pins to a `high' state,
%% and all of the odd numbered pins to a `low' state can be accomplished in two lines:
%%
%% <pre>
%% gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
%% gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
%% </pre>
%%
%% To set the same state for all of the pins that have been previously configured as outputs
%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the
%% same bank that have been configured as inputs, so it is safe to use with mixed direction
%% modes on a bank.
%%
%% The LED pin on the Pico-W can be controlled on the extended pin `{wl, 0}', and does not
%% require or accept `set_pin_mode' or `set_pin_pull' before use.
%% @end
%%-----------------------------------------------------------------------------
-spec digital_write(Pin :: pin(), Level :: level()) -> ok.
digital_write(_Pin, _Level) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number of the pin to read
%% @returns high | low | raise(Error)
%% @doc Read the digital state of a GPIO pin
%%
%% Read if an input pin state is high or low.
%% Warning: if the pin was not previously configured as an input using
%% `gpio:set_pin_mode/2' it will always read as low.
%%
%% The VBUS detect pin on the Pico-W can be read on the extended pin `{wl, 2}',
%% and does not require or accept `set_pin_mode' or `set_pin_pull' before use.
%% @end
%%-----------------------------------------------------------------------------
-spec digital_read(Pin :: pin()) -> high | low.
digital_read(_Pin) ->
erlang:nif_error(undefined).
%%-----------------------------------------------------------------------------
%% @param Pin number of the pin to set the interrupt on
%% @param Trigger is the state that will trigger an interrupt
%% @returns ok | {error, Reason}
%% @doc Convenience function for `gpio:set_int/3'
%%
%% This is a convenience function for `gpio:set_int/3' that allows an
%% interrupt to be set using only the pin number and trigger as
%% arguments.
%%
%% This function should only be used when only one gpio trigger is
%% used in an application. If multiple pins are being configured with
%% interrupt triggers gpio:set_int/3 should be used otherwise there is
%% a race condition when start() is called internally by this function.
%%
%% The rp2040 (Pico) port does not support gpio interrupts at this time.
%% @end
%%-----------------------------------------------------------------------------
-spec attach_interrupt(Pin :: pin(), Trigger :: trigger()) ->
ok | {error, Reason :: atom()}.
attach_interrupt(Pin, Trigger) ->
set_int(start(), Pin, Trigger).
%-----------------------------------------------------------------------------
%% @param Pin number of the pin to remove the interrupt
%% @returns ok | {error, Reason}
%% @doc Convenience function for `gpio:remove_int/2'
%%
%% This is a convenience function for `gpio:remove_int/2' that allows an
%% interrupt to be removed using only the pin number as an argument.
%%
%% Unlike `gpio:attach_interrupt/2' this function can be safely used
%% regardless of the number of interrupt pins used in the application.
%%
%% The rp2040 (Pico) port does not support gpio interrupts at this time.
%% @end
%%-----------------------------------------------------------------------------
-spec detach_interrupt(Pin :: pin()) -> ok | {error, Reason :: atom()}.
detach_interrupt(Pin) ->
remove_int(whereis(gpio), Pin).