From 72e8500a27bc2b017d226797c500bebbb41bc73d Mon Sep 17 00:00:00 2001 From: TuxMonkey <8196772+tuxmonkey@user.noreply.gitee.com> Date: Tue, 25 Nov 2025 14:28:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=20usart?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- User_Code/bsp/usart/bsp_usart.c | 146 +++++++++++++++++++++++++++++++- User_Code/bsp/usart/bsp_usart.h | 79 +++++++++++++++-- 2 files changed, 215 insertions(+), 10 deletions(-) diff --git a/User_Code/bsp/usart/bsp_usart.c b/User_Code/bsp/usart/bsp_usart.c index c198db7..f773b4a 100644 --- a/User_Code/bsp/usart/bsp_usart.c +++ b/User_Code/bsp/usart/bsp_usart.c @@ -1,5 +1,143 @@ -// -// Created by tuxmonkey on 2025/10/28. -// +/** + * @file bsp_usart.c + * @author + * @brief 串口bsp层的实现 + * @version beta + * @date + * + * @copyright + * + */ -#include "bsp_usart.h" \ No newline at end of file +#include "bsp_usart.h" +#include "bsp_log.h" +#include "stdlib.h" +#include "memory.h" + +/* usart service instance, modules' info would be recoreded here using USARTRegister() */ +/* usart服务实例,所有注册了usart的模块信息会被保存在这里 */ +static uint8_t idx; +static USARTInstance *usart_instance[DEVICE_USART_CNT] = {NULL}; + +/** + * @brief 启动串口服务,会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收 + * + * @todo 串口服务会在每个实例注册之后自动启用接收,当前实现为DMA接收,后续可能添加IT和BLOCKING接收 + * 可能还要将此函数修改为extern,使得module可以控制串口的启停 + * + * @param _instance instance owned by module,模块拥有的串口实例 + */ +void USARTServiceInit(USARTInstance *_instance) +{ + HAL_UARTEx_ReceiveToIdle_DMA(_instance->usart_handle, _instance->recv_buff, _instance->recv_buff_size); + // 关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback() + // 这是HAL库的一个设计失误,发生DMA传输完成/半完成以及串口IDLE中断都会触发HAL_UARTEx_RxEventCallback() + // 我们只希望处理第一种和第三种情况,因此直接关闭DMA半传输中断 + __HAL_DMA_DISABLE_IT(_instance->usart_handle->hdmarx, DMA_IT_HT); +} + +USARTInstance *USARTRegister(USART_Init_Config_s *init_config) +{ + if (idx >= DEVICE_USART_CNT) // 超过最大实例数 + while (1) + LOGERROR("[bsp_usart] USART exceed max instance count!"); + + for (uint8_t i = 0; i < idx; i++) // 检查是否已经注册过 + if (usart_instance[i]->usart_handle == init_config->usart_handle) + while (1) + LOGERROR("[bsp_usart] USART instance already registered!"); + + USARTInstance *instance = (USARTInstance *) malloc(sizeof(USARTInstance)); + memset(instance, 0, sizeof(USARTInstance)); + + instance->usart_handle = init_config->usart_handle; + instance->recv_buff_size = init_config->recv_buff_size; + instance->module_callback = init_config->module_callback; + + usart_instance[idx++] = instance; + USARTServiceInit(instance); + return instance; +} + +/* @todo 当前仅进行了形式上的封装,后续要进一步考虑是否将module的行为与bsp完全分离 */ +void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode) +{ + switch (mode) + { + case USART_TRANSFER_BLOCKING: + HAL_UART_Transmit(_instance->usart_handle, send_buf, send_size, 100); + break; + case USART_TRANSFER_IT: + HAL_UART_Transmit_IT(_instance->usart_handle, send_buf, send_size); + break; + case USART_TRANSFER_DMA: + HAL_UART_Transmit_DMA(_instance->usart_handle, send_buf, send_size); + break; + default: + while (1); // illegal mode! check your code context! 检查定义instance的代码上下文,可能出现指针越界 + break; + } +} + +/* 串口发送时,gstate会被设为BUSY_TX */ +uint8_t USARTIsReady(USARTInstance *_instance) +{ + if (_instance->usart_handle->gState | HAL_UART_STATE_BUSY_TX) + return 0; + else + return 1; +} + +/** + * @brief 每次dma/idle中断发生时,都会调用此函数.对于每个uart实例会调用对应的回调进行进一步的处理 + * 例如:视觉协议解析/遥控器解析/裁判系统解析 + * + * @note 通过__HAL_DMA_DISABLE_IT(huart->hdmarx,DMA_IT_HT)关闭dma half transfer中断防止两次进入HAL_UARTEx_RxEventCallback() + * 这是HAL库的一个设计失误,发生DMA传输完成/半完成以及串口IDLE中断都会触发HAL_UARTEx_RxEventCallback() + * 我们只希望处理,因此直接关闭DMA半传输中断第一种和第三种情况 + * + * @param huart 发生中断的串口 + * @param Size 此次接收到的总数居量,暂时没用 + */ +void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) +{ + for (uint8_t i = 0; i < idx; ++i) + { + // find the instance which is being handled + if (huart == usart_instance[i]->usart_handle) + { + // call the callback function if it is not NULL + if (usart_instance[i]->module_callback != NULL) + { + usart_instance[i]->module_callback(); + memset(usart_instance[i]->recv_buff, 0, Size); // 接收结束后清空buffer,对于变长数据是必要的 + } + HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff, + usart_instance[i]->recv_buff_size); + __HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT); + return; // break the loop + } + } +} + +/** + * @brief 当串口发送/接收出现错误时,会调用此函数,此时这个函数要做的就是重新启动接收 + * + * @note 最常见的错误:奇偶校验/溢出/帧错误 + * + * @param huart 发生错误的串口 + */ +void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) +{ + for (uint8_t i = 0; i < idx; ++i) + { + if (huart == usart_instance[i]->usart_handle) + { + HAL_UARTEx_ReceiveToIdle_DMA(usart_instance[i]->usart_handle, usart_instance[i]->recv_buff, + usart_instance[i]->recv_buff_size); + __HAL_DMA_DISABLE_IT(usart_instance[i]->usart_handle->hdmarx, DMA_IT_HT); + LOGWARNING("[bsp_usart] USART error callback triggered, instance idx [%d]", i); + return; + } + } +} diff --git a/User_Code/bsp/usart/bsp_usart.h b/User_Code/bsp/usart/bsp_usart.h index 588d0a7..4aa5d0c 100644 --- a/User_Code/bsp/usart/bsp_usart.h +++ b/User_Code/bsp/usart/bsp_usart.h @@ -1,8 +1,75 @@ -// -// Created by tuxmonkey on 2025/10/28. -// +#ifndef BSP_USART_H +#define BSP_USART_H -#ifndef TRONONEH7_SCAFFOLD_BSP_USART_H -#define TRONONEH7_SCAFFOLD_BSP_USART_H +#include +#include "main.h" -#endif //TRONONEH7_SCAFFOLD_BSP_USART_H \ No newline at end of file +#define DEVICE_USART_CNT 5 // 喵板至多分配5个串口 +#define USART_RXBUFF_LIMIT 256 // 如果协议需要更大的buff,请修改这里 + +// 模块回调函数,用于解析协议 +typedef void (*usart_module_callback)(); + +/* 发送模式枚举 */ +typedef enum +{ + USART_TRANSFER_NONE = 0, + USART_TRANSFER_BLOCKING, + USART_TRANSFER_IT, + USART_TRANSFER_DMA, +} USART_TRANSFER_MODE; + +// 串口实例结构体,每个module都要包含一个实例. +// 由于串口是独占的点对点通信,所以不需要考虑多个module同时使用一个串口的情况,因此不用加入id;当然也可以选择加入,这样在bsp层可以访问到module的其他信息 +typedef struct +{ + uint8_t recv_buff[USART_RXBUFF_LIMIT]; // 预先定义的最大buff大小,如果太小请修改USART_RXBUFF_LIMIT + uint8_t recv_buff_size; // 模块接收一包数据的大小 + UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle + usart_module_callback module_callback; // 解析收到的数据的回调函数 +} USARTInstance; + +/* usart 初始化配置结构体 */ +typedef struct +{ + uint8_t recv_buff_size; // 模块接收一包数据的大小 + UART_HandleTypeDef *usart_handle; // 实例对应的usart_handle + usart_module_callback module_callback; // 解析收到的数据的回调函数 +} USART_Init_Config_s; + +/** + * @brief 注册一个串口实例,返回一个串口实例指针 + * + * @param init_config 传入串口初始化结构体 + */ +USARTInstance *USARTRegister(USART_Init_Config_s *init_config); + +/** + * @brief 启动串口服务,需要传入一个usart实例.一般用于lost callback的情况(使用串口的模块daemon) + * + * @param _instance + */ +void USARTServiceInit(USARTInstance *_instance); + + +/** + * @brief 通过调用该函数可以发送一帧数据,需要传入一个usart实例,发送buff以及这一帧的长度 + * @note 在短时间内连续调用此接口,若采用IT/DMA会导致上一次的发送未完成而新的发送取消. + * @note 若希望连续使用DMA/IT进行发送,请配合USARTIsReady()使用,或自行为你的module实现一个发送队列和任务. + * @todo 是否考虑为USARTInstance增加发送队列以进行连续发送? + * + * @param _instance 串口实例 + * @param send_buf 待发送数据的buffer + * @param send_size how many bytes to send + */ +void USARTSend(USARTInstance *_instance, uint8_t *send_buf, uint16_t send_size, USART_TRANSFER_MODE mode); + +/** + * @brief 判断串口是否准备好,用于连续或异步的IT/DMA发送 + * + * @param _instance 要判断的串口实例 + * @return uint8_t ready 1, busy 0 + */ +uint8_t USARTIsReady(USARTInstance *_instance); + +#endif