單片機串口異步打印
文章目錄
- 單片機串口異步打印
- 前言
- 設計思路
- 準備
- 隊列創建
- 完整代碼
- 總結
前言
🌊在單片機開發中串口的異步打印異步打印允許單片機在執行其他任務的同時進行打印操作,無需等待打印完成后再繼續執行后續代碼,避免了在多處調用的時候數據覆蓋的問題。
設計思路
- 👍通過創建環形緩沖區隊列,設計任務調度或緩沖區管理機制,在
FreeRTOS
中創建隊列來進行數據的接收,新建一個打印任務優先級設置為最低優先級,用來檢測FIFO
中是否有數據,如果有數據就將FIFO
中的數據打印出來。 - 通過定時器定時檢測實現非阻塞數據打印。
準備
- 移植好
FreeRTOS
操作系統 - 串口輸出重定向到
printf
隊列創建
🚗隊列的創建涉及到了數據結構,詳解可以參考數據結構與算法
書籍。
/* 定義環形緩沖區結構體 */
typedef struct {uint8_t buffer[BUFFER_SIZE]; /* 緩沖區 */ volatile uint32_t head; /* 寫入指針 */ volatile uint32_t tail; /* 讀取指針 */ SemaphoreHandle_t mutex; /* 互斥鎖,保證線程安全 */
} ringbuffer_t;
這里新建了ringbuffer_t
里面包含了buffer數據包,它的大小由BUFFER_SIZE這個宏來決定,可以通過這個宏來修改預期的buffer大小。👌
😁實例化結構體,給結構體的成員進行賦值操作。
ringbuffer_t ringBuffer = {.head = 0,.tail = 0,.mutex = NULL
};
🎈初始化環形緩沖區隊列。通過創建RingBuffer_Init
函數來實現,傳入的參數是ringbuffer_trb
的結構體指針。這時隊列的head
和tail
為0表示這個隊列是空的,接著創建了一個互斥量,用來保護線程安全。
/* 初始化環形緩沖區 */
void RingBuffer_Init(ringbuffer_t *rb) {rb->head = 0;rb->tail = 0;rb->mutex = xSemaphoreCreateMutex();if (rb->mutex == NULL) {printf("Mutex creation failed.\n");while (1);}
}
🥣判斷隊列是否為空,傳入環形緩沖區的結構體,如果隊列的head和隊列的tail相等那代表這個隊列是空的。
/* 判斷環形緩沖區是否為空 */
int32_t RingBuffer_IsEmpty(ringbuffer_t *rb) {/* head == tail 為空 */return (rb->head == rb->tail);
}
📡判斷隊列是否是滿,傳入緩沖區結構體,如果滿足head + 1 %對buffer的大小求余數,如果余數等于tail那么就代表這個隊列已經填滿數據了。
/* 判斷環形緩沖區是否已滿 */
int32_t RingBuffer_IsFull(ringbuffer_t *rb) {return ((rb->head + 1) % BUFFER_SIZE == rb->tail);
}
😍向隊列寫入數據,是按照字節進行寫入,傳入的參數的ringbuffer_t 結構體指針,和寫入的數據data。
/* 向環形緩沖區寫入數據 */
int32_t RingBuffer_Write(ringbuffer_t *rb, char data) {int32_t result = pdFALSE;if (xSemaphoreTake(rb->mutex, portMAX_DELAY) == pdTRUE) {if (!RingBuffer_IsFull(rb)) {rb->buffer[rb->head] = data;rb->head = (rb->head + 1) % BUFFER_SIZE;result = pdTRUE;}xSemaphoreGive(rb->mutex); /* 釋放互斥鎖 */}return result;
}
😘從環形緩沖區讀取數據,思路是傳入ringbuffer_t 的結構體指針,和需要讀取的指針類型字符,在讀取函數中先判斷是否獲取到了互斥鎖,如果是就判斷隊列是否非空,如果隊列非空,就將buffer數據指針就指向尾部每讀取隊列的一個數就指針向前偏移一位,直到讀取完成,滿足if語句每次讀取完成之后返回true
,讀取而結束就釋放互斥鎖🔒。
/* 從環形緩沖區讀取數據 */
int32_t RingBuffer_Read(ringbuffer_t *rb, uint8_t *data) {int32_t result = pdFALSE;if (xSemaphoreTake(rb->mutex, portMAX_DELAY) == pdTRUE) {if (!RingBuffer_IsEmpty(rb)) {*data = rb->buffer[rb->tail];rb->tail = (rb->tail + 1) % BUFFER_SIZE;result = pdTRUE;}xSemaphoreGive(rb->mutex); /* 釋放互斥鎖 */}return result;
}
😊在填充數據的時候參考了C語言中標準格式化輸出,需要進行標準的格式化輸出需要先引入其頭文件#include"stdarg.h"
之后就可以進行標準的格式化輸出了。在函數內部,其中可以使用 va_start() 和 va_end() 宏來訪問變長參數列表中的值,創建臨時的緩沖區來存放格式化的數據,然后再通過vsnprintf函數將可變的數據存到locabuffer中,返回寫入的長度。
/* 格式化并寫入數據的函數 */
void RingBufferWriteFormatted(ringbuffer_t* rb, const uint8_t* format, ...) {va_list args;va_start(args, format);/* 使用 vsnprintf 來格式化字符串到局部緩沖區 */ uint8_t localBuffer[BUFFER_SIZE]; // 根據需要調整大小uint32_t written = vsnprintf(localBuffer, sizeof(localBuffer), format, args);/* 檢查格式化是否成功 */ if (written > 0) {/* 將格式化后的字符串逐字符寫入環形緩沖區 */ for (int i = 0; i < written; ++i) { if (!RingBuffer_Write(rb, localBuffer[i])) {break;}}}va_end(args);
}
😄創建打印任務不斷的取讀取環形緩沖區的數據如果有數據就打印出來。
/* 數據讀取和打印任務 */
void PrintTask(void *pvParameters) {char data;while (1) {/* 讀取緩沖區,有數據就打印 */if (RingBuffer_Read(&ringBuffer, &data)) {printf("%c",data);} }
}
完整代碼
/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "stdarg.h"#define BUFFER_SIZE 1024 /* 環形緩沖區大小 根據實際的數據大小進行調整 */ /* 定義環形緩沖區結構體 */
typedef struct {uint8_t buffer[BUFFER_SIZE]; /* 緩沖區 */ volatile uint32_t head; /* 寫入指針 */ volatile uint32_t tail; /* 讀取指針 */ SemaphoreHandle_t mutex; /* 互斥鎖,保證線程安全 */
} ringbuffer_t;/* 創建一個全局環形緩沖區實例 */
ringbuffer_t ringBuffer = {.head = 0,.tail = 0,.mutex = NULL
};/* 初始化環形緩沖區 */
void RingBuffer_Init(ringbuffer_t *rb) {rb->head = 0;rb->tail = 0;rb->mutex = xSemaphoreCreateMutex();if (rb->mutex == NULL) {printf("Mutex creation failed.\n");while (1);}
}/* 判斷環形緩沖區是否為空 */
int32_t RingBuffer_IsEmpty(ringbuffer_t *rb) {/* head == tail 為空 */return (rb->head == rb->tail);
}/* 判斷環形緩沖區是否已滿 */
int32_t RingBuffer_IsFull(ringbuffer_t *rb) {return ((rb->head + 1) % BUFFER_SIZE == rb->tail);
}/* 向環形緩沖區寫入數據 */
int32_t RingBuffer_Write(ringbuffer_t *rb, char data) {int32_t result = pdFALSE;if (xSemaphoreTake(rb->mutex, portMAX_DELAY) == pdTRUE) {if (!RingBuffer_IsFull(rb)) {rb->buffer[rb->head] = data;rb->head = (rb->head + 1) % BUFFER_SIZE;result = pdTRUE;}xSemaphoreGive(rb->mutex); /* 釋放互斥鎖 */}return result;
}/* 從環形緩沖區讀取數據 */
int32_t RingBuffer_Read(ringbuffer_t *rb, char *data) {int32_t result = pdFALSE;if (xSemaphoreTake(rb->mutex, portMAX_DELAY) == pdTRUE) {if (!RingBuffer_IsEmpty(rb)) {*data = rb->buffer[rb->tail];rb->tail = (rb->tail + 1) % BUFFER_SIZE;result = pdTRUE;}xSemaphoreGive(rb->mutex); /* 釋放互斥鎖 */}return result;
}
/* 格式化并寫入數據的函數 */
void RingBufferWriteFormatted(ringbuffer_t* rb, const char* format, ...) {va_list args;va_start(args, format);/* 使用 vsnprintf 來格式化字符串到局部緩沖區 */ char localBuffer[BUFFER_SIZE]; // 根據需要調整大小int written = vsnprintf(localBuffer, sizeof(localBuffer), format, args);// 檢查格式化是否成功if (written > 0) {// 將格式化后的字符串逐字符寫入環形緩沖區for (int i = 0; i < written; ++i) { if (!RingBuffer_Write(rb, localBuffer[i])) {break;}}}va_end(args);
}
/* 數據填充任務 測試任務向隊列填充數據依次進行添加 */
void DataFillTask(void *p) {RingBufferWriteFormatted(&ringBuffer," 1 test test test %d\r\n",0x88);RingBufferWriteFormatted(&ringBuffer, "2 test size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "3 xx size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "4 00 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "5 11 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "6 22 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "7 33 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "8 44 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "9 55 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "10 66 size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "11 ww size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "12 xiao size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer, "13 bai size value %d\r\n", 0x88);RingBufferWriteFormatted(&ringBuffer,"*************DataFillTask******************\n");while (1) {vTaskDelay(200);}
}/* 數據讀取和打印任務 */
void PrintTask(void *pvParameters) {char data;while (1) {/* 讀取緩沖區,有數據就打印 */if (RingBuffer_Read(&ringBuffer, &data)) {printf("%c",data);// printf("Data '%c' read from buffer.\n", data); /* 從緩沖區去取數據并打印 */} }
}/* 創建任務 */
void app_CreateTasks(void) {if(pdPASS != xTaskCreate(DataFillTask, "DataFillTask", 512, NULL, 1, NULL)) {printf("Task DataFillTask creation failed!\r\n");}/* 設置打印任務為最低優先級 */if (pdPASS != xTaskCreate(PrintTask, "PrintTask", 256, NULL, 0, NULL)) {printf("Task PrintTask creation failed!\r\n");}
}
/* 函數入口 */
int main(void) {/* 在這里進行硬件的初始化操作 */printf("rtos_log print\r\n");/* 初始化環形緩沖區*/ RingBuffer_Init(&ringBuffer);/* 創建任務 */ app_CreateTasks();/* 啟動調度器*/ vTaskStartScheduler();/* 如果調度器啟動失敗 */while (1) {};return 0;
}
總結
本文主要介紹了在單片機中實現串口的異步打印,避免了數據覆蓋的問題。