一、起因:看不懂江协的延时函数配置
图片内容来自江协Stm32的延时函数配置。
其中参数来源于
也就是说江协是不通过封装好的函数进行传参,而是自行通过赋值变量达到效果,可能是为了方便启动和关闭Systick。
二、Stm32标准库函数的一些内容---分部说明
1.简述:关键词——volatile:用于定义直接访问内存的变量,不让编译器优化访问流程。
前补知识:当CPU需要访问某个变量时,如果该变量已经在缓存区中,则可以直接从缓存区获取,而不需要访问主内存,从而提高访问速度。
非volatile声明的变量,可能会被编译器认为是不变的,然后直接访问缓存区里面的值(来源于内存区,但需要更新),但是可能内存区的值≠缓存区的值,为了读取到真实的值,就要volatile。
#define A B;的语句的作用是把B加了个外号名为A。这条语句之后,可以同时用A,B定义变量。
volatile
声明一个变量是易变的,这意味着该变量的值可能会被程序之外的因素(如中断服务程序、多线程环境或硬件)改变。
对于非volatile
变量,编译器会进行优化,可能会将变量的值缓存到寄存器中,从而减少对内存的访问次数。
这种优化在单线程环境中通常是安全的,但在多线程或多处理器环境中可能会导致可见性问题。编译器不会对volatile
变量进行这种优化,从而确保了变量的可见性和安全性。
使用volatile
关键字可以提高变量的可见性和安全性:
①可见性:在多线程或多处理器环境中,volatile
关键字确保变量的值在内存中是可见的。每次读取或写入操作都会直接从内存中获取或更新变量的值,而不是使用缓存中的旧值。这有助于避免由于编译器优化导致的变量值不一致问题。
②安全性:volatile
关键字防止编译器对变量的访问进行优化,确保每次访问都是直接的内存操作。这在处理硬件寄存器或共享内存时尤为重要,因为这些变量可能会被外部因素修改,而编译器优化可能会导致程序行为不符合预期。
2.简述:认识变量名:
CTRL :系统控制和状态寄存器。LOAD :重新加载值寄存器。
VAL : 当前值寄存器。CALIB :校准寄存器。
Offset:偏移量。
意思是变量所描述的寄存器位置为 内存基地址+偏移量地址。地址在物理层面是固定的,要访问特定寄存器只要知道 基地址和偏移量 就行。
SysTick:系统滴答
CTRL 0x00:系统控制和状态寄存器。
用于控制systick的启动与关闭。
LOAD 0x04:重新加载值寄存器。
对这个写值就是多少个systick进入一次中断。也就是中断周期
VAL 0x08: 当前值寄存器。
记录当前systick的值。在定时时,需要清空,否则会从未清空的值开始计数,导致第一个中断的时间不一定等于目标时间。
CALIB 0x0C:校准寄存器。
3.简述:SysTick是一个(2.)提到的结构体的指针型变量名
绿色选定的SysTicks那一行的意思:将SysTick_BASE 取个别名 SysTick。
SysTick_BASE前面的(SysTick_Type *) 在②中有说明是一个结构体类型名,
整体( (SysTick_Type *) SysTick_BASE )的意思是:定义了一个(SysTick_Type *)类型的变量SysTick_BASE ,并且给 SysTick_BASE 取个别名 SysTick。
补充:
结构体定义和结构体指针定义法的区别:
用SysTick_Type定义:SysTick_Type A,那么变量访问形式是A.CTRL
用SysTick_Type *定义:SysTick_Type * A,那么变量访问形式是A->CTRL
4.简述:Pos是地址,Msk是标志位。
①SysTick_CTRL_COUNTFLAG_Msk:标志计数器是否减到0(完成计数周期)(1是0非,1时读取会清零)
②SysTick_CTRL_CLKSOURCE_Msk: 系统时钟(SYSCLK)。
配置: |= SysTick_CTRL_CLKSOURCE_Msk
③SysTick_CTRL_TICKINT_Msk:使能或禁止SysTick定时器的中断。1开启,0关闭。
配置: |=SysTick_CTRL_TICKINT_Msk
④SysTick_CTRL_ENABLE_Msk:定时器使能标志位。
配置: |=SysTick_CTRL_ENABLE_Msk。
⑤SysTick_LOAD_RELOAD_Msk:表示SysTick定时器的重装载寄存器(LOAD)的最大值(0xFFFFFF )。
16进制表示24位,一位16进制表示四个2进制,0xFFFFFF =1111 1111 1111 1111 1111 1111
配置:SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
⑥SysTick_VAL_CURRENT_Msk:表示SysTick定时器的当前值掩码。
SysTick_VAL_CURRENT_Msk = 0;
A = SysTick_VAL_CURRENT_Msk;
⑦SysTick_CALIB_NOREF_Msk:这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。
配置:|= SysTick_CALIB_NOREF_Msk;
⑧SysTick_CALIB_SKEW_Msk:如果SKEW位被设置,表示SysTick计数器的周期不精确,即SysTick计数器的周期与预期的周期之间存在偏差。
配置:|= SysTick_CALIB_SKEW_Msk;
⑨SysTick_CALIB_TENMS_Msk:1毫秒的计数值,通常为10500。由时钟频率计算得出,取决于系统的时钟配置。
SysTick定时器:24位的倒计数定时器,常用于实现精确的延时功能。
位置(position),用于说明状态标志位在CTRL、LOAD、VAL、CALLB状态寄存器的位置。SysTick_XXX_YYYY_Pos 表示XXX寄存器中YYYY状态标志位的位置。pos=Position。
掩码(mask):标识SysTick定时器的带有的状态寄存器的各标志位。
SysTick_CTRL_COUNTFLAG_Msk:
COUNTFLAG标志位用于指示SysTick定时器是否已经完成了计数周期。当SysTick定时器的计数值从非零值减到0时,COUNTFLAG标志位会被置位(即设置为1),表示计数周期已经结束。读取COUNTFLAG标志位,该位自动清零。
所以可以用(这句函数最后面会解释,看不懂也可接着往下看)
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
等待计数到0
SysTick_CTRL_CLKSOURCE_Msk:选择SysTick定时器的时钟源是系统时钟(SYSCLK)。
SysTick_CTRL_TICKINT_Msk:用于控制SysTick定时器的中断使能,用于使能或禁止SysTick定时器的中断。1开启,0关闭。
SysTick_CTRL_ENABLE_Msk:用于控制系统定时器(SysTick)的使能。用于控制SysTick定时器是否开始计数。当 SysTick_CTRL_ENABLE_Msk
被设置1时,SysTick定时器开始倒数;当它被清除时,SysTick定时器停止倒数。
SysTick_LOAD_RELOAD_Msk:用于表示SysTick定时器的重装载寄存器(LOAD)的最大值。
具体来说,SysTick_LOAD_RELOAD_Msk
的值是 0xFFFFFFul << SysTick_LOAD_RELOAD_Pos
,其中 SysTick_LOAD_RELOAD_Pos
是重装载寄存器的位置,位置是0,所以相当于没改变值。
配置SysyTick定时器:SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
这里的 ticks
是用户希望设置的重装载值,通过 & SysTick_LOAD_RELOAD_Msk
操作,确保 ticks
的值不会超过 SysTick_LOAD_RELOAD_Msk
定义的最大值。然后减去1是为了确保定时器能够正确地从 ticks-1
开始计数,从而经过 0~ticks
个脉冲
SysTick_VAL_CURRENT_Msk:表示SysTick定时器的当前值掩码。
SysTick_CALIB_NOREF_Msk:(延迟函数配置不涉及)这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。这个宏定义用于检查SysTick定时器的校准寄存器中是否设置了NOREF位。NOREF位表示SysTick定时器是否使用外部参考时钟。
SysTick_CALIB_SKEW_Msk:(延迟函数配置不涉及)SysTick_CALIB_SKEW_Msk用于检查SYST_CALIB寄存器中的SKEW位是否被设置。如果SKEW位被设置,表示SysTick计数器的周期不精确,即SysTick计数器的周期与预期的周期之间存在偏差。
SysTick_CALIB_TENMS_Msk:(延迟函数配置不涉及)这个字段用于指定SysTick定时器每1毫秒的计数值。在STM32的SysTick定时器中,TENMS字段的值是固定的,通常为10500,表示每1毫秒有10500个计数周期。这个值是根据SysTick定时器的时钟频率计算得出的,具体取决于系统的时钟配置。
三、看了那么多,再分析江协延时函数
这里我用江协的注释来对应代码
①SysTick->LOAD = 72 * xus;
设置重装值是 72*us:因为系统时钟是72Mhz(72 000 000Hz) = 1s = 1 000ms = 1 000 000us。所以1us = 72 Hz。(这里不是正确对应结果,只是为了理解而作)
②SysTick->VAL = 0x00;
清空当前计数值:因为Val是不知道,为了保证第一个计数周期准确,需要清零val。
③SysTick->CTRL = 0x00000005;(难点1)
设置时钟源为HCLK,启动定时器:先看到Stm32封装好的函数定义
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
再回想起CTRL相关标志位的基地址是0x00
CLKSOURCE时钟源标志位的位是2,定时中断标志位的位置是1,时钟使能位的位置是0,
由于延时函数不需要在完成标志位的时候做什么,所以不需要开启中断
所以由二进制的数值表示为(位2 = 1)(位1 = 1)(位0 = 1)=> 101 =>16进制是5。
0x00000005 = 0000 0000 0000 0000 0000 0000 0101。
④ while(!(SysTick->CTRL & 0x00010000)); (难点2)
等待计数到0 :
Stm32给的封装是while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
而SysTick_CTRL_COUNTFLAG_Msk的地址偏移是16
(1ul << SysTick_CTRL_COUNTFLAG_Pos):(unsigned long 类型的)1<<16 得到SysTick_CTRL_COUNTFLAG_Msk。
所以SysTick_CTRL_COUNTFLAG_Msk 的值是 1000 0000 0000 0000,而0x00010000=1*(16^4) = 1 * ((2)^ 4)^ 4 = 1*2^16 = 1000 0000 0000 0000。
补充说明:SysTick_CTRL_COUNTFLAG_Msk就是一个在二进制数的表示形式上只有第16位是1,其余是0的一个变量。而SysTick->CTRL是一个很多位都表示不同状态标志位值的一个组合值,其中SysTick->CTRL的第16位为计数标志位COUNTFLAG
,只有当计数值为0的时候,第16位计数值标志位COUNTFLAG
才会被硬件置0,否则为1。
在代码中使用while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
时,程序会不断检查COUNTFLAG
标志位是否被设置为1。只有当COUNTFLAG
标志位被设置为1时,循环才会退出。由于COUNTFLAG
标志位在每次读取后会自动清零,(也就是当COUNTFLAG == 1时,下一次执行SysTick->CTRL就会把COUNTFLAG值0
),因此循环会在SysTick
计数器完成一次递减到0的过程后退出,而不会导致死循环。
⑤SysTick->CTRL = 0x00000004;
关闭定时器:如果看懂了③,那就可以知道⑤是把101对应的时钟使能位置0从而关闭了时钟,没有时钟自然就无法工作,也就是关闭了定时器。