UART demo, making UART read easier.

This commit is contained in:
Derek Jamison 2023-06-29 14:26:28 -05:00
parent 4c304ac075
commit a0eff12f17
8 changed files with 806 additions and 0 deletions

59
gpio/uart_demo/README.md Normal file
View File

@ -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);
```

View File

@ -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",
)

View File

@ -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 <furi.h>
/**
* 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;
}

View File

@ -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 <furi.h>
/**
* 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);

142
gpio/uart_demo/uart_demo.c Normal file
View File

@ -0,0 +1,142 @@
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/modules/submenu.h>
#include <gui/view_dispatcher.h>
#include <gui/canvas.h>
#include <input/input.h>
#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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -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 <furi_hal.h>
#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);
}

View File

@ -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 <furi.h>
/**
* 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);