1. 从“卡顿”到“丝滑”:为什么你的传感器总是不听话?
大家好,我是老李,一个在嵌入式坑里摸爬滚打了十多年的老码农。今天咱们不聊那些虚头巴脑的理论,直接从一个让我抓狂了好几天的问题说起。
前段时间,我接了个小项目,要用STM32驱动一个DHT11温湿度传感器。这玩意儿大家应该都熟,一个经典的单总线数字传感器。我心想,这还不简单?照着数据手册的时序图,用HAL库的HAL_Delay()函数写几个延时,分分钟搞定。结果呢?读取的数据十次里有八次是错的,要么全是0xFF,要么温度湿度值跳得跟心电图似的。我一度怀疑是传感器坏了,换了三四个新的,问题依旧。那几天,我对着逻辑分析仪抓出来的波形图,头发都薅掉了一大把。
后来我静下心来仔细对比波形才发现,问题就出在HAL_Delay()这个函数上。数据手册要求,主机(也就是我们的STM32)发送开始信号后,需要等待20-40微秒(μs)的拉低,然后等待传感器80微秒的响应低电平,再等待80微秒的响应高电平……每一个关键节点,都是几十到上百微秒的量级。而HAL_Delay()的最小单位是1毫秒(ms),也就是1000微秒。你想用这个“米尺”去量“毫米”甚至“微米”的尺寸,那不是开玩笑吗?你让它延时1毫秒,它可能给你延时了1.0001毫秒,误差不大;但你让它“模拟”一个40微秒的延时,你只能写HAL_Delay(1),然后祈祷它快点执行完其他指令,这精度完全不可控,时序自然就乱套了。
这不仅仅是DHT11的问题。像DS18B20温度传感器、WS2812B彩灯(虽然不算传感器,但时序要求同样苛刻)、某些红外接收头,它们都依赖于精确的微秒级甚至纳秒级时序。总线协议在“说话”的时候,每一个比特位的宽度、每一个应答信号的间隔,都是严格规定好的。你的MCU如果“口齿不清”,或者“反应迟钝”,对方就完全听不懂你在说什么,通信必然失败。
所以,要驯服这些“娇气”的传感器,第一步就是扔掉那把刻度太粗的“米尺”——HAL_Delay(),换上一把高精度的“游标卡尺”。而SysTick,这个ARM内核自带的系统定时器,就是我们手边最好用、最现成的那把卡尺。它不像外部通用定时器(TIMx)那样需要额外的硬件资源和复杂的配置,它是芯片“与生俱来”的能力,我们只需要学会如何精准地读取它的“刻度”就行了。
2. 庖丁解牛:SysTick如何成为我们的“精密秒表”?
在动手写代码之前,我们得先搞清楚SysTick这把“秒表”是怎么工作的。很多教程一上来就贴代码,我觉得那是耍流氓。你不明白原理,代码稍微变个花样你就懵了,出了问题更不知道怎么调试。
SysTick本质上是一个24位的向下计数器。你可以把它想象成一个倒计时的沙漏。这个沙漏的沙子总数(最大值)是 2^24 - 1,大概1600多万。沙漏的流速由系统时钟决定,比如你的STM32F1主频是72MHz,那么一秒钟就会“流下”7200万颗“沙子”(时钟脉冲)。SysTick的工作流程是这样的:
- 我们给
LOAD寄存器设置一个初始值,比如71999。 - 启动后,
VAL寄存器(当前值)从这个初始值开始,每来一个时钟脉冲就减1。 - 当
VAL减到0时,会触发一个SysTick中断(如果开启了的话),同时硬件会自动把LOAD的值重新装载到VAL里,然后开始下一轮倒计时。
HAL库默认就是把SysTick配置成每1ms中断一次,用来给HAL_GetTick()和HAL_Delay()提供基准的。这个“1ms”是怎么算出来的呢?很简单:系统时钟72MHz,那么一个时钟周期就是 1/72 us。要产生1ms(1000us)的间隔,就需要 72MHz * 0.001s = 72000 个时钟周期。所以LOAD寄存器就设置为 72000 - 1 = 71999。从71999开始减到0,正好72000次计数。
好了,关键点来了:HAL_Delay()是靠“数中断次数”来延时的。它不管两次中断之间VAL具体走到了多少,它只关心“中断发生了多少次”。这就好比你看沙漏,只关心它漏完了几次,而不关心这次漏到一半还剩多少沙子。所以它的精度就被限制在了“一次漏完”的时间,也就是1ms。
我们的思路恰恰相反:我们不依赖中断,而是直接去“看”沙漏里当前还剩多少沙子(实时读取VAL寄存器的值)。通过计算两次“看”的时候,沙子减少了多少,我们就能精确地知道过去了多少个时钟周期,从而换算出精确的微秒甚至纳秒时间。这就把精度从“一次沙漏的时间”提升到了“一颗

:SysTick 微秒级延时在传感器驱动中的实战应用&spm=1001.2101.3001.5002&articleId=151241328&d=1&t=3&u=31490218166d4abd8e7d7b4f8295d79e)
1万+

被折叠的 条评论
为什么被折叠?



