第一次提交
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @file bsp_usart.c
|
||||
* @author neozng
|
||||
* @brief 串口bsp层的实现
|
||||
* @version beta
|
||||
* @date 2022-11-01
|
||||
*
|
||||
* @copyright Copyright (c) 2022
|
||||
*
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef BSP_RC_H
|
||||
#define BSP_RC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "main.h"
|
||||
|
||||
#define DEVICE_USART_CNT 8 // MC02串口数量8个,其中
|
||||
#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
|
||||
@@ -0,0 +1,80 @@
|
||||
# bsp_usart
|
||||
|
||||
<p align='right'>neozng1@hnu.edu.cn</p>
|
||||
|
||||
> TODO: 增加发送队列以解决短时间内调用`USARTSend()`发生丢包的问题,目前仅支持DMA。还需要提供阻塞和IT模式以供选择,参考bspiic和spi进行实现。
|
||||
> 可以直接在发送函数的参数列表添加发送模式选择,或增加instance成员变量,并提供设置模式接口,两者各有优劣
|
||||
|
||||
## 使用说明
|
||||
|
||||
若你需要构建新的基于串口的module,首先需要拥有一个`usart_instance`的指针用于操作串口对象。
|
||||
|
||||
需要在串口实例下设定接收的数据包的长度,实例对应的串口硬件(通过`UART_HandleTypeDef`指定,如`&huart1`),解析接收数据对应的回调函数这三个参数。然后,调用`USARTRegister()`并传入配置好的`usart_instance`指针即可。
|
||||
|
||||
若要发送数据,调用`USARTSend()`。注意buffsize务必小于你创建的buff的大小,否则造成指针越界后果未知。
|
||||
|
||||
串口硬件收到数据时,会将其存入`usart_instance.recv_buff[]`中,当收到完整一包数据,会调用设定的回调函数`module_callback`(即你注册时提供的解析函数)。在此函数中,你可以通过`usart_instance.recv_buff[]`访问串口收到的数据。
|
||||
|
||||
## 代码结构
|
||||
|
||||
.h文件内包括了外部接口和类型定义,以及模块对应的宏。c文件内为私有函数和外部接口的定义。
|
||||
|
||||
## 类型定义
|
||||
|
||||
```c
|
||||
#define DEVICE_USART_CNT 3 // C板至多分配3个串口
|
||||
#define USART_RXBUFF_LIMIT 128 // if your protocol needs bigger buff, modify here
|
||||
|
||||
typedef void (*usart_module_callback)();
|
||||
|
||||
/* usart_instance struct,each app would have one instance */
|
||||
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; // 解析收到的数据的回调函数
|
||||
} usart_instance;
|
||||
```
|
||||
|
||||
- `DEVICE_USART_CNT`是开发板上可用的串口数量。
|
||||
|
||||
- `USART_RXBUFF_LIMIT`是串口单次接收的数据长度上限,暂时设为128,如果需要更大的buffer容量,修改该值。
|
||||
|
||||
- `usart_module_callback()`是模块提供给串口接收中断回调函数使用的协议解析函数指针。对于每个需要串口的模块,需要定义一个这样的函数用于解包数据。
|
||||
|
||||
- 每定义一个`usart_instance`,就代表一个串口的**实例**(对象)。一个串口实例内有接收buffer,单个数据包的大小,该串口对应的`HAL handle`(代表其使用的串口硬件具体是哪一个)以及用于解包数据的回调函数。
|
||||
|
||||
|
||||
## 外部接口
|
||||
|
||||
```c
|
||||
void USARTRegister(usart_instance *_instance);
|
||||
void USARTSend(usart_instance *_instance, uint8_t *send_buf, uint16_t send_size);
|
||||
```
|
||||
|
||||
- `USARTRegister`是用于初始化串口对象的接口,module层的模块对象(也应当为一个结构体)内要包含一个`usart_instance`。
|
||||
|
||||
**在调用该函数之前,需要先对其成员变量`*usart_handle`,`module_callback()`以及`recv_buff_size`进行赋值。**
|
||||
|
||||
- `USARTSend()`是通过模块通过其拥有的串口对象发送数据的接口,调用时传入的参数为串口实例指针,发送缓存以及此次要发送的数据长度(8-bit\*n)。
|
||||
|
||||
## 私有函数和变量
|
||||
|
||||
在.c文件内设为static的函数和变量
|
||||
|
||||
```c
|
||||
static usart_instance *instance[DEVICE_USART_CNT];
|
||||
```
|
||||
|
||||
这是bsp层管理所有串口实例的入口。
|
||||
|
||||
```c
|
||||
static void USARTServiceInit(usart_instance *_instance)
|
||||
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
|
||||
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
|
||||
```
|
||||
|
||||
- `USARTServiceInit()`会被`USARTRegister()`调用,开启接收中断
|
||||
|
||||
- `HAL_UARTEx_RxEventCallback()`和`HAL_UART_ErrorCallback()`都是对HAL的回调函数的重定义(原本的callback是`__week`修饰的弱定义),前者在发生**IDLE中断**或**单次DMA传输中断**后会被调用(说明收到了完整的一包数据),随后在里面根据中断来源,调用拥有产生了该中断的模块的协议解析函数进行数据解包;后者在串口传输出错的时候会被调用,重新开启接收。
|
||||
Reference in New Issue
Block a user