RT-Thread串口接收的BUG(DMA缓存区太小)
Posted Rabbit-susu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RT-Thread串口接收的BUG(DMA缓存区太小)相关的知识,希望对你有一定的参考价值。
RT-Thread串口接收的问题(ringbuffer溢出)
问题描述
串口+DMA接收数据,发送数据一般会用到缓存区.这几天使用UART+DMA+ringbuff+缓存区的方式处理json数据时,发现程序会跑飞,也就是ringbuffer溢出.
按理说,官方的代码应该不会有什么问题,但问题就是在这,不清楚是中断的问题,还是缓存区的问题.
问题是怎么发生的 :
- 首先,如下代码:
#define RT_SERIAL_RB_BUFSZ 128
RT_SERIAL_RB_BUFSZ
这个宏决定了DMA的buffer大小和serial的ringbuffer大小.
- 配置 DMA buffer 大小的代码如下:
/* enable interrupt */
if (flag == RT_DEVICE_FLAG_DMA_RX)
rx_fifo = (struct rt_serial_rx_fifo *)serial->serial_rx;
/* Start DMA transfer */
if (HAL_UART_Receive_DMA(&(uart->handle), rx_fifo->buffer, serial->config.bufsz) != HAL_OK)
/* Transfer error in reception process */
RT_ASSERT(0);
CLEAR_BIT(uart->handle.Instance->CR3, USART_CR3_EIE);
__HAL_UART_ENABLE_IT(&(uart->handle), UART_IT_IDLE);
- 分配 serial 的 ringbuffer 大小的代码如下:
struct rt_serial_rx_fifo* rx_fifo;
rx_fifo = (struct rt_serial_rx_fifo*) rt_malloc (sizeof(struct rt_serial_rx_fifo) + serial->config.bufsz);
RT_ASSERT(rx_fifo != RT_NULL);
rx_fifo->buffer = (rt_uint8_t*) (rx_fifo + 1);
rt_memset(rx_fifo->buffer, 0, serial->config.bufsz);
rx_fifo->put_index = 0;
rx_fifo->get_index = 0;
rx_fifo->is_full = RT_FALSE;
serial->serial_rx = rx_fifo;
/* configure fifo address and length to low level device */
serial->ops->control(serial, RT_DEVICE_CTRL_CONFIG, (void *) RT_DEVICE_FLAG_DMA_RX);
通过RT-Thread的源码可知 DMA buffer 和 serial 的 ringbuffer 公用一个 buffer.
我发送的数据长度为129,发送间隔>250ms(就是象征性表示这个速度不快),连续发几帧(没摸到规律,一般是4帧有时候也不一定),就会出现这个问题.
当然之前发送的是192+bytes的数据,现象一样的.
hard fault on handler
bus fault:
SCB_CFSR_BFSR:0x04 IMPRECISERR
- 串口的回调函数为
rt_err_t uart2_input(rt_device_t device, size_t size)
rt_mb_send(&serial2_rx_mb, size);
return RT_EOK;
- 这是线程中读取的代码,使用的邮箱来阻塞线程.
rt_uint8_t buffer[512] = 0;
/* 省略一部分无关代码 */
result = rt_mb_recv(&serial2_rx_mb, &length, RT_WAITING_FOREVER);
/* 省略一部分无关代码 */
result = rt_device_read(device,0,buffer,length);
LOG_D("r_len:%d", result);
i = i+result;
通过LOG_D
输出result,length这俩变量的值.
上面这个图没有加入这段代码:
cjson_ptr = cJSON_Parse((char *)json_buffer);
LOG_I("%s", cJSON_GetErrorPtr());
if(cjson_ptr == NULL)
LOG_I("cJSON_Parse failed");
continue;
加入这段cJSON的防错代码之后.
目前的解决方式 :
增加DMA buffer的大小RT_SERIAL_RB_BUFSZ
#define RT_SERIAL_RB_BUFSZ 256
目的是确保一帧数据(这里就是一帧JSON报文),小于这个RT_SERIAL_RB_BUFSZ
.
这样,就可以在1帧/100ms的速度下,可以稳定的运行,不出现错误.
本文来自博客园,作者:当最后一片树叶落下,转载请注明原文链接:https://www.cnblogs.com/Rabbit-susu/p/17400727.html
RT-Thread uart2串口dma idle接收不断帧
硬件STM32F407,IDE使用RT-Thread Studio。
uart2串口使用这两个引脚:
功能 | IO端口 |
---|---|
UART2-TX | PA2 |
UART2-RX | PA3 |
UART2 - DMA接收配置
-
先使能DMA接收,RX缓冲区可以稍微调大些。
-
board.h 中添加宏,来使能 RX_DMA。
-
既然都打开了 board.h,再顺便把时钟源改为外部晶振。
-
编写 UART2 DMA 接收测试代码。
-
发生接收事件后,会触发回调。回调内记录本次接收的消息长度,并发送信号量。线程内接收到信号量后开始执行后续的任务,调试输出接收到的长度和内容。
main.c
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "drivers/serial.h"
rt_device_t u2_dev = RT_NULL;
struct serial_configure u2_cfg = RT_SERIAL_CONFIG_DEFAULT;
struct rt_semaphore u2_rx_sem;
rt_thread_t u2_recv_thread;
rt_size_t u2_rx_len = 0;
rt_err_t u2_rx_callback(rt_device_t dev, rt_size_t size)
u2_rx_len = size; // 记录消息长度
rt_sem_release(&u2_rx_sem);
return RT_EOK;
void u2_recv_entry(void *parameter)
char rx_buf[256];
rt_size_t len = 0;
while (1)
rt_sem_take(&u2_rx_sem, RT_WAITING_FOREVER); // 等待信号量
len = rt_device_read(u2_dev, 0, rx_buf, u2_rx_len);
rx_buf[len] = '\\0';
rt_kprintf("u2 recv: %d, %s\\n", len, rx_buf);
int main(void)
rt_err_t err;
u2_dev = rt_device_find("uart2");
if (u2_dev == RT_NULL)
LOG_E("uart2 rt_device_find failed");
return -EINVAL;
//rt_device_open(u2_dev, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX);
err = rt_device_open(u2_dev, RT_DEVICE_FLAG_DMA_RX);
if (err < 0)
LOG_E("uart2 rt_device_open failed");
return err;
rt_device_control(u2_dev, RT_DEVICE_CTRL_CONFIG, (void *)&u2_cfg);
rt_device_set_rx_indicate(u2_dev, u2_rx_callback); // 设置接收回调函数
err = rt_sem_init(&u2_rx_sem, "u2_rx", 0, RT_IPC_FLAG_FIFO);
if (err < 0)
LOG_E("uart2 rt_sem_init failed");
return err;
u2_recv_thread = rt_thread_create("u2_recv", u2_recv_entry, NULL, 1024*2, 8, 5); // 优先级8,时间片长度5
rt_thread_startup(u2_recv_thread);
rt_device_write(u2_dev, 0, "hello", 5);
return RT_EOK;
编译错误解决
-
编译后会提示缺少文件等错误。按如下更改。
-
改动RTT源码,添加缺少的头文件。
-
再配置交叉编译器include目录,增加如下:
"$workspace_loc:/$ProjName/rt-thread/components/drivers/include/drivers"
"$workspace_loc:/$ProjName/rt-thread/components/drivers/include/ipc"
- 添加目录时请使用
工作空间
,使用的是相对工程文件的路径。(文件系统指的是磁盘的绝对路径,复制工程又要重新配置)
运行测试RX DMA接收,解决断帧
-
接收正常,但当接收的一帧字节数较长时,非常容易出现断帧。
-
断帧的解决方法我参考这个,实测好用:
使用RT-Thread的串口空闲+DMA收发数据 -
找打 drv_usart.c,注释掉这两句。
-
编译验证,确认经如上修改后就没有了断帧。
-
但当接收的一帧字节数大于缓冲区容量时,以前的内容会被覆盖。所以在硬件允许时,uart rx缓冲区可设置的大一些。
-
测试发送一帧270字节,当缓冲区容量为256字节时,仅提示收到14字节,前面的内容被覆盖。
关于RT-Thread的学习
- 官方应用实例、Demo:文档中心
- API文档:RT-Thread API参考手册
- IDE使用RT-Thread Studio,入门教程可以去B站看千峰教育的。
本来自己是没计划去学习 RT-Thread 的,但在MCU国产化替代进程中,发现大多数IC厂商提供的支持十分有限,可以说几乎都是在近几年内以STM32为蓝本进行的仿制,其中隐藏的雷坑单靠个人摸索去解决相当不推荐。再加之大多数原厂Demo是裸机的,很少有RTOS的。于是就想到了国产的 RT-Thread。
试用了 RT-Thread Studio 感觉相当好用,MCU的外设驱动不用用户操心,又有比较多的扩展组件可用,开发者可在熟悉RT-Thread的基础上,专注于应用的开发。其对国产MCU的支持正在逐步完善,暂时有 AT32、APM32、CH32、ES32、GD32、MM32 等,虽然只支持部分型号,但个人目前只中意AT32F403A,好巧不巧正好有它的芯片支持包。希望国产MCU能抓住替换潮机会,抓紧完善生态,不然等价格回落又会有一大批人转回STM32。望疫情早些过去,想念前三年芯片的价格。
以上是关于RT-Thread串口接收的BUG(DMA缓存区太小)的主要内容,如果未能解决你的问题,请参考以下文章
RT-Thread uart2串口dma idle接收不断帧
玩转RT-Thread系列教程--消息队列的使用(串口DMA)