diff --git a/CMakeModules/BuildErlang.cmake b/CMakeModules/BuildErlang.cmake index f034411aaa..0ac37a2ba6 100644 --- a/CMakeModules/BuildErlang.cmake +++ b/CMakeModules/BuildErlang.cmake @@ -287,7 +287,7 @@ macro(pack_runnable avm_name main) foreach(archive_name ${ARGN}) if(NOT ${archive_name} STREQUAL "exavmlib") set(pack_runnable_${avm_name}_archives ${pack_runnable_${avm_name}_archives} ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/${archive_name}.avm) - if(NOT ${archive_name} MATCHES "^eavmlib|estdlib|alisp|avm_network|avm_esp32|avm_rp2|avm_stm32|avm_emscripten$") + if(NOT ${archive_name} MATCHES "^(eavmlib|estdlib|alisp|avm_network|avm_esp32|avm_rp2|avm_stm32|avm_emscripten)$") set(${avm_name}_dialyzer_beams_opt ${${avm_name}_dialyzer_beams_opt} "-r" ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/beams/) endif() else() diff --git a/libs/avm_rp2/src/gpio.erl b/libs/avm_rp2/src/gpio.erl index 0d8b4e4e16..deeecfca94 100644 --- a/libs/avm_rp2/src/gpio.erl +++ b/libs/avm_rp2/src/gpio.erl @@ -65,7 +65,7 @@ -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. +%% Valid pin levels can be atom or integer representation. -type gpio() :: pid(). %% This is the pid returned by `gpio:start/0'. Unlike ESP32 and STM32, this %% is not a real port but a process wrapping NIF calls. @@ -118,6 +118,8 @@ close(GPIO) -> GPIO ! {'$call', {self(), Ref}, {close}}, receive {Ref, Result} -> Result + after 5000 -> + {error, timeout} end. %%----------------------------------------------------------------------------- @@ -227,7 +229,7 @@ remove_int(_GPIO, _Pin) -> %%----------------------------------------------------------------------------- -spec init(Pin :: pin()) -> ok. init(_Pin) -> - ok. + erlang:nif_error(undefined). %%----------------------------------------------------------------------------- %% @param Pin number to deinitialize @@ -237,7 +239,7 @@ init(_Pin) -> %%----------------------------------------------------------------------------- -spec deinit(Pin :: pin()) -> ok. deinit(_Pin) -> - ok. + erlang:nif_error(undefined). %%----------------------------------------------------------------------------- %% @param Pin number to set operational mode diff --git a/libs/avm_stm32/src/gpio.erl b/libs/avm_stm32/src/gpio.erl index bf9550a44b..1ab14fc403 100644 --- a/libs/avm_stm32/src/gpio.erl +++ b/libs/avm_stm32/src/gpio.erl @@ -56,7 +56,7 @@ %% a list of pin numbers, or the atom `all'. -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(). +-type direction() :: input | output | output_od. %% 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. %% 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()}. @@ -68,7 +68,7 @@ -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. +%% Valid pin levels can be atom or integer representation. -type gpio() :: port(). %% This is the port returned by `gpio:start/0'. -type trigger() :: none | rising | falling | both | low | high. @@ -79,7 +79,7 @@ %% @doc Start the GPIO driver port %% %% Returns the port of the active GPIO port driver, otherwise the GPIO -%% port driver will be stared and registered as `gpio'. The use of +%% port driver will be started and registered as `gpio'. The use of %% `gpio:open/0' or `gpio:start/0' is required before using any functions %% that require a GPIO port as a parameter. %% @end @@ -97,7 +97,7 @@ start() -> %% @returns Port | error | {error, Reason} %% @doc Start the GPIO driver port %% -%% The GPIO port driver will be stared and registered as `gpio'. If the +%% The GPIO port driver will be started 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 @@ -163,7 +163,7 @@ read(GPIO, Pin) -> %% Pins can be used for input, output, or output with open drain. %% @end %%----------------------------------------------------------------------------- --spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> +-spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction() | mode_config()) -> ok | {error, Reason :: atom()} | error. set_direction(GPIO, Pin, Direction) -> port:call(GPIO, {set_direction, Pin, Direction}). @@ -274,7 +274,7 @@ deinit(_Pin) -> %% %% @end %%----------------------------------------------------------------------------- --spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> +-spec set_pin_mode(Pin :: pin(), Direction :: direction() | mode_config()) -> ok | {error, Reason :: atom()} | error. set_pin_mode(_Pin, _Mode) -> erlang:nif_error(undefined). diff --git a/libs/eavmlib/src/gpio_hal.erl b/libs/eavmlib/src/gpio_hal.erl index 703df71789..33ef787a44 100644 --- a/libs/eavmlib/src/gpio_hal.erl +++ b/libs/eavmlib/src/gpio_hal.erl @@ -24,6 +24,106 @@ %% This module defines the behavior that platform-specific GPIO modules %% must implement. It provides a common interface for basic GPIO operations %% across all supported platforms (ESP32, RP2, STM32). +%% +%% There are two APIs for GPIO operations: +%% +%%

NIF-based API

+%% +%% The NIF-based API provides direct access to GPIO pins without requiring +%% a driver process or port. Functions operate directly on pin numbers. +%% +%% +%% +%%

Port-based API

+%% +%% The port-based API requires starting a GPIO driver using `start/0' or +%% `open/0', which returns a handle (a port on ESP32 and STM32, a pid on +%% RP2). This handle is passed to all subsequent operations. +%% +%% +%% +%%

Pin definitions

+%% +%% Pin definitions vary by platform: +%% +%% +%%

Platform differences

+%% +%% +%% +%%

Example usage (NIF-based API)

+%% +%% The following example configures pin 2 as an output and sets it high: +%% +%% ``` +%% gpio:init(2), +%% gpio:set_pin_mode(2, output), +%% gpio:digital_write(2, high). +%% ''' +%% +%% The following example configures pin 4 as an input with a pull-up +%% resistor and reads the level: +%% +%% ``` +%% gpio:init(4), +%% gpio:set_pin_mode(4, input), +%% gpio:set_pin_pull(4, up), +%% Level = gpio:digital_read(4). +%% ''' +%% +%%

Example usage (Port-based API)

+%% +%% ``` +%% GPIO = gpio:start(), +%% gpio:set_direction(GPIO, 2, output), +%% gpio:set_level(GPIO, 2, high), +%% gpio:set_int(GPIO, 4, rising), +%% receive +%% {gpio_interrupt, 4} -> io:format("Pin 4 triggered!~n") +%% end. +%% ''' %% @end %%----------------------------------------------------------------------------- -module(gpio_hal). @@ -34,6 +134,7 @@ -type pull() :: up | down | up_down | floating. %% Internal resistor pull mode. +%% Note: STM32 does not support `up_down'. -type low_level() :: low | 0. -type high_level() :: high | 1. @@ -42,53 +143,130 @@ -type trigger() :: none | rising | falling | both | low | high. %% Event type that will trigger a `gpio_interrupt'. +%% Setting trigger to `none' disables the interrupt. -type gpio() :: port() | pid(). %% Handle returned by `start/0' or `open/0'. +%% On ESP32 and STM32, this is a port. On RP2, this is a pid. -export_type([direction/0, pull/0, level/0, trigger/0, gpio/0]). %% NIF-based API +% Initialize a pin for GPIO use. +% +% This must be called before using a pin on the RP2 platform. On ESP32 +% and STM32, this function is a no-op and always returns `ok'. -callback init(Pin :: term()) -> ok. +% Deinitialize a pin, releasing it from GPIO use. +% +% Resets the pin back to its default (NULL) function. -callback deinit(Pin :: term()) -> ok. +% Set the operational mode of a pin. +% +% Configures a pin as `input', `output', or `output_od' (output with +% open drain). The pin should be initialized with `init/1' first on +% platforms that require it. -callback set_pin_mode(Pin :: term(), Direction :: direction()) -> ok | {error, Reason :: atom()} | error. +% Configure the internal pull resistor of a pin. +% +% Pins can be pulled `up', `down', `up_down' (pulled in both +% directions), or left `floating'. Not all pull modes are supported +% on all platforms (e.g. STM32 does not support `up_down'). -callback set_pin_pull(Pin :: term(), Pull :: pull()) -> ok | error. +% Set the digital output level of a pin. +% +% Sets the pin to `high' (or `1') or `low' (or `0'). The pin should +% be configured as an output using `set_pin_mode/2' first. -callback digital_write(Pin :: term(), Level :: level()) -> ok | {error, Reason :: atom()} | error. +% Read the digital input level of a pin. +% +% Returns `high' or `low'. The pin should be configured as an input +% using `set_pin_mode/2' first; otherwise it may always read as `low'. -callback digital_read(Pin :: term()) -> high | low | {error, Reason :: atom()} | error. %% Port-based API +% Start the GPIO driver. +% +% Returns the handle of an existing GPIO driver if one is already +% registered, or starts a new one. The returned handle is required +% for all port-based API functions. -callback start() -> gpio() | {error, Reason :: atom()} | error. +% Open a new GPIO driver instance. +% +% Always starts a new GPIO driver instance and registers it. Fails +% if a driver is already registered. Use `start/0' to get an existing +% instance or start a new one. -callback open() -> gpio() | {error, Reason :: atom()} | error. +% Close a GPIO driver and release its resources. +% +% This disables any active interrupts, stops the driver, and frees +% all associated resources. -callback close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. +% Stop the registered GPIO driver. +% +% If a GPIO driver is registered, it is closed and its resources are +% freed. If no driver is registered, returns `ok'. -callback stop() -> ok | {error, Reason :: atom()} | error. +% Read the digital input level of a pin using the port-based API. +% +% Returns `high' or `low'. The pin should be configured as an input +% using `set_direction/3' first; otherwise it may always read as `low'. -callback read(GPIO :: gpio(), Pin :: term()) -> high | low | {error, Reason :: atom()} | error. +% Set the operational mode of a pin using the port-based API. +% +% Configures a pin as `input', `output', or `output_od' (output with +% open drain). -callback set_direction(GPIO :: gpio(), Pin :: term(), Direction :: direction()) -> ok | {error, Reason :: atom()} | error. +% Set the digital output level of a pin using the port-based API. +% +% Sets the pin to `high' (or `1') or `low' (or `0'). The pin should +% be configured as an output using `set_direction/3' first. -callback set_level(GPIO :: gpio(), Pin :: term(), Level :: level()) -> ok | {error, Reason :: atom()} | error. +% Set a GPIO interrupt on a pin. +% +% Configures the pin to trigger an interrupt on the specified condition. +% When triggered, a message `{gpio_interrupt, Pin}' is sent to the +% calling process (the exact format of Pin in the message depends on +% the platform). +% +% Not supported on all platforms. RP2 returns `{error, not_supported}'. -callback set_int(GPIO :: gpio(), Pin :: term(), Trigger :: trigger()) -> ok | {error, Reason :: atom()} | error. +% Set a GPIO interrupt on a pin, sending notifications to a +% specific process. +% +% Same as `set_int/3', but the interrupt message `{gpio_interrupt, Pin}' +% is sent to the specified `Pid' instead of the calling process. +% +% Not supported on all platforms. RP2 returns `{error, not_supported}'. -callback set_int(GPIO :: gpio(), Pin :: term(), Trigger :: trigger(), Pid :: pid()) -> ok | {error, Reason :: atom()} | error. +% Remove a GPIO interrupt from a pin. +% +% Disables a previously configured interrupt on the specified pin. +% +% Not supported on all platforms. RP2 returns `{error, not_supported}'. -callback remove_int(GPIO :: gpio(), Pin :: term()) -> ok | {error, Reason :: atom()} | error. diff --git a/libs/eavmlib/src/i2c_hal.erl b/libs/eavmlib/src/i2c_hal.erl index 2f5c1c86de..650d77d8bf 100644 --- a/libs/eavmlib/src/i2c_hal.erl +++ b/libs/eavmlib/src/i2c_hal.erl @@ -22,54 +22,150 @@ %% @doc I2C Hardware Abstraction Layer behavior %% %% This module defines the behavior that platform-specific I2C modules -%% must implement. It provides a common interface for I2C operations -%% across all supported platforms. +%% must implement. It provides a common interface for I2C (Inter-Integrated +%% Circuit) operations across all supported platforms. +%% +%% Currently, only ESP32 provides an I2C implementation. +%% +%%

Lifecycle

+%% +%% An I2C bus is opened with `open/1' and closed with `close/1'. The +%% returned handle is passed to all subsequent operations. +%% +%%

Reading data

+%% +%% Data can be read from I2C devices using `read_bytes/3' or +%% `read_bytes/4': +%% +%% +%% +%%

Writing data

+%% +%% There are two approaches for writing data: +%% +%% Direct writes: `write_bytes/3' and `write_bytes/4' write data +%% to a device address, optionally specifying a register: +%% +%% ``` +%% i2c:write_bytes(I2C, 16#68, 16#0D, <<16#FF>>). +%% ''' +%% +%% Transaction-based writes: For more control, use +%% `begin_transmission/2', followed by one or more calls to +%% `write_byte/2' or `write_bytes/2', and finalize with +%% `end_transmission/1': +%% +%% ``` +%% i2c:begin_transmission(I2C, 16#68), +%% i2c:write_byte(I2C, 16#0D), +%% i2c:write_bytes(I2C, <<16#FF, 16#00>>), +%% i2c:end_transmission(I2C). +%% ''' +%% +%%

Configuration parameters

+%% +%% The `open/1' function accepts a proplist of configuration parameters. +%% Common parameters include: +%% +%% +%% +%%

Example

+%% +%% ``` +%% I2C = i2c:open([{scl, 22}, {sda, 21}, {clock_speed_hz, 100000}]), +%% {ok, Data} = i2c:read_bytes(I2C, 16#68, 16#75, 1), +%% i2c:write_bytes(I2C, 16#68, 16#6B, 0), +%% i2c:close(I2C). +%% ''' %% @end %%----------------------------------------------------------------------------- -module(i2c_hal). -type i2c() :: port() | pid() | term(). %% Handle returned by `open/1'. +%% On ESP32, this is either a port (port driver mode) or a resource +%% tuple (NIF mode). -type address() :: non_neg_integer(). -%% I2C device address. +%% I2C device address (typically 7-bit, e.g. `16#68'). -type register() :: non_neg_integer(). %% Register address within an I2C device. -type params() :: [term()]. %% Initialization parameters for the I2C bus. +%% See the module documentation for common parameters. -export_type([i2c/0, address/0, register/0, params/0]). +% Open an I2C bus with the given configuration parameters. +% +% Returns a handle that must be passed to all subsequent I2C +% operations. -callback open(Params :: params()) -> i2c(). +% Close an I2C bus and release its resources. -callback close(I2C :: i2c()) -> ok | {error, Reason :: term()}. +% Begin a write transaction to a device at the given address. +% +% After calling this function, use `write_byte/2' or `write_bytes/2' +% to send data, then call `end_transmission/1' to complete the +% transaction. -callback begin_transmission(I2C :: i2c(), Address :: address()) -> ok | {error, Reason :: term()}. +% Write a single byte as part of a transaction. +% +% Must be called between `begin_transmission/2' and +% `end_transmission/1'. -callback write_byte(I2C :: i2c(), Byte :: byte()) -> ok | {error, Reason :: term()}. +% Write binary data as part of a transaction. +% +% Must be called between `begin_transmission/2' and +% `end_transmission/1'. -callback write_bytes(I2C :: i2c(), Bytes :: binary()) -> ok | {error, Reason :: term()}. +% End a write transaction started with `begin_transmission/2'. +% +% Sends all buffered data to the device. -callback end_transmission(I2C :: i2c()) -> ok | {error, Reason :: term()}. +% Read a number of bytes from a device at the given address. -callback read_bytes(I2C :: i2c(), Address :: address(), Count :: non_neg_integer()) -> {ok, Data :: binary()} | {error, Reason :: term()}. +% Read a number of bytes from a specific register within a device. -callback read_bytes( I2C :: i2c(), Address :: address(), Register :: register(), Count :: non_neg_integer() ) -> {ok, Data :: binary()} | {error, Reason :: term()}. +% Write data to a device at the given address. +% +% BinOrInt can be a binary or a single byte value. -callback write_bytes(I2C :: i2c(), Address :: address(), BinOrInt :: binary() | byte()) -> ok | {error, Reason :: term()}. +% Write data to a specific register within a device. +% +% BinOrInt can be a binary or a single byte value. -callback write_bytes( - I2C :: i2c(), Address :: address(), Register :: register(), BinOrInt :: binary() | integer() + I2C :: i2c(), Address :: address(), Register :: register(), BinOrInt :: binary() | byte() ) -> ok | {error, Reason :: term()}. diff --git a/libs/eavmlib/src/spi_hal.erl b/libs/eavmlib/src/spi_hal.erl index 274962de9b..cd037ce6db 100644 --- a/libs/eavmlib/src/spi_hal.erl +++ b/libs/eavmlib/src/spi_hal.erl @@ -22,8 +22,89 @@ %% @doc SPI Hardware Abstraction Layer behavior %% %% This module defines the behavior that platform-specific SPI modules -%% must implement. It provides a common interface for SPI operations -%% across all supported platforms. +%% must implement. It provides a common interface for SPI (Serial +%% Peripheral Interface) operations across all supported platforms. +%% +%% Currently, only ESP32 provides an SPI implementation. +%% +%%

Lifecycle

+%% +%% An SPI bus is opened with `open/1' and closed with `close/1'. The +%% `open/1' function takes a configuration that includes both bus +%% parameters (pins, peripheral) and device configurations. Multiple +%% devices can share the same SPI bus. +%% +%%

Device names

+%% +%% Each device on the SPI bus is identified by a `device_name()' atom. +%% Device names are defined during `open/1' configuration and used in +%% all subsequent read/write operations. For example, if the +%% configuration includes `{my_sensor, [{cs, 5}]}', then `my_sensor' +%% is used as the device name in calls like +%% `spi:read_at(SPI, my_sensor, Address, Len)'. +%% +%%

Simple read/write

+%% +%% For simple address-based operations: +%% +%% +%% +%%

Transaction-based operations

+%% +%% For more complex operations, use transaction maps with `write/3' +%% or `write_read/3': +%% +%% ``` +%% Transaction = #{ +%% command => 16#42, +%% address => 16#00, +%% write_data => <<1, 2, 3>>, +%% write_bits => 24, +%% read_bits => 8 +%% }, +%% {ok, ReadData} = spi:write_read(SPI, my_device, Transaction). +%% ''' +%% +%%

Configuration parameters

+%% +%% The `open/1' function accepts a proplist or map with bus and device +%% configuration. Bus parameters include: +%% +%% +%% +%% Device parameters are specified as `{DeviceName, DeviceOpts}' entries: +%% +%% +%% +%%

Example

+%% +%% ``` +%% SPI = spi:open([ +%% {sclk, 18}, {miso, 19}, {mosi, 23}, +%% {my_device, [{cs, 5}, {clock_speed_hz, 1000000}]} +%% ]), +%% {ok, Value} = spi:read_at(SPI, my_device, 16#0F, 8), +%% spi:close(SPI). +%% ''' %% @end %%----------------------------------------------------------------------------- -module(spi_hal). @@ -32,13 +113,15 @@ %% Handle returned by `open/1'. -type device_name() :: atom(). -%% Name identifying an SPI device, as specified in the device configuration. +%% Name identifying an SPI device, as specified in the device +%% configuration passed to `open/1'. -type address() :: non_neg_integer(). %% SPI device address. -type params() :: [term()] | map(). -%% Initialization parameters for the SPI bus. +%% Initialization parameters for the SPI bus and its devices. +%% See the module documentation for common parameters. -type transaction() :: #{ command => integer(), @@ -47,19 +130,34 @@ write_bits => non_neg_integer(), read_bits => non_neg_integer() }. -%% SPI transaction map. +%% SPI transaction map. Fields are all optional and depend on the +%% operation being performed. -export_type([spi/0, device_name/0, address/0, params/0, transaction/0]). +% Open an SPI bus with the given configuration. +% +% The configuration includes both bus parameters (pins, peripheral) +% and device configurations. Returns a handle for subsequent +% operations. -callback open(Params :: params()) -> spi(). +% Close the SPI bus and release its resources. -callback close(SPI :: spi()) -> ok. +% Read data from a device at the given address. +% +% Returns `{ok, Value}' where Value is an integer representing the +% read data, or `{error, Reason}'. -callback read_at( SPI :: spi(), DeviceName :: device_name(), Address :: address(), Len :: non_neg_integer() ) -> {ok, integer()} | {error, Reason :: term()}. +% Write data to a device at the given address. +% +% Returns `{ok, Value}' where Value is the response from the device, +% or `{error, Reason}'. -callback write_at( SPI :: spi(), DeviceName :: device_name(), @@ -69,8 +167,16 @@ ) -> {ok, integer()} | {error, Reason :: term()}. +% Write data to a device using a transaction map. +% +% The transaction map specifies the command, address, and data to +% write. See the `transaction()' type for details. -callback write(SPI :: spi(), DeviceName :: device_name(), Transaction :: transaction()) -> ok | {error, Reason :: term()}. +% Write data to and read data from a device in a single transaction. +% +% Performs a simultaneous write and read operation. Returns the read +% data as a binary. -callback write_read(SPI :: spi(), DeviceName :: device_name(), Transaction :: transaction()) -> {ok, ReadData :: binary()} | {error, Reason :: term()}. diff --git a/libs/eavmlib/src/uart_hal.erl b/libs/eavmlib/src/uart_hal.erl index 63a40b0d5c..1dc8cbf9de 100644 --- a/libs/eavmlib/src/uart_hal.erl +++ b/libs/eavmlib/src/uart_hal.erl @@ -22,8 +22,60 @@ %% @doc UART Hardware Abstraction Layer behavior %% %% This module defines the behavior that platform-specific UART modules -%% must implement. It provides a common interface for UART operations -%% across all supported platforms. +%% must implement. It provides a common interface for UART (Universal +%% Asynchronous Receiver-Transmitter) operations across all supported +%% platforms. +%% +%% Currently, only ESP32 provides a UART implementation. +%% +%%

Lifecycle

+%% +%% A UART port is opened with `open/1' or `open/2' and closed with +%% `close/1'. The `open/2' variant is a convenience that takes the +%% peripheral name as a separate argument. The returned handle is +%% passed to all subsequent operations. +%% +%%

Reading and writing

+%% +%% +%% +%%

Configuration parameters

+%% +%% The `open/1' function accepts a proplist of configuration +%% parameters. Common parameters include: +%% +%% +%% +%%

Example

+%% +%% ``` +%% UART = uart:open([{tx, 17}, {rx, 16}, {speed, 115200}]), +%% uart:write(UART, <<"Hello\r\n">>), +%% case uart:read(UART, 5000) of +%% {ok, Data} -> io:format("Received: ~p~n", [Data]); +%% {error, timeout} -> io:format("No response~n") +%% end, +%% uart:close(UART). +%% ''' %% @end %%----------------------------------------------------------------------------- -module(uart_hal). @@ -32,22 +84,40 @@ %% Handle returned by `open/1' or `open/2'. -type peripheral() :: string() | binary(). -%% UART peripheral name. +%% UART peripheral name (e.g. `"UART0"', `"UART1"'). -type params() :: [term()]. -%% Initialization parameters for the UART bus. +%% Initialization parameters for the UART port. +%% See the module documentation for common parameters. -export_type([uart/0, peripheral/0, params/0]). +% Open a UART port with the given configuration parameters. +% +% Returns a handle for subsequent read/write operations. -callback open(Params :: params()) -> uart() | {error, Reason :: term()}. +% Open a UART port on the specified peripheral. +% +% Convenience wrapper that adds `{peripheral, Name}' to the +% parameters and calls `open/1'. -callback open(Name :: peripheral(), Params :: params()) -> uart() | {error, Reason :: term()}. +% Close a UART port and release its resources. -callback close(UART :: uart()) -> ok | {error, Reason :: term()}. +% Non-blocking read from the UART port. +% +% Returns `{ok, Data}' if data is available, or `{error, timeout}' +% if no data is ready. -callback read(UART :: uart()) -> {ok, Data :: iodata()} | {error, Reason :: term()}. +% Blocking read from the UART port with a timeout. +% +% Waits up to `Timeout' milliseconds for data to arrive. Returns +% `{error, timeout}' if no data arrives within the timeout period. -callback read(UART :: uart(), Timeout :: pos_integer()) -> {ok, Data :: iodata()} | {error, Reason :: term()}. +% Write data to the UART port. -callback write(UART :: uart(), Data :: iodata()) -> ok | {error, Reason :: term()}.