diff --git a/doc/src/programmers-guide.md b/doc/src/programmers-guide.md index 643be98291..4f075bd05d 100644 --- a/doc/src/programmers-guide.md +++ b/doc/src/programmers-guide.md @@ -1043,16 +1043,47 @@ The `esp:get_mac/1` function can be used to retrieve the network Media Access Co ## Peripherals -The AtomVM virtual machine and libraries support APIs for interfacing with peripheral devices connected to the ESP32. This section provides information about these APIs. +The AtomVM virtual machine and libraries support APIs for interfacing with peripheral devices connected to the ESP32 and other supported microcontrollers. This section provides information about these APIs. Unless otherwise stated the documentation for these peripherals is specific to the ESP32, most peripherals are not yet supported on rp2040 or stm32 devices - but work is on-going to expand support on these platforms. ### GPIO +The GPIO peripheral has nif support on all platforms. One notable difference on the STM32 platform is that `Pin`s are defined as a tuple consisting of the bank (a.k.a. port) and pin number. For example a pin labeled PB7 on your board would be `{b,7}`. + You can read and write digital values on GPIO pins using the `gpio` module, using the `digital_read/1` and `digital_write/2` functions. You must first set the direction of the pin using the `gpio:set_direction/2` function, using `input` or `output` as the direction parameter. -To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`: +#### Digital Read + +To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`. + +For ESP32 family: + + %% erlang + Pin = 2, + gpio:set_direction(Pin, input), + case gpio:digital_read(Pin) of + high -> + io:format("Pin ~p is high ~n", [Pin]); + low -> + io:format("Pin ~p is low ~n", [Pin]) + end. + +For STM32 only the line with the Pin definition needs to be a tuple: + + %% erlang + Pin = {c, 13}, + gpio:set_direction(Pin, input), + case gpio:digital_read(Pin) of + high -> + io:format("Pin ~p is high ~n", [Pin]); + low -> + io:format("Pin ~p is low ~n", [Pin]) + end. + +The Pico has an additional initialization step `gpio:init/1` before using a pin for gpio: %% erlang Pin = 2, + gpio:init(Pin), gpio:set_direction(Pin, input), case gpio:digital_read(Pin) of high -> @@ -1061,15 +1092,36 @@ To read the value of a GPIO pin (`high` or `low`), use `gpio:digital_read/1`: io:format("Pin ~p is low ~n", [Pin]) end. -To set the value of a GPIO pin (`high` or `low`), use `gpio:digital_write/2`: +#### Digital Write + +To set the value of a GPIO pin (`high` or `low`), use `gpio:digital_write/2`. + +For ESP32 family: %% erlang Pin = 2, gpio:set_direction(Pin, output), gpio:digital_write(Pin, low). +For the STM32 use a pin tuple: + + %% erlang + Pin = {b, 7}, + gpio:set_direction(Pin, output), + gpio:digital_write(Pin, low). + +Pico needs the extra `gpio:init/1` before `gpio:read/1` too: + + %% erlang + Pin = 2, + gpio:init(Pin), + gpio:set_direction(Pin, output), + gpio:digital_write(Pin, low). + #### Interrupt Handling +Interrupts are supported on both the ESP32 and STM32 platforms. + You can get notified of changes in the state of a GPIO pin by using the `gpio:set_int/2` function. This function takes a reference to a GPIO Pin and a trigger. Allowable triggers are `rising`, `falling`, `both`, `low`, `high`, and `none` (to disable an interrupt). When a trigger event occurs, such as a pin rising in voltage, a tuple will be delivered to the process containing the atom `gpio_interrupt` and the pin. diff --git a/libs/eavmlib/src/gpio.erl b/libs/eavmlib/src/gpio.erl index 5c545bd08c..f91ac90d1d 100644 --- a/libs/eavmlib/src/gpio.erl +++ b/libs/eavmlib/src/gpio.erl @@ -25,13 +25,21 @@ %% (General Purpose Input and Output) pins. %% %% Note: `-type pin()' used in this driver refers to a pin number on Espressif -%% chips, or a tuple {GPIO_GROUP, PIN} for stm32 chips. +%% chips, or a tuple {GPIO_BANK, PIN} for stm32 chips. %% @end %%----------------------------------------------------------------------------- -module(gpio). -export([ - start/0, open/0, read/2, set_direction/3, set_level/3, set_int/3, remove_int/2, stop/0, close/1 + start/0, + open/0, + read/2, + set_direction/3, + set_level/3, + set_int/3, + remove_int/2, + stop/0, + close/1 ]). -export([ init/1, @@ -49,25 +57,42 @@ ]). -type gpio() :: pid(). --type pin() :: non_neg_integer() | {atom(), non_neg_integer()}. --type direction() :: input | output | output_od. +%% 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, on the STM32 platform it is a tuple. +-type pin_tuple() :: {gpio_bank(), 0..15}. +%% A pin parameter on STM32 is a tuple consisting of a GPIO bank and pin number. +-type gpio_bank() :: a | b | c | d | e | f | g | h | i | j | k. +%% STM32 gpio banks vary by board, some only break out `a' thru `h'. +-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 +%% @returns Pid | error | {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(). +-spec start() -> gpio() | {error, Reason :: atom()} | error. start() -> case whereis(gpio) of undefined -> @@ -77,7 +102,7 @@ start() -> end. %%----------------------------------------------------------------------------- -%% @returns Pid +%% @returns Pid | error | {error, Reason} %% @doc Start the GPIO driver port %% %% The GPIO port driver will be stared and registered as `gpio'. If the @@ -85,49 +110,57 @@ start() -> %% `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(). +-spec open() -> gpio() | {error, Reason :: atom()} | error. open() -> open_port({spawn, "gpio"}, []). %%----------------------------------------------------------------------------- %% @param GPIO pid that was returned from gpio:start/0 -%% @returns ok atom +%% @returns ok | error | {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. +-spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. close(GPIO) -> port:call(GPIO, {close}). %%----------------------------------------------------------------------------- -%% @returns ok atom +%% @returns ok | error | {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. +-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 +%% @returns high | low | error | {error, Reason} %% @doc Read the digital state of a GPIO pin %% %% Read if an input pin state is `high' or `low'. -%% Warning: if a is not previously configured as an input using +%% 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. +-spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. read(GPIO, Pin) -> port:call(GPIO, {read, Pin}). @@ -135,13 +168,29 @@ read(GPIO, 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 +%% @returns ok | error | {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: +%% +%%
+%% gpio:set_direction({b, [0,7,14], output) +%%+%% +%% Not supported on rp2040 (Pico), use `gpio:set_pin_mode/2' instead. %% @end %%----------------------------------------------------------------------------- --spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> ok | error. +-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}). @@ -149,13 +198,33 @@ set_direction(GPIO, Pin, Direction) -> %% @param GPIO pid that was returned from `gpio:start/0' %% @param Pin number of the pin to write %% @param Desired output level to set -%% @returns ok | error +%% @returns ok | error | {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: +%% +%%
+%% 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}). +%%+%% +%% 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. +-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}). @@ -163,31 +232,37 @@ set_level(GPIO, 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 +%% @returns ok | error | {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. +%% 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. +-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 remove the interrupt -%% @returns ok | error +%% @returns ok | error | {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. +-spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | {error, Reason :: atom()} | error. remove_int(GPIO, Pin) -> port:call(GPIO, {remove_int, Pin}). @@ -216,13 +291,26 @@ deinit(_Pin) -> %%----------------------------------------------------------------------------- %% @param Pin number to set operational mode %% @param Direction is `input', `output', or `output_od' -%% @returns ok | error +%% @returns ok | error | {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: +%% +%%
+%% gpio:set_direction({b, [0,7,14], output) +%%%% @end %%----------------------------------------------------------------------------- --spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> ok | error. +-spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. set_pin_mode(_Pin, _Mode) -> erlang:nif_error(undefined). @@ -234,6 +322,10 @@ set_pin_mode(_Pin, _Mode) -> %% %% 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 | error. @@ -258,6 +350,8 @@ set_pin_pull(_Pin, _Pull) -> %% 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 | error. @@ -278,6 +372,8 @@ hold_en(_Pin) -> %% 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 | error. @@ -301,6 +397,8 @@ hold_dis(_Pin) -> %% 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. @@ -310,6 +408,8 @@ deep_sleep_hold_en() -> %%----------------------------------------------------------------------------- %% @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. @@ -319,34 +419,49 @@ deep_sleep_hold_dis() -> %%----------------------------------------------------------------------------- %% @param Pin number of the pin to write %% @param Desired output level to set -%% @returns ok | error +%% @returns ok | error | {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: +%% +%%
+%% 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}). +%%+%% +%% 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. %% @end %%----------------------------------------------------------------------------- --spec digital_write(Pin :: pin(), Level :: level()) -> ok | error. +-spec digital_write(Pin :: pin(), Level :: level()) -> ok | {error, Reason :: atom()} | error. digital_write(_Pin, _Level) -> erlang:nif_error(undefined). %%----------------------------------------------------------------------------- %% @param Pin number of the pin to read -%% @returns high | low +%% @returns high | low | error | {error, Reason} %% @doc Read the digital state of a GPIO pin %% %% Read if an input pin state is high or low. -%% Warning: if a is not previously configured as an input using +%% Warning: if the pin was not previously configured as an input using %% `gpio:set_pin_mode/2' it will always read as low. %% @end %%----------------------------------------------------------------------------- --spec digital_read(Pin :: pin()) -> high | low. +-spec digital_read(Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. 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 +%% @returns ok | error | {error, Reason} %% @doc Convenience function for `gpio:set_int/3' %% %% This is a convenience function for `gpio:set_int/3' that allows an @@ -357,15 +472,18 @@ digital_read(_Pin) -> %% 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. +-spec attach_interrupt(Pin :: pin(), Trigger :: trigger()) -> + ok | {error, Reason :: atom()} | error. attach_interrupt(Pin, Trigger) -> set_int(start(), Pin, Trigger). %----------------------------------------------------------------------------- %% @param Pin number of the pin to remove the interrupt -%% @returns ok | error +%% @returns ok | error | {error, Reason} %% @doc Convenience function for `gpio:remove_int/2' %% %% This is a convenience function for `gpio:remove_int/2' that allows an @@ -373,8 +491,10 @@ attach_interrupt(Pin, Trigger) -> %% %% 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. +-spec detach_interrupt(Pin :: pin()) -> ok | {error, Reason :: atom()} | error. detach_interrupt(Pin) -> remove_int(whereis(gpio), Pin).