diff --git a/doc/src/programmers-guide.md b/doc/src/programmers-guide.md index 07836e637..7e80d66df 100644 --- a/doc/src/programmers-guide.md +++ b/doc/src/programmers-guide.md @@ -1043,13 +1043,16 @@ 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`: +For ESP32 or RP2040: %% erlang Pin = 2, @@ -1061,15 +1064,37 @@ 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. +For STM32 only the line with the Pin definition needs to be changed: + + %% 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. + To set the value of a GPIO pin (`high` or `low`), use `gpio:digital_write/2`: +For ESP32 or RP2040: %% erlang Pin = 2, gpio:set_direction(Pin, output), gpio:digital_write(Pin, low). +And Similarly for the STM32: + + %% erlang + Pin = {b, 7}, + 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 5c545bd08..fb3fe5320 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,13 +57,27 @@ ]). -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() | stm32_pin(). +%% The pin definition on the STM32 platform varies from ESP32 and PR2040. +-type stm32_pin() :: {gpio_bank(), 0..15}. +%% A pin argument 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 | stm32_direction(). +%% 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. +-type stm32_direction() :: {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 @@ -65,6 +87,8 @@ %% 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(). @@ -85,6 +109,8 @@ 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(). @@ -98,6 +124,8 @@ open() -> %% %% 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. @@ -110,6 +138,8 @@ close(GPIO) -> %% %% 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. @@ -123,8 +153,10 @@ stop() -> %% @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. @@ -139,6 +171,17 @@ read(GPIO, Pin) -> %% @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 stm32_direction() 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. @@ -153,6 +196,24 @@ set_direction(GPIO, Pin, Direction) -> %% @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. @@ -172,6 +233,10 @@ set_level(GPIO, Pin, Level) -> %% `{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. @@ -185,6 +250,8 @@ set_int(GPIO, Pin, Trigger) -> %% @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. @@ -220,6 +287,15 @@ deinit(_Pin) -> %% @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 stm32_direction() 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. @@ -234,6 +310,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 +338,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 +360,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 +385,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 +396,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. @@ -323,6 +411,20 @@ deep_sleep_hold_dis() -> %% @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. @@ -335,7 +437,7 @@ digital_write(_Pin, _Level) -> %% @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 %%----------------------------------------------------------------------------- @@ -357,6 +459,8 @@ 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. @@ -373,6 +477,8 @@ 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.