UART demo, making UART read easier.
This commit is contained in:
parent
4c304ac075
commit
a0eff12f17
59
gpio/uart_demo/README.md
Normal file
59
gpio/uart_demo/README.md
Normal 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);
|
||||
```
|
||||
|
10
gpio/uart_demo/application.fam
Normal file
10
gpio/uart_demo/application.fam
Normal 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",
|
||||
)
|
166
gpio/uart_demo/ring_buffer.c
Normal file
166
gpio/uart_demo/ring_buffer.c
Normal 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;
|
||||
}
|
88
gpio/uart_demo/ring_buffer.h
Normal file
88
gpio/uart_demo/ring_buffer.h
Normal 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
142
gpio/uart_demo/uart_demo.c
Normal 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;
|
||||
}
|
BIN
gpio/uart_demo/uart_demo_10px.png
Normal file
BIN
gpio/uart_demo/uart_demo_10px.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
266
gpio/uart_demo/uart_helper.c
Normal file
266
gpio/uart_demo/uart_helper.c
Normal 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);
|
||||
}
|
75
gpio/uart_demo/uart_helper.h
Normal file
75
gpio/uart_demo/uart_helper.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user