RTThread使用DMA串口接收数据不连续的问题
RTThread使用DMA接收串口数据的问题
问题/现象
使用RTThread的DMA接收串口数据,数据不连续,即IDLE中断没有起到作为一个frame的判定.
经过对serial和drv_uarts源码的分析,得出原因:
从上图可知,发生IDLE中断时,USART1_IRQHandler
调用的是和UART1_DMA_RX_IRQHandler
相同的接口 —— rt_hw_serial_isr
.
这就造成无法区分是IDLE中断还是DMA中断.
不改变源码的情况下,仍使用DMA+IDLE中断,目前这两种方式是比较好的
解决方式①
- 接收数据
/* 接收数据回调函数 */
static rt_err_t uart2_input(rt_device_t dev, rt_size_t size)
{
rt_err_t result;
result = rt_sem_release(&serial2_sem);/*通知serial_thread_entry线程,有数据了*/
if ( result == RT_EOK)
{
rt_kprintf("sem release error!\n");
}
return result;
}
static void serial_thread_entry(void *parameter)
{
rt_err_t result;
rt_uint8_t c = 0;
rt_uint8_t i = 0, rx_state = SERIAL2_STATE_WAIT_FRAME;
rt_device_t serial2 = rt_device_find("uart2");
struct frame_msg msg;
while (1) {
/* 使用信号量触发接收数据 */
switch (rx_state) {
case SERIAL2_STATE_WAIT_FRAME: {
result = rt_sem_take(&serial2_sem, RT_WAITING_FOREVER);
if (result == RT_EOK) {
rt_kprintf("%s: frame start\n", __func__);
rx_state = SERIAL2_STATE_RECV_DATA;
i = 0;
}
break;
}
case SERIAL2_STATE_RECV_DATA: {
/* 读取一段数据 */
while (rt_device_read(serial2, 0, &c, 1) != 0) {
framebuf[i] = c;
i++;
if (i >= UART2_RB_BUFSZ) {
i = 0;
rx_state = SERIAL2_STATE_BAD_FRAME;/* 坏帧 */
rt_kprintf("%s: frame abandon.\n", __func__);
break;
}
}
result = rt_sem_take(&serial2_sem, 10);/* 等待下一段 */
if (result == -RT_ETIMEOUT) {
/* 给其它线程通信 */
framebuf[i] = '\0';
msg.data = framebuf;
msg.size = i;
result = rt_mq_send_wait(&rtc_rx_mq, (void*)&msg, sizeof(struct frame_msg), 10);
if (result != RT_EOK) {
rt_kprintf("%s: msgqueue send error-[%d]\n", __func__, result);
}
rx_state = SERIAL2_STATE_WAIT_FRAME;/* 一帧结束 */
rt_kprintf("%s: frame end\n", __func__);
} else {
/*接收下一段数据*/
rt_kprintf("(%d):%d\n", __LINE__, i);
}
break;
}
#if BUG_FIXED
case SERIAL2_STATE_BAD_FRAME: {
/* 读取一段数据 */
while (rt_device_read(serial2, 0, &c, 1) != 0) {
framebuf[i] = c;
i++;
if (i >= UART2_RB_BUFSZ) {
i = 0;
rt_kprintf("%s: bad frame.\n", __func__);
break;
}
}
result = rt_sem_take(&serial2_sem, 10);/* 等待下一段 */
if (result == -RT_ETIMEOUT) {
/* 丢弃该帧 */
rx_state = SERIAL2_STATE_WAIT_FRAME;/* 一帧结束 */
rt_kprintf("%s: bad frame end\n", __func__);
} else {
/*接收下一段数据*/
rt_kprintf("(%d):%d\n", __LINE__, i);
}
break;
}
#endif
default:
break;
}
}
}s
解决方式②
把信号量换成消息队列,邮箱,事件应该也都是可以的.
其它问题
1.局部变量的生存期
代码中的几个局部变量在线程中具有了和线程一样长的生存期周期
life_circle(serial_thread_entry) = life_circle(variable(i))
= life_circle(variable(rx_state))
= life_circle(variable(serial2))
= ...
2.这样是不是意味着thread的stack一直被占用?
嗯,是的.
3.帧的长度与帧的超时判定
帧与帧之间的间隔不能小于10个 OStick ,我这边 OStick=1ms .
上面代码中存在几个问题 (2023.05.04):
① 数据过快.
stack overflow.
② 单帧超过buffer大小 (UART2_RB_BUFSZ)
程序流程异常,这种情况是模拟,恶意注入长数据造成异常
③ 开启了DMA的HT,TC,以及UART的IDLE中断.
这三个中断调用uart_input()
函数,从而信号量 serial2_sem 会多次+1.这会导致读取完一帧数据后,仍然存在多个sem信号,这就会需要 rt_sem_take(,10) 将这些额外的sem信号消除掉.
- BUG修复(2023.05.05)
宏
BUG_FIXED
注释的那部分,为主要添加的代码(还有上面一些零碎的逻辑).
- 宏
BUG_FIXED
主要修复了 BUG② ,理想情况下帧间隔是 10 OSTicks,但是因为有信号量 serial2_sem 多次+1的问题,所以,帧间隔将大于 10 OSTicks,为了避免发送的数据帧失效,建议通过实际测试来取值,即帧的发送频率与波特率,一帧数据的长度有关系\[freq(帧的发送频率) = function(波特率,一帧数据的长度) \]\[freq(帧的发送频率) \propto 波特率 \]\[freq(帧的发送频率) \propto 1/一帧数据的长度 \]
- BUG① 的问题是因为没有加下面这部分代码:
if (i >= UART2_RB_BUFSZ) { i = 0; rt_kprintf("%s: bad frame.\n", __func__); break; }
- BUG③ 也没办法解决,除非去掉HT,TC中断处理中
dma_recv_isr()
函数.不过这可能会丢帧,丢数据.
总结
上面的程序已经实现了使用RTThread提供的,串口DMA+IDLE中断的方式,接收一帧数据,并且对'bad frame'的处理,具有了"防御性编程"的意味.
使用时,需要对帧的发送频率进行控制,否则会出现'bad frame'的情况.
建议:使用信号量接收串口数据时,不要用
RT_DEVICE_FLAG_DMA_RX
来处理,因为会触发HT,TC,IDLE事件.会导致uart2_input()
回调函数调用多次,然后,信号量的值>1的情况.见上面分析的③.
补充:
RT-Thread的I/O设备之UART设备是有不合理之处的.
1.UART设备在开启 RT_DEVICE_FLAG_DMA_RX
时,启用了HT,TC,IDLE三个事件来触发中断.
本文来自博客园,作者:当最后一片树叶落下,转载请注明原文链接:https://www.cnblogs.com/Rabbit-susu/p/17369923.html