当前位置: 首页 > news >正文

使用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;
}

总结

本文主要介绍了在单片机中实现串口的异步打印,避免了数据覆盖的问题。

相关文章:

  • Spark-Streaming
  • 第一章-语言基础\2.竞赛常用库函数\其他库函数
  • vite详细打包配置,包含性能优化、资源处理...
  • 通过dogssl申请ssl免费证书
  • 如何一键提取多个 PPT 幻灯片中的备注到 TXT 记事本文件中
  • 通过AI工具或模型创建PPT的不同方式详解,结合 Assistants API、DALL·E 3 等工具的功能对比及表格总结
  • Word处理控件Spire.Doc系列教程:C# 为 Word 文档设置背景颜色或背景图片
  • 什么是snmp协议?在优雅草星云智控AI物联网监控系统中如何添加设备进行监控【星云智控手册01】-优雅草卓伊凡
  • HarmonyOS:网络HTTP数据请求
  • 离散化区间和 java c++
  • WebRTC服务器Coturn服务器相关测试工具
  • 2023蓝帽杯初赛内存取证-5
  • 开源模型应用落地-Podcastfy-从文本到声音的智能跃迁-Docker(二)
  • Debian 12.10 root 登录失败,两步解决!
  • 精益数据分析(14/126):基于数据洞察优化产品与运营
  • RK3588 Buildroot 新建板级DTS
  • 从ChatGPT到GPT-4:大模型如何重塑人类认知边界?
  • Idea创建项目的搭建
  • yooAsset打包后材质丢失
  • DAY6-UFS基本概念
  • 王珊珊读《吾自绝伦》|摘掉皮普斯的“假发”
  • 美股反弹,纳斯达克中国金龙指数大涨3.69%
  • 王毅同英国外交大臣拉米通电话
  • 告别国泰海通,黄燕铭下一站将加盟东方证券,负责研究业务
  • 陕西一批干部任职公示:西安市未央、雁塔、阎良区委书记拟调整
  • 江西省人大教育科学文化卫生委员会主任委员王水平被查