使用FreeRTOS解決單片機串口異步打印

單片機串口異步打印

文章目錄

  • 單片機串口異步打印
    • 前言
      • 設計思路
      • 準備
      • 隊列創建
      • 完整代碼
    • 總結

前言

🌊在單片機開發中串口的異步打印異步打印允許單片機在執行其他任務的同時進行打印操作,無需等待打印完成后再繼續執行后續代碼,避免了在多處調用的時候數據覆蓋的問題。

設計思路

  • 👍通過創建環形緩沖區隊列,設計任務調度或緩沖區管理機制,在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的結構體指針。這時隊列的headtail為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;
}

總結

本文主要介紹了在單片機中實現串口的異步打印,避免了數據覆蓋的問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77683.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77683.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77683.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

代碼顏色模式python

1. CMYK&#xff08;印刷場景&#xff09; 例子&#xff1a;某出版社設計書籍封面時&#xff0c;使用 Adobe Illustrator 繪制圖案。 紅色封面的 CMYK 值可能為&#xff1a;C0, M100, Y100, K0&#xff08;通過洋紅和黃色油墨混合呈現紅色&#xff09;。印刷前需將設計文件轉…

HarmonyOS NEXT 詩詞元服務項目開發上架全流程實戰(二、元服務與應用APP簽名打包步驟詳解)

在HarmonyOS應用開發過程中&#xff0c;發布應用到應用市場是一個重要的環節。沒經歷過的童鞋&#xff0c;首次對HarmonyOS的應用簽名打包上架可能感覺繁瑣。需要各種秘鑰證書生成和申請&#xff0c;混在一起分不清。其實搞清楚后也就那會事&#xff0c;各個文件都有它存在的作…

【BotSharp框架示例 ——實現聊天機器人,并通過 DeepSeek V3實現 function calling】

BotSharp框架示例 ——實現聊天機器人&#xff0c;并通過 DeepSeek V3實現 function calling 一、一點點感悟二、創建項目1、創建項目2、添加引用3、MyWeatherPlugin項目代碼編寫4、WeatherApiDefaultService項目代碼編寫5、WebAPI MyWeatherAPI 的項目代碼編寫6、data文件夾中…

百度CarLife實現手機車機無縫互聯

百度CarLife是百度推出的智能車聯網解決方案&#xff0c;通過手機與車機互聯技術&#xff0c;為用戶提供安全便捷的車載互聯網服務體驗。 CarLife 實現手機與車機屏幕的無縫互聯&#xff0c;讓應用內容同步至車載系統&#xff0c;減少駕駛過程中操作手機的頻率&#xff0c;提升…

基于STM32的虛線繪制函數改造

改造前&#xff1a; uint16_t DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { // GUI_DrawLine( x1, y1, x2, y2); // return 1;int16_t deltaX, deltaY;int16_t error, stepErrorLT, stepErrorGE;int16_t stepX, stepY;int16_t steep;int16_t…

Java高頻面試之并發編程-10

hello啊&#xff0c;各位觀眾姥爺們&#xff01;&#xff01;&#xff01;本baby今天來報道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面試官&#xff1a;ThreadLocalMap 怎么解決 Hash 沖突的&#xff1f; ThreadLocalMap 是 ThreadLocal 的核心實現&#xff0c;它采用 開放…

AI應用實戰:Excel表的操作工具

有個小需求是這樣的&#xff0c;需要在一份數據表里&#xff0c;將1000多個客戶的月報數據分別單獨截圖存檔&#xff0c;有客戶需要的時候就要發給客戶&#xff0c;截圖下來的也是以客戶為命名&#xff0c;這樣查找時也比較容易匹配上。 在沒有寫工具之前&#xff0c;以往財務…

使用 DoH 查詢域名 —— 以 core.tantanapp.com 為例的實戰分析

前言 在現代 iOS 應用中&#xff0c;為了確保 DNS 查詢的隱私和完整性&#xff0c;我們可以使用 DoH&#xff08;DNS over HTTPS&#xff09; 來查詢域名信息。 本文將以 https://cloudflare-dns.com/dns-query?namecore.tantanapp.com&typeA 為例&#xff0c;通過 Postm…

Python----卷積神經網絡(卷積為什么能識別圖像)

一、卷積的概念 卷積是一種數學運算&#xff0c;通常用于信號處理和圖像分析。在卷積神經網絡中&#xff0c;卷積操作用于提取輸入數據&#xff08;如圖像&#xff09;中的特征。通過將輸入數據與卷積核&#xff08;濾波器&#xff09;進行卷積運算&#xff0c;CNN能夠識別圖像…

linux FTP服務器搭建

FTP服務器搭建 系統環境&#xff1a;ubuntu 搭建方式&#xff1a;win系統下通過ssh連接ubuntu&#xff0c;搭建FTP服務 一、ssh連接 ssh -p 端口 用戶名IP ssh -p 22 ubuntu192.168.1.109 密碼&#xff1a;ubuntu123456 二、安裝配置FTP服務器 1、安裝 sudo apt install v…

語音合成之十韻律之美:TTS如何模擬語音的節奏和語調

韻律之美&#xff1a;TTS如何模擬語音的節奏和語調 1. 引言&#xff1a;韻律在語音合成中的重要性1.1 追求自然的TTS&#xff1a;超越可懂度1.2 定義韻律&#xff1a;語音的音樂1.3 韻律為何重要&#xff1a;傳遞意義、情感與自然度 2. TTS韻律建模的基礎技術2.1 利用文本&…

基于強化學習的用于非剛性圖像配準的引導式超聲采集|文獻速遞-深度學習醫療AI最新文獻

Title 題目 Guided ultrasound acquisition for nonrigid image registration usingreinforcement learning 基于強化學習的用于非剛性圖像配準的引導式超聲采集 01 文獻速遞介紹 超聲成像通常用于引導手術和其他醫療程序&#xff0c;在這些過程中&#xff0c;臨床醫生會持…

數據庫中DDL、DML、DCL的區別是什么?

數據庫中DDL、DML、DCL的區別是什么&#xff1f; 在數據庫的使用過程中&#xff0c;SQL&#xff08;結構化查詢語言&#xff09;常常被用來執行不同的操作&#xff0c;主要分為三類&#xff1a;DDL&#xff08;數據定義語言&#xff09;、DML&#xff08;數據操縱語言&#xf…

海量聊天消息處理:ShardingJDBC分庫分表、ClickHouse冷熱數據分離、ES復合查詢方案、Flink實時計算與SpringCloud集成

海量聊天消息處理&#xff1a;ShardingJDBC分庫分表、ClickHouse冷熱數據分離、ES復合查詢方案、Flink實時計算與SpringCloud集成 一、背景介紹 每天有2000萬條聊天消息&#xff0c;一年下來幾千萬億海量數據。為應對這種規模的數據存儲和處理需求&#xff0c;本文將從以下幾…

Vim 中替換字符或文本

在 Vim 中替換字符或文本可以使用 替換命令&#xff08;substitute&#xff09;&#xff0c;其基本語法為&#xff1a; :[range]s/old/new/[flags]1. 基本替換 命令說明:s/foo/bar/替換當前行的第一個 foo 為 bar:s/foo/bar/g替換當前行的 所有 foo 為 bar:%s/foo/bar/g替換 …

當傳統美術館遇上數字革命:觀眾體驗將迎來哪些顛覆性變革?

當數字科技與藝術創作深度交織&#xff0c;美術館與藝術機構正經歷前所未有的顛覆性浪潮。這是否宣告傳統展覽空間已正式跨入數字媒介主導的新紀元&#xff1f;投影映射與虛擬現實技術不斷突破物理限制&#xff0c;畫布與雕塑的邊界在光影與代碼中逐漸消融。這場革命不僅重構了…

內容/社區APP增長:用Deeplink讓用戶分享的內容“一鍵直達”

對于內容平臺和互動社區APP而言&#xff0c;優質內容的自發傳播是用戶增長和活躍度提升的核心驅動力之一。用戶發現一篇深度好文、一個精彩視頻或是一個引人入勝的討論帖&#xff0c;自然而然地想要分享給好友。然而&#xff0c;這個看似簡單的分享動作&#xff0c;卻往往在觸達…

Uniapp:vite.config.js全局配置

目錄 一、基本概述二、配置自動引入插件一、基本概述 vite.config.js 是一個可選的配置文件,如果項目的根目錄中存在這個文件,那么它會被自動加載,一般用于配置 vite 的編譯選項 二、配置自動引入插件 在項目命令行終端中執行如下代碼 npm install unplugin-auto-import…

JavaScript 與 Java 學習筆記

一、JavaScript 簡介 1. 定義 瀏覽器腳本語言&#xff1a;主要用于實現網頁交互功能&#xff08;鼠標點擊、鍵盤輸入響應等&#xff09; 服務器端擴展&#xff1a;通過 Node.js 運行時環境可進行后端開發 2. 核心特點 動態性&#xff1a;可實時修改 DOM 結構&#xff08;增…

Shell腳本-隨機數實戰案例

在Shell腳本編程中&#xff0c;生成隨機數是一項非常實用的技能。無論是用于模擬、測試、游戲開發還是安全相關的應用&#xff08;如生成密碼&#xff09;&#xff0c;能夠靈活地生成隨機數都是非常有用的。本文將通過幾個實際的應用案例來展示如何在Shell腳本中使用隨機數解決…