在STM32上实现高性能模拟UART

2019-12-13 18:30发布

本帖最后由 bigk2000 于 2018-12-22 14:37 编辑

最近的一个项目上要2个UART,我用的STM32F103上有2个,但其中一个的IO被其他功能占用且不能调整;
虽然波特率为9600,但由于CPU关键任务是FOC电机控制,经过评估,使用CPU直接操作IO的方式时,我无法保证通信的可靠性;
经过一番思考,灵感来袭,于是就有了这个帖子。

使用IO模拟UART并不复杂,但如何做到高性能的模拟UART则是一个挑战;

是否有可能使纯软件模拟的UART满足以下特性:
1,全双工,半双工均可;
2,最大波特率可达115200及以上,甚至可以做到512000;
3,任意IO模拟TX RX;
4,发送时序可做到0误差;
5,CPU负载率小;


下面来探讨如何在STM32上实现满足以上特性的模拟UART。

基本思路:
  使用定时器触发DMA的方式模拟UART的收发;

具体实现,以通讯格式9600,8N1为例:
  将TIM2(或TIM3)的周期设为波特率周期约104us,设一个通道的比较匹配值为52us;
  发送时,仅开启计数溢出触发DMA,设置DMA搬运次数为10(1个起始位, 8个数据位,1个停止位),格式化要发送的数据保存到发送数组中(注解),
  设置DMA把发送数组的数据搬运到GPIO;
  然后启动定时器,当DMA搬运完成时,这个字节就发送完毕了;
  接收时,仅开启计数匹配触发DMA,设置DMA搬运次数为10,设置DMA把GPIO搬运到接收数组,设置RX脚下降沿触发IO中断;
  当IO中断触发时,清零定时器并启动;  当DMA搬运完成中断触发时,停止定时器,此时字节已经接收完毕;
  
  注解:
    以TX脚位为PIN4,发送0x23为例,定义一个10元素的数组,元素长度为16bit(对应GPIO位宽);
    格式化后的10个元素依次为:
    起始位  :XXXX XXXX XXX0 XXXX
    数据位0:XXXX XXXX XXX0 XXXX
    数据位1:XXXX XXXX XXX1 XXXX
    数据位2:XXXX XXXX XXX0 XXXX
    数据位3:XXXX XXXX XXX0 XXXX
    数据位4:XXXX XXXX XXX1 XXXX
    数据位5:XXXX XXXX XXX1 XXXX
    数据位6:XXXX XXXX XXX0 XXXX
    数据位7:XXXX XXXX XXX0 XXXX
    停止位  :XXXX XXXX XXX1 XXXX
   
  另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
       解决这个问题有2个办法:
       1,使用其他IO都是输入模式的GPIO作为TX脚(如STM32的GPIOC和GPIOD,pin脚少,容易实现仅一个输出模式的IO);
       2,把DMA的目标地址设为GPIO_BSRR即可,但要注意数组格式化的方法不同,元素长度变为32bit,要置位pin脚时使用低16bit,清零pin脚时用高16bit;
      
程序代码:
  我已经在工程中验证了这个方法的可行性,经过测试,收发在115200时无误码(高波特率接收时,IO下降沿中断和接收完毕中断的优先级要高);
  这里暂不提供DEMO了。。。。(坑啊。。。)
  
缺点:
  需要使用1个定时器,2个DMA通道;
  并非严格意义的全双工,使用1个定时器时,收发不能同时进行;(要实现同时收发,可以使用2个定时器分别触发DMA)

总结:
  发送时,CPU只要启动定时器就可以了,发送完成时可以不使用DMA中断,使用查询就可以;
  接收时,起始位的下降沿触发IO中断时,需要进入中断开启定时器,字节接收完成时也需要进入DMA中断读取接收到的字节;
  因此CPU只要在收发开始和结束时干预一下就好了;
  通过把DMA的目标地址设为GPIO_BSRR可以实现任意IO作为TX,RX脚;
  经过测量,应该是可以实现512000波特率收发的,但未验证;
  本方法可以用在有空闲定时器和DMA的任何MCU上;
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
51条回答
saccapanna
1楼-- · 2019-12-14 15:28
另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
____________

  别的不评价,只提醒一点,F103的寄存器是可以位寻址的,操作位带,完全不影响其他IO。
bigk2000
2楼-- · 2019-12-14 17:22
saccapanna 发表于 2018-12-22 15:36
另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
____________

DMA无法访问位带。。。
saccapanna
3楼-- · 2019-12-14 23:10
bigk2000 发表于 2018-12-22 15:38
DMA无法访问位带。。。

从 RAM 到 位带,不可以DMA吗?这个还真不知道了,有需要的写代码试试,我暂时不想试,用不到这种操作。
chunjiu
4楼-- · 2019-12-15 04:58
我也做过,如果要考虑纠错的话,不应该超过 9600,不然可靠性会下降很多。
Eworm001
5楼-- · 2019-12-15 07:01
 精彩回答 2  元偷偷看……
bigk2000
6楼-- · 2019-12-15 08:21
chunjiu 发表于 2018-12-22 15:59
我也做过,如果要考虑纠错的话,不应该超过 9600,不然可靠性会下降很多。 ...

上述方法,发送可说是0误差的;
接收时,误差在于起始位的下降沿到启动定时器的时间差,
最小可以控制在1us以内,最大(被其他高级中断打断)在我的电机控制中为25us以内,因此9600的波特率完全不会有误码;
时间差太大时可以把定时器的匹配值设小,比如原来的52us改为30us;

我特意测量过,从起始位下降沿到启动定时器最小可以0.5us,因此我说512000的波特率也是可以的;

如果再深入探讨,还可以更高的 波特率:
使用另外一个定时器的输入捕捉功能,检测到下降沿触发DMA,DMA启动接收定时器,后面就自动接收了;
但这时一个字节接收完毕时,要在DMA完成中断里解析出该字节放到FIFO时,可能耗时太长,超出一个停止位的时间,导致下个字节已经开始发送了,但接收准备工作还没来得及做;
但仍有弥补办法,把接收的数据暂时不解析,而是放在大数组里,等所有字节都接收完一次解析;
为了减小数组大小,可以选择GPIO中的pin0-pin7作为TX,RX;
至此,模拟UART已经是极致了。。。

一周热门 更多>