我想每一个单片机爱好者及工程开发设计人员都有过点灯的经历。流水灯是个好东西,尤为是在调试资源有限的环境中,有时会帮上大忙。app
然在最初入门时,如何让这些小灯们按照咱们的想法欢快地跑起来呢,绝大多数小朋友的作法是:在一个while循环里加上延时程序,让小灯在每一个状态下停留一段时间,再进入下一个状态,这样小灯们就会在不一样的状态中切换,就能够根据咱们设计的程序闪烁了。函数
这样这里就会涉及到一个延时程序的编写的问题,而通常的作法是一个for循环里去减一个很大的数,直到为0,则延时完成,那个数的值则是根据时钟频率和指令运行周期,估算出来的,还记得较久之前看过一篇帖子介绍51单片机精确延时的几种方法,有一种方法是在keil中设定好时钟频率,而后经过软件仿真试来算延时时间,以达到较精肯定时。ui
但这些方法通常都不够方便,延时也不够精确,更高阶一点的方法即是开一个定时器,在定时中断里面计数达到精确延时的目的。spa
在STM32的应用中,可考虑利用SysTick系统嘀嗒定时器来实现。但在STM32开发手册中对它的介绍却不多,几乎到没有的程度。由于它是Cortex内核的部分,CM3为它专门开出一个异常类型,而且在中断向量表中占有一席之地(异常号15),这样它能够很方便的移植到不一样厂商出CM3内核的芯片上,而且对于有实时操做系统的软件,它通常会做为整个系统的时基,这个对操做系统很是重要。有关SysTick的详细介绍可参考《Cortex-M3 权威指南》第133 页第八章及第179页第十三章。操作系统
SysTick总共有四个寄存器:设计
一、
调试
对应于软件中 SysTick->CTRL;code
二、
事件
对应于软件中 SysTick-> LOAD;资源
三、
对应于软件中 SysTick-> VAL;
四、
对应于软件中 SysTick-> CALIB (如上图),没有用过,也不经常使用,暂不做介绍。
这几个寄存器的偏移量以下图所示:
寄存器结构体的定义在 \CMSIS\CM3\CoreSupport core_cm3.h中,以下
/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick memory mapped structure for SysTick @{ */ typedef struct { __IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */ __IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */ __IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */ __I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */ } SysTick_Type;
SysTick这个脉冲计数值被保存到SysTick->VAL 当前计数值寄存器中,它只能向下计数,每接收到一个时钟脉冲SysTick->VAL 的值就向下减 1,直至0,而后由硬件自动把重载寄存器SysTick->LOAD 中的值到SysTick->VAL从新计数,而且当SysTick->VAL值计数到0时,触发异常,调用void SysTick_Handler(void)函数,能够在此中断服务函数中处理定时中断事件了, 是一个24 位的定时器,即一次最多能够计数 224个时钟脉冲,一般是对设定值进行递减计数操做。只要不把它在SysTick控制及状态寄存器SysTick->CTRL中的第0位使能位清除,就永不停息。
SysTick 中断优先级问题这里须要强调下。
它属于系统异常,是内核级中断,而且优先级是能够设置的,具体设置也是在 core_cm3.h中
/** * @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); SysTick->VAL = 0; SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0); /* Function successful */ }
其中以下这句就是设置优先级的函数,此函数对内核中断优先级和外部中断优先级设置通吃,比较强大,但须要手动算出来抢占和从优先级,不太方便,当跳进此函数,咱们能够算出Systick默认优先是最低的(效果至关于SCB->SHP[11] = 0xF0;)
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
此时若其它外部中断优先级设置比它高时,能够剥夺它进而转向外部中断。
能够作以下实验验证:
先设置一事件中断,把优先级设置高一些,
void Exti_Config(void) { EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line1; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
注:中断分组我在实验中,最初初始化设置为以下:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
设为第二组。
在
void SysTick_Handler(void) { EXTI_GenerateSWInterrupt(EXTI_SWIER_SWIER1); LED_1 = ON; Delay(); }
系统滴答中断里触发外部中断事件,并点亮LED1 。
外部中断处理函数以下
void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) != RESET) { EXTI_ClearITPendingBit(EXTI_Line1); LED_0 = ON; Delay(); } }
此延时函数为阻塞延时以下:
void Delay(void) { u32 i; for(i=0 ; i < 0xFFFFF; i++){} }
加入延时是为了看出来哪一个灯先亮。
当外部中断优先级比较高时,它能够抢占Systick中断先执行,以上代码实验结果为,LED0先点亮后,再回到LED1再点亮。
当把外部中断设置为与systick相同的优先级时,则systick优先级就会相对较高,例如把上面的优先级改成
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
则会LED1先亮,执行完SysTick_Handle函数后才轮到EXTI1_IRQHandler执行。
我的认为,若要实现systick精确延时,最好把systick优先级设置高一些,例如
NVIC_SetPriority (SysTick_IRQn, 0);
即把SCB->SHP[11] = 0x00;则可达到systick优先级高于任合外部中断的效果,此时延时会比较精准。
另外对于SysTick的时钟源的选择,要注意它的时钟源可选择内部时钟(FCLK,CM3上的自由运行时钟,STM32中对应是AHB),或者是外部时钟( CM3处理器上的STCLK信号,STM32中对应是AHB/8)
可参考以下图
它是在SysTick->CTRL第二位CLKSOURCE时钟源选择中设置。
有关systick延时函数的编写可参考野火《零死角玩转stm32-初级篇》。
至此咱们能够简单的实现一流水灯程序
while(1) { LED_0 =OFF; LED_1 = ON; Delay_ms(500); LED_0 =OFF; LED_1 = ON; Delay_ms(500); }
然而这样作真的好吗 ?这里用的是 阻塞延时哦,CPU的效率很大一部分就耗在了空转上了,太浪费资源。
假设系统时钟频率为72MHZ或者几十上百MHZ时,当完成一个循环只须要几十或十几纳秒级或者更短,而在这个循环之中阻塞延时个几十至几百毫秒的话,就像是在高速公路上忽然横出一条坑坑洼洼的泥泞路,那可想整条路都会所以而慢下来,甚至会出现灾难性的后果,我的认为,一般在系统初始化过程当中,各芯片的时序对时间有要求,能够用下阻塞延时,只须要系统启动时运行一下,当系统跑起来以后,最好就别再傻呼呼的这么作了。
这时主要采用的是在定时器里计数,在外部循环中对变量查询,达到某个值时再执行某个动做,达到延时的效果,而在时间未到时,系统还能够不停的跑圈圈,作别的事情去。
在定时中断里每毫秒计数一次 gticks
while(1) { if(500 == gticks) { LED_0 =OFF; LED_1 = ON; } if(1000 == gticks) { LED_0 =OFF; LED_1 = ON; gticks = 0 } Do_others(); }
以上须要在事件处理过程当中对gticks进行处理,增长了代码的耦合度,更容易出错,若是在一个事件处理中对gticks清除了,而下个事件中又须要查询它,这样就可能致使处理时序的错乱,相互干扰。
可否在事件处理中只提供查询功能,而定时的事情就交给定时本身去作?
下节高手将登场了,为你们介绍个我曾在一项目中学到的,非阻塞延时的精妙设计。