使用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.mzph.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脚本中使用随机数解决…