Hello,小伙伴们,大家好,这里是左左右。上一期视频,我们通过直接配置寄存器。实现了一个毫秒级的延时函数。它的核心逻辑很简单。程序执行到延时函数时。通过配置滴答计时器实现1毫秒的延时。通过循环累积多个1毫秒,从而达到所需的延时长度。
那么,有没有其他实现延时的方法呢?当然,有。我们可以换一种思路。在刚进入延时函数时,记录一个“开始时间点”,每隔一段时间看一下表。记录一下当前时间。当“当前时间”减去“开始时间”等于我们需要的“延时长度”时就退出函数。实际上,HAL 库中的 HAL_Delay 函数。采用的就是这种“查询时间差”的方法。
今天,我们就一起来深入探索一下它的源码实现过程。在STM32CubeIDE中,按下Ctrl键并单击 HAL_Delay 函数,就跳转到定义这个函数的地方了。在这里我们可以看到,函数一开始就用变量 tickstart 记录了调用时刻的“时间戳”,也就是“开始时间”。然后,将我们要延时的毫秒数 delay 赋值给变量 wait。

紧接着有一个 if 判断:如果延时值 wait 小于一个延时最大值。则会将 wait 自增一个数,这个数就是1,这稍后解释。最关键的是while循环,其条件正是。当前时间减去开始时间小于需要等待的时间,只要条件成立,程序就在这里循环。模拟“等待”的过程,这也就是我们前面说的“看表”动作。
循环中调用了 HAL GetTick() 来获取“当前时间”。它返回的是一个名为 uwTick 的全局变量。到这里貌似到了一个死胡同了。但是,靠前面知识的积累。直觉告诉我们这个uwTick肯定是一个每隔1毫秒递增一个数。这样才能起到计时作用。带着这个疑问。我们从程序启动的地方开始寻找答案。在 main 函数之前。
系统会调用 HAL_ Init() 进行初始化。在这里,我们一眼就能看到带有“Tick”字样的函数——HAL_InitTick。点进去看看。它内部调用了 HAL SYSTICK_Config,这里传入的值是 8000。然而,在跟踪代码时我们发现。这里不就是上期视频讲到的滴答定时器的相关寄存器吗?

我们来看看它是如何配置的,这里配置滴答定时器重装载寄存器值为7999。按理说我们这里设置的是72兆赫兹。重装载寄存器值为71999才对。我们打印输出来看一下SystemCoreClock这个变量的值。结果是72000000。没有问题,这说明后面的代码将SystemCoreClock这个变量重新赋值了。

接着,往下看,当前值寄存器设置为0。控制与状态寄存器配置成这样,这里的配置开启了中断。那么,SystemCoreClock 这个决定重装载值的关键变量。是在何时被正确设置为72000000的呢?
接下来我们返回main.c来找找SystemCoreClock变量是什么时候被重新赋值的?前面这些都是在配置时钟树。这里的每行都是和时钟树那期视频是可以一一对应的。大家一定要自己去看,看不懂的地方可以在评论区留言。接着,打开HAL_RCC_ClockConfig这个函数看看。
在这里我们终于找到了SystemCoreClock这个全局变量被更新的地方了。由于这里源代码涉及到RCC寄存器的相关知识,我们暂时不做讲解,这里直接告诉大家答案。结果就是72000000 ,其实RCC寄存器就是用来配置时钟树的。
最后,SysTick中断服务函数在哪里?它在 stm32f1xx_it.c 文件中,it 即 interrupt 的缩写。找到 SysTick Handler 函数。我们发现它内部调用了 HAL IncTick()。这里我们终于找到了uwTick这个变量了。太不容易了,这个函数的内容极其简单,使全局变量 uwTick 加1。
也就是滴答定时器每隔1毫秒进入中断使uwTick加1。这时uwTick就变成了一个每隔1毫秒自动加1的变量了。这时他不就是一个精确的钟表吗?也就是我们前面提到的“看表”中的“表”。至此,谜底全部揭晓:1.系统启动后,时钟被配置为72兆赫兹。System Core Clock 被更新为72000000。

2.SysTick定时器被配置为每72000个时钟周期即1毫秒中断一次。3.每次SysTick中断,uwTick 这个“软件时钟”就加1。HAL_Delay 函数通过对比当前的 uwTick 和进入函数时记录的 uwTick。实现精准的毫秒级延时。前面提到 wait 这里为什么要加1呢?
这里假设没有这段代码,我们需要1毫秒的延时。我们都知道当“systic当前值寄存器”中的值从71999降为0时。uwtick中的值才会加一。假设tickstart取值时systic当前值寄存器中的值正好为1,当这个寄存器中的值为0时,uwtick加1。条件满足,跳出延时函数。

时间上真实的延时值为72000分之1毫秒。几乎就是没有延时的,这里wait加1就是为了避免这种极端情况的存在。这时有的小伙伴就要说了。这样做不就有了1毫秒的误差吗?其实,从前面的动画也可以看出tickstar在读取uwtick时本来就是有1毫秒的误差的,而且uwtick取值是一个向下取整的过程。
加1对它几乎是没有任何影响的。我为什么要带着大家如此细致地解读HAL库源码呢?首先,这能帮助我们穿透抽象层。理解本质。每次调用HAL库函数,如HAL_Delay。你脑海中不再只是一行神秘的代码,而是一幅清晰的画面。时钟树如何工作,SysTick如何计时?一个全局变量如何被中断服务程序默默更新?
这种深刻的理解,是单纯调用HAL库函数无法获得的。其次,这是学习优秀编程思维的绝佳途径。HAL库的代码结构、模块化设计、对硬件抽象的方式。都值得我们学习。坚持阅读源码。就像与优秀的工程师对话。能极大地提升我们的编程内功。还有一点也是最重要的一点。我也会根据大家的反馈调整视频内容。
如果大家对源码解析这样的课程不感兴趣,我也会减少这类视频的更新,大家可以把自己的想法打到公屏上。“读书百遍,其义自见。”。学习嵌入式开发也是如此,不要被暂时的困难吓倒。通过积累每一次这样的源码探索。你会发现自己对系统的掌控力越来越强。希望我的坚持分享,能换来你的坚持学习。
好的,今天的源码探秘之旅就到这里。如果觉得有收获,一定要关注、点赞、收藏,评论。你的支持是我这样的小博主持续更新下去的最大动力。我们下期再见!