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