SysTick——系统的心跳(基于SSD1306芯片的0.96寸OLED连续写入数据在加入单片机软件延时的情况下出现的数据错误问题相关探索和认识)
本文记录了在编写SSD1306驱动时发现的一个奇特BUG:在I2C通信中加入软件延时后,数据出现特定模式的错误(感叹号形状)。经过排查发现,错误数据与从机地址和写命令的二进制数据吻合。通过一系列实验证明,问题根源在于频繁开关SysTick系统时钟导致MCU“心跳震颤”,这种干扰会随着延时循环次数增加而加剧。最终解决方案是保持SysTick持续运行,避免中途开关。这一案例深刻揭示了系统时钟对嵌入式系
目录
序
本文是一篇过程记录文章,本躯在单片机逻辑编程时发现了一个很有意思的BUG,并最终引发了对系统时钟SysTick的思考。
一、问题描述
我在写SSD1306驱动的时候,为了可视化不同寻址模式,在连续数据写入的循环中加入了单片机软件延时。于是出现了以下问题——数据无规律的出现错误,但所有错误都为两种感叹号形状。感觉挺有意思的,于是决定深入思考一下。
如图1-1和图1-2,出现频率最高的为两字节感叹号错误,还有一种为单字节感叹号错误,单字节感叹号错误正好是双字节感叹号错误的右半部分。
这里说明一下,这是0.96寸OLED,从上至下分为八页显示,每一页竖向有八个OLED点阵,横向有128个OLED点阵,所以这是128*(8*8)分辨率的显示屏。
如图1-3,如果开启显示反转,并反向写入数据。
这到底是怎么回事呢。
二、问题探索
2.1 前期发现
如图2-1,我们可以直观的看到:
双字节的数据错误为:0b{00011110:00000010}
单字节的数据错误为:0b{00000010}
原始数据应为:0b{11111111:11111111}
已知的条件是:
1.我使用的是软件IIC,时序严格对应SSD1306官方器件手册。
2.数据错误的类型并非随机,而是只有两种可能。
3.数据错误并不会影响后续数据。
4.数据错误出现的位置是随机的。
5.数据错误的类型并非简单的移位错误。
这真是让人百思不得其解,简直像玄学一样。
但我坚信所有未知的BUG都是由于不够了解底层,一切玄学都来源于知识不足。
这时我突然想到,问题是否在于软件延时?
于是我关闭了软件延时,这次果然没有出现数据错误。
可是,为什么呢?
2.2 深入思考
为了搞明白这个问题,我需要先思考软件延时和通信时序的关系。
如图2-3,我使用的是系统时钟,产生精确的阻塞式延时,在延时期间,系统并不会执行任何操作。
如图2-4,逻辑非常简单清晰,每次写入一个字节并延时,直到OLED显示屏全部写入。
这有鸡毛关系啊。
这时候我突然发现一个问题,写入数据的时序中,写入从机地址0x78和写数据命令0x40,对应的二进制数据是:0b{01111000}和0b{01000000}。
由于数据写入时是从高位开始发送,所以,两种数据错误正对应了从机地址和写数据命令!
可是,又出现了以下疑问:
1.这和阻塞式的软件延时有什么关系?
2.为什么出现的位置是随机的?
3.没有写入从机地址和写数据命令的前序,这两个字节是怎么写入进去的?
2.3 真没招了
我觉得问题3比较容易解决,准备先从第三个问题开始解决。
首先我需要深入学习SSD1306的IIC通信时序。
如图2-6、图2-7、图2-8,官方详细说明了IIC的写入模式。
我检查了我IIC的时序,确定时序是完整的。但我没有加入接收ACK确认信号。
可是,在完整的时序下,如何能做到以上数据错误呢?
及——起始条件→通信地址→数据写入命令→数据→结束条件→数据→数据
也就是说,在同一次通信中,连续发送了多个数据字节。
大感叹号为——将通信地址和数据写入命令当作RAM数据发送了
小感叹号为——将数据写入命令当为RAM数据发送了
嗯,如果是这样的话——
1.不加入延时连续发送512个字节:数据正常


2.加入毫秒级延时连续发送512个字节:数据出现错误,并且数据个数出现错误




奇怪的是,数据长度错误并不和小感叹号强相关,但和大感叹号强相关。
那如果延时放在数据连续写入之前呢?
3.系统开机多次延时,并连续发送512个字节:数据错误

也就是说,延时哪怕是不放进字节写入的循环中,也干扰了IIC通信。
4.延时不循环,并连续发送512个字节:数据正常

也就是说,问题出在循环延时中!
5.通过多次试验,发现多次的循环延时如果在SSD1306的初始化之后,就会影响IIC通信。
byd为什么啊,GPIO口是被锁定的,不会受到系统时钟的影响,循环延时也不在IIC通信时序中间,理论上来说不会影响通信时序啊。
真没招了,我决定暴力破解。
6.通过多次试验,发现IIC通信出错的概率是一直存在的。
7.通过多次实验,发现出错率随连续写入数据的循环次数的递增而递增。
8.通过多次试验,发现出错率与“系统时钟延时循环次数和延时us长度的乘积”成正相关。
大概是:出错率=延时循环次数* 延时长度*连续写入数据循环次数
9.经过大量猜测和试验,发现问题出在——SysTick频繁开启和关闭

更改为需要时开启,不用时关闭则IIC通信不受影响

也就是说,系统时钟的频繁开关引入了未知的错误:IIC通信出现了假起始和结束位,或丢失了结束位。归根结底,是规定的通信协议不再可靠。
三、问题解决和反思
感谢这次特殊的机会让我深刻、直观的理解了SysTick系统时钟的重要性和夸张的破坏能力。
在片上操作系统中,SysTick无疑是最重要的时钟,是整个系统的心跳。可是,我之前根本没有意识到,哪怕对于裸机编程这种强逻辑的编程方式来说,SysTick都有毁灭性的破坏能力。
就像此次问题,所有逻辑都是完美的,时序是极其精确的,在如此强大的MCU的加持下,居然会由于SysTick的频繁开关导致MCU心跳的“震颤”。
这种“震颤”是伴随整个系统的生命周期的,并且会随着“震颤”次数的增加而不断叠加。这对于任何系统来说都是毁灭性的。
任何系统的开始前,甚至于一切的外设内设的配置初始化之前,SysTick都必须提前开启,并提供一个不会死亡的时间基准,一直保持到结束,关闭系统时钟是极其危险的操作。
对于电子系统来说,系统时钟就是它的心跳,系统时钟开始跳动的那一刻开始,就必须保持到此次生命周期的结束。否则一切基准都不再值得信任,一切电平跳动都将趋于失控。
四、总结
一旦你下决心去做某件事情,那么开始之后就永远不要停下。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)