相关链接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语音智能体开发日记十四为语音智能体打造 LCD 表情动画位图BMP显示-CSDN博客AI语音智能体开发日记十五智能体LCD屏幕GIF动画显示方案——从GIF到BMP的完整实战-CSDN博客AI语音智能体开发日记十六智能体OTA失败的“401未授权”玄学问题排查-CSDN博客AI语音智能体开发日记十七智能体“失语“ -- 大量MCP指令与LCD内存分配引发的问题-CSDN博客AI语音智能体开发日记十八智能体服务器xiaozhi-esp32-server源码部署指南-CSDN博客AI语音智能体开发日记十九智能体服务器xiaozhi-esp32-server配置与调试-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总线等的经典做法非常值得掌握。