本章知识的总结大部分来自于《M3--权威指南》
SysTick属于NVIC的一部分(即属于内核的一部分),可以产生SysTick异常,为简单的24位向下计数器,可以使用外部参考时钟或内部时钟。SysTick有两个重要的特征:一,它是为OS服务的,作为一个周期性的中断来定期触发OS(这需要后续的探索);二是处理器设计要确保运行在非特权等级的应用无法禁止该定时器,否则任务会禁止SysTick定时器并锁定整个系统。
SysTick定时器存在四个寄存器。CMSIS-Core头文件中定义了一个名为SysTick的结构体。
若系统中不需要OS,SysTick定时器可用作定时器的简单外设,用以产生周期性的中断,延时和时间测量。(这三个方面是本篇的重点)若想产生周期性的SysTIck中断,最简单的方法是使用CMSIS-Core的函数SysTick_Config(),下面的例程就使用了该函数。
下面来看程序。
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_SysTick.h"
int main(void)
{
LED_GPIO_Config ();
SysTick_Init();
while(1)
{
LED1 (ON);
Delay_us (10000); //10000*10us=100ms
LED1 (OFF);
}
依旧是在一个简单的led灯点亮的实验基础上,增添了定时器的内容。在主函数中,出现了SysTick的配置函数,消失了以往的用while(i--)的简单方式进行的延时函数。进入SysTick_Init(),观察配置的细节。
void SysTick_Init(void)
{
if (SysTick_Config(SystemCoreClock / 100000))
{
/* Capture error */
while (1);
}
调用库函数SysTick_Config(),如果为真,就出现错误,进入无限循环。为了搞清楚这个,我们进入这个库函数。
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 */
}
首先注意声明函数的关键字static(static的三种用法会详细学习介绍,简单来说static函数可以很好地解决不同原文件中函数同名的问题,因为一个源文件对于其他源文件中的static函数是不可见的。 当你的程序中有很多个源文件的时候,你肯定会让某个源文件只提供一些外界需要的接口,其他的函数可能是为了实现这些接口而编写,这些其他的函数你可能并不希望被外界(非本源文件)所看到,这时候就可以用static修饰这些“其他的函数”。)ticks为中断间隔,即多少个时钟周期中断一次,若大于SysTick_LOAD_RELOAD_MSK,就返回1,即为真
/* 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是装载和重装载寄存器内的值,为0xFFFFFF(24)位,末尾的“ul”表示强制转换为unsigned long。
当ticks数满足要求后,ticks与重加载寄存器数值位与,放入重装载寄存器,数值应该减1,因为要向下计数到0.
设置中断优先级,研究一下NVIC_SetPriority()函数,设置优先级为15.
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 */
}
如果优先级编号小于0,设置系统中断;反之设置设备中断。在CMSIS-Core中SysTick的枚举值为-1,所以当然是前者。SCB是系统控制块,追踪SCB可以发现
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
SCB_BASE为0xE000E000,SCB_Type是SCB的数据的数据结构
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
里面有y一堆寄存器。SCB->SHP[12]是系统处理优先级寄存器,SCB在低功耗方面发挥了巨大作用。
设置完优先级后,清除当前值和计数标志。
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
设置SysTick的控制和状态寄存器:使用内核时钟(第2位),滴答计时器减至0时产生异常(第1位),定时器使能(第0位)。
至此,SysTick_Config结束。如果调用此函数返回1,就出错。反之,关闭计时器。配置计时器完成。
让我们复盘整个过程。
当每次调用延时函数时,传参数10000给TimingDelay,同时使计数器使能SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;打开中断,进入中断函数。在中断函数中,调用一个外部函数TimingDelay_Decrement()。
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
每完成一次中断周期,即t=ticks(设定的中断周期数)*系统的时钟周期(1/f)= (72000000/100000) * (1/72000000) = 10us ,TimingDelay自减一次,直到减完10000次10ms,最后一次退出中断,退出函数,完成一次延时。