技术交流QQ群【JAVA,C++,Python,.NET,BigData,AI】:170933152
之前咱们用的delay中的这个延时函数,这个函数
就是用的systick来实现的.
这个systick定时器,其实就是一个比如,他会自动的从100数到0,然后再从100数到0这样循环.
当定时器走到0的时候,他会自动去加载初始化值100.再进行倒数.
Systick这个也可以产生中断异常,比如每当定时器到0的时候,就会有中断.
然后中断优先级也可以设置.这个理的NVIC就是中断优先级.
systick有4个寄存器,他的工作是这样的,
比如这里reload寄存器,就是systick自动重装载除值寄存器,R,这里比如记录了100,然后
VAL这个当前值寄存器,这里Val会倒计数,倒计数到0的时候,又会用RELOAD,把100重装载到当前值.
这里是这样的,CTRL寄存器,这里
一个reload寄存器,一个val寄存器,然后val这个寄存器会自动的倒数到0的时候回自动再比如从100倒数,
这里可以定义,比如倒数到0的时候,设置CTRL位1位1的时候,当定时器,倒数到0的时候就会产生systick的异常,
就可以做到中断了.
然后这里的位16,就是当systick定时器,到0以后会自动的把countflag,自动的设置为1,也就是说
如果到0了,你去读这个读出的是1的话,那么说明定时器到0了,并且读完1以后,这个位会自动的
变成0,然后,如果这个时候倒计数没有到0的话,再去读读出的不是1,说明,定时器还没有倒数到0.
主要是用来防止误读的.
这里还要一个位挺重要,这类的位2,CLKSOURCE,对于这个位来说,
可以设置,systick定时器使用的是外部时钟源,还是内核时钟,这里的
外部时钟就是HCLK,AHB总线时钟的8分之一,也就是对于conterx-M3内核来说,这个
频率是72mhz/8=9
然后对于conterx-m4内核来说这个频率是168/8=21mhz
这个寄存器,有24位,因为systick有24位,所以这个重装载寄存器也有24位.这里会存有一个要重装载的值,这个寄存器如果有32位
也是只有24位是有效的.
CURRENT,读这个寄存器的时候,会返回这个寄存器当前倒数的值.
写这个寄存器会清除他为0,并且countflag这个标志也会被清0.
还有一个校准寄存器这个用的少,就没说
然后看一下这个库函数,这里的第一个可以配置时钟源,
第二个可以设置是否开启中断,就是这里可以设置ticks,也就是说比如1秒钟,需要中断多少次这样.
有中断的话,就需要用到中断服务函数.
.接下来看看代码
用跑马灯实验看
走到SysTick这个声明的地方去.
可以看到这里这个Systick定义到了core_cm3.h这个文件中,因为这部分是内核级别的
所以就定义到这里.
然后去看一下对应的结构体,可以看到他定义了要用到的这几个寄存器.
把寄存器和地址映射起来.
注意这里的部分,这里的作用是选择时钟源的,9M
这里可以看到SysTick_CLKSource这个参数可以有两个选择,一个是HCLK_Div8这个是HCLK的8分之一.也就是72/8=9mhz
一个是HCLK也就是72mhz
然后再看这个core_cm3.h这个文件
这类有个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)
{
//1.可以看到这里的这句话的作用就是做一个验证
//因为systick是24位的,这里如果超过24位是要返回失败的.
//
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
//2.然后这里设置这个Load的值,也就是从多少开始计数
//比如这里ticks=1000,那么这里的load寄存器中要设置
//成999,然后再从当前值开始倒计数.
//这里一般1000的话,要减去1,因为装载会消耗一个时钟
//周期
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 */
}
然后:优先级后面再说
/**
* @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 */
//1.然后这里设置优先级以后,再把VAL初值设置为0
SysTick->VAL = 0; /* Load the SysTick Counter Value */
//2.然后这里,首先再去设置时钟源
//然后再配置中断
//然后再使能这个定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0); /* Function successful */
}
这里是设置的时钟源,这里有两个.
这里再说一下.
先设置比如装载值是1000,然后,定时器去倒计数,倒计数到0的时候产生中断,然后
在中断函数中我们就可以写逻辑代码.
这里有一个用SysTick定时器写的delay延时函数
主要看一下这里,比如这里的SystemCoreClock是72mhz,也就是72000000,
然后除以1000,就是72000,
72000/72000000 s
注意这里是由个频率概念的,因为72mhz是1s中震动的频率
也就是咱们设置时钟周期为72000的话,也就是
72000/72000000 s,会震动72000次.
也就是72000,也就是把中断周期设置为1ms了.
也就是把中短间隔设置为1ms.
也就是说每隔1ms就会自动执行上图中的systick_handle这个函数.
仔细看一下这个delay延时函数,使用systick来实现的.
这里可以看到再main函数中,调用了delay(200);也就是延时200毫秒.
这里延时的timingDelay就是200,然后
每当触发一次中断,也就是花费了1ms,就会这个timingdelay就会--减去1,指导
timingdelay为0的时候就退出这个delay函数,从而就实现了
延时的目的.
但是这里通过定时器这样延时是挺耗费系统资源的
所以这里,咱们的系统中是这样做的
比如这里的delay.h和delay.c
.h文件中
定义了这几个函数.
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2010/1/1
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//
void delay_init(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);
#endif
然后去看他的实现.delay.c中
#include "delay.h"
//
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(适合STM32F10x系列)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2010/1/1
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.2修改说明
//修正了中断中调用出现死循环的错误
//防止延时不准确,采用do while结构!
//V1.3修改说明
//增加了对UCOSII延时的支持.
//如果使用ucosII,delay_init会自动设置SYSTICK的值,使之与ucos的TICKS_PER_SEC对应.
//delay_ms和delay_us也进行了针对ucos的改造.
//delay_us可以在ucos下使用,而且准确度很高,更重要的是没有占用额外的定时器.
//delay_ms在ucos下,可以当成OSTimeDly来用,在未启动ucos时,它采用delay_us实现,从而准确延时
//可以用来初始化外设,在启动了ucos之后delay_ms根据延时的长短,选择OSTimeDly实现或者delay_us实现.
//V1.4修改说明 20110929
//修改了使用ucos,但是ucos未启动的时候,delay_ms中中断无法响应的bug.
//V1.5修改说明 20120902
//在delay_us加入ucos上锁,防止由于ucos打断delay_us的执行,可能导致的延时不准。
//V1.6修改说明 20150109
//在delay_ms加入OSLockNesting判断。
//V1.7修改说明 20150319
//修改OS支持方式,以支持任意OS(不限于UCOSII和UCOSIII,理论上任意OS都可以支持)
//添加:delay_osrunning/delay_ostickspersec/delay_osintnesting三个宏定义
//添加:delay_osschedlock/delay_osschedunlock/delay_ostimedly三个函数
//V1.8修改说明 20150519
//修正UCOSIII支持时的2个bug:
//delay_tickspersec改为:delay_ostickspersec
//delay_intnesting改为:delay_osintnesting
//
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
//1.---注意这里SYSTEM_SUPPORT_OS定义这个的目的是
//当使用ucos等实时操作系统的时候,这个就有用了,现在用不到.
//
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
// delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
// delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
// delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
// delay_ostimedly:用于OS延时,可以引起任务调度.
//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef OS_CRITICAL_METHOD //OS_CRITICAL_METHOD定义了,说明要支持UCOSII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
#endif
//支持UCOSIII
#ifdef CPU_CFG_CRITICAL_METHOD //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OSCfg_TickRate_Hz //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
#endif
//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedLock(&err); //UCOSIII的方式,禁止调度,防止打断us延时
#else //否则UCOSII
OSSchedLock(); //UCOSII的方式,禁止调度,防止打断us延时
#endif
}
//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
OS_ERR err;
OSSchedUnlock(&err); //UCOSIII的方式,恢复调度
#else //否则UCOSII
OSSchedUnlock(); //UCOSII的方式,恢复调度
#endif
}
//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD
OS_ERR err;
OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err); //UCOSIII延时采用周期模式
#else
OSTimeDly(ticks); //UCOSII延时
#endif
}
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
#endif
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为HCLK时钟的1/8
//SYSCLK:系统时钟
void delay_init()
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的1/8
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为K
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
#endif
}
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
tcnt=0;
delay_osschedlock(); //阻止OS调度,防止打断us延时
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
delay_osschedunlock(); //恢复OS调度
}
//延时nms
//nms:要延时的ms数
void delay_ms(u16 nms)
{
if(delay_osrunning&&delay_osintnesting==0) //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
delay_ostimedly(nms/fac_ms); //OS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
#else //不用OS时
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
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; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
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; //清空计数器
}
#endif
//1.---注意这里SYSTEM_SUPPORT_OS定义这个的目的是
//当使用ucos等实时操作系统的时候,这个就有用了,现在用不到.
//
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
这里我们先看这个delay_init()
然后先看上面
上面有两个因子,一个是微妙的因子,一个是毫秒的因子.
毫秒的意思是,比如我们定时1毫秒,需要多少个systick周期.
实际上当systick的时钟确定好了以后,这两个数就是个常数了.
这里首先设置systick定时器的时钟来源,然后
再设置fac_us,也就是一微妙,需要9个时钟周期.
然后
这部分os的,先不看
然后
可以看到这里fac_ms=fac-us *1000 所以这里就可以看出来
毫秒就是把微妙*1000实现的.
也就是初始化的时候,就是把这个延时微妙和毫秒设置好了.
延时微妙需要9次,也就是系统时钟的8分之一
也就是这个微妙因子是9,也就是系统时钟的8分之一
也就是系统时钟的8分之一是1微妙.
这里还是要说明一下,我们知道一秒是72000000次震荡.
然后,1ms的话是,72000/72000000,也就是72000000/1000 次震荡,也就是72000次震荡是1ms
1us的话就是72次震荡.
这里不太明白怎么计算的.
那么前面说了,由于装载也需要1个时钟周期,所以如果指定1000个时钟周期后中断,一般都指定为999
而毫秒因子是9000.
有了这两个因子以后,就可以实现下面两个函数了
delay_us和delay_ms这两个函数了.
这个函数的意思是:
因为1us要震荡,fac_us个时钟周期,然后这里多少us就震荡多少次fac_us,相乘就可以了,这里就是给
Load寄存器,设置,要从多少个时钟周期,开始倒计数
然后,就是清空计数器,
然后使能CTRL寄存器,也就是开始倒计数.,也就是设置的CTRL寄存器的最低位
然后一直去读取CTRL寄存器,
直到,判断这个CTRL寄存器的位16,也就是判断是否倒计数到0了.
到了0以后,然后就把这个计数器关闭,然后再清空计数器.
这个延时毫秒也是一样的.
这里要注意,这个微妙,也好,毫秒也好,这里
都是有限制的,也就是说,因为LOAD寄存器是24位有效,
也就是最大值不能超过2^24-1这个值.
这里注意使用的时候首先引入delay.h然后
要调用delay_init(),这个作用是,先去选择systick定时器的时钟源,然后
再去计算出两个常数.