Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
DMA
串口
串口DMA接收
[已解决] RTT 串口DMA接受 开头丢失一整包的问题
5.00
发布于 2022-10-21 14:28:32 浏览:2709
订阅该版
[tocm] ## 起因 项目需求需要设备通过串口的方式发送信息,STM32F4将串口的信息接收并保存到SD卡中;通过XCOM串口助手发送文件的方式进行功能测试,测试的时候发现总会丢弃一部分头; 项目需要两路串口都保存到SD卡中,目前只调出来一路串口,因为写入串口的时候偶发会有一个很长的写入时间,正常4KB写入为15ms左右,偶发会有200+ms,在我双路波特率为460800的时候经常会爆缓存区。**(尚未解决,如果哪位好心人知道的话可以在下面留言讨论)** 在调试过程中就觉得很奇怪,为什么会丢弃头?如果丢弃文件结尾的话可以理解,是因为当时接收到的包不足4KB,拼包线程还在等待数据拼成4KB再进行写入,但是丢弃头真的理解不了。开始的时候以为是线程优先级的问题,串口中断来了,拼包线程还没创建或者还没准备好,排除了这个问题之后,就开始检查中断的问题了。 ------------ ## 过程 ### **在drv_usart.c中** 如果我们打开了**DMA接收的话** ,比如我这里使用的**STM32F405RGT6**,打开了USART3 RX DMA,我们可以看到系统自动为我们打开了 ```c #if defined(BSP_USING_UART3) void USART3_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); uart_isr(&(uart_obj[UART3_INDEX].serial)); /* leave interrupt */ rt_interrupt_leave(); } #if defined(RT_SERIAL_USING_DMA) && defined(BSP_UART3_RX_USING_DMA) void UART3_DMA_RX_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); HAL_DMA_IRQHandler(&uart_obj[UART3_INDEX].dma_rx.handle); /* leave interrupt */ rt_interrupt_leave(); } ``` 以上两个函数,分别是串口3中断函数跟DMA请求中断函数。 其中中断函数调用了uart_isr(),这里比较重要,需要深究. DMA请求中断函数调用了HAL_DMA_IRQHandler(),内部主要是根据DMA->ISR寄存器,提示的标志位进行对应中断、错误的重置,并在收到UART_RX_DMA_IT_TC_FLAG/UART_RX_DMA_IT_HT_FLAG和错误标志的时候调用对应的处理函数,其处理函数如下所示: ```c /** * @brief UART error callbacks * @param huart: UART handle * @note This example shows a simple way to report transfer error, and you can * add your own implementation. * @retval None */ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { RT_ASSERT(huart != NULL); struct stm32_uart *uart = (struct stm32_uart *)huart; LOG_D("%s: %s %d\n", __FUNCTION__, uart->config->name, huart->ErrorCode); UNUSED(uart); } /** * @brief Rx Transfer completed callback * @param huart: UART handle * @note This example shows a simple way to report end of DMA Rx transfer, and * you can add your own implementation. * @retval None */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { struct stm32_uart *uart; RT_ASSERT(huart != NULL); uart = (struct stm32_uart *)huart; dma_recv_isr(&uart->serial, UART_RX_DMA_IT_TC_FLAG); } /** * @brief Rx Half transfer completed callback * @param huart: UART handle * @note This example shows a simple way to report end of DMA Rx Half transfer, * and you can add your own implementation. * @retval None */ void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { struct stm32_uart *uart; RT_ASSERT(huart != NULL); uart = (struct stm32_uart *)huart; dma_recv_isr(&uart->serial, UART_RX_DMA_IT_HT_FLAG); } ``` 我们再来探究一下我们刚提到但是还没有看源码的uart_isr()函数 ```c static void uart_isr(struct rt_serial_device *serial) { struct stm32_uart *uart; RT_ASSERT(serial != RT_NULL); uart = rt_container_of(serial, struct stm32_uart, serial); /* UART in mode Receiver -------------------------------------------------*/ if ((__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_RXNE) != RESET)) { rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_IND); } #ifdef RT_SERIAL_USING_DMA else if ((uart->uart_dma_flag) && (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_IDLE) != RESET) && (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_IDLE) != RESET)) { dma_recv_isr(serial, UART_RX_DMA_IT_IDLE_FLAG); __HAL_UART_CLEAR_IDLEFLAG(&uart->handle); } else if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TC) && (__HAL_UART_GET_IT_SOURCE(&(uart->handle), UART_IT_TC) != RESET)) { if ((serial->parent.open_flag & RT_DEVICE_FLAG_DMA_TX) != 0) { HAL_UART_IRQHandler(&(uart->handle)); } UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TC); } #endif else { if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_ORE) != RESET) { __HAL_UART_CLEAR_OREFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_NE) != RESET) { __HAL_UART_CLEAR_NEFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_FE) != RESET) { __HAL_UART_CLEAR_FEFLAG(&uart->handle); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_PE) != RESET) { __HAL_UART_CLEAR_PEFLAG(&uart->handle); } #if !defined(SOC_SERIES_STM32L4) && !defined(SOC_SERIES_STM32WL) && !defined(SOC_SERIES_STM32F7) && !defined(SOC_SERIES_STM32F0) \ && !defined(SOC_SERIES_STM32L0) && !defined(SOC_SERIES_STM32G0) && !defined(SOC_SERIES_STM32H7) \ && !defined(SOC_SERIES_STM32G4) && !defined(SOC_SERIES_STM32MP1) && !defined(SOC_SERIES_STM32WB) \ && !defined(SOC_SERIES_STM32L5) && !defined(SOC_SERIES_STM32U5) #ifdef SOC_SERIES_STM32F3 if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_LBDF) != RESET) { UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_LBDF); } #else if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_LBD) != RESET) { UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_LBD); } #endif #endif if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_CTS) != RESET) { UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_CTS); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_TXE) != RESET) { UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_TXE); } if (__HAL_UART_GET_FLAG(&(uart->handle), UART_FLAG_RXNE) != RESET) { UART_INSTANCE_CLEAR_FUNCTION(&(uart->handle), UART_FLAG_RXNE); } } } ``` 可以观察到,在上述函数中主要处理了**UART_IT_RXNE**和**UART_IT_IDLE**; 对RXNE不做细究, 我们直接看IDLE空闲中断中会做什么处理。 IDLE函数调用了dma_recv_isr(serial, UART_RX_DMA_IT_IDLE_FLAG); **该函数是我们需要仔细研究的地方.... 问题就出现在这里** 如果看到这里,你不是很愚钝的话,应该已经发现了这几个函数的共性。 他们最终的处理函数都是dma_recv_isr(),只不过他们的输入参数不同罢了. ### 输入参数分析 那他们的输入参数分别是什么呢? 我们简单的列出他们的输入参数 - DMA传输过半中断 dma_recv_isr(&uart->serial, UART_RX_DMA_IT_HT_FLAG); - DMA传输完成中断 dma_recv_isr(&uart->serial, UART_RX_DMA_IT_TC_FLAG); - 串口空闲中断 dma_recv_isr(serial, UART_RX_DMA_IT_IDLE_FLAG); ------------ ## 最终归宿 那**活着(问题)**的最终归宿是什么呢,很明显,是dma_recv_isr() ```c #ifdef RT_SERIAL_USING_DMA static void dma_recv_isr(struct rt_serial_device *serial, rt_uint8_t isr_flag) { struct stm32_uart *uart; rt_base_t level; rt_size_t recv_len, counter; RT_ASSERT(serial != RT_NULL); uart = rt_container_of(serial, struct stm32_uart, serial); level = rt_hw_interrupt_disable(); recv_len = 0; counter = __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle)); switch (isr_flag) { case UART_RX_DMA_IT_IDLE_FLAG: if (counter <= uart->dma_rx.remaining_cnt) recv_len = uart->dma_rx.remaining_cnt - counter; else recv_len = serial->config.bufsz + uart->dma_rx.remaining_cnt - counter; break; case UART_RX_DMA_IT_HT_FLAG: if (counter < uart->dma_rx.remaining_cnt) recv_len = uart->dma_rx.remaining_cnt - counter; break; case UART_RX_DMA_IT_TC_FLAG: if(counter >= uart->dma_rx.remaining_cnt) recv_len = serial->config.bufsz + uart->dma_rx.remaining_cnt - counter; default: break; } if (recv_len) { uart->dma_rx.remaining_cnt = counter; rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8)); } rt_hw_interrupt_enable(level); } ``` 我们来分析一下这个函数的主要逻辑 每次进函数的时候都会初始化当前接收的recv_len=0,并且去获取对应的NDTR寄存器的值(counter = __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle));). 我们需要分成两种情况来讨论接收和处理. - 上位机发送字符数量小于我缓冲区大小(一次能收完) 这种情况非常简单,通过一开始写入NDTR的值-当前获得的,我们能获取当次串口接受到的字符数量,也就是recv_len。 - 上位机发送字符数量大于我缓冲区大小(缓冲区需要重载) 这种情况相对于第一种情况就略显复杂了, 我们需要统计两次,因为DMA设置了循环模式,所以他会自动重载.我们需要读取上一次进中断时NDRT的值; 先计算[上一次进中断时NDRT-0],这是重载发生前接收的字符数量;还需要计算[重载值(buffsize)-当前NDRT的值],这是重载发生后接收的字符数量. 如果理解了上述的两种情况,那么我们对下面这段代码就非常好理解了. ``` c if (counter <= uart->dma_rx.remaining_cnt) recv_len = uart->dma_rx.remaining_cnt - counter; //未发生重载 else // 发生重载 recv_len = serial->config.bufsz + uart->dma_rx.remaining_cnt - counter; break; ``` 那么问题出现在哪里呢? 问题就出现在发生第一次重载的时候, 计算[重载值(buffsize)-当前NDRT的值]上. **RTT初始化uart->dma_rx.remaining_cnt为0, 当我们第一次接收到大于重载值(buffsize)的包的时候,我们会先进入DMA传输半中断,但是此时在DMA传输半中断中什么都没有做,因为dma_rx.remaining_cnt=0!!!** ```c case UART_RX_DMA_IT_HT_FLAG: if (counter < uart->dma_rx.remaining_cnt) recv_len = uart->dma_rx.remaining_cnt - counter; break; case UART_RX_DMA_IT_TC_FLAG: if(counter >= uart->dma_rx.remaining_cnt) recv_len = serial->config.bufsz + uart->dma_rx.remaining_cnt - counter; ``` 观察上面两个处理方式,在半中断的时候其实已经计算了一次recv_len,并在后序调用用户callback函数告知用户需要取走数据;但由于dma_rx.remaining_cnt=0,第一次进入半中断其实啥也没干,recv_len也是默认值0,用户也取不到数据;等到发生DMA接受中断的时候,因为DMA是循环模式,数据已经被覆盖掉了,所以丢失掉了完整的一个包,包长度为(buffsize). ------------ ## 解决方案 **设置uart->dma_rx.remaining_cnt为buffsize就好了** 给出修改后的代码, 稍后会提交PR. ```c static rt_err_t stm32_configure(struct rt_serial_device *serial, struct serial_configure *cfg) { struct stm32_uart *uart; RT_ASSERT(serial != RT_NULL); RT_ASSERT(cfg != RT_NULL); uart = rt_container_of(serial, struct stm32_uart, serial); uart->handle.Instance = uart->config->Instance; uart->handle.Init.BaudRate = cfg->baud_rate; uart->handle.Init.Mode = UART_MODE_TX_RX; uart->handle.Init.OverSampling = UART_OVERSAMPLING_16; switch (cfg->flowcontrol) { case RT_SERIAL_FLOWCONTROL_NONE: uart->handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; break; case RT_SERIAL_FLOWCONTROL_CTSRTS: uart->handle.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS; break; default: uart->handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; break; } switch (cfg->data_bits) { case DATA_BITS_8: if (cfg->parity == PARITY_ODD || cfg->parity == PARITY_EVEN) uart->handle.Init.WordLength = UART_WORDLENGTH_9B; else uart->handle.Init.WordLength = UART_WORDLENGTH_8B; break; case DATA_BITS_9: uart->handle.Init.WordLength = UART_WORDLENGTH_9B; break; default: uart->handle.Init.WordLength = UART_WORDLENGTH_8B; break; } switch (cfg->stop_bits) { case STOP_BITS_1: uart->handle.Init.StopBits = UART_STOPBITS_1; break; case STOP_BITS_2: uart->handle.Init.StopBits = UART_STOPBITS_2; break; default: uart->handle.Init.StopBits = UART_STOPBITS_1; break; } switch (cfg->parity) { case PARITY_NONE: uart->handle.Init.Parity = UART_PARITY_NONE; break; case PARITY_ODD: uart->handle.Init.Parity = UART_PARITY_ODD; break; case PARITY_EVEN: uart->handle.Init.Parity = UART_PARITY_EVEN; break; default: uart->handle.Init.Parity = UART_PARITY_NONE; break; } #ifdef RT_SERIAL_USING_DMA if (!(serial->parent.open_flag & RT_DEVICE_OFLAG_OPEN)) { // uart->dma_rx.remaining_cnt = 0; uart->dma_rx.remaining_cnt = cfg->bufsz; } #endif if (HAL_UART_Init(&uart->handle) != HAL_OK) { return -RT_ERROR; } return RT_EOK; } ``` ## 因为我只修改了STM32 的固件包,如果其他固件发现也有问题,可以参考这里的做法进行修改. ------------ ## 特别致谢 贡献者: fizecoding [@fuxiaoer](https://club.rt-thread.org/u/c7af60d3c6a60bb0 'fuxiaoer') ItsGettingWorse [@ItsGettingWorse](https://club.rt-thread.org/u/62b086fc5e4a853a 'ItsGettingWorse')
15
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
ItsGettingWorse
这家伙很懒,什么也没写!
文章
1
回答
0
被采纳
0
关注TA
发私信
相关文章
1
串口DMA发送数据时,数据被覆盖
2
关于串口DMA模式下rt_device_close问题
3
利用stm32f427实现usb转串口,电脑端什么也没有识别到
4
finsh 控制台 适配 RS 485请大神指点????
5
uart_sample.c 中,读串口设备时偏移量pos要设置为-1而不是0?
6
【结贴】at_device软件包中对串口接收数据缺少判断导致数据接收异常
7
串口无法接受数据,但可以发送
8
串口如何有效的清除掉接收缓冲,而不必一个一个的去读取
9
串口接收使用方式问题
10
雅特力FINSH问题
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
freemodbus
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
Debug
中断
编译报错
rt_mq_消息队列_msg_queue
SFUD
keil_MDK
msh
ulog
C++_cpp
MicroPython
本月问答贡献
a1012112796
20
个答案
3
次被采纳
红枫
8
个答案
2
次被采纳
踩姑娘的小蘑菇
7
个答案
2
次被采纳
三世执戟
7
个答案
1
次被采纳
张世争
6
个答案
1
次被采纳
本月文章贡献
YZRD
3
篇文章
6
次点赞
catcatbing
3
篇文章
6
次点赞
lizimu
2
篇文章
12
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部