生活中,我们通过看表来知道当前时间,知道我们在这个点应该做什么事情,那么芯片呢,就通过各种定时器来记录时间,滴答时钟就是定时器的一种,它可以用来做操作系统的节拍(心跳),可以用来计时,可以用来做闹钟,延时,今天,我们就简单看一下滴答时钟。
void Delay(unsigned int xx)
{
while(xx--);
}
滴答时钟也是填入一个值,然后倒数到零,然后通知芯片,但是相比上边的粗略延时函数就精确了很多很多,可以精确到微秒、毫秒。
滴答时钟配置有两种:
- 查询式(不断去查询有没有计时完毕)
- 中断式(计时完毕发出信号)
因为还没讲到中断,这章先讲解查询式。
这里得知道 1MHZ=1μs
一、滴答时钟在STM32的哪里
首先我们得知道,ARM公司制造了CortexM3,ST公司以CortexM3为内核,造了STM32,这个滴答时钟就是在CortexM3里,也就是STM32内核中,所以用到的寄存器地址可以在Cortex-权威指南里查找。
二、滴答时钟相关的寄存器
1、SysTick->CTRL(控制和状态寄存器)
因为是查询式,所以我们不用第1位
- 0位(打开滴答时钟)
- 2位(选择时钟源,我们一般选择外部时钟源)
- 16位(查看是否数到了零)
2、SysTick->RELOAD(重装载数值寄存器)
这个寄存器就比较简单了,这个就是重新向滴答时钟里加载计时次数,可以看到总共有24位可设置,所以重新加载值最大不能超过24位。
3、SysTick->VAL(当前数值寄存器)
这个寄存器作用在读取的时候返回当前计数值,在写入的时候就是计数器清零,同时还清除掉了SysTick->CTRL第16位的标志。
4、SysTick->CALIB(校准数值寄存器)
这个寄存器不常用,所以我们不再讲解。
以上四种寄存器,我们常用的有:
- SysTick->CTRL(控制和状态寄存器)
- SysTick->RELOAD(重装载数值寄存器)
- SysTick->VAL(当前数值寄存器)
三、寄存器地址定义
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
寄存器少的时候,我们可以像以上这样定义,那么寄存器多的时候呢?我们就用C语言里学的结构体来定义,比如下边这次我们的滴答时钟寄存器地址
//-----------------SysTick寄存器地址----------------------
#define SysTick_Base 0xE000E010 //这是我们结构体的首地址,也是结构体第一个成员地址
#define SysTick ((SysTick_Typedef*)SysTick_Base) //转换为指针类型
typedef struct
{
volatile unsigned int CTRL; //控制和状态寄存器 0xE000E010
volatile unsigned int RELOAD; //重装载寄存器 0xE000E014
volatile unsigned int VAL; //当前值寄存器 0xE000E018
volatile unsigned int CALIB; //校准寄存器 0xE000E01C
}SysTick_Typedef;
四、滴答时钟配置步骤
在时钟树里可以看到,对于Cortex时钟,有两种选择方法,一是选用经过八分频的外部时钟源,二是选用内核时钟,我们一般在设置的时候选用八分频后的外部时钟源,我们之前设置了系统时钟为72MHZ,经过八分频后变为了9MHZ,也就是说,滴答时钟的频率是9MHZ,那让滴答时钟计1次,时间过去了1/9μs,滴答时钟计9次,才是1μs,于是我们有了以下的设计:
- 计1μs:我们向计数器里放入9
- 计1ms:我们向计数器里放入9000
1ms(毫秒)=1μs(微秒)
配置滴答时钟步骤如下: - 1、清空计数器
- 2、重新装载数值
- 3、打开定时器
- 4、等待计时器数到0
- 5、关闭计数器
- 6、清空计数器
五、微秒延时和毫秒延时总程序
1、微秒延时
//-----------------SysTick寄存器地址----------------------
#define SysTick_Base 0xE000E010
#define SysTick ((SysTick_Typedef*)SysTick_Base)
//-----------------SysTick寄存器定义----------------------
typedef struct
{
volatile unsigned int CTRL; //控制和状态寄存器
volatile unsigned int RELOAD; //重装载寄存器
volatile unsigned int VAL; //当前值寄存器
volatile unsigned int CALIB; //校准寄存器
}SysTick_Typedef;
//----------------------滴答定时器---------------------------
void SysTick_us(unsigned int time)
{
unsigned long int num;
SysTick->VAL=0; //计数器清零
SysTick->RELOAD=9*time; //重装载计数值
SysTick->CTRL|=1<<0; //定时器使能,打开定时器
do
{
num=SysTick->CTRL;
}
while((num&0x01)&&!(num&(1<<16))); //等待计数器到0
SysTick->CTRL&=~(1<<0); //关闭计数器
SysTick->VAL=0; //计数器清零
}
int main(void)
{
SysTick_us(10); //调用滴答定时器,延时10微秒
}
2、毫秒延时
//-----------------SysTick寄存器地址----------------------
#define SysTick_Base 0xE000E010
#define SysTick ((SysTick_Typedef*)SysTick_Base)
//-----------------SysTick寄存器定义----------------------
typedef struct
{
volatile unsigned int CTRL; //控制和状态寄存器
volatile unsigned int RELOAD; //重装载寄存器
volatile unsigned int VAL; //当前值寄存器
volatile unsigned int CALIB; //校准寄存器
}SysTick_Typedef;
//----------------------滴答定时器---------------------------
void SysTick_ms(unsigned int time)
{
unsigned long int num;
SysTick->VAL=0; //计数器清零
SysTick->RELOAD=9000*time; //重装载计数值
SysTick->CTRL|=1<<0; //定时器使能,打开定时器
do
{
num=SysTick->CTRL;
}
while((num&0x01)&&!(num&(1<<16)));//等待计数器到0
SysTick->CTRL&=~(1<<0); //关闭计数器
SysTick->VAL=0; //计数器清零
}
int main(void)
{
SysTick_ms(10); //调用滴答定时器,延时10毫秒
}
/*
while((num&0x01)&&!(num&(1<<16)));//等待计数器到0
(num&0x01) 这一句是判断用来判断SysTick定时器是否处于开启状态,
可以防止SysTick被意外关闭导致的死循环。
(num&(1<<16)) 这一句是用来判断SysTick->CTRL第16位是否为1,如
果为一,说明计数器延时时间已到
定时器正常开启,那么 (num&0x01) 为1,如果倒数时间已到,
(num&(1<<16)) 为 1,(num&0x01)&&!(num&(1<<16)) ==>(1)&&!(1)
可以看到后边的1取反为0,那么 (1)&&(0)=0,while(0)循环结束,执行后
边的语句。
*/