大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。 对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正因如此,才有了借助 CSDN 平台寻求认同感和成就感 的想法。在这里,我准备陆续把自己花了很多心思的教学设计分享出来,主要面向广大师生朋友,单片机老鸟就略过吧。欢迎点赞+关注,各位的支持是本人持续输出的动力,多谢多谢!
众所周知,衡量一款处理器的性能,最重要的一个指标就是主频,对于STM32来说也不例外。主频的背后其实是一套复杂的时钟系统,而这套系统关乎所有外设的工作。因此,在我们继续深入学习之前,有必要了解STM32时钟系统的脉络,进而才能理解所有跟时间有关的机制和配置。其实,一开始我们用到的delay延时功能就是通过配置时钟系统实现的,现在是时候对它一探究竟了。
【学习目标】
- 认识STM32的系统时钟树,理解时钟的产生过程;
- 了解系统时钟配置函数的实现脉络;
- 知道SysTick定时器的地位和作用;
- 了解SysTick寄存器的功能;
- 理解延时函数的实现原理
与STM32时钟有关的信息量不小,为了不让篇幅太长,本章打算分两个部分来讲解,本文是第二部分。
二、SysTick系统定时器
2.1 SysTick的地位和作用
SysTick系统定时器是Cortex-M3内核中的一个外设,它是一个24位的向下递减的计数器,STM32一上电这个计数器就开始工作,每计数一次的时间是1/HCLK或8/HCLK。当计数值递减到0的时候,SysTick系统定时器就产生一次中断,以此循环往复。
由于SysTick属于Cortex-M3内核,那么所有基于该内核的单片机都具有这个系统定时器,使得软件可以很容易的移植。系统定时器一般用于操作系统的时基,以维持操作系统的心跳。没有操作系统的时候,也可以用于精确的延时,我们一直在用的delay延时就是通过配置这个寄存器实现的。
2.2 SysTick寄存器解读
SysTick寄存器比较简单,也是我们为数不多的对寄存器的直接分析。请看图4,它有4个寄存器,一般只需要配置前3个就可以得到你想要的时长了,我们把它们的功能列在表1中。
图4 SysTick的4个寄存器
表1 SysTick寄存器功能表
三、延时函数的代码剖析
解读了SysTick寄存器的功能,可能你还不清楚该如何配置它,现在我们就来分析delay延时的源码。读完源码,也许就豁然开朗了。
3.1 delay文件清单
我们在创建工程模板的时候,就已经把delay.c和delay.h这一对文件加进了工程,我们现在再来看一下它们所在的目录,如图5所示。
图5 delay文件清单
3.2 delay.h文件源码
这个文件的源码很简单,就声明了三个函数,如代码清单1所示。
cpp
//-------------------------------------------
// 代码清单1:delay.h
//-------------------------------------------
#ifndef _DELAY_H
#define _DELAY_H
#include "stm32f10x.h"
//-------------------------------------------
// 函数声明
//-------------------------------------------
void delay init(void); //延时初始化函数
void delay us(u32 nus); //微秒级延时函数
void delay ms(ul6 nms); //毫秒级延时函数
#endif
3.3 delay.c文件源码
为了增加代码的可读性,在下面的代码清单中,我们把原工程文件中与操作系统有关的部分都去掉了,这并不影响代码的运行。
1)delay_init()函数源码
如代码清单2所示,该函数执行延时初始化配置,选择HCLK/8作为SysTick定时器的时钟源,并确定得到1us和1ms所需的计数值。
cpp
//------------------------------------------------------
// 代码清单2:delay.c中的delay_init()函数
//------------------------------------------------------
#include "delay.h"
static u8 fac_us = 0; //微秒延时倍乘数
static ul6 fac_ms = 0; //毫秒延时倍乘数
void delay_init(void)
{
//sysTick定时器的时钟源为HCLK/8
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//按系统时钟的1/8来计数,fac_us个脉冲就是1us
fac_us = SystemCoreClock/8000000;
//1ms就是1000us
fac_ms = (u16)fac_us * 1000;
}
首先用了 SysTick_CLKSourceConfig() 这个库函数来选择时钟源为HCLK/8。在时钟主线的分析中,已经确定了HCLK = 72MHz,那么8分频就是9MHz。那么为了得到精确的1us,就需要9个时钟源脉冲。接着,SystemCoreClock 这个宏就是系统时钟频率72MHz,除以8M得到的fac_us不就是9么。至于最后一行,就比较好理解了,1000个fac_us不就是1ms么。
2)delay_us()函数源码
如代码清单3所示,该函数将需要的微秒数nus换算成计数值填入相应的寄存器,使能后开始计时,计时过程中通过do...while循环不断关注使能位和计数标志位的变化,时间到了则退出循环,停止计数。
cpp
//------------------------------------------------------------
// 代码清单3:delay_us()函数
//------------------------------------------------------------
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = nus*fac_us; //时间装载
SysTick->VAL = 0x00; //计数值清0
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开始倒数计时
do
{
temp = SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL &= SysTick_CTRL_ENABLE_Msk; //关闭计数
SysTick->VAL = 0x00; //清空计数器
}
3)delay_ms()函数源码
该函数与 delay_us() 函数的构造是一致的,如代码清单4所示,就是在装载计数器初值时不一样。使用这个函数需要特别注意,由于SysTick->LOAD寄存器是24位的,所以有nms <= 0xffffff*8*1000/HCLK。按照HCLK = 72MHz来计算,nms的最大值为1864。
cpp
//------------------------------------------------------------
// 代码清单4:delay_ms()函数
//------------------------------------------------------------
void delay_ms(u16 nms) //注意:nms<1864
{
u32 temp;
SysTick->LOAD = (u32)nms*fac_ms; //时间装载
SysTick->VAL = 0x00; //计数值清0
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开始倒数计时
do
{
temp = SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL &= SysTick_CTRL_ENABLE_Msk; //关闭计数
SysTick->VAL = 0x00; //清空计数器
}
(第二部分完,共两部分)