目录

一、问题描述

二、问题探索

2.1 前期发现

2.2 深入思考

2.3 真没招了

三、问题解决和反思

四、总结


本文是一篇过程记录文章,本躯在单片机逻辑编程时发现了一个很有意思的BUG,并最终引发了对系统时钟SysTick的思考。

一、问题描述

我在写SSD1306驱动的时候,为了可视化不同寻址模式,在连续数据写入的循环中加入了单片机软件延时。于是出现了以下问题——数据无规律的出现错误,但所有错误都为两种感叹号形状。感觉挺有意思的,于是决定深入思考一下。

如图1-1和图1-2,出现频率最高的为两字节感叹号错误,还有一种为单字节感叹号错误,单字节感叹号错误正好是双字节感叹号错误的右半部分。

这里说明一下,这是0.96寸OLED,从上至下分为八页显示,每一页竖向有八个OLED点阵,横向有128个OLED点阵,所以这是128*(8*8)分辨率的显示屏。

图1-1 双字节错误
图1-2 双字节错误和单字节错误

如图1-3,如果开启显示反转,并反向写入数据。

图1-3 数据反转显示

这到底是怎么回事呢。

二、问题探索

2.1 前期发现

如图2-1,我们可以直观的看到:

双字节的数据错误为:0b{00011110:00000010}

单字节的数据错误为:0b{00000010}

原始数据应为:0b{11111111:11111111}

图2-1 放大图

已知的条件是:

1.我使用的是软件IIC,时序严格对应SSD1306官方器件手册。

2.数据错误的类型并非随机,而是只有两种可能。

3.数据错误并不会影响后续数据。

4.数据错误出现的位置是随机的。

5.数据错误的类型并非简单的移位错误。

这真是让人百思不得其解,简直像玄学一样。

但我坚信所有未知的BUG都是由于不够了解底层,一切玄学都来源于知识不足。

这时我突然想到,问题是否在于软件延时?

表情2-1 舰载激光系统哈激米

于是我关闭了软件延时,这次果然没有出现数据错误。

图2-2 关闭软件延时后的连续写入

可是,为什么呢?

2.2 深入思考

为了搞明白这个问题,我需要先思考软件延时和通信时序的关系。

图2-3 单片机的微秒级软件延时函数

如图2-3,我使用的是系统时钟,产生精确的阻塞式延时,在延时期间,系统并不会执行任何操作。

图2-4 循环写入和阻塞式延时

如图2-4,逻辑非常简单清晰,每次写入一个字节并延时,直到OLED显示屏全部写入。

这有鸡毛关系啊。

图2-5 向SSD1306写入数据函数

这时候我突然发现一个问题,写入数据的时序中,写入从机地址0x78和写数据命令0x40,对应的二进制数据是:0b{01111000}0b{01000000}

由于数据写入时是从高位开始发送,所以,两种数据错误正对应了从机地址和写数据命令!

可是,又出现了以下疑问:

1.这和阻塞式的软件延时有什么关系?

2.为什么出现的位置是随机的?

3.没有写入从机地址和写数据命令的前序,这两个字节是怎么写入进去的?

2.3 真没招了

我觉得问题3比较容易解决,准备先从第三个问题开始解决。

首先我需要深入学习SSD1306的IIC通信时序。

如图2-6、图2-7、图2-8,官方详细说明了IIC的写入模式。

图2-6 IIC写入模式
图2-7 起始和停止条件的定义
图2-8 确认信号条件的定义

我检查了我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都必须提前开启,并提供一个不会死亡的时间基准,一直保持到结束,关闭系统时钟是极其危险的操作。

对于电子系统来说,系统时钟就是它的心跳,系统时钟开始跳动的那一刻开始,就必须保持到此次生命周期的结束。否则一切基准都不再值得信任,一切电平跳动都将趋于失控。

四、总结

一旦你下决心去做某件事情,那么开始之后就永远不要停下。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐