20 新的STM32串口DMA驱动的一点疑问?

发布于 2020-11-26 17:01:09

之前一直使用的是rt-thread 2.x版本,想升级到rt-thread 4.x版本;硬件平台STM32F767;串口接收使用DMA方式;串口DMA驱动主要涉及:
1) 硬件串口的IDLE中断;
2) DMA传输完成TC中断;其中TC中断主要是处理DMA CIRCULAR模式缓冲区数据指针从零计数的问题,以前的TC中断处理的代码如下:

    level = rt_hw_interrupt_disable();

    recv_len = serial->config.bufsz - uart->dma.last_index;
    uart->dma.last_index = 0;

    rt_hw_interrupt_enable(level);
    if (recv_len)
    {
        rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));
    }

这个串口驱动的DMA使用下来没有问题;

最新的针对STM32串口DMA接收的驱动代码,IDLE中断处理时一样的,针对TC中断的处理有一些小的更改:

    if ((__HAL_DMA_GET_IT_SOURCE(&(uart->dma_rx.handle), DMA_IT_TC) != RESET) ||
            (__HAL_DMA_GET_IT_SOURCE(&(uart->dma_rx.handle), DMA_IT_HT) != RESET))
    {
        level = rt_hw_interrupt_disable();
        recv_total_index = serial->config.bufsz - __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle));
        if (recv_total_index == 0)
        {
            recv_len = serial->config.bufsz - uart->dma_rx.last_index;
        }
        else
        {
            recv_len = recv_total_index - uart->dma_rx.last_index;
        }
        uart->dma_rx.last_index = recv_total_index;
        rt_hw_interrupt_enable(level);

        if (recv_len)
        {
            rt_hw_serial_isr(serial, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));
        }
    }

和之前的代码相比,
1) 增加了对缓冲区半满的处理,这部分和串口IDLE中断的处理类似,就是对应 (recv_total_index != 0)的情况;
2) 从代码上看, if (recv_total_index == 0)这个条件应该对应的是TC(传输完成)的情况,由于工作在CIRCULAR模式(STM32的数据手册上说,TC完成时,NDTR会自动装载初始设定的值),__HAL_DMA_GET_COUNTER()得到的是初始设定时的config.bufsz,得到recv_total_index == 0; uart->dma_rx.last_index = 0正常复位;

现在的问题是,因为DMA工作在CIRCULAR模式,数据的传输是连续不断的,在针对TC中断处理的时候,串口有可能有新的数据,这样__HAL_DMA_GET_COUNTER()得到的就不是config.bufsz。比如在处理TC时新收到了一个字节,recv_total_index = serial->config.bufsz - __HAL_DMA_GET_COUNTER(&(uart->dma_rx.handle))得到的值为1,这样的结果并不符合实际情况,相当于漏掉了针对缓冲区满指针回零的处理。

个人感觉之前的DMA驱动针对TC的处理更合理一些,

    recv_len = serial->config.bufsz - uart->dma.last_index;
    uart->dma.last_index = 0;

就是把uart->dma.last_index到config.bufsz的数全部读出来,在TC处理时新进来的数据会在下一次IDLE中断时通过__HAL_DMA_GET_COUNTER()的调用处理而不会遗漏。

不知道有没有使用过STM32平台新的串口DMA驱动的同学,大家针对这个问题有没有什么建议?还是说在针对TC的处理完成前,串口DMA在CIRCULAR模式时也不会进行新的传输?

查看更多

关注者
1
被浏览
508
2 个回答
我夏了夏天
我夏了夏天 认证专家 2020-11-26

要解决这个问题,你需要搞清楚 DMA 中断的触发条件,把断点加载串口中断处理函数和 DMA 中断处理函数上,观察这两个中断的触发顺序,以及 DMA 中断触发时的情况,可能会对你理解这个问题有帮助。

基本上使用串口 DMA 的流程是这样的,配置时将串口设备与某个通道的 DMA 中断绑定起来,此时如果发生串口中断,则会触发 DMA 传输,DMA 完成后将会回调我们在串口驱动中编写的中断处理函数,更新串口 device 的缓冲区 index。

Acuity
Acuity 2020-11-26

1、之前的DMA实质上是不合理,只使用了TC中断,即是DMA 单buf接收;如果有持续数据进来,将可能覆盖数据;不过,串口这种低速总线,怎么整都很难丢数据;
2、新的STM32串口DMA接收做的很好,用了半满中断(之前没去细看,一直想提交个老STM32的DMA 串口接收补丁,原来已经实现了,哈哈。。);关于你的TC中断处理疑问:TC中断后,CPU介入将DMA buf后半部拷贝数据到内存中,这个时候即使有串口数据来,DMA只是将数据搬运到buf的前半部分,在DMA将buf前半部分填满并触发HT中断前,CPU早就把后半部分数拷贝走了(如果CPU还没拷贝完成,就要考虑整个系统设计问题了,是否有过于频繁的中断资源占用CPU等等,或者增大DMA buf);HT中断也与之同理;
3、ST高系列,支持DMA 双buf(乒乓缓存),这个就好使多了;当然半断也够用了。
参考,STM32F0/F1 DMA收发实现,实测1.5Mbps波特率不翻车:
文章
代码仓库

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览