本帖最后由 muyichuan2012 于 2024-5-15 19:53 编辑
目的
硬件资源
函数实现
初始化 hal_uart_init()
轮询收发 hal_uart_send()/hal_uart_recv()
中断收发 hal_uart_send_it()/hal_uart_recv_it()
中断发送
中断接收
收发完成回调 hal_uart_tx_cplt_callback()/hal_uart_rx_cplt_callback()
中断处理函数 hal_uart_irq_handler()
BUG解决
BUG
解决
总结
雷达模块通信协议
控制帧格式
回复帧格式
协议实现
示例流程
结果
|
目的
采用 AT-START-F405 开发板+雷达模块,检测人体存在、运动、微动感应信息,把检测结果显示在GUI上。
第一阶段,通过USART读取雷达检测结果,并通过串口打印出来;
第二阶段,雷达检测结果显示在GUI上,使用 LVGL 制作界面。
硬件资源
两路USART管脚分配,USART1打印日志,USART5和雷达模块通信。
这里仅通过 WorkBench 配置 USART5 管脚,把它用来和雷达模块通信,PB5 标签改为 HCI_RX,PB6 标签改为 HCI_TX。
USART1 在 at32f402_405_board.c 中已经配置,PA9 作为 USART1_TX 管脚,没有配置 USART1_RX 管脚。
硬件连接如下图所示,开发板除了要和雷达模块连接UART两个管脚,还需要供电3.3V以及共地。
USART通信
硬件连接完毕,开始写代码,参考 SDK 中断的几个 USART 示例,傻眼了,SDK 提供的驱动库是标准库,提供了 USART 寄存器级别的 API,相比较于HAL库,提供完备的USART初始化、轮询/中断/DMA收发、回调函数等API,简单了点。
为了和雷达模块通信,需要先封装一层 UART 收发API,然后再提供一套字节流和解析雷达模块 HCI 协议相互转换的 API。
封装USART驱动
这里参考某厂HAL库,实现一套简化的 USART 中断收发API。
新建 hal 文件夹,并在其中新建三个文件:
- hal_def.h,存放通用的宏定义、数据结构等;
- hal_uart.c,提供一套 hal_uart_xxx() API;
- hal_uart.h,提供抽象的 uart 初始化结构体、句柄结构体,以及 hal_uart_xxx() 函数声明;
函数实现
初始化 hal_uart_init()
由于使用 WorkBench 配置管脚资源、生成初始化代码,所以目前这个函数为空。
当然,如果需要实现的话,需要在这个函数中完成以下几件事:
- USART 时钟初始化
- USART 功能配置,例如波特率、数据位、停止位、校验位、流控等参数配置;
- USART 使能;
- USART 对应的管脚使能,按照管理应当在 hal_uart.c 实现一个弱定义的函数 hal_uart_msp_init(),由用户在自己的 APP 中重新定义 hal_uart_msp_init() 并在其中初始化使用到的 GPIO 管脚:使能对应的GPIO时钟,管脚上下拉、复用功能配置等;
轮询收发 hal_uart_send()/hal_uart_recv()
轮询发送,需要先检查 USART_TDBE_FLAG,为空才能发数据寄存器写一个数据;
轮询接收,需要先检查 USART_RDBF_FLAG,不为空才能读取一个数据;
中断收发 hal_uart_send_it()/hal_uart_recv_it()
中断发送
简化形式,在 hal_uart_send_it() 中初始化句柄参数,记录 tx_buf, tx_buf_size 以及 tx_cnt = 0,然后开启 USART_TDBE_INT 中断。
实际的数据发送过程在中断服务程序中完成。
中断接收
简化形式,在 hal_uart_recv_it() 中初始化句柄参数,记录 rx_buf, rx_buf_size 以及 rx_cnt = 0,然后开启 USART_RDBF_INT 中断。
注意:实际上 USART 接收多少个数据是任意的,为了实现不定长的数据接收,就得想一个办法来检测 USART 何时接收终止。这里选择 RTOD -- 接收超时,来判断USART接收完成。配置接收超时时间并使能 USART_RTOD_INT 中断。
实际的数据接收过程在中断服务程序中完成。
收发完成回调 hal_uart_tx_cplt_callback()/hal_uart_rx_cplt_callback()
在 hal_uart.c 中定义两个 __WEAK 修饰的函数,实现为空函数。它们仅在下面的 hal_uart_irq_handler() 函数中调用,当发送完成时调用 hal_uart_tx_cplt_callback() 函数,当接收完成时调用 hal_uart_rx_cplt_callback() 函数。
实际上如果用户需要用中断收发,需要用户在自己的源文件中重新实现这两个函数,由于用户实现的函数没有加 __WEAK 修饰,链接器会使用用户实现的函数覆盖 hal_uart.c 中定义的两个若函数,不用担心函数重复定义的问题。
中断处理函数 hal_uart_irq_handler()
综上所述,需要在中断中处理3个中断类型,分别是:
1. USART_RTODF_FLAG,接收超时
- 先调用 hal_uart_rx_cplt_callbak()
- 禁止 USART_RTOD_INT 和 USART_RDBF_INT 中断,即 USART 不再通过中断接收数据;
- 清除 USART_RTODF_FLAG 中断,这个需要手动清除;
2. USART_RDBF_FLAG,接收缓冲区满
- 当看到这个中断标记,说明可以取出数据放到用户缓冲区,调用 usart_data_receive() 取出数据;
- 为了防止接收缓冲区溢出,再判断一下接收的数据个数是否大于等于 rx_buf_size。如果大于,就关闭 USART_RDBF_INT 中断;
3. USART_TDBE_FLAG,发送缓冲区空
- 看到这个中断来了,说明可以发送数据了,调用 usart_data_transmit() 发送一个数据;
- 当 tx_cnt 大于 tx_buf_size 时,说明发送完成了,调用 hal_uart_tx_cplt_callback(),然后禁止 USART_TDBE_INT 中断。
BUG解决
BUG
在调试 hal_uart_xxx() API 时,为了实时查看 USART 寄存器,把 SVD 文件加载进来,实时查看 USART5 的寄存器。这下把我坑惨了。
如下图所示,在中断服务程序中打断点,虽然雷达模块返回了数据,但是 STS.RDBF 再也没有置位了,这是为什么呢?
解决
这个问题我调了一天半,对比了示例工程,NVIC 配置了,中断也使能了,雷达模块也返回数据了,但是 AT32F405 就是读不到 RDBF 中断状态。
后来把 SVD 文件关闭,不再实时查看寄存器,问题解决了。
总结
- 此bug不是代码的问题,也不是硬件问题;
- 此bug产生的原因是调试器实时读取 RDBF 寄存器和 USART_DT 寄存器,这相当于 MCU 去读取这两个寄存器,MCU的数字实现会把 RDBF 标记清零,等我中断代码再来读取 RDBF 标记,肯定始终是0;
- 此bug我踩坑N次了,只不过调了2天代码之后才反映过来;
- 在调试代码时,没事就不用看外设寄存器,除非实在有必要;
雷达模块通信协议
这里的 HCI 协议是自定义的协议,协议分控制帧和回复帧。
回复帧的 CMD ID 必须和控制帧的 CMD ID 相同。
控制帧格式
HEAD
(U8)
| CMD
(U8)
| LEN
(U8)
| PAYLOAD
(N Bytes)
| CHECKSUM
(U16)
| 0x58
| 唯一的CMD ID
| 后面 PAYLOAD 长度,最大255字节
| | 校验和,2个字节
|
回复帧格式
HEAD
(U8)
| CMD
(U8)
| LEN
(U8)
| PAYLOAD
(N Bytes)
| CHECKSUM
(U16)
| 0x59
| 唯一的CMD ID
| 后面 PAYLOAD 长度,最大255字节
| | 校验和,2个字节
|
协议实现
由于控制帧和回复帧格式相同,仅帧头固定字节不同,所以可以用同一个结构体表达,如下所示:
首先实现两个函数,函数1实现字节流转换为协议帧,函数2实现协议帧转换为字节流。
示例流程
- 新建一个 task,在这个 task 中完成雷达模块通信、数据解析、消息通知。
- 构建一个HCI命令,通过UART中断形式发送,等待发送完成;
- 等待HCI命令响应,通过UART中断形式接收,等待接收完成;
- 解析 HCI 响应,确保接收数据合法,验证是一个完整的响应帧;
- HCI 命令遍历,找到对应的命令处理函数并调用。这里仅打印每个字段,输出到串口。
- 消息通知,发送给 lvgl 线程,更新UI。
TODO:
- 其中第5步还没有做;
- 这个线程的任务很繁琐,应当把 UART 通信部分放到一个后台线程中,例如全丢到中断中,并且解析HCI响应帧、查找命令处理函数都应该在后台进行。这里时间有限,就都放到线程中处理。
结果
串口打印结果如下:
- 显示 dump ci 响应帧二进制数据流;
- 解析 CI 响应帧;
- 提取雷达检测结果,包括检测存在是否有效、距离纤细、角度信息等信息。
|