diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf733c9c5..d1ca553319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added RISC-V 64-bit (RV64IMAC) JIT backend - Added arm32 JIT backend - Added DWARF debug information support for JIT-compiled code -- Added I2C and SPI APIs to rp2 platform +- Added I2C, SPI and UART APIs to rp2 platform - Added `code:get_object_code/1` ### Changed diff --git a/examples/erlang/CMakeLists.txt b/examples/erlang/CMakeLists.txt index e008edd574..27af47be7e 100644 --- a/examples/erlang/CMakeLists.txt +++ b/examples/erlang/CMakeLists.txt @@ -47,3 +47,4 @@ pack_runnable(i2c_scanner i2c_scanner eavmlib estdlib DIALYZE_AGAINST avm_esp32 pack_runnable(i2c_lis3dh i2c_lis3dh eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32) pack_runnable(spi_flash spi_flash eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2) pack_runnable(spi_lis3dh spi_lis3dh eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2) +pack_runnable(sim800l sim800l eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2) diff --git a/examples/erlang/rp2/CMakeLists.txt b/examples/erlang/rp2/CMakeLists.txt index 5a81d9d41f..82ca2b1ce7 100644 --- a/examples/erlang/rp2/CMakeLists.txt +++ b/examples/erlang/rp2/CMakeLists.txt @@ -70,6 +70,16 @@ add_custom_command( add_custom_target(spi_lis3dh_uf2 ALL DEPENDS spi_lis3dh.uf2) add_dependencies(spi_lis3dh_uf2 spi_lis3dh) +set(SIM800L_AVM ${CMAKE_BINARY_DIR}/examples/erlang/sim800l.avm) +add_custom_command( + OUTPUT sim800l.uf2 + DEPENDS ${SIM800L_AVM} UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o sim800l.uf2 -f universal -s 0x10180000 ${SIM800L_AVM} + COMMENT "Creating UF2 file sim800l.uf2" + VERBATIM +) +add_custom_target(sim800l_uf2 ALL DEPENDS sim800l.uf2) +add_dependencies(sim800l_uf2 sim800l) pack_uf2(picow_blink picow_blink) pack_uf2(picow_wifi_sta picow_wifi_sta) pack_uf2(picow_wifi_ap picow_wifi_ap) diff --git a/examples/erlang/sim800l.erl b/examples/erlang/sim800l.erl new file mode 100644 index 0000000000..ee802cd7fd --- /dev/null +++ b/examples/erlang/sim800l.erl @@ -0,0 +1,180 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% 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 SIM800L AT command demo. +%% +%% Opens a UART connection to a SIM800L GSM module and verifies it responds +%% to basic AT commands. Prints firmware identification and signal quality +%% every 10 seconds. +%% +%% Be careful: SIM800L boards can draw up to 2A and shouldn't be powered by +%% the 3.3V of the usual Pico / ESP32 boards. It's ok for this demo but +%% do not put a SIM card in them to avoid damaging your board. +%% +%% The SIM800L communicates at 115200 baud (8N1) by default. +%% +%% Default pins are auto-detected from the platform and chip model: +%% +%% Pico (UART1): TX=GP4, RX=GP5 +%% ESP32/S2/S3 (UART1): TX=17, RX=16 +%% ESP32-C3/C5 (UART1): TX=4, RX=5 +%% ESP32-C6/C61 (UART1): TX=4, RX=5 +%% @end +%%----------------------------------------------------------------------------- +-module(sim800l). +-export([start/0]). + +-define(AT_TIMEOUT, 2000). + +start() -> + {TX, RX} = default_pins(), + io:format("Opening UART1 on TX=~B RX=~B~n", [TX, RX]), + UART = uart:open("UART1", [ + {tx, TX}, + {rx, RX}, + {speed, 115200} + ]), + %% SIM800L takes 3-5 seconds to boot after power-on + case wait_for_module(UART, 5) of + ok -> + io:format("SIM800L responding to AT commands~n"), + at_identify(UART), + loop(UART); + error -> + io:format("SIM800L not responding, giving up~n"), + uart:close(UART) + end. + +loop(UART) -> + at_signal_quality(UART), + timer:sleep(10000), + loop(UART). + +wait_for_module(_UART, 0) -> + error; +wait_for_module(UART, Retries) -> + drain(UART), + case at_command(UART, <<"AT">>) of + {ok, _} -> + ok; + {error, _} -> + timer:sleep(1000), + wait_for_module(UART, Retries - 1) + end. + +at_identify(UART) -> + case at_command(UART, <<"ATI">>) of + {ok, Response} -> + io:format("Module info: ~s~n", [Response]); + {error, Reason} -> + io:format("ATI failed: ~p~n", [Reason]) + end. + +at_signal_quality(UART) -> + case at_command(UART, <<"AT+CSQ">>) of + {ok, Response} -> + io:format("Signal quality: ~s~n", [Response]); + {error, Reason} -> + io:format("AT+CSQ failed: ~p~n", [Reason]) + end. + +%%----------------------------------------------------------------------------- +%% @private Send an AT command and collect the response until OK or ERROR. +%%----------------------------------------------------------------------------- +at_command(UART, Command) -> + uart:write(UART, [Command, <<"\r\n">>]), + collect_response(UART, []). + +collect_response(UART, Acc) -> + case uart:read(UART, ?AT_TIMEOUT) of + {ok, Data} -> + NewAcc = [Data | Acc], + Combined = erlang:iolist_to_binary(lists:reverse(NewAcc)), + case parse_response(Combined) of + {ok, Body} -> {ok, Body}; + error -> {error, Combined}; + incomplete -> collect_response(UART, NewAcc) + end; + {error, timeout} when Acc =/= [] -> + Combined = erlang:iolist_to_binary(lists:reverse(Acc)), + case parse_response(Combined) of + {ok, Body} -> {ok, Body}; + _ -> {error, {partial, Combined}} + end; + {error, timeout} -> + {error, timeout} + end. + +%% Look for OK or ERROR in the accumulated response +parse_response(Data) -> + case binary:match(Data, <<"\r\nOK\r\n">>) of + {_Pos, _Len} -> + Body = strip_status(Data), + {ok, Body}; + nomatch -> + case binary:match(Data, <<"\r\nERROR\r\n">>) of + {_Pos2, _Len2} -> error; + nomatch -> incomplete + end + end. + +%% Extract body between echo/first CRLF and final status line +strip_status(Data) -> + Trimmed = trim_leading_crlf(Data), + case binary:match(Trimmed, <<"\r\nOK\r\n">>) of + {Pos, _} -> binary:part(Trimmed, 0, Pos); + nomatch -> Trimmed + end. + +trim_leading_crlf(<<"\r\n", Rest/binary>>) -> trim_leading_crlf(Rest); +trim_leading_crlf(Data) -> Data. + +%% Discard any pending data in the UART buffer +drain(UART) -> + case uart:read(UART, 100) of + {ok, _} -> drain(UART); + {error, timeout} -> ok + end. + +%%----------------------------------------------------------------------------- +%% Platform-specific default pins +%%----------------------------------------------------------------------------- +default_pins() -> + default_pins(atomvm:platform()). + +%% {TX, RX} +default_pins(pico) -> {4, 5}; +default_pins(esp32) -> esp32_default_pins(). + +esp32_default_pins() -> + #{model := Model} = erlang:system_info(esp32_chip_info), + esp32_default_pins(Model). + +%% {TX, RX} +esp32_default_pins(esp32) -> {17, 16}; +esp32_default_pins(esp32_s2) -> {17, 16}; +esp32_default_pins(esp32_s3) -> {17, 16}; +esp32_default_pins(esp32_c2) -> {4, 5}; +esp32_default_pins(esp32_c3) -> {4, 5}; +esp32_default_pins(esp32_c5) -> {4, 5}; +esp32_default_pins(esp32_c6) -> {4, 5}; +esp32_default_pins(esp32_c61) -> {4, 5}; +esp32_default_pins(_) -> {17, 16}. diff --git a/libs/avm_rp2/src/CMakeLists.txt b/libs/avm_rp2/src/CMakeLists.txt index c76d8b1675..dce842b829 100644 --- a/libs/avm_rp2/src/CMakeLists.txt +++ b/libs/avm_rp2/src/CMakeLists.txt @@ -27,6 +27,7 @@ set(ERLANG_MODULES i2c pico spi + uart ) pack_archive(avm_rp2 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES}) diff --git a/libs/avm_rp2/src/uart.erl b/libs/avm_rp2/src/uart.erl new file mode 100644 index 0000000000..9982fe0298 --- /dev/null +++ b/libs/avm_rp2/src/uart.erl @@ -0,0 +1,528 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% 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 AtomVM UART interface for RP2 (Pico) +%% +%% This module provides an interface to the UART hardware on RP2 platforms. +%% +%% Two API levels are provided: +%% +%% Low-level API +%% {@link init/2}, {@link deinit/1}, {@link set_baudrate/2}, +%% {@link set_format/4}, {@link set_hw_flow/3}, {@link set_fifo_enabled/2}, +%% {@link set_break/2}, {@link is_writable/1}, {@link tx_wait_blocking/1}, +%% {@link write_blocking/2}, {@link read_blocking/2}, +%% {@link is_readable/1}, {@link is_readable_within_us/2}, +%% {@link putc/2}, {@link putc_raw/2}, {@link puts/2}, {@link getc/1}. +%% These operate on a bare resource reference returned by {@link init/2}. +%% Pin muxing must be done separately via `gpio:set_function/2'. +%% +%% High-level API (`uart_hal' behavior) +%% {@link open/1}, {@link open/2}, {@link close/1}, +%% {@link read/1}, {@link read/2}, {@link write/2}. +%% {@link open/1} handles pin setup automatically. +%% @end +%%----------------------------------------------------------------------------- +-module(uart). + +-behaviour(uart_hal). + +%% High-level API (uart_hal behaviour) +-export([ + open/1, open/2, + close/1, + read/1, read/2, + write/2 +]). + +%% Low-level API (Pico SDK) +-export([ + init/2, + deinit/1, + set_baudrate/2, + set_format/4, + set_hw_flow/3, + set_fifo_enabled/2, + set_break/2, + is_writable/1, + tx_wait_blocking/1, + write_blocking/2, + read_blocking/2, + is_readable/1, + is_readable_within_us/2, + putc/2, + putc_raw/2, + puts/2, + getc/1 +]). + +-type pin() :: non_neg_integer(). +-type freq_hz() :: non_neg_integer(). +-type peripheral() :: 0 | 1. +-type param() :: + {tx, pin()} + | {rx, pin()} + | {rts, pin()} + | {cts, pin()} + | {speed, pos_integer()} + | {data_bits, 5..8} + | {stop_bits, 1 | 2} + | {parity, none | even | odd} + | {flow_control, none | hardware} + | {peripheral, peripheral() | string() | binary()}. +-type params() :: [param()]. +-type uart_resource() :: reference(). +-type uart() :: pid(). + +-export_type([uart/0, uart_resource/0]). + +-define(DEFAULT_SPEED, 115200). +-define(DEFAULT_DATA_BITS, 8). +-define(DEFAULT_STOP_BITS, 1). +-define(DEFAULT_PARITY, none). +-define(DEFAULT_FLOW_CONTROL, none). +-define(DEFAULT_PERIPHERAL, 0). + +%% --------------------------------------------------------------------------- +%% High-level API (uart_hal behaviour) +%% --------------------------------------------------------------------------- + +%%----------------------------------------------------------------------------- +%% @param Name UART peripheral name (`"UART0"' or `"UART1"') +%% @param Params Initialization parameters +%% @returns UART handle (pid) +%% @doc Open a connection to the UART driver +%% +%% This function provides compatibility with the ESP32 UART driver +%% interface. The `Name' parameter is converted to a peripheral +%% number and prepended to the parameters. +%% @end +%%----------------------------------------------------------------------------- +-spec open(Name :: string() | binary(), Params :: params()) -> uart(). +open(Name, Params) -> + open([{peripheral, Name} | Params]). + +%%----------------------------------------------------------------------------- +%% @param Params Initialization parameters +%% @returns UART handle (pid) +%% @doc Open a connection to the UART driver +%% +%% This function configures the GPIO pins for UART function, +%% initializes the UART peripheral, and configures data format +%% and flow control. +%% +%% Supported parameters: +%% +%% @end +%%----------------------------------------------------------------------------- +-spec open(Params :: params()) -> uart(). +open(Params) -> + TX = proplists:get_value(tx, Params), + RX = proplists:get_value(rx, Params), + RTS = proplists:get_value(rts, Params, undefined), + CTS = proplists:get_value(cts, Params, undefined), + Speed = proplists:get_value(speed, Params, ?DEFAULT_SPEED), + DataBits = proplists:get_value(data_bits, Params, ?DEFAULT_DATA_BITS), + StopBits = proplists:get_value(stop_bits, Params, ?DEFAULT_STOP_BITS), + Parity = proplists:get_value(parity, Params, ?DEFAULT_PARITY), + FlowControl = proplists:get_value(flow_control, Params, ?DEFAULT_FLOW_CONTROL), + PeripheralParam = proplists:get_value(peripheral, Params, ?DEFAULT_PERIPHERAL), + Peripheral = normalize_peripheral(PeripheralParam), + gpio:set_function(TX, uart), + gpio:set_function(RX, uart), + maybe_set_uart_function(RTS), + maybe_set_uart_function(CTS), + {ok, {_ActualBaudrate, Resource}} = ?MODULE:init(Peripheral, Speed), + ?MODULE:set_format(Resource, DataBits, StopBits, Parity), + case FlowControl of + hardware -> + ?MODULE:set_hw_flow(Resource, CTS =/= undefined, RTS =/= undefined); + none -> + ok + end, + spawn_link(fun() -> loop(Resource) end). + +%%----------------------------------------------------------------------------- +%% @param UART UART handle created via `open/1' +%% @returns `ok' +%% @doc Close the connection to the UART driver and free resources. +%% @end +%%----------------------------------------------------------------------------- +-spec close(UART :: uart()) -> ok | {error, Reason :: term()}. +close(Pid) -> + call(Pid, close). + +%%----------------------------------------------------------------------------- +%% @param UART UART handle created via `open/1' +%% @returns `{ok, Data}' or `{error, timeout}' +%% @doc Read currently available data from the UART FIFO. +%% +%% Returns `{error, timeout}' immediately if no data is available. +%% @end +%%----------------------------------------------------------------------------- +-spec read(UART :: uart()) -> {ok, binary()} | {error, Reason :: term()}. +read(Pid) -> + call(Pid, read). + +%%----------------------------------------------------------------------------- +%% @param UART UART handle created via `open/1' +%% @param Timeout Timeout in milliseconds +%% @returns `{ok, Data}' or `{error, timeout}' +%% @doc Read data from the UART with a timeout. +%% +%% Waits up to `Timeout' milliseconds for data to become available. +%% Once data arrives, reads all currently available bytes from the +%% FIFO. +%% @end +%%----------------------------------------------------------------------------- +-spec read(UART :: uart(), Timeout :: pos_integer()) -> + {ok, binary()} | {error, Reason :: term()}. +read(Pid, Timeout) -> + call(Pid, {read, Timeout}). + +%%----------------------------------------------------------------------------- +%% @param UART UART handle created via `open/1' +%% @param Data iodata to write +%% @returns `ok' +%% @doc Write data to the UART. +%% @end +%%----------------------------------------------------------------------------- +-spec write(UART :: uart(), Data :: iodata()) -> ok | {error, Reason :: term()}. +write(Pid, Data) -> + case is_iolist(Data) of + true -> call(Pid, {write, Data}); + false -> error(badarg) + end. + +%% --------------------------------------------------------------------------- +%% Low-level API (Pico SDK) +%% --------------------------------------------------------------------------- + +%%----------------------------------------------------------------------------- +%% @param Peripheral UART peripheral number (0 or 1) +%% @param Baudrate Baudrate in Hz (e.g. 115200) +%% @returns `{ok, {ActualBaudrate, Resource}}' +%% @doc Initialize the UART HW block. +%% +%% Pin muxing must be done separately via `gpio:set_function/2'. +%% @end +%%----------------------------------------------------------------------------- +-spec init(Peripheral :: peripheral(), Baudrate :: freq_hz()) -> + {ok, {ActualBaudrate :: freq_hz(), Resource :: uart_resource()}}. +init(_Peripheral, _Baudrate) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @returns `ok' +%% @doc Disable the UART HW block. +%% @end +%%----------------------------------------------------------------------------- +-spec deinit(Resource :: uart_resource()) -> ok. +deinit(_Resource) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Baudrate Baudrate in Hz +%% @returns `{ok, ActualBaudrate}' +%% @doc Set UART baudrate. +%% @end +%%----------------------------------------------------------------------------- +-spec set_baudrate(Resource :: uart_resource(), Baudrate :: freq_hz()) -> + {ok, ActualBaudrate :: freq_hz()}. +set_baudrate(_Resource, _Baudrate) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param DataBits Number of data bits per character (5..8) +%% @param StopBits Number of stop bits (1 or 2) +%% @param Parity Parity setting (`none', `even', or `odd') +%% @returns `ok' +%% @doc Set UART data format. +%% @end +%%----------------------------------------------------------------------------- +-spec set_format( + Resource :: uart_resource(), DataBits :: 5..8, StopBits :: 1 | 2, Parity :: none | even | odd +) -> ok. +set_format(_Resource, _DataBits, _StopBits, _Parity) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param CTS Enable CTS flow control +%% @param RTS Enable RTS flow control +%% @returns `ok' +%% @doc Set UART hardware flow control. +%% +%% The corresponding CTS/RTS pins must have been set to UART +%% function via `gpio:set_function/2' before enabling. +%% @end +%%----------------------------------------------------------------------------- +-spec set_hw_flow(Resource :: uart_resource(), CTS :: boolean(), RTS :: boolean()) -> ok. +set_hw_flow(_Resource, _CTS, _RTS) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Data Binary data to write +%% @returns `ok' +%% @doc Write to UART, blocking until all data is sent. +%% @end +%%----------------------------------------------------------------------------- +-spec write_blocking(Resource :: uart_resource(), Data :: binary()) -> ok. +write_blocking(_Resource, _Data) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Count Number of bytes to read +%% @returns `{ok, Data}' +%% @doc Read from UART, blocking until `Count' bytes have been received. +%% @end +%%----------------------------------------------------------------------------- +-spec read_blocking(Resource :: uart_resource(), Count :: non_neg_integer()) -> + {ok, binary()}. +read_blocking(_Resource, _Count) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @returns `true' if data is available, `false' otherwise +%% @doc Check if UART has data available to read. +%% @end +%%----------------------------------------------------------------------------- +-spec is_readable(Resource :: uart_resource()) -> boolean(). +is_readable(_Resource) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Us Maximum wait time in microseconds +%% @returns `true' if data became available, `false' on timeout +%% @doc Wait for UART data with a microsecond timeout. +%% @end +%%----------------------------------------------------------------------------- +-spec is_readable_within_us(Resource :: uart_resource(), Us :: non_neg_integer()) -> boolean(). +is_readable_within_us(_Resource, _Us) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @returns `true' if space is available in TX FIFO, `false' otherwise +%% @doc Check if UART TX FIFO has space available. +%% @end +%%----------------------------------------------------------------------------- +-spec is_writable(Resource :: uart_resource()) -> boolean(). +is_writable(_Resource) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @returns `ok' +%% @doc Wait for the UART TX FIFO to be drained. +%% @end +%%----------------------------------------------------------------------------- +-spec tx_wait_blocking(Resource :: uart_resource()) -> ok. +tx_wait_blocking(_Resource) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Enabled `true' to enable FIFOs (default), `false' to disable +%% @returns `ok' +%% @doc Enable or disable the UART FIFOs. +%% @end +%%----------------------------------------------------------------------------- +-spec set_fifo_enabled(Resource :: uart_resource(), Enabled :: boolean()) -> ok. +set_fifo_enabled(_Resource, _Enabled) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Enable `true' to assert break (TX held low), `false' to clear +%% @returns `ok' +%% @doc Assert or clear a break condition on UART transmission. +%% @end +%%----------------------------------------------------------------------------- +-spec set_break(Resource :: uart_resource(), Enable :: boolean()) -> ok. +set_break(_Resource, _Enable) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Char Character to send (0..255) +%% @returns `ok' +%% @doc Write a single character with optional CR/LF conversion. +%% @end +%%----------------------------------------------------------------------------- +-spec putc(Resource :: uart_resource(), Char :: byte()) -> ok. +putc(_Resource, _Char) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param Char Character to send (0..255) +%% @returns `ok' +%% @doc Write a single character without CR/LF conversion. +%% @end +%%----------------------------------------------------------------------------- +-spec putc_raw(Resource :: uart_resource(), Char :: byte()) -> ok. +putc_raw(_Resource, _Char) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @param String Binary string to send +%% @returns `ok' +%% @doc Write a string with CR/LF conversion. +%% @end +%%----------------------------------------------------------------------------- +-spec puts(Resource :: uart_resource(), String :: binary()) -> ok. +puts(_Resource, _String) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Resource UART resource returned by `init/2' +%% @returns `{ok, Char}' where Char is 0..255 +%% @doc Read a single character, blocking until one is available. +%% @end +%%----------------------------------------------------------------------------- +-spec getc(Resource :: uart_resource()) -> {ok, byte()}. +getc(_Resource) -> + erlang:nif_error(undefined). + +%% --------------------------------------------------------------------------- +%% Internal helpers +%% --------------------------------------------------------------------------- + +%% @private +call(Pid, Request) -> + Ref = make_ref(), + Pid ! {self(), Ref, Request}, + receive + {Ref, Reply} -> Reply + end. + +%% @private +loop(Resource) -> + receive + {From, Ref, Request} -> + case handle_request(Resource, Request) of + {reply, Reply, stop} -> + From ! {Ref, Reply}; + {reply, Reply} -> + From ! {Ref, Reply}, + loop(Resource) + end + end. + +%% @private +handle_request(Resource, close) -> + ?MODULE:deinit(Resource), + {reply, ok, stop}; +handle_request(Resource, read) -> + case ?MODULE:is_readable(Resource) of + true -> + Data = read_available(Resource), + {reply, {ok, Data}}; + false -> + {reply, {error, timeout}} + end; +handle_request(Resource, {read, Timeout}) -> + Deadline = erlang:system_time(millisecond) + Timeout, + case poll_readable(Resource, Deadline) of + true -> + Data = read_available(Resource), + {reply, {ok, Data}}; + false -> + {reply, {error, timeout}} + end; +handle_request(Resource, {write, Data}) -> + Bin = erlang:iolist_to_binary(Data), + ?MODULE:write_blocking(Resource, Bin), + {reply, ok}. + +%% @private +read_available(Resource) -> + read_available(Resource, []). + +%% @private +read_available(Resource, Acc) -> + {ok, Byte} = ?MODULE:read_blocking(Resource, 1), + case ?MODULE:is_readable(Resource) of + true -> + read_available(Resource, [Byte | Acc]); + false -> + erlang:iolist_to_binary(lists:reverse([Byte | Acc])) + end. + +%% @private +poll_readable(Resource, Deadline) -> + case ?MODULE:is_readable_within_us(Resource, 1000) of + true -> + true; + false -> + case erlang:system_time(millisecond) >= Deadline of + true -> false; + false -> poll_readable(Resource, Deadline) + end + end. + +%% @private +normalize_peripheral(N) when is_integer(N) -> N; +normalize_peripheral("UART0") -> 0; +normalize_peripheral("UART1") -> 1; +normalize_peripheral(<<"UART0">>) -> 0; +normalize_peripheral(<<"UART1">>) -> 1. + +%% @private +maybe_set_uart_function(undefined) -> ok; +maybe_set_uart_function(Pin) -> gpio:set_function(Pin, uart). + +%% @private +is_iolist([]) -> + true; +is_iolist(B) when is_binary(B) -> + true; +is_iolist(I) when is_integer(I) andalso 0 =< I andalso I =< 255 -> + true; +is_iolist([H | T]) -> + case is_iolist(H) of + true -> + is_iolist(T); + false -> + false + end; +is_iolist(_) -> + false. diff --git a/libs/eavmlib/src/uart_hal.erl b/libs/eavmlib/src/uart_hal.erl index 1dc8cbf9de..83349fed07 100644 --- a/libs/eavmlib/src/uart_hal.erl +++ b/libs/eavmlib/src/uart_hal.erl @@ -26,7 +26,7 @@ %% Asynchronous Receiver-Transmitter) operations across all supported %% platforms. %% -%% Currently, only ESP32 provides a UART implementation. +%% ESP32 and RP2 platforms provide UART implementations. %% %%

Lifecycle

%% diff --git a/src/platforms/rp2/src/lib/CMakeLists.txt b/src/platforms/rp2/src/lib/CMakeLists.txt index 7e0e535a4d..7410586f04 100644 --- a/src/platforms/rp2/src/lib/CMakeLists.txt +++ b/src/platforms/rp2/src/lib/CMakeLists.txt @@ -33,6 +33,7 @@ set(SOURCE_FILES gpiodriver.c i2cdriver.c spidriver.c + uartdriver.c networkdriver.c otp_crypto_platform.c platform_defaultatoms.c @@ -62,6 +63,7 @@ target_link_libraries( hardware_gpio hardware_i2c hardware_spi + hardware_uart hardware_sync pico_float pico_mbedtls @@ -126,4 +128,4 @@ if (NOT AVM_DISABLE_JIT) target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,jit_stream_flash_get_nif") endif() -target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif -Wl,-u -Wl,i2c_nif -Wl,-u -Wl,spi_nif -Wl,-u -Wl,otp_crypto_nif") +target_link_options(libAtomVM${PLATFORM_LIB_SUFFIX} PUBLIC "SHELL:-Wl,-u -Wl,gpio_nif -Wl,-u -Wl,i2c_nif -Wl,-u -Wl,spi_nif -Wl,-u -Wl,uart_nif -Wl,-u -Wl,otp_crypto_nif") diff --git a/src/platforms/rp2/src/lib/uartdriver.c b/src/platforms/rp2/src/lib/uartdriver.c new file mode 100644 index 0000000000..a5f1ba4ede --- /dev/null +++ b/src/platforms/rp2/src/lib/uartdriver.c @@ -0,0 +1,570 @@ +/* + * This file is part of AtomVM. + * + * Copyright 2026 Paul Guyot + * + * 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 + */ + +#include +#include +#include + +#include + +#include "context.h" +#include "defaultatoms.h" +#include "erl_nif.h" +#include "erl_nif_priv.h" +#include "globalcontext.h" +#include "interop.h" +#include "memory.h" +#include "nifs.h" +#include "rp2_sys.h" +#include "term.h" + +// #define ENABLE_TRACE +#include "trace.h" + +#define NUM_UART_INSTANCES 2 + +static ErlNifResourceType *uart_resource_type; + +struct UARTResource +{ + uart_inst_t *uart_inst; +}; + +static term create_pair(Context *ctx, term term1, term term2) +{ + term ret = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(ret, 0, term1); + term_put_tuple_element(ret, 1, term2); + return ret; +} + +static bool get_uart_resource(Context *ctx, term resource_term, struct UARTResource **rsrc_obj) +{ + void *rsrc_obj_ptr; + if (UNLIKELY(!enif_get_resource(erl_nif_env_from_context(ctx), resource_term, uart_resource_type, &rsrc_obj_ptr))) { + return false; + } + *rsrc_obj = (struct UARTResource *) rsrc_obj_ptr; + return true; +} + +static term nif_uart_init(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + VALIDATE_VALUE(argv[0], term_is_integer); + VALIDATE_VALUE(argv[1], term_is_integer); + + int peripheral = term_to_int(argv[0]); + if (UNLIKELY(peripheral < 0 || peripheral >= NUM_UART_INSTANCES)) { + RAISE_ERROR(BADARG_ATOM); + } + + uint baudrate = (uint) term_to_int(argv[1]); + uart_inst_t *inst = uart_get_instance((uint) peripheral); + + uint actual_baudrate = uart_init(inst, baudrate); + + struct UARTResource *rsrc_obj = enif_alloc_resource(uart_resource_type, sizeof(struct UARTResource)); + if (IS_NULL_PTR(rsrc_obj)) { + uart_deinit(inst); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + rsrc_obj->uart_inst = inst; + + if (UNLIKELY(memory_ensure_free(ctx, TERM_BOXED_RESOURCE_SIZE) != MEMORY_GC_OK)) { + uart_deinit(inst); + enif_release_resource(rsrc_obj); + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term obj = term_from_resource(rsrc_obj, &ctx->heap); + enif_release_resource(rsrc_obj); + + size_t requested_size = TUPLE_SIZE(2) + TUPLE_SIZE(2); + if (UNLIKELY(memory_ensure_free_with_roots(ctx, requested_size, 1, &obj, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + + term inner = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(inner, 0, term_from_int(actual_baudrate)); + term_put_tuple_element(inner, 1, obj); + + return create_pair(ctx, OK_ATOM, inner); +} + +static term nif_uart_deinit(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + + if (IS_NULL_PTR(rsrc_obj->uart_inst)) { + RAISE_ERROR(BADARG_ATOM); + } + uart_deinit(rsrc_obj->uart_inst); + rsrc_obj->uart_inst = NULL; + + return OK_ATOM; +} + +static term nif_uart_set_baudrate(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + + uint baudrate = (uint) term_to_int(argv[1]); + uint actual = uart_set_baudrate(rsrc_obj->uart_inst, baudrate); + + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + return create_pair(ctx, OK_ATOM, term_from_int(actual)); +} + +static term nif_uart_set_format(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + VALIDATE_VALUE(argv[2], term_is_integer); + VALIDATE_VALUE(argv[3], term_is_atom); + + uint data_bits = (uint) term_to_int(argv[1]); + uint stop_bits = (uint) term_to_int(argv[2]); + + term parity_term = argv[3]; + uart_parity_t parity; + if (parity_term == globalcontext_make_atom(ctx->global, ATOM_STR("\x4", "none"))) { + parity = UART_PARITY_NONE; + } else if (parity_term == globalcontext_make_atom(ctx->global, ATOM_STR("\x4", "even"))) { + parity = UART_PARITY_EVEN; + } else if (parity_term == globalcontext_make_atom(ctx->global, ATOM_STR("\x3", "odd"))) { + parity = UART_PARITY_ODD; + } else { + RAISE_ERROR(BADARG_ATOM); + } + + uart_set_format(rsrc_obj->uart_inst, data_bits, stop_bits, parity); + + return OK_ATOM; +} + +static term nif_uart_set_hw_flow(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_atom); + VALIDATE_VALUE(argv[2], term_is_atom); + + bool cts = (argv[1] == TRUE_ATOM); + bool rts = (argv[2] == TRUE_ATOM); + + uart_set_hw_flow(rsrc_obj->uart_inst, cts, rts); + + return OK_ATOM; +} + +static term nif_uart_write_blocking(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_binary); + + const uint8_t *data = (const uint8_t *) term_binary_data(argv[1]); + size_t len = term_binary_size(argv[1]); + + uart_write_blocking(rsrc_obj->uart_inst, data, len); + + return OK_ATOM; +} + +static term nif_uart_is_readable(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + + bool readable = uart_is_readable(rsrc_obj->uart_inst); + return readable ? TRUE_ATOM : FALSE_ATOM; +} + +static term nif_uart_read_blocking(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + + avm_int_t count = term_to_int(argv[1]); + if (UNLIKELY(count < 0)) { + RAISE_ERROR(BADARG_ATOM); + } + + if (UNLIKELY(memory_ensure_free_opt(ctx, TUPLE_SIZE(2) + term_binary_heap_size(count), MEMORY_NO_GC) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term data = term_create_uninitialized_binary(count, &ctx->heap, ctx->global); + uint8_t *buf = (uint8_t *) term_binary_data(data); + + uart_read_blocking(rsrc_obj->uart_inst, buf, (size_t) count); + + return create_pair(ctx, OK_ATOM, data); +} + +static term nif_uart_is_writable(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + + bool writable = uart_is_writable(rsrc_obj->uart_inst); + return writable ? TRUE_ATOM : FALSE_ATOM; +} + +static term nif_uart_tx_wait_blocking(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + + uart_tx_wait_blocking(rsrc_obj->uart_inst); + + return OK_ATOM; +} + +static term nif_uart_set_fifo_enabled(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_atom); + + bool enabled = (argv[1] == TRUE_ATOM); + uart_set_fifo_enabled(rsrc_obj->uart_inst, enabled); + + return OK_ATOM; +} + +static term nif_uart_set_break(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_atom); + + bool en = (argv[1] == TRUE_ATOM); + uart_set_break(rsrc_obj->uart_inst, en); + + return OK_ATOM; +} + +static term nif_uart_putc_raw(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + + char c = (char) term_to_int(argv[1]); + uart_putc_raw(rsrc_obj->uart_inst, c); + + return OK_ATOM; +} + +static term nif_uart_putc(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + + char c = (char) term_to_int(argv[1]); + uart_putc(rsrc_obj->uart_inst, c); + + return OK_ATOM; +} + +static term nif_uart_puts(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_binary); + + size_t len = term_binary_size(argv[1]); + const char *data = term_binary_data(argv[1]); + for (size_t i = 0; i < len; i++) { + uart_putc(rsrc_obj->uart_inst, data[i]); + } + + return OK_ATOM; +} + +static term nif_uart_getc(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + + char c = uart_getc(rsrc_obj->uart_inst); + + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + return create_pair(ctx, OK_ATOM, term_from_int((uint8_t) c)); +} + +static term nif_uart_is_readable_within_us(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + struct UARTResource *rsrc_obj; + if (UNLIKELY(!get_uart_resource(ctx, argv[0], &rsrc_obj))) { + RAISE_ERROR(BADARG_ATOM); + } + VALIDATE_VALUE(argv[1], term_is_integer); + + uint32_t us = (uint32_t) term_to_int(argv[1]); + bool readable = uart_is_readable_within_us(rsrc_obj->uart_inst, us); + return readable ? TRUE_ATOM : FALSE_ATOM; +} + +static void uart_resource_dtor(ErlNifEnv *caller_env, void *obj) +{ + UNUSED(caller_env); + struct UARTResource *rsrc_obj = (struct UARTResource *) obj; + if (!IS_NULL_PTR(rsrc_obj->uart_inst)) { + uart_deinit(rsrc_obj->uart_inst); + rsrc_obj->uart_inst = NULL; + } +} + +static const ErlNifResourceTypeInit UARTResourceTypeInit = { + .members = 1, + .dtor = uart_resource_dtor, +}; + +// +// NIF structs +// +static const struct Nif uart_init_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_init +}; +static const struct Nif uart_deinit_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_deinit +}; +static const struct Nif uart_set_baudrate_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_set_baudrate +}; +static const struct Nif uart_set_format_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_set_format +}; +static const struct Nif uart_set_hw_flow_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_set_hw_flow +}; +static const struct Nif uart_write_blocking_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_write_blocking +}; +static const struct Nif uart_is_readable_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_is_readable +}; +static const struct Nif uart_read_blocking_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_read_blocking +}; +static const struct Nif uart_is_writable_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_is_writable +}; +static const struct Nif uart_tx_wait_blocking_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_tx_wait_blocking +}; +static const struct Nif uart_set_fifo_enabled_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_set_fifo_enabled +}; +static const struct Nif uart_set_break_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_set_break +}; +static const struct Nif uart_putc_raw_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_putc_raw +}; +static const struct Nif uart_putc_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_putc +}; +static const struct Nif uart_puts_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_puts +}; +static const struct Nif uart_getc_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_getc +}; +static const struct Nif uart_is_readable_within_us_nif = { + .base.type = NIFFunctionType, + .nif_ptr = nif_uart_is_readable_within_us +}; + +static void uart_nif_init(GlobalContext *global) +{ + ErlNifEnv env; + erl_nif_env_partial_init_from_globalcontext(&env, global); + uart_resource_type = enif_init_resource_type(&env, "uart_resource", &UARTResourceTypeInit, ERL_NIF_RT_CREATE, NULL); +} + +static const struct Nif *uart_nif_get_nif(const char *nifname) +{ + if (strncmp("uart:", nifname, 5) != 0) { + return NULL; + } + const char *rest = nifname + 5; + if (strcmp("init/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_init_nif; + } + if (strcmp("deinit/1", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_deinit_nif; + } + if (strcmp("set_baudrate/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_set_baudrate_nif; + } + if (strcmp("set_format/4", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_set_format_nif; + } + if (strcmp("set_hw_flow/3", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_set_hw_flow_nif; + } + if (strcmp("write_blocking/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_write_blocking_nif; + } + if (strcmp("is_readable/1", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_is_readable_nif; + } + if (strcmp("read_blocking/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_read_blocking_nif; + } + if (strcmp("is_writable/1", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_is_writable_nif; + } + if (strcmp("tx_wait_blocking/1", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_tx_wait_blocking_nif; + } + if (strcmp("set_fifo_enabled/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_set_fifo_enabled_nif; + } + if (strcmp("set_break/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_set_break_nif; + } + if (strcmp("putc_raw/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_putc_raw_nif; + } + if (strcmp("putc/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_putc_nif; + } + if (strcmp("puts/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_puts_nif; + } + if (strcmp("getc/1", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_getc_nif; + } + if (strcmp("is_readable_within_us/2", rest) == 0) { + TRACE("Resolved uart nif %s ...\n", nifname); + return &uart_is_readable_within_us_nif; + } + return NULL; +} + +REGISTER_NIF_COLLECTION(uart, uart_nif_init, NULL, uart_nif_get_nif)