From a0eff12f173e7ab522f43cf0aacc3ef5a6633e4f Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Thu, 29 Jun 2023 14:26:28 -0500 Subject: [PATCH] UART demo, making UART read easier. --- gpio/uart_demo/README.md | 59 +++++++ gpio/uart_demo/application.fam | 10 ++ gpio/uart_demo/ring_buffer.c | 166 +++++++++++++++++++ gpio/uart_demo/ring_buffer.h | 88 ++++++++++ gpio/uart_demo/uart_demo.c | 142 ++++++++++++++++ gpio/uart_demo/uart_demo_10px.png | Bin 0 -> 7293 bytes gpio/uart_demo/uart_helper.c | 266 ++++++++++++++++++++++++++++++ gpio/uart_demo/uart_helper.h | 75 +++++++++ 8 files changed, 806 insertions(+) create mode 100644 gpio/uart_demo/README.md create mode 100644 gpio/uart_demo/application.fam create mode 100644 gpio/uart_demo/ring_buffer.c create mode 100644 gpio/uart_demo/ring_buffer.h create mode 100644 gpio/uart_demo/uart_demo.c create mode 100644 gpio/uart_demo/uart_demo_10px.png create mode 100644 gpio/uart_demo/uart_helper.c create mode 100644 gpio/uart_demo/uart_helper.h diff --git a/gpio/uart_demo/README.md b/gpio/uart_demo/README.md new file mode 100644 index 0000000..a314cc6 --- /dev/null +++ b/gpio/uart_demo/README.md @@ -0,0 +1,59 @@ +# UART_DEMO +This is a demonstration of using the UART to read (RX) and transmit (TX) data. You can connect +pins RX and TX together with a wire or resistor to create a loopback. If you have a second +Flipper Zero then you can connect TX of the first Flipper Zero to RX of the second and the RX of +the first to TX of the second. You also should connect one of the ground pins from the first +Flipper Zero to the second Flipper Zero. + +When you run the demo application, you will see three options. +- The first option "Clear" will clear the menu. +- The second option "Seng Msg 1" will send "Hello World!" follow by new-line delimiter. +- The third option "Seng Msg 2" will send "Testing 123." follow by new-line delimiter. + +Whenever data is received and the delimiter is detected, the new text will be added as an additional +entry in the menu. + +# How it works +uart_demo.c is the main class that you should look at. The goal was to simplify all +the complexity of worker threads and buffers into other files that you should not have +to look at. + +We include uart_helper.h... +```c +#include "uart_helper.h" +``` + +We create a callback to get invoked whenever a new line (text+delimiter) is detected... +```c +// This callback will be invoked whenever a new line is read on the UART! +static void uart_demo_process_line(FuriString* line, void* context) { + // For the demo, we have code to add the line to the submenu. +} +``` + +We configure the UartHelper as follows. We specify the baud rate, the delimiter of our +message, if we want the delimiter included in our callback data, and which callback to +invoke. When data is received by the UART it will be added to a ring buffer. Once the +delimiter is detected in the data, our callback will be invoked with the received data. +The UartHelper uses worker threads and ring buffers to do the processing, but all we need +to do is call ``uart_helper_alloc`` to start receiving data. +```c +#define DEVICE_BAUDRATE 115200 +#define LINE_DELIMITER '\n' +#define INCLUDE_LINE_DELIMITER false + +app->uart_helper = uart_helper_alloc(); +uart_helper_set_baud_rate(app->uart_helper, DEVICE_BAUDRATE); +uart_helper_set_delimiter(app->uart_helper, LINE_DELIMITER, INCLUDE_LINE_DELIMITER); +uart_helper_set_callback(app->uart_helper, uart_demo_process_line, app); +``` + +We can also send a string using two helper methods... +```c +// Send a c-style string +uart_helper_send(app->uart_helper, "Hello World!\n", 13); + +// Send a FuriString +uart_helper_send_string(app->uart_helper, my_furi_string); +``` + diff --git a/gpio/uart_demo/application.fam b/gpio/uart_demo/application.fam new file mode 100644 index 0000000..1d31fb7 --- /dev/null +++ b/gpio/uart_demo/application.fam @@ -0,0 +1,10 @@ +App( + appid="uart_demo", + name="UART Demo", + apptype=FlipperAppType.EXTERNAL, + entry_point="uart_demo_main", + requires=["gui"], + stack_size=1 * 1024, + fap_icon="uart_demo_10px.png", + fap_category="GPIO", +) diff --git a/gpio/uart_demo/ring_buffer.c b/gpio/uart_demo/ring_buffer.c new file mode 100644 index 0000000..c8ced8b --- /dev/null +++ b/gpio/uart_demo/ring_buffer.c @@ -0,0 +1,166 @@ +/** + * A ring buffer. This is a circular buffer that can be used to store data until a + * delimiter is found, at which point the line can be extracted. The ring buffer is a + * fixed size and will overwrite old data if the buffer is full. + * + * @author CodeAllNight +*/ + +#include + +/** + * The size of the ring buffer. This is the maximum number of bytes that can be stored in the + * buffer. If the buffer is full, the oldest data will be overwritten. +*/ +static const uint32_t ring_buffer_size = 4096; + +/** + * A ring buffer. This is used to store data received from the UART. The data is stored in a + * ring buffer so that it can be processed in the background while the main loop is doing other + * things. +*/ +typedef struct { + // The delimiter character + char delimiter; + + // If true, the delimiter will be included in the extracted line + bool include_delimiter; + + // The ring buffer. This is a circular buffer that can be used to store data until a + // delimiter is found, at which point the line can be extracted. The ring buffer is a + // fixed size and will overwrite old data if the buffer is full. + uint8_t* ring_buffer; + + // The next index to read from the ring buffer. An empty buffer will have + // ring_buffer_read == ring_buffer_write + size_t ring_buffer_read; + + // The next index to write to the ring buffer + size_t ring_buffer_write; +} RingBuffer; + +RingBuffer* ring_buffer_alloc() { + RingBuffer* buffer = malloc(sizeof(RingBuffer)); + buffer->ring_buffer = malloc(ring_buffer_size); + buffer->ring_buffer_read = 0; + buffer->ring_buffer_write = 0; + buffer->delimiter = '\n'; + buffer->include_delimiter = false; + return buffer; +} + +void ring_buffer_free(RingBuffer* buffer) { + free(buffer->ring_buffer); + free(buffer); +} + +void ring_buffer_set_delimiter(RingBuffer* rb, char delimiter, bool include_delimiter) { + rb->delimiter = delimiter; + rb->include_delimiter = include_delimiter; +} + +size_t ring_buffer_available(RingBuffer* rb) { + size_t available; + if(rb->ring_buffer_write == rb->ring_buffer_read) { + // Empty buffer has size - 1 available bytes + available = ring_buffer_size - 1; + } else if(rb->ring_buffer_read > rb->ring_buffer_write) { + // Write can go up to read - 1 + available = rb->ring_buffer_read - rb->ring_buffer_write; + } else { + // Write can go up to end of buffer, then from start to read - 1 + available = (ring_buffer_size - rb->ring_buffer_write) + rb->ring_buffer_read; + } + return available; +} + +bool ring_buffer_add(RingBuffer* rb, uint8_t* data, size_t length) { + bool hasDelim = false; + + for(size_t i = 0; i < length; i++) { + // Copy the data into the ring buffer + rb->ring_buffer[rb->ring_buffer_write] = data[i]; + + // Check if the data is the delimiter + if(data[i] == (uint8_t)rb->delimiter) { + hasDelim = true; + } + + // Update the write pointer, wrapping if necessary + if(++rb->ring_buffer_write >= ring_buffer_size) { + rb->ring_buffer_write = 0; + } + + // Check if the buffer is full + if(rb->ring_buffer_write == rb->ring_buffer_read) { + // ERROR: buffer is full, discard oldest byte (read index) + if(++rb->ring_buffer_read >= ring_buffer_size) { + rb->ring_buffer_read = 0; + } + } + } + + return hasDelim; +} + +size_t ring_buffer_find_delim(RingBuffer* rb) { + size_t index = FURI_STRING_FAILURE; + + // Search for the delimiter, starting at the read index + size_t i = rb->ring_buffer_read; + + // While the buffer is not empty and the delimiter has not been found + while(i != rb->ring_buffer_write) { + // Check if the current byte is the delimiter + if(rb->ring_buffer[i] == (uint8_t)rb->delimiter) { + // Found the delimiter + index = i; + break; + } + + // Update the index, wrapping if necessary + if(++i >= ring_buffer_size) { + i = 0; + } + } + + return index; +} + +void ring_buffer_extract_line(RingBuffer* rb, size_t delim_index, FuriString* line) { + if(delim_index > rb->ring_buffer_read) { + // line is in one chunk + furi_string_set_strn( + line, + (char*)&rb->ring_buffer[rb->ring_buffer_read], + delim_index - rb->ring_buffer_read + (rb->include_delimiter ? 1 : 0)); + } else { + // line is split across the buffer wrap, so we need to copy it in two chunks + // first chunk is from read index to end of buffer + furi_string_set_strn( + line, + (char*)&rb->ring_buffer[rb->ring_buffer_read], + ring_buffer_size - rb->ring_buffer_read); + + // second chunk is from start of buffer to delimiter + for(size_t i = 0; i < delim_index; i++) { + furi_string_push_back(line, (char)rb->ring_buffer[i]); + } + + // add the delimiter if required + if(rb->include_delimiter) { + furi_string_push_back(line, (char)rb->ring_buffer[delim_index]); + } + } + + // update the buffer read pointer, wrapping if necessary + rb->ring_buffer_read = delim_index + 1; + if(rb->ring_buffer_read >= ring_buffer_size) { + rb->ring_buffer_read = 0; + } +} + +void ring_buffer_clear(RingBuffer* rb) { + rb->ring_buffer_read = 0; + rb->ring_buffer_write = 0; +} \ No newline at end of file diff --git a/gpio/uart_demo/ring_buffer.h b/gpio/uart_demo/ring_buffer.h new file mode 100644 index 0000000..be0f025 --- /dev/null +++ b/gpio/uart_demo/ring_buffer.h @@ -0,0 +1,88 @@ +/** + * A ring buffer. This is a circular buffer that can be used to store data until a + * delimiter is found, at which point the line can be extracted. The ring buffer is a + * fixed size and will overwrite old data if the buffer is full. + * + * @author CodeAllNight +*/ + +#include + +/** + * Ring buffer structure +*/ +typedef struct RingBuffer RingBuffer; + +/** + * Allocates a new ring buffer. This is a circular buffer that can be used to store data + * until a delimiter is found, at which point the line can be extracted. The ring buffer + * is a fixed size and will overwrite old data if the buffer is full. + * + * @return A new ring buffer +*/ +RingBuffer* ring_buffer_alloc(); + +/** + * Frees a ring buffer. + * + * @param rb The ring buffer to free +*/ +void ring_buffer_free(RingBuffer* rb); + +/** + * Sets the delimiter for the ring buffer. The delimiter is the character that will be + * searched for when extracting a line from the buffer. + * + * @param rb The ring buffer + * @param delimiter The delimiter character + * @param include_delimiter If true, the delimiter will be included in the extracted line +*/ +void ring_buffer_set_delimiter(RingBuffer* rb, char delimiter, bool include_delimiter); + +/** + * Returns the number of bytes available in the ring buffer. This is the number of bytes + * that can be added to the buffer before it is full. + * + * @param rb The ring buffer + * + * @return The number of bytes available in the ring buffer +*/ +size_t ring_buffer_available(RingBuffer* rb); + +/** + * Adds data to the ring buffer. If the buffer is full, the oldest data will be overwritten. + * + * @param rb The ring buffer + * @param data The data to add + * @param length The length of the data to add + * + * @return True if the data was added successfully, false if the buffer is full +*/ +bool ring_buffer_add(RingBuffer* rb, uint8_t* data, size_t length); + +/** + * Searches the ring buffer for the delimiter. If the delimiter is found, the index of the + * delimiter is returned. If the delimiter is not found, the function returns FURI_STRING_FAILURE + * (-1). + * + * @param rb The ring buffer + * + * @return The index of the delimiter, or FURI_STRING_FAILURE if the delimiter is not found +*/ +size_t ring_buffer_find_delim(RingBuffer* rb); + +/** + * Extracts a line from the ring buffer. The line is stored in the provided FuriString. + * + * @param rb The ring buffer + * @param delim_index The index of the delimiter + * @param line The FuriString to store the line in +*/ +void ring_buffer_extract_line(RingBuffer* rb, size_t delim_index, FuriString* line); + +/** + * Clears the ring buffer. This will remove all data from the buffer. + * + * @param rb The ring buffer +*/ +void ring_buffer_clear(RingBuffer* rb); \ No newline at end of file diff --git a/gpio/uart_demo/uart_demo.c b/gpio/uart_demo/uart_demo.c new file mode 100644 index 0000000..fac7d4b --- /dev/null +++ b/gpio/uart_demo/uart_demo.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +#include + +#include "uart_helper.h" + +#define DEVICE_BAUDRATE 115200 +#define LINE_DELIMITER '\n' +#define INCLUDE_LINE_DELIMITER false + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + uint32_t index; + UartHelper* uart_helper; + FuriString* message2; +} UartDemoApp; + +typedef enum { + UartDemoSubMenuViewId = 1, +} UartDemoViewIds; + +/** + * This callback function is called when a submenu item is clicked. + * + * @param context The context passed to the submenu. + * @param index The index of the submenu item that was clicked. +*/ +static void uart_demo_submenu_item_callback(void* context, uint32_t index); + +/** + * Adds the default submenu entries. + * + * @param submenu The submenu. + * @param context The context to pass to the submenu item callback function. +*/ +static void uart_demo_submenu_add_default_entries(Submenu* submenu, void* context) { + UartDemoApp* app = context; + submenu_reset(submenu); + submenu_add_item(submenu, "Clear", 0, uart_demo_submenu_item_callback, context); + submenu_add_item(submenu, "Send Msg 1", 1, uart_demo_submenu_item_callback, context); + submenu_add_item(submenu, "Send Msg 2", 2, uart_demo_submenu_item_callback, context); + + app->index = 3; +} + +static void uart_demo_submenu_item_callback(void* context, uint32_t index) { + UartDemoApp* app = context; + + if(index == 0) { + // Clear the submenu and add the default entries. + uart_demo_submenu_add_default_entries(app->submenu, app); + } else if(index == 1) { + // Send a "Hello World!" message over the UART. + uart_helper_send(app->uart_helper, "Hello World!\n", 13); + } else if(index == 2) { + furi_string_printf(app->message2, "Index is %ld.\n", app->index); + uart_helper_send_string(app->uart_helper, app->message2); + } else { + // The item was received data. + } +} + +static void uart_demo_process_line(FuriString* line, void* context) { + UartDemoApp* app = context; + submenu_add_item( + app->submenu, + furi_string_get_cstr(line), + app->index++, + uart_demo_submenu_item_callback, + app); +} + +static bool uart_demo_navigation_callback(void* context) { + UNUSED(context); + // We don't want to handle any navigation events, the back button should exit the app. + return true; +} + +static uint32_t uart_demo_exit(void* context) { + UNUSED(context); + // Exit the app. + return VIEW_NONE; +} + +static UartDemoApp* uart_demo_app_alloc() { + UartDemoApp* app = malloc(sizeof(UartDemoApp)); + + // Initialize the GUI. Create a view dispatcher and attach it to the GUI. + // Create a submenu, add default entries and add the submenu to the view + // dispatcher. Set the submenu as the current view. + app->gui = furi_record_open(RECORD_GUI); + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + app->submenu = submenu_alloc(); + uart_demo_submenu_add_default_entries(app->submenu, app); + view_dispatcher_add_view( + app->view_dispatcher, UartDemoSubMenuViewId, submenu_get_view(app->submenu)); + view_set_previous_callback(submenu_get_view(app->submenu), uart_demo_exit); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, uart_demo_navigation_callback); + view_dispatcher_switch_to_view(app->view_dispatcher, UartDemoSubMenuViewId); + + // Allocate a string to store the second message. + app->message2 = furi_string_alloc(); + + // Initialize the UART helper. + app->uart_helper = uart_helper_alloc(); + uart_helper_set_baud_rate(app->uart_helper, DEVICE_BAUDRATE); + uart_helper_set_delimiter(app->uart_helper, LINE_DELIMITER, INCLUDE_LINE_DELIMITER); + uart_helper_set_callback(app->uart_helper, uart_demo_process_line, app); + + return app; +} + +static void uart_demo_app_free(UartDemoApp* app) { + uart_helper_free(app->uart_helper); + + furi_string_free(app->message2); + + view_dispatcher_remove_view(app->view_dispatcher, UartDemoSubMenuViewId); + view_dispatcher_free(app->view_dispatcher); + submenu_free(app->submenu); + furi_record_close(RECORD_GUI); + + free(app); +} + +int32_t uart_demo_main(void* p) { + UNUSED(p); + + UartDemoApp* app = uart_demo_app_alloc(); + view_dispatcher_run(app->view_dispatcher); + uart_demo_app_free(app); + + return 0; +} \ No newline at end of file diff --git a/gpio/uart_demo/uart_demo_10px.png b/gpio/uart_demo/uart_demo_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..3d527f306c2d325fc72380856f14860f51b3742c GIT binary patch literal 7293 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 literal 0 HcmV?d00001 diff --git a/gpio/uart_demo/uart_helper.c b/gpio/uart_demo/uart_helper.c new file mode 100644 index 0000000..49bf88f --- /dev/null +++ b/gpio/uart_demo/uart_helper.c @@ -0,0 +1,266 @@ +/** + * UartHelper is a utility class that helps with reading lines of data from a UART. + * It uses a stream buffer to receive data from the UART ISR, and a worker thread + * to dequeue data from the stream buffer and process it. The worker thread uses + * a ring buffer to hold data until a delimiter is found, at which point the line + * is extracted and the process_line callback is invoked. + * + * @author CodeAllNight +*/ + +#include +#include "ring_buffer.h" + +/** + * Callback invoked when a line is read from the UART. +*/ +typedef void (*ProcessLine)(FuriString* line, void* context); + +/** + * UartHelper is a utility class that helps with reading lines of data from a UART. +*/ +typedef struct { + // UART bus & channel to use + FuriHalBus uart_bus; + FuriHalUartId uart_channel; + bool uart_init_by_app; + + // Stream buffer to hold incoming data (worker will dequeue and process) + FuriStreamBuffer* rx_stream; + + // Worker thread that dequeues data from the stream buffer and processes it + FuriThread* worker_thread; + + // Buffer to hold data until a delimiter is found + RingBuffer* ring_buffer; + + // Callback to invoke when a line is read + ProcessLine process_line; + void* context; +} UartHelper; + +/** + * WorkerEventFlags are used to signal the worker thread to exit or to process data. + * Each flag is a bit in a 32-bit integer, so we can use the FuriThreadFlags API to + * wait for either flag to be set. +*/ +typedef enum { + WorkerEventDataWaiting = 1 << 0, // bit flag 0 - data is waiting to be processed + WorkerEventExiting = 1 << 1, // bit flag 1 - worker thread is exiting +} WorkerEventFlags; + +/** + * Invoked by ISR when a byte of data is received on the UART bus. This function + * adds the byte to the stream buffer and sets the WorkerEventDataWaiting flag. + * + * @param ev Event type + * @param data Byte of data received + * @param context UartHelper instance +*/ +static void uart_helper_received_byte_callback(UartIrqEvent ev, uint8_t data, void* context) { + UartHelper* helper = context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(helper->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventDataWaiting); + } +} + +/** + * Worker thread that dequeues data from the stream buffer and processes it. When + * a delimiter is found in the data, the line is extracted and the process_line callback + * is invoked. This thread will exit when the WorkerEventExiting flag is set. + * + * @param context UartHelper instance + * @return 0 +*/ +static int32_t uart_helper_worker(void* context) { + UartHelper* helper = context; + FuriString* line = furi_string_alloc(); + uint32_t events; + + do { + events = furi_thread_flags_wait( + WorkerEventDataWaiting | WorkerEventExiting, FuriFlagWaitAny, FuriWaitForever); + + if(events & WorkerEventDataWaiting) { + size_t length_read = 0; + do { + // We will read out of the stream into this temporary read_buffer in chunks + // of up to 32 bytes. A tricker implementation would be to read directly + // into our ring_buffer, avoiding the extra copy. + uint8_t read_buffer[32]; + + size_t available = ring_buffer_available(helper->ring_buffer); + if(available == 0) { + // In our case, the buffer is large enough to hold the entire response, + // so we should never hit this case & data loss is fine in the + // exceptional case. + + // If loosing data is unacceptable, you could transfer the buffer into + // a string and prepend that to the response, or you could increase the + // size of the buffer. + + // Buffer is full, so data loss will happen at beginning of string! + // We read one byte (hopefully the delimiter). + available = 1; + } + + // We will read up to 32 bytes, but no more than the available space in the + // ring buffer. + size_t request_bytes = available < COUNT_OF(read_buffer) ? available : + COUNT_OF(read_buffer); + + // hasDelim will be true if at least one delimiter was found in the data. + bool hasDelim = false; + + // Read up to 32 bytes from the stream buffer into our temporary read_buffer. + length_read = + furi_stream_buffer_receive(helper->rx_stream, read_buffer, request_bytes, 0); + + // Add the data to the ring buffer, check for delimiters, and process lines. + if(length_read > 0) { + // Add the data to the ring buffer. If at least one delimiter is found, + // hasDelim will be true. + hasDelim = ring_buffer_add(helper->ring_buffer, read_buffer, length_read); + + if(hasDelim) { + size_t index; + do { + // Find the next delimiter in the ring buffer. + index = ring_buffer_find_delim(helper->ring_buffer); + + // If a delimiter was found, extract the line and process it. + if(index != FURI_STRING_FAILURE) { + // Extract the line from the ring buffer, advancing the read + // pointer to the next byte after the delimiter. + ring_buffer_extract_line(helper->ring_buffer, index, line); + + // Invoke the callback to process the line. + if(helper->process_line) { + helper->process_line(line, helper->context); + } + } + } while(index != FURI_STRING_FAILURE); + } + } + // Keep reading until we have read all of the data from the stream buffer. + } while(length_read > 0); + } + // Keep processing data until the WorkerEventExiting flag is set. + } while((events & WorkerEventExiting) != WorkerEventExiting); + + furi_string_free(line); + + return 0; +} + +UartHelper* uart_helper_alloc() { + // rx_buffer_size should be large enough to hold the entire response from the device. + const size_t rx_buffer_size = 2048; + + // worker_stack_size should be large enough stack for the worker thread (including functions it calls). + const size_t worker_stack_size = 1024; + + // uart_baud is the default baud rate for the UART. + const uint32_t uart_baud = 115200; + + // The uart_helper uses USART1. + UartHelper* helper = malloc(sizeof(UartHelper)); + helper->uart_bus = FuriHalBusUSART1; + helper->uart_channel = FuriHalUartIdUSART1; + // Typically the UART is already enabled and will assert if you try to enable again. + helper->uart_init_by_app = !furi_hal_bus_is_enabled(helper->uart_bus); + if(helper->uart_init_by_app) { + furi_hal_uart_init(helper->uart_channel, uart_baud); + } else { + // If UART is already initialized, disable the console so it doesn't write to the UART. + furi_hal_console_disable(); + } + + // process_line callback gets invoked when a line is read. By default the callback is not set. + helper->process_line = NULL; + + // Set the baud rate for the UART + furi_hal_uart_set_br(helper->uart_channel, uart_baud); + + // When a byte of data is received, it will be appended to the rx_stream. A worker thread + // will dequeue the data from the rx_stream. + helper->rx_stream = furi_stream_buffer_alloc(rx_buffer_size, 1); + + // The worker thread will remove the data from the rx_stream and copy it into the ring_buffer. + // The worker thread will then process the data in the ring_buffer, invoking the process_line + // callback whenever a delimiter is found in the data. + helper->ring_buffer = ring_buffer_alloc(); + + // worker_thread is the routine that will process data from the rx_stream. + helper->worker_thread = + furi_thread_alloc_ex("UartHelperWorker", worker_stack_size, uart_helper_worker, helper); + furi_thread_start(helper->worker_thread); + + // Set the callback to invoke when data is received. + furi_hal_uart_set_irq_cb(helper->uart_channel, uart_helper_received_byte_callback, helper); + + return helper; +} + +void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter) { + // Update the delimiter character and flag to determine if delimiter should be part + // of the response to the process_line callback. + ring_buffer_set_delimiter(helper->ring_buffer, delimiter, include_delimiter); +} + +void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context) { + // Set the process_line callback and context. + helper->process_line = process_line; + helper->context = context; +} + +void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate) { + // Update the baud rate for the UART. + furi_hal_uart_set_br(helper->uart_channel, baud_rate); + ring_buffer_clear(helper->ring_buffer); +} + +void uart_helper_send(UartHelper* helper, const char* data, size_t length) { + // Transmit data via UART TX. + furi_hal_uart_tx(helper->uart_channel, (uint8_t*)data, length); +} + +void uart_helper_send_string(UartHelper* helper, FuriString* string) { + const char* str = furi_string_get_cstr(string); + + // UTF-8 strings can have character counts different then lengths! + // Count the bytes until a null is encountered. + size_t length = 0; + while(str[length] != 0) { + length++; + } + + // Transmit data + uart_helper_send(helper, str, length); +} + +void uart_helper_free(UartHelper* helper) { + // Signal that we want the worker to exit. It may be doing other work. + furi_thread_flags_set(furi_thread_get_id(helper->worker_thread), WorkerEventExiting); + + // Wait for the worker_thread to complete it's work and release its resources. + furi_thread_join(helper->worker_thread); + + // Free the worker_thread. + furi_thread_free(helper->worker_thread); + + // If the UART was initialized by the application, deinitialize it, otherwise re-enable the console. + if(helper->uart_init_by_app) { + furi_hal_uart_deinit(helper->uart_channel); + } else { + furi_hal_console_enable(); + } + + // Free the rx_stream and ring_buffer. + furi_stream_buffer_free(helper->rx_stream); + ring_buffer_free(helper->ring_buffer); + + free(helper); +} diff --git a/gpio/uart_demo/uart_helper.h b/gpio/uart_demo/uart_helper.h new file mode 100644 index 0000000..0752fb2 --- /dev/null +++ b/gpio/uart_demo/uart_helper.h @@ -0,0 +1,75 @@ +/** + * UartHelper is a utility class that helps with reading lines of data from a UART. + * It uses a stream buffer to receive data from the UART ISR, and a worker thread + * to dequeue data from the stream buffer and process it. The worker thread uses + * a ring buffer to hold data until a delimiter is found, at which point the line + * is extracted and the process_line callback is invoked. + * + * @author CodeAllNight +*/ + +#include + +/** + * A helper class for parsing data received over UART. +*/ +typedef struct UartHelper UartHelper; + +/** + * Callback function for processing a line of data. + * + * @param line The line of data to process. +*/ +typedef void (*ProcessLine)(FuriString* line, void* context); + +/** + * Allocates a new UartHelper. The UartHelper will be initialized with a baud rate of 115200. + * Log messages will be disabled since they also use the UART. + * + * IMPORTANT -- FURI_LOG_x calls will not work! + * + * @return A new UartHelper. +*/ +UartHelper* uart_helper_alloc(); + +/** + * Sets the delimiter to use when parsing data. The default delimeter is '\n' and + * the default value for include_delimiter is false. + * + * @param helper The UartHelper. + * @param delimiter The delimiter to use. + * @param include_delimiter If true, the delimiter will be included in the line of data passed to the callback function. +*/ +void uart_helper_set_delimiter(UartHelper* helper, char delimiter, bool include_delimiter); + +/** + * Sets the callback function to be called when a line of data is received. + * + * @param helper The UartHelper. + * @param process_line The callback function. + * @param context The context to pass to the callback function. +*/ +void uart_helper_set_callback(UartHelper* helper, ProcessLine process_line, void* context); + +/** + * Sets the baud rate for the UART. The default is 115200. + * + * @param helper The UartHelper. + * @param baud_rate The baud rate. +*/ +void uart_helper_set_baud_rate(UartHelper* helper, uint32_t baud_rate); + +/** + * Sends data over the UART TX pin. +*/ +void uart_helper_send(UartHelper* helper, const char* data, size_t length); + +/** + * Sends a string over the UART TX pin. +*/ +void uart_helper_send_string(UartHelper* helper, FuriString* string); + +/** + * Frees the UartHelper & enables log messages. +*/ +void uart_helper_free(UartHelper* helper); \ No newline at end of file