Skip to content

Commit

Permalink
Add read with timout to uart driver
Browse files Browse the repository at this point in the history
Adds a new uart:read/2 that take a timout parameter for reads. If no data is received during the
timout period `{error, timeout}` is returned, and the listener is removed allowing for another read
without getting the `{error, ealready}` error tuple.

Closes atomvm#1446

Signed-off-by: Winford <[email protected]>
  • Loading branch information
UncleGrumpy committed Feb 2, 2025
1 parent 5184636 commit 687d360
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 2 deletions.
31 changes: 30 additions & 1 deletion libs/eavmlib/src/uart.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
%

-module(uart).
-export([open/1, open/2, close/1, read/1, write/2]).
-export([open/1, open/2, close/1, read/1, read/2, write/2]).

-type uart_opts() :: [
{tx, _Tx_pin :: integer()}
Expand Down Expand Up @@ -101,6 +101,35 @@ close(Pid) when is_pid(Pid) ->
read(Pid) when is_pid(Pid) ->
port:call(Pid, read).

%%-----------------------------------------------------------------------------
%% @param Pid of the uart port to be read
%% @param Timeout millisecond to wait for data to become available
%% @returns {ok, Data} io_list or {error, Reason}
%% @doc Read data from a UART port
%%
%% This function will return any data that is available within the
%% timeout period to the process. After the timeout has expired a new
%% read command may be used regardless of whether the last read was
%% sent a payload.
%% Example:
%% ```
%% Data = case uart:read(Uart, 3000) of
%% {ok, Bin} -> Bin;
%% {error, timeout} -> timeout
%% end,
%% '''
%% Any data sent to the esp32 over uart between reads with a timeout will
%% be lost, so be sure this is what you want. Most applications will want
%% a single process to read from UART and continue to listen until a payload
%% is received, and likely pass the payload off for processing and
%% immediately begin another read.
%% @end
%%-----------------------------------------------------------------------------
-spec read(Pid :: pid(), Timeout :: integer()) ->
{ok, Data :: binary()} | {error, _Reason :: term()}.
read(Pid, Timeout) when is_pid(Pid) ->
port:call(Pid, {read, Timeout - 1}, Timeout).

%%-----------------------------------------------------------------------------
%% @param Pid of the uart port to be written to
%% @param Data to be written to the given uart port
Expand Down
2 changes: 1 addition & 1 deletion src/platforms/esp32/components/avm_builtins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ endif()
idf_component_register(
SRCS ${AVM_BUILTIN_COMPONENT_SRCS}
INCLUDE_DIRS "include"
PRIV_REQUIRES "libatomvm" "avm_sys" "nvs_flash" "driver" "esp_event" "esp_wifi" "fatfs" ${ADDITIONAL_PRIV_REQUIRES}
PRIV_REQUIRES "libatomvm" "avm_sys" "nvs_flash" "driver" "esp_event" "esp_timer" "esp_wifi" "fatfs" ${ADDITIONAL_PRIV_REQUIRES}
${OPTIONAL_WHOLE_ARCHIVE}
)

Expand Down
29 changes: 29 additions & 0 deletions src/platforms/esp32/components/avm_builtins/uart_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <driver/uart.h>

#include <esp_log.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

Expand Down Expand Up @@ -65,6 +66,7 @@ struct UARTData
term reader_process_pid;
uint64_t reader_ref_ticks;
uint8_t uart_num;
uint64_t timeout;
};

static const AtomStringIntPair parity_table[] = {
Expand Down Expand Up @@ -96,10 +98,27 @@ static const AtomStringIntPair cmd_table[] = {
SELECT_INT_DEFAULT(UARTInvalidCmd)
};

static void vTaskReadTimeout(void * pvParameters)
{
struct UARTData *uart_data = (struct UARTData *) pvParameters;
while (esp_timer_get_time() < uart_data->timeout) {
vTaskDelay(1);
}
uart_data->reader_process_pid = term_invalid_term();
uart_data->reader_ref_ticks = 0;
vTaskDelete(NULL);
}

EventListener *uart_interrupt_callback(GlobalContext *glb, EventListener *listener)
{
struct UARTData *uart_data = GET_LIST_ENTRY(listener, struct UARTData, listener);

uint64_t timenow = esp_timer_get_time();
if (uart_data->timeout < timenow) {
uart_data->reader_process_pid = term_invalid_term();
uart_data->reader_ref_ticks = 0;
}

uart_event_t event;
if (xQueueReceive(uart_data->rxqueue, (void *) &event, (TickType_t) portMAX_DELAY)) {
switch (event.type) {
Expand Down Expand Up @@ -304,6 +323,7 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
struct UARTData *uart_data = ctx->platform_data;
term pid = gen_message.pid;
term ref = gen_message.ref;
term msg = gen_message.req;
uint64_t ref_ticks = term_to_ref_ticks(ref);

int local_pid = term_to_local_process_id(pid);
Expand Down Expand Up @@ -342,6 +362,15 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
port_send_reply(ctx, pid, ref, ok_tuple);

} else {
if (term_is_tuple(msg)) {
term timeover = term_get_tuple_element(msg, 1);
if (UNLIKELY(!term_is_integer(timeover))) {
globalcontext_send_message(glb, local_pid, BADARG_ATOM);
}
uint64_t timeout = ((uint64_t) term_to_int(timeover) * 1000u) + esp_timer_get_time();
uart_data->timeout = timeout;
xTaskCreate(vTaskReadTimeout, "timeout", 1000, uart_data, 1, NULL);
}
uart_data->reader_process_pid = pid;
uart_data->reader_ref_ticks = ref_ticks;
}
Expand Down

0 comments on commit 687d360

Please sign in to comment.