今天我们来学习STM32中的SysTick定时器。
SysTick被捆绑在NVIC中,用于产生SysTick异常(异常号:15)。SysTick对操作系统来说非常重要,在以前,操作系统以及所有使用了时基的系统,都必须要一个硬件定时器来产生需要的“滴答”中断,作为整个系统的时基。
SysTick定时器能产生中断,CM3为它专门开辟出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为所有的CM3芯片都带有这个定时器,且所有CM3产品对SysTick的处理方式都是相同的。
根据《CM3权威指南》,我们可以知道与SysTick有关的寄存器有4个,它们分别是
SysTick控制及状态寄存器(STK_CTRL),其说明如图1所示。
图1 SysTick控制及状态寄存器说明
SysTick重装载数值寄存器(STK_LOAD),其说明如图2所示。
图2 SysTick重装载数值寄存器说明
SysTick当前数值寄存器(STK_VAL),其说明如图3所示。
图3 SysTick当前数值寄存器说明
SysTick校准数值寄存器(STK_CALIB),其说明如图4所示。
图4 SysTick校准数值寄存器说明
假设我们要用SysTick定时器来实现精准的延时,先来看看代码。
/** * @brief 启动系统滴答定时器 SysTick * @param 无 * @retval 无 */ void SysTick_Init(void) { /* SystemFrequency / 100000 10us中断一次 */ if (SysTick_Config(SystemCoreClock / 100000))// ST3.5.0库版本 { /* Capture error */ while (1); } // 滴答定时器不使能 SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; }
SystemCoreClock定义如下:
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz; /*!< System Clock Frequency (Core Clock) */
这个变量值是可以通过前面的宏定义来选择的,这里我们只讨论目前这个定义,再来看下SYSCLK_FREQ_72MHz的定义
#define SYSCLK_FREQ_72MHz 72000000
所以 SystemCoreClock / 100000 = 72000 000 / 100 000 = 720;
即代码变成 if(SysTick_Config(720)),现在来看看SysTick_Config()函数的定义
/** * @brief Initialize and start the SysTick counter and its interrupt. * * @param ticks number of ticks between two interrupts * @return 1 = failed, 0 = successful * * Initialise the system tick timer and its interrupt and start the * system tick timer / counter in free running mode to generate * periodical interrupts. */ static __INLINE uint32_t SysTick_Config(uint32_t ticks) { if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ SysTick->VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ }
该定义位于<core_cm3.h>头文件中,说明这个函数是stm32的库函数,现在我们来分析它的代码。
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
SysTick_LOAD_RELOAD_Msk是什么呢?
/* SysTick Reload Register Definitions */ #define SysTick_LOAD_RELOAD_Pos 0 /*!< SysTick LOAD: RELOAD Position */ #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) /*!< SysTick LOAD: RELOAD Mask */
即SysTick_LOAD_RELOAD_Msk = 0xFFFFFFul << 0 = 0xFFFFFF,所以代码变成 if( 720 > 0xFFFFFF ) return(1);
显然,这是个检查输入参数有没有越界的判断,至于为什么是不能大于0xFFFFFF呢?这是有依据的。
如上所示,因为SysTick是一个24位的定时器,所以其计数量最多只有2^24=16777216个,即其赋值范围为0x0000 0001 - 0x00FF FFFF。
很明显上面没有越界,跳过后面跟随的return(1)语句,继续往下执行代码
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
由上面SysTick_LOAD_RELOAD_Msk定义可知 SysTick->LOAD = ( 720 & 0xFFFFFF) - 1 = 0X02D0 - 1 = 719;
这里的SysTick与前面文章所述一样,为一个结构体指针,并指向相关寄存器首地址,具体细节不再赘述,这里即把719赋给重装载值寄存器。继续往下执行
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */
SysTick_IRQn在<stm32f10x.h>里面被定义为 -1,而__NVIC_PRIO_BITS也在该文件内被定义为4。
SysTick_IRQn = -1, /*!< 15 Cortex-M3 System Tick Interrupt */ #define __NVIC_PRIO_BITS 4 /*!< STM32 uses 4 Bits for the Priority Levels */
所以代码变成 NVIC_SetPriority( -1, (1 << 4) - 1 ),即NVIC_SetPriority( -1, 15);
到这里就要分析NVIC_SetPriority()函数。
/** * @brief Set the priority for an interrupt * * @param IRQn The number of the interrupt for set priority * @param priority The priority to set * * Set the priority for the specified interrupt. The interrupt * number can be positive to specify an external (device specific) * interrupt, or negative to specify an internal (core) interrupt. * * Note: The priority cannot be set for every core interrupt. */ static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) { if(IRQn < 0) { SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */ else { NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */ }
由上述函数定义可得SCB->SHP[( (uint32_t)(-1) & 0xF ) - 4] = ( (15 << (8 - 4)) & 0xff);
即得 SCB->SHP[ (0xFFFFFFFF & 0xF) - 4] = 0xf0; 这里把(uint32_t)(-1)转换成0xFFFFFFFF,这个转换我们将在后面论述。
即得 SCB->SHP[11] = 0xf0;
这个赋值的意思是什么呢?我们先在《CortexM3 Programming Manual》里面找到SCB的描述。
上面的代码即表示对该寄存器的高8位操作,PRI_15[7:4]全置1,PRI_15[3:0]全置0,配置PRI_15即SysTick异常的优先级。
好,继续往下
SysTick->VAL = 0;
这句代码的意思是SysTick当前数值寄存器赋值0,再继续往下
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
SysTick_CTRL_CLKSOURCE_Msk、SysTick_CTRL_TICKINT_Msk、SysTick_CTRL_ENABLE_Msk的定义分别如下
#define SysTick_CTRL_CLKSOURCE_Pos 2 /*!< SysTick CTRL: CLKSOURCE Position */ #define SysTick_CTRL_CLKSOURCE_Msk (1ul << SysTick_CTRL_CLKSOURCE_Pos) /*!< SysTick CTRL: CLKSOURCE Mask */ #define SysTick_CTRL_TICKINT_Pos 1 /*!< SysTick CTRL: TICKINT Position */ #define SysTick_CTRL_TICKINT_Msk (1ul << SysTick_CTRL_TICKINT_Pos) /*!< SysTick CTRL: TICKINT Mask */ #define SysTick_CTRL_ENABLE_Pos 0 /*!< SysTick CTRL: ENABLE Position */ #define SysTick_CTRL_ENABLE_Msk (1ul << SysTick_CTRL_ENABLE_Pos) /*!< SysTick CTRL: ENABLE Mask */
即SysTick_CTRL_CLKSOURCE_Msk = 0x04,SysTick_CTRL_TICKINT_Msk = 0x02,SysTick_CTRL_ENABLE_Msk = 0x01,三者位或的最终结果为0x07,即
SysTick->CTRL = 0x07;由图1可知,SysTick被配置为AHB时钟——72MHz、计数到0便产生一次SysTick异常请求、使能计数器。
由此SysTick便被配置为一个可产生中断的定时器,定时周期为720 / 72 000 000 = 1/100 000,即每10us产生一次中断。
中断配置好了,那怎么利用中断实现精准延时呢?这个就很容易了。
前面已经说过,每次中断间隔为10us,如果我们要实现10ms延时,那就进1000次中断呗,设定一个变量,赋值为1000,每次进入中断就减1,如果没减至0,那么程序就停在这里,如果减到了0,就退出,这样便能实现10ms延时。
关于把(uint32_t)(-1)转换成0xFFFFFFFF
用unsigned把负数转正并不改变变量的内存形态,也就是二进制编码不会改变,而逻辑数值上的变化可通过二进制的补码来计算,等价于[ 2 ^ (变量类型位数) - (负数绝对值) ]。
比如一个32位的负数值为(-N),那么转换成unsigned的值为 (2 ^ 32 - N),所以(uint32_t)(-1) = 2 ^ 32 - 1 = 4 294 967 295 = 0xFFFF FFFF
(该知识点参考自C语言使用unsigned把负数转换成整数是怎么计算的?,非常感谢原作者。)
因作者水平有限,有错误处还请批评指正。
原创作品