都什么年代了,还有人用单片机IO口模拟串口?
你还别说,最近我也又碰到了该需求,因为客户用的MCU本身只有2个串口,而实际需要3个串口,所以需要额外模拟一个出来。
4年前写过一篇关于GPIO模拟串口的文章:GPIO模拟串口
于是把之前的代码又重新移植并测试了下,之前代码发送的时候波特率可以达到115200bps,但是接收却不行。当时没有细致分析过原因,这次认真的分析了一下。
这次使用的MCU主频运行在48Mhz,模拟串口发送的逻辑和实现比较简单,按照1/波特率周期,在定时器中断服务函数里去发送串口对应的bit位即可。实测波特率可以支持到256000bps。

接收的逻辑是:当RX引脚检测到下降沿时,进入GPIO中断,然后开启一个定时器,第一次定时器周期设置为1/波特率的一半(目的是为了在中心处判断是否为低电平,以表示是否为起始位),再之后就可以设置定时器周期为1/波特率,每隔此周期在定时器中断服务函数里去采样RX引脚电平,将数据接收完毕。

为什么接收时,波特率高了之后就数据异常呢? 未修改代码之前,我测试波特率最高只能支持到19200bps,
我们在接收定时器中断服务函数里加入点代码,在串口每一个bit位处理前后进行一次GPIO翻转。通过波形来分析接收的处理时序:
加入上述代码后,接收波特率最高只能支持到14400bps了,以下是波形图,其中Channel 0是翻转IO的波形,Channel 1是串口接收引脚波形

从上图中我们首先看到第一个IO翻转时间为14us,明显高于后续其他翻转时间。这个原因经分析是代码里用了一个除法运算导致的。
我们优化一下代码,不用每次都在这里做一次除法。改完之后时间从原来的14us缩短为2us。除法导致运行时间长具体可以查看之前的文章:在KEIL中勾选微库后,延时函数为什么不准了?
另外发现RX下降沿到IO口开始反转的时间是50.8us,理论值应该是35us才对。

为什么延迟了这么多呢?问题出在这里,这里也用了除法,同样的我们也做一下修改
改完之后,这个时间就到了39us,偏差就小了很多。

可以进一步优化,在进入下降沿中断里一开始就先配置并启动定时器
这样对应的时间还可以更准确一点。

经过以上修改后,19200bps接收正常,但是38400bps波特率还是异常,我们看一下波形:

问题出在第一个起始位采样点有3us偏移,导致后续的采样点相比自身的中心点偏的越来越大,以至于第2字节数据的起始位没有来得及处理,进而导致后续的数据处理错误。
我们将采样首次的采样时间做进一步优化,让其更靠近中心点,

这样修改后波特率可以达到56000bps,但是再增加到57600bbps后还是有问题。其实原因也很简单。还是因为首次1/2周期采集依然有偏差,随着后续不断按照1/波特率周期去采集,误差越来越大。

我们进一步做优化,将后续按照固定周期去采集的方式改为动态调整周期值,最简单的方式是周期大小做交替变化,这样到最后一个字节就不会偏差那么大。
改完之后可以支持到57600bps,但是115200bps还是不行。
通过观测波形可以看到,第一个IO翻转下降沿到第二个IO翻转下降沿的时间间隔出现了错误,理论应该是9us,但实际只有5us。

经分析原因是随着波特率的提高,定时器的周期越来越短,当波特率为115200bps时,1/波特率的一半 周期只有约4us,而起始位的代码执行时间已经接近4us,所以定时器周期还没有更新生效的时候,原来的一半周期中断又来了。代码优化如下,首先更改周期。
这样就可以达到115200bps的波特率:


以上记录本次调试过程中遇到的问题及解决办法。总结一下:要想GPIO模拟串口能够提高波特率,需要通过精确的定时器配置、精简的中断服务程序、动态误差补偿等措施来实现。
------------ END ------------
关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。
点击“阅读原文”查看更多分享。