搜索
bottom↓
回复: 51

在STM32上实现高性能模拟UART

  [复制链接]

出0入0汤圆

发表于 2018-12-22 14:31:09 | 显示全部楼层 |阅读模式
本帖最后由 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上;

出0入0汤圆

发表于 2018-12-22 14:41:40 | 显示全部楼层
一个定时器就可以,使用3倍速率中断,在51上测试过, STC时钟11.0592MHz, 19200稳定。
STM32上也做个一个产品,只使用了9600,很稳定。

出0入0汤圆

 楼主| 发表于 2018-12-22 14:45:26 | 显示全部楼层
KongQuan 发表于 2018-12-22 14:41
一个定时器就可以,使用3倍速率中断,在51上测试过, STC时钟11.0592MHz, 19200稳定。
STM32上也做个一个 ...

考虑过这个方法,但由于有电机控制会严重影响时序,
这个方法发送还好,接收就不可靠了;

出0入0汤圆

发表于 2018-12-22 15:00:13 | 显示全部楼层
个人觉得如果这个单片机本来就没有2个串口可用,那无话可说;然而单片机本来就有2个串口可用,却因为其中一个的IO被其他功能占用且不能调整,这是严重失误了

出0入0汤圆

 楼主| 发表于 2018-12-22 15:02:00 | 显示全部楼层
负西弱 发表于 2018-12-22 15:00
个人觉得如果这个单片机本来就没有2个串口可用,那无话可说;然而单片机本来就有2个串口可用,却因为其中一 ...

嗯,同意你的话
但,实际是那2个IO必须用来控制电机

出0入0汤圆

发表于 2018-12-22 15:15:21 来自手机 | 显示全部楼层
bigk2000 发表于 2018-12-22 15:02
嗯,同意你的话
但,实际是那2个IO必须用来控制电机

那两个io能做的其它io不能做?

出0入0汤圆

 楼主| 发表于 2018-12-22 15:18:04 | 显示全部楼层
本帖最后由 bigk2000 于 2018-12-22 15:28 编辑
xfdr 发表于 2018-12-22 15:15
那两个io能做的其它io不能做?


6个IO链接定时器用来控制电机,输出严格的PWM
10个模拟通道用来AD采样
能用的TX,RX只有一组,IO映射也组不出第二组

出100入113汤圆

发表于 2018-12-22 15:36:32 | 显示全部楼层
另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
____________

  别的不评价,只提醒一点,F103的寄存器是可以位寻址的,操作位带,完全不影响其他IO。

出0入0汤圆

 楼主| 发表于 2018-12-22 15:38:20 | 显示全部楼层
saccapanna 发表于 2018-12-22 15:36
另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
____________

DMA无法访问位带。。。

出100入113汤圆

发表于 2018-12-22 15:50:23 | 显示全部楼层
bigk2000 发表于 2018-12-22 15:38
DMA无法访问位带。。。

从 RAM 到 位带,不可以DMA吗?这个还真不知道了,有需要的写代码试试,我暂时不想试,用不到这种操作。

出280入168汤圆

发表于 2018-12-22 15:59:46 | 显示全部楼层
我也做过,如果要考虑纠错的话,不应该超过 9600,不然可靠性会下降很多。

出0入0汤圆

发表于 2018-12-22 16:04:58 来自手机 | 显示全部楼层
dma到bitband地址,

出0入0汤圆

 楼主| 发表于 2018-12-22 16:35:23 | 显示全部楼层
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已经是极致了。。。

出20入12汤圆

发表于 2018-12-22 16:42:07 来自手机 | 显示全部楼层
用spi+dma模拟uart感觉会更好

出280入168汤圆

发表于 2018-12-22 16:55:22 | 显示全部楼层
bigk2000 发表于 2018-12-22 16:35
上述方法,发送可说是0误差的;
接收时,误差在于起始位的下降沿到启动定时器的时间差,
最小可以控制在1 ...

不仅仅是边沿误差,还有采样中的干扰需要考虑,

我原来的做法是采三个样,有两个高电平才能是高电平,否则是低电平。

这样才能过滤信号线上的偶发干扰。

你仔细研究一下 UART 的硬件,有一些值得注意的地方。

出0入0汤圆

 楼主| 发表于 2018-12-22 17:00:11 | 显示全部楼层
chunjiu 发表于 2018-12-22 16:55
不仅仅是边沿误差,还有采样中的干扰需要考虑,

我原来的做法是采三个样,有两个高电平才能是高电平,否 ...

恩,我这个办法做不到多次采样

出0入0汤圆

发表于 2018-12-22 18:03:44 来自手机 | 显示全部楼层
有用过定时器的输入捕获和输出比较做过模拟串口,会比三倍采样节省资源

出0入0汤圆

发表于 2018-12-23 09:45:30 | 显示全部楼层
总感觉软件模拟的很占MCU。

出0入0汤圆

发表于 2018-12-23 11:11:59 | 显示全部楼层
saccapanna 发表于 2018-12-22 15:36
另注:用DMA直接搬运数据到IO会干扰GPIO上其他处于输出状态的IO,
____________

bitband是CPU的功能,DMA和CPU无关。

出0入0汤圆

发表于 2018-12-23 11:32:35 | 显示全部楼层
定时器基准和中断接收,没有信号时不用计时,半双工完全可以,全双工要多倍的定时频率或双定时器,在8位简单的芯片上用过,接收时要注意采样位置

出0入0汤圆

发表于 2018-12-23 11:36:34 | 显示全部楼层
STM32F103我记得最少都是3个UART吧

出0入0汤圆

发表于 2018-12-23 12:22:48 | 显示全部楼层
接收因为要检测边沿,所以最好是用4倍采样到8倍采样,同时也因为两边时钟误差及干扰的存在所以需要做一些处理,最好不要用软件模拟,太消耗资源了.

出0入0汤圆

发表于 2018-12-23 19:20:42 | 显示全部楼层
UART用的16倍采样或8倍采样,这种IO模拟方式只能说能用。再说STM32串口不止2个吧。

出0入0汤圆

 楼主| 发表于 2018-12-23 19:40:05 | 显示全部楼层
哎,自以为是害人不浅

出0入0汤圆

发表于 2018-12-24 14:59:51 | 显示全部楼层
既然TIM和DMA都用了, 为啥操作BSRR...而不是tim相关?
单次脉冲模式了解一下, 我之前做过, 挺好用的, 接收是捕获DMA, 发送是单次脉冲DMA

出0入0汤圆

 楼主| 发表于 2018-12-24 17:50:59 | 显示全部楼层
snoopyzz 发表于 2018-12-24 14:59
既然TIM和DMA都用了, 为啥操作BSRR...而不是tim相关?
单次脉冲模式了解一下, 我之前做过, 挺好用的, 接收是 ...

TIM相关就不能任意IO了;
操作BSRR就是为了任意IO,且不影响其他处于输出态的IO;

接收是捕获DMA, 发送是单次脉冲DMA?
用输入捕捉模式触发DMA?也是可以的,但IO不能任意;

出0入0汤圆

 楼主| 发表于 2018-12-24 17:53:18 | 显示全部楼层
linjpxt 发表于 2018-12-23 12:22
接收因为要检测边沿,所以最好是用4倍采样到8倍采样,同时也因为两边时钟误差及干扰的存在所以需要做一些处理 ...

多次采样太消耗CPU了,
在干扰多的时候多次采样是必须的

出0入0汤圆

发表于 2018-12-25 09:34:49 | 显示全部楼层
bigk2000 发表于 2018-12-24 17:50
TIM相关就不能任意IO了;
操作BSRR就是为了任意IO,且不影响其他处于输出态的IO;

是不能任意, 但省些RAM, DMA的内容是时间长度, 你这样buff区太大了, 数据量大波特率也很难做高

我做到921600

出0入8汤圆

发表于 2018-12-25 09:59:18 | 显示全部楼层
感谢楼主和snoopyzz,刚好有项目需要用到,太及时了!

出0入0汤圆

 楼主| 发表于 2018-12-25 11:42:39 | 显示全部楼层
snoopyzz 发表于 2018-12-25 09:34
是不能任意, 但省些RAM, DMA的内容是时间长度, 你这样buff区太大了, 数据量大波特率也很难做高

我做到92 ...

不错                        

出0入0汤圆

 楼主| 发表于 2018-12-25 11:43:38 | 显示全部楼层
yujintian 发表于 2018-12-25 09:59
感谢楼主和snoopyzz,刚好有项目需要用到,太及时了!

哈哈
低容量STM32上,可以模拟出来3个

出0入0汤圆

 楼主| 发表于 2018-12-25 12:13:58 | 显示全部楼层
snoopyzz 发表于 2018-12-25 09:34
是不能任意, 但省些RAM, DMA的内容是时间长度, 你这样buff区太大了, 数据量大波特率也很难做高

我做到92 ...

接收如何做到这么高波特率?
刚收到的字节在解析,下个字节可能就来了,
或者用13楼的方法?

出0入0汤圆

发表于 2018-12-26 08:52:10 | 显示全部楼层
要我说,直接换一块stm32f2的芯片,5个串口随便用

出0入0汤圆

发表于 2018-12-26 11:50:05 | 显示全部楼层
资深雨粉 发表于 2018-12-26 08:52
要我说,直接换一块stm32f2的芯片,5个串口随便用

是6个串口

出0入0汤圆

发表于 2018-12-26 11:51:49 | 显示全部楼层
用TIM的边缘触发中断,可以设置硬件滤波。效果比GPIO中断好很多的。

出0入0汤圆

发表于 2019-1-4 12:56:12 | 显示全部楼层
bigk2000 发表于 2018-12-24 17:53
多次采样太消耗CPU了,
在干扰多的时候多次采样是必须的

不是干扰的问题,是时钟偏差的问题,两个系统的时钟有偏差,所以你要把采样点放在位的中间,而要做到这点,你需要比较准确的知道一个位的边沿.

出0入362汤圆

发表于 2019-1-4 13:23:11 来自手机 | 显示全部楼层
elm-chan有,我在avr上试过好用,他也有stm32的版本。

出0入0汤圆

发表于 2019-1-4 13:34:38 | 显示全部楼层
果然妙啊,思路不错,不过收发要把字节和位进行切换,如果你用位带模式,是不是就不需要这么切换了,stm32的灵活性真的不是盖的,定时器和dma都非常灵活

出0入0汤圆

 楼主| 发表于 2019-1-4 17:12:37 | 显示全部楼层
linjpxt 发表于 2019-1-4 12:56
不是干扰的问题,是时钟偏差的问题,两个系统的时钟有偏差,所以你要把采样点放在位的中间,而要做到这点,你 ...

采样点不是问题;
低波特率可以偏多点;
高波特率接收时,要把起始位的边沿检测中断优先级设高点,或者定时器捕捉这个边沿;

出0入0汤圆

 楼主| 发表于 2019-1-4 17:13:08 | 显示全部楼层
myxiaonia 发表于 2019-1-4 13:34
果然妙啊,思路不错,不过收发要把字节和位进行切换,如果你用位带模式,是不是就不需要这么切换了,stm32 ...

DMA不能访问位带

出0入0汤圆

发表于 2019-1-4 17:44:26 来自手机 | 显示全部楼层
高手,这是高手

出0入0汤圆

发表于 2019-1-4 21:37:39 | 显示全部楼层
DMA是可以做位宽转换的,用Pin0-7可以用字节,节省空间,之前用过定时器+DMA驱动IO。

出0入0汤圆

发表于 2019-1-5 18:39:38 | 显示全部楼层
高手,这是高手

出0入0汤圆

发表于 2019-1-7 15:45:30 | 显示全部楼层
优秀,厉害 了

出0入0汤圆

发表于 2019-1-8 11:08:16 来自手机 | 显示全部楼层
你的系统内没有用到定时器么,把uart处理附在已有的定时后面可能更好些。

出0入0汤圆

发表于 2019-1-22 23:46:57 | 显示全部楼层
tomzbj 发表于 2019-1-4 13:23
elm-chan有,我在avr上试过好用,他也有stm32的版本。

能不能给个链接看看,stm32版本的,谢谢

出0入362汤圆

发表于 2019-1-23 08:59:04 | 显示全部楼层
sup888 发表于 2019-1-22 23:46
能不能给个链接看看,stm32版本的,谢谢

附件是avr的, stm32的我没找着, 可能我记错了.
照着avr的改改就行了, 也不难吧.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2019-1-23 09:02:42 | 显示全部楼层
tomzbj 发表于 2019-1-23 08:59
附件是avr的, stm32的我没找着, 可能我记错了.
照着avr的改改就行了, 也不难吧. ...

谢谢,下载看了一下是汇编写的,头大

出0入362汤圆

发表于 2019-1-23 09:10:24 | 显示全部楼层
sup888 发表于 2019-1-23 09:02
谢谢,下载看了一下是汇编写的,头大

一共没几行吧, 随便找个avr的datasheet, 最后面都有指令集, 对照着看看就明白了.

不过lz还是选个uart多的mcu吧, 比如stm32f030cc, 有6个uart, stm32f091全系6或8个uart,  f2xx/f405/f407都是6个, f427及以上都是8个.
或者直接上个CH438, 用8位并行总线转8个uart.
链接: http://www.wch.cn/products/CH438.html

出0入0汤圆

发表于 2019-2-12 15:23:23 | 显示全部楼层
KongQuan 发表于 2018-12-22 14:41
一个定时器就可以,使用3倍速率中断,在51上测试过, STC时钟11.0592MHz, 19200稳定。
STM32上也做个一个 ...

能否上次51的代码来学习、

出0入0汤圆

发表于 2019-2-12 21:10:50 | 显示全部楼层
liangws201 发表于 2019-2-12 15:23
能否上次51的代码来学习、

也是网上下载修改的,搜索一下就有。

出675入8汤圆

发表于 2019-2-15 07:36:41 来自手机 | 显示全部楼层
正能用上
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-5 08:26

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表