STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯
作者:互联网
学习板:STM32F103ZET6
强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结
STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区
参考:
STM32F103五分钟入门系列(六)时钟框图+相关寄存器总结+系统时钟来源代码(寄存器)
STM32F103五分钟入门系列(七)SystemInit()函数、SetSysClock()函数
SysTick滴答定时器+SysTick中断实现跑马灯
前言
本博客总结一下SysTick定时器的几个相关寄存器和库函数及它们的使用方法;而SysTick定时器是可以产生中断的,为了更好的理解SysTick并为之后中断总结提供方便,本博着重总结了SysTick中断的使用方法。不过SysTick去实现中断一般情况下最好不要用,32有许多定时器都可以产生中断,没必要用这个,本博举例子只是为了更好的去理解SysTick中断。
一、Systick滴答定时器
Systick定时器的参考资料在《Cortex-M3权威指南》第8章NVIC与中断控制中。然后注意一下这个计数器是递减计数器
(一)SysTick相关寄存器
SysTick定义在core_cm3.h头文件中:
与SysTick相关寄存器也定义在该文件中:
1、SysTick控制和状态寄存器(CTRL)
该寄存器位2:0、位16为有效位
位0
为该寄存器使能位,置1对该寄存器使能,置0使该寄存器失能。操作方式:
SysTick->CTRL|=1;//使能CTRL寄存器
位1
该位设置是否产生中断,置1时,倒数到0产生中断请求;置0时,不产生中断请求。该中断有专门的中断服务函数,中断服务函数定义在:stm32f10x_it.h中:
这个中断完全可以用在各类实验中,不过最好还是用别的定时器,毕竟32的定时器很多的,没必要用systick这个处理器内部包含的定时器来中断。下一博客会通过这个Systick总结一下延时函数,但是也强调一下,延时函数就是一个坑,不到万不得已不要用!!!
设置方法:
SysTick->CTRL|=1<<1;//倒计数到0时产生中断
SysTick->CTRL&=0xfffd;//倒计数到0不产生中断
位2
该位是选择时钟源的,当该位置0时采用外部时钟源,对于32,外部时钟源是AHB总线时钟的1/8。(下图红色箭头所示)
之前两篇博客也总结过,AHB默认状态下为1分频,所以AHB出来的时钟为系统时钟,默认状态下系统时钟为72MHZ,所以当该寄存器位2软件置0时,时钟为72MHZ/8=9MHZ。
当该寄存器软件置1时采用内核时钟,即HCLK(默认状态下为sysclk时钟的1分频)=72MHZ。(下图红色箭头)
所以:
位2为0:
SysTick的时钟为:(SYSCLK/(AHB分频系数))/8,默认状态下为9MHZ
位2为1:
SysTick的时钟为:SYSCLK/(AHB分频系数),默认状态下为72MHZ
位16
该位为状态标志位,如果倒计数到0,则该位硬件置1,如果未计数到0,则该位为0。计数到0后要不产生中断,要不重新装载初值,进入下一次倒计数。可以如下代码判断是否计数到0:
if((SysTick->CTRL&0x10000)!=0)//倒计数到0
{
}
else//未倒计数到0
{
}
2、SysTick重装载数值寄存器(LOAD)
该寄存器也是32位寄存器,不过有效位是24位,高8位为保留位。那么还寄存器最大装载的值为(2^24)-1=16777215,所以该重载值定义为:u32、 uint32_t类型。
重装载值操作:
SysTick->LOAD=(u32)一个数;
3、SysTick当前数值寄存器(VAL)
该寄存器用来获取当前倒计数的值(读操作),如果对它进行写操作,会把它清零,并且将CTRL寄存器的位24清零。(这个写操作一般不会用到)
对该寄存器的操作:
u32 temp=SysTick->VAL;
4、SysTick校准数值寄存器(CALIB)
该寄存器一般不会用到,了解即可。
通过该寄存器,可以使系统在不同的CM3产品上运行,且可以产生恒定的SysTick中断频率。可以直接把TENMS的值写入重装载寄存器,这样一来,只要没突破系统极限,就能做到每10ms来一次 SysTick异常。
二、SysTick相关库函数
1、时钟源选择函数SysTick_CLKSourceConfig()
该函数定义在misc.h头文件中:
打开函数体:
代码很简单,我们现看一下这个函数传递的参数:
顾名思义,就是我们之前总结CTRL寄存器时说的,时钟源可以是HCLK、HCLK/8。
仔细看看代码:
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK;
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
}
当传递过来的参数是SysTick_CLKSource_HCLK、SysTick_CLKSource_HCLK_Div8,则分别执行对应的一行代码。
把标识换为16进制数:
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= 0x00000004;
}
else
{
SysTick->CTRL &= 0xFFFFFFFB;
其中执行SysTick->CTRL |= 0x00000004
后,CTRL寄存器第2位为1,即我们之前总结的:使用HCLK内核时钟源,默认状态下为72MHZ(=SYSCLK)(AHB默认1分频)
执行SysTick->CTRL &= 0xFFFFFFFB
后,CTRL寄存器第2位为0,即我们之前总结的:使用外部时钟源,即HCLK/8,默认状态下,9MHZ(=SYSCLK/8)(AHB默认1分频)
2、SysTick配置函数SysTick_Config()
(1)函数实现
该函数定义在core_cm3.h中,毕竟是Cortex-M3自带的简单定时器,定义到这个文件夹很合理。
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 */
}
该函数是由返回值的,返回0和1,返回0表示设置成功,返回1表示重装载失败(注意不是设置失败)
在看一下SysTick_LOAD_RELOAD_Msk
标识:
而这个值就是我们之前总结的最大的重装载值,即LOAD寄存器23:0全为1.如此的话if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
这行代码就不难理解,因为传递的重装载值超过了最大可装载值,所以返回1表示重装载失败。
虽然能返回1报错,但是程序并没有结束,还会继续往下执行:SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
这行代码就是在设置装载值的,如果ticks小于等于最大可装载值,则当传递的装载值ticks与0xffffff位与运算后,结果还是ticks;如果ticks大于最大可装载值,(eg:0x1000000,结果为0;eg0x1000001,结果为1)经过位与运算,会把重装载值运算到小于最大可装载值的数。
然后是设置中断优先级,这个先不总结了,等总结完中断这一块应该就清楚了。
SysTick->VAL = 0;
这行代码是对VAL的写操作,之前总结过,如果是写操作,就将VAL寄存器清零(注意这是对VAL寄存器清零且不产生中断的方法!)同时清除CTRL的位24为0,此时虽然VAL为0,但是CTRL的位24仍为0,系统判断该计数器没有倒计数到0,所以不会产生中断。
接下来的代码:
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
将标识改为16进制数:(ul表示无符号长整型)
SysTick->CTRL = 1ul | 1ul |1ul;
即:
SysTick->CTRL = 1;
即对CTRL寄存器第0位赋1,其它位赋0。
(2)总结
通过该函数设置后,状态如下:
①如果重装载值大于最大可装载值,则返回1,且继续向下执行,最后再返回0
②如果重装载值小于等于最大可装载值,继续向下执行,最后返回0
③如果重装载值大于最大可装载值,会通过位与运算将重装载值减小到小于最大可装载值的一个数。
④清空VAL寄存器
⑤使能SysTick定时器
3、中断服务函数SysTick_Handler()
中断服务函数定义在stm32f10x_it.h中
这个函数默认有函数体,有时候不想用这个函数体,就注释掉,再把这个中断服务函数框架复制到自己想要的文件中继续编写,当然如果SysTick计数器不产生中断时(CTRL寄存器位1始终置0),就不用写中断服务函数了。
三、利用SysTick写一个LED闪烁实验(0.5s循环)
LED配置那一块就不再总结了,可以看我这个系列的第一篇博客。
1、初始化LED
#include "stm32f10x.h"
#include "led.h"
int main(void)
{
LED_Init();
}
2、设置SysTick时钟源
为了让SysTick倒计数器计数慢点,重装载值尽量小一点,采用小的时钟周期,即外部时钟源、AHB总线时钟的1/8。
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟源72MHZ/8=9MHZ
//SysTick->CTRL &=0xFFFFFFFB
3、设置CTRL寄存器
到计数到0后会产生中断,进入中断服务函数,所以CTRL寄存器的位1置1:
SysTick->CTRL|=1<<1;//允许产生中断
4、设置重装载值
SysTick时钟为9MHZ,一个时钟周期计数一次,则:
1s 计数: 9000000次
1ms计数:9000次
1us计数: 9次
如果让LED0每0.5s闪一次,即每0.5s中断一次,在中断服务函数中对LED0取反。
则需要计数0.5×9000000=4500000次
而最大可重装载值为0xffffff=16777215,没有超过最大可装载值,所以把4500000装载到LOAD寄存器中,完成一次计数即可。
SysTick->LOAD=4500000;//重装载初值
5、清空VAL寄存器
#include "stm32f10x.h"
#include "led.h"
int main(void)
{
LED_Init();
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟源72MHZ/8=9MHZ
//SysTick->CTRL &=0xFFFFFFFB
SysTick->CTRL|=1<<1;//允许产生中断
SysTick->LOAD=4500000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
}
6、使能SysTick时钟,开始计数
SysTick->CTRL|=1;//使能SysTick时钟,开始计数
7、屏蔽原有的中断服务函数
中断服务函数定义在stm32f10x_it.h中,可以用全局搜索:
或者直接Ctrl+F
打开函数体:
将它注释掉
8、编写中断服务函数
void SysTick_Handler()
{
LED0=~LED0;
}
这个代码只能持续一次,因为计数完成后并没有重新装载初值,还得下一步
9、重复装载初值
在中断服务函数中再次装载初值,每中断一次就装载一次初值:
oid SysTick_Handler(void)
{
LED0=~LED0;
SysTick->LOAD=4500000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
}
10、完整代码
//led.h
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
#endif
//led.c
#include "led.h"
#include "stm32f10x.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
//main.c
#include "stm32f10x.h"
#include "led.h"
#include "sys.h"
#define LED0 PBout(5) // DS0
int main(void)
{
LED_Init();
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟源72MHZ/8=9MHZ
//SysTick->CTRL &=0xFFFFFFFB
SysTick->CTRL|=1<<1;//允许产生中断
SysTick->LOAD=4500000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
SysTick->CTRL|=1;//使能SysTick时钟,开始计数
while(1)
{
}
}
void SysTick_Handler(void)
{
LED0=~LED0;
SysTick->LOAD=4500000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
}
11、效果动图
四、利用SysTick写一个LED闪烁实验(3s循环)
之前的计数没有超过最大可装载值,这次搞一个超过最大可装载值的实验,就让LED3s闪烁
1、思路
SysTick时钟为9MHZ,一个时钟周期计数一次,则:
1s 计数: 9000000次
1ms计数:9000次
1us计数: 9次
如果让LED每3s亮、3s灭,则中断一次需要的计数值:
9000000×3=27000000>(2^24 -1)
所以需要多次重装载
如重装载10次,每次装载值为2700000
程序中需要改变的地方:
2、完整代码
//led.h
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
#endif
//led.c
#include "led.h"
#include "stm32f10x.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
//main.c
#include "stm32f10x.h"
#include "led.h"
#include "sys.h"
#define LED0 PBout(5) // DS0
static u16 a=10;
int main(void)
{
LED_Init();
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟源72MHZ/8=9MHZ
//SysTick->CTRL &=0xFFFFFFFB
SysTick->CTRL|=1<<1;//允许产生中断
SysTick->LOAD=2700000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
SysTick->CTRL|=1;//使能SysTick时钟,开始计数
while(1)
{
}
}
void SysTick_Handler(void)
{
a--;
if(!a)
{
LED0=~LED0;
a=10;
}
SysTick->LOAD=2700000;//重装载初值
SysTick->VAL=0;//清空VAL寄存器
}
3、效果动图
标签:STM32F103,CTRL,SysTick,装载,跑马灯,寄存器,GPIO,时钟 来源: https://blog.csdn.net/Curnane0_0/article/details/118421831