相关链接:
AI语音智能体开发日记(一)如何为“小智”服务器启用并调试 License 功能-CSDN博客
AI语音智能体开发日记(二)解决 Wi-Fi 配网小程序的兼容性问题-CSDN博客
AI语音智能体开发日记(三)解决小程序配网中的蓝牙命名与MAC地址获取问题-CSDN博客
AI语音智能体开发日记(四)在FreeRTOS中构建线程安全的UART2通信模块-CSDN博客
AI语音智能体开发日记(五)为智能设备注入“灵魂”——详解MCP工具的注册与使用-CSDN博客
AI语音智能体开发日记(六)为智能体注入旋律——七牛云音乐服务的接入与避坑指南-CSDN博客
AI语音智能体开发日记(七)搞定功放控制——详解GX8006平台Mute电平配置-CSDN博客
AI语音智能体开发日记(八)LVGL 8.4.0 移植实战——从零构建嵌入式GUI-CSDN博客
AI语音智能体开发日记(九)LVGL 8.4.0 中文字体配置全攻略-CSDN博客
AI语音智能体开发日记(十)LVGL 图标字体实战——从 FontAwesome 到屏幕显示-CSDN博客
AI语音智能体开发日记(十一)为智能设备“声”临其境——详解音频资源自动化生成流程-CSDN博客
AI语音智能体开发日记(十二)GX8006 固件定制指南——从双唤醒词到 UART 音频传输-CSDN博客
AI语音智能体开发日记(十三)一次由寄存器溢出引发的串口波特率“玄学”问题排查-CSDN博客
推荐链接:
AI语音智能体架构解析(二)大模型(AI 的大脑)-CSDN博客
AI语音智能体架构解析(三)智控台(指挥中心)-CSDN博客
AI语音智能体架构解析(四)AI 语音终端(执行器官)-CSDN博客
AI语音智能体架构解析(五)小程序/APP(遥控器)-CSDN博客
推荐链接:
AI 应用 图文 解说 (一) -- 百度智能云 实现 语音 聊天-CSDN博客
AI 应用 图文 解说 (二) -- 百度智能云 ASR LIM TTS 语音AI助手程序 -CSDN博客
开发手记:在FreeRTOS中构建线程安全的UART2通信模块
在嵌入式开发中,串口通信是调试和数据交互最常用的方式。但在多任务的FreeRTOS环境下,如果多个任务同时尝试通过串口发送数据,很容易出现数据交错、混乱的问题。
今天就来记录一下,如何在我们的lightning项目中,通过引入互斥锁(Mutex)来构建一个线程安全的UART2发送模块。
需求背景
我们的项目需要增加一个通过UART2进行数据发送的功能。这个功能不仅要求能正常收发数据,还必须保证在FreeRTOS多任务环境下的稳定性,防止因多个任务同时调用发送函数而导致的数据混乱。
解决方案:分步实现
整个实现过程可以分为四个主要步骤:GPIO引脚配置、FreeRTOS发送函数构建、主函数调用以及状态指示LED的配置。
第一步:配置UART2的GPIO引脚
首先,我们需要在底层硬件抽象层(HAL)中配置UART2所使用的GPIO引脚。根据原理图,我们选择PA1作为RX(接收),PA4作为TX(发送)。
在 project/ln_model_public/bsp/serial_hw.c 文件中,找到 uart_io_pin_request 函数,并添加对 SER_PORT_UART2 的支持:

1// 文件: project/ln_model_public/bsp/serial_hw.c
2
3static void uart_io_pin_request(struct Serial *serial)
4{
5 // ... 其他UART的配置 ...
6
7 else if (serial->port_id == SER_PORT_UART2)
8 {
9 /******************zrh add******************/
10 // 配置 PA1 为 UART2 的 RX 功能
11 hal_gpio_pin_afio_select(GPIOA_BASE, GPIO_PIN_1, UART2_RX);
12 // 配置 PA4 为 UART2 的 TX 功能
13 hal_gpio_pin_afio_select(GPIOA_BASE, GPIO_PIN_4, UART2_TX);
14
15 // 使能 PA1 和 PA4 的复用功能
16 hal_gpio_pin_afio_en(GPIOA_BASE, GPIO_PIN_1, HAL_ENABLE);
17 hal_gpio_pin_afio_en(GPIOA_BASE, GPIO_PIN_4, HAL_ENABLE);
18 /*******************************************/
19 }
20}
这段代码的作用是将PA1和PA4引脚的功能复用(AFIO)为UART2的接收和发送端,并启用该功能。
第二步:构建线程安全的发送函数
这是整个功能的核心。我们需要在 project/ln_model_public/app/servers/qiniu/mcp_server/mcp_tools/smart_toilet/uart.c 文件中实现初始化、数据发送和互斥锁保护。

1// 文件: .../smart_toilet/uart.c
2
3#define LOG_PORT_BAUDRATE CFG_UART_BAUDRATE_LOG
4
5// 定义一个全局的串口句柄
6Serial_t cmd_Serial;
7// 定义一个互斥锁句柄,用于保护串口写操作
8static OS_Mutex_t uart_write_mutex;
9
10// 串口初始化函数
11int uart_init(void)
12{
13 // 初始化 cmd_Serial,指定使用 UART2,并设置波特率
14 serial_init(&cmd_Serial, SER_PORT_UART2, LOG_PORT_BAUDRATE, NULL);
15 return 0;
16}
17
18// 互斥锁初始化函数
19void uart_write_mutex_init(void)
20{
21 OS_MutexCreate(&uart_write_mutex);
22}
23
24// 基础的串口写函数
25int cmd_stdio_write(char *buf, size_t size)
26{
27 int ret = 0;
28 // 调用底层的 serial_write 函数进行实际的数据发送
29 ret = serial_write(&cmd_Serial, (const void *)buf, size);
30 return ret;
31}
32
33// 线程安全的串口写函数
34void safe_cmd_uart_write(char *buf, size_t size)
35{
36 // 判断调度器是否已经运行
37 if (OS_ThreadIsSchedulerRunning()) {
38 // 如果调度器已运行,检查互斥锁是否有效,无效则创建
39 if (!OS_MutexIsValid(&uart_write_mutex)) {
40 OS_MutexCreate(&uart_write_mutex);
41 }
42 // 获取互斥锁,如果已被其他任务占用,则等待
43 OS_MutexLock(&uart_write_mutex, OS_WAIT_FOREVER);
44 }
45
46 // 执行实际的发送操作
47 cmd_stdio_write(buf, size);
48
49 // 发送完成后,释放互斥锁
50 if (OS_ThreadIsSchedulerRunning()) {
51 OS_MutexUnlock(&uart_write_mutex);
52 }
53}
关键点分析:
uart_init: 负责初始化指定的串口(UART2)。safe_cmd_uart_write: 这是提供给其他任务调用的接口。它在发送数据前会先尝试获取互斥锁uart_write_mutex。这确保了在任何时刻,只有一个任务能够执行cmd_stdio_write,从而避免了数据交错。发送完成后,立即释放锁。
第三步:在主函数中调用初始化
最后,我们需要在系统启动时调用我们刚刚创建的初始化函数。
在 project/ln_model_public/app/main.c 文件的 main 函数中,添加如下调用:

1// 文件: project/ln_model_public/app/main.c
2
3int main(int argc, char *argv[])
4{
5 // ... 系统时钟、中断等初始化 ...
6
7 log_init();
8
9 /******************zrh add******************/
10 // 初始化UART
11 uart_init();
12 // 初始化UART写操作的互斥锁
13 uart_write_mutex_init();
14 /*******************************************/
15
16 // ... 其他初始化 ...
17}
确保在系统调度器启动前完成这些初始化工作。
第四步:配置网络状态指示LED
为了方便调试,我们可以配置一个LED来指示网络状态。
在 project/ln_model_public/app/usr_app.c 文件中,定义LED所使用的GPIO引脚:

1// 文件: project/ln_model_public/app/usr_app.c
2
3// 定义网络状态指示灯的端口和引脚
4#define CONFIG_NET_STATUS_PORT (GPIOB_BASE)
5#define CONFIG_NET_STATUS_PIN (GPIO_PIN_6)
这里我们将网络状态指示灯配置在了PB6引脚上。
总结
通过以上四个步骤,我们成功地在项目中实现了一个功能完整且线程安全的UART2通信模块。
- 硬件层:正确配置了UART2的GPIO引脚。
- 软件层:利用FreeRTOS的互斥锁机制,封装了安全的发送函数,解决了多任务环境下的资源竞争问题。
- 系统集成:在
main函数中完成了模块的初始化,并配置了状态指示灯,便于后续调试。
这个模式是嵌入式开发中处理共享资源(如串口、I2C总线等)的经典做法,非常值得掌握。
在FreeRTOS中构建线程安全的UART2通信模块&spm=1001.2101.3001.5002&articleId=163383484&d=1&t=3&u=0a873a315cab4a509be3ae567271ac82)
1648

被折叠的 条评论
为什么被折叠?



