当前位置:   article > 正文

STM32学习笔记——十四、SysTick——系统定时器_stm32 sys

stm32 sys

0 前言

本章参考资料

  • 《Cortex-M3 内核编程手册》-4.5 章节SysTick Timer(STK)
  • 《Cortex-M3 内核编程手册》4.48 章节SHPRx,其中STK 这个章节有SysTick 的简介和寄存器的详细描述
    • 因为SysTick 是属于CM3 内核的外设,有关寄存器的定义和部分库函数都在core_CM3.h 这个头文件中实现
    • 学习SysTick 的时候可以参考这两个资料,一个是文档,一个是源码。

1 SysTick简介

系统定时器 SysTic

  • 属于CM3 内核中的一个外设,内嵌在NVIC 中
  • 系统定时器是一个24bit的向下递减的计数器
    • 计数器每计数一次的时间为1/SYSCLK
    • 一般我们设置系统时钟SYSCLK等于72M
  • 当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复
  • 因为SysTick 是属于CM3 内核的外设,所以所有基于CM3 内核的单片机都具有这个系统定时器,使得软件在CM3 单片机中可以很容易的移植
  • 系统定时器一般用于操作系统,用于产生时基维持操作系统的心跳

2 SysTick寄存器介绍

SysTick—系统定时器有4 个寄存器,简要介绍如下:

  • 在使用SysTick 产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用

2.1 SysTick 寄存器

在这里插入图片描述

2.1.1 SysTick 控制及状态寄存器

在这里插入图片描述

2.1.2 SysTick 重装载数值寄存器

在这里插入图片描述

2.1.3 SysTick 当前数值寄存器

在这里插入图片描述

2.1.4 SysTick 校准数值寄存器

  • 系统定时器的校准数值寄存器在定时实验中不需要用到
  • 有关各个位的描述这里引用手册里面的英文版本,比较晦涩难懂,暂时不知道这个寄存器用来干什么
    在这里插入图片描述

3 SysTick定时实验

3.1 硬件设计

SysTick 属于单片机内部的外设,不需要额外的硬件电路,剩下的只需一个LED 灯即可。

3.2 软件设计

  • 创建两个文件:bsp_SysTick.c 和bsp_ SysTick.h 文件用来存放SysTick 驱动程序 及相关 宏定义
  • 中断服务函数放在stm32f10x_it.c 文件中。

3.2.1 编程要点

  1. 设置重装载寄存器的值
  2. 清除当前数值寄存器的值
  3. 配置控制与状态寄存器

3.2.2 代码分析

SysTick 属于内核的外设

  • 有关的寄存器定义和库函数都在内核相关的库文件core_cm3.h 中

3.2.2.1 SysTick 配置库函数

  • 用固件库编程的时候我们只需要调用库函数SysTick_Config() 即可

    • 形参ticks 用来设置重装载寄存器的值
      • 最大不能超过重装载寄存器的值22^4
    • 当重装载寄存器的值递减到0 的时候产生中断
    • 然后重装载寄存器的值又重新装载往下递减计数
    • 以此循环往复
    • 紧随其后设置好中断优先级
    • 最后配置系统定时器的时钟等于AHBCLK=72M
    • 使能定时器和定时器中断
  • 这样系统定时器就配置好了,一个库函数搞定

  • SysTick_Config() 库函数主要配置了SysTick 中的三个寄存器

    • LOAD
    • VAL
    • CTRL
1 __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
2 {
3 		// 不可能的重装载值,超出范围
4 		if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {
5 				return (1UL);
6 		}
7
8 		// 设置重装载寄存器
9 		SysTick->LOAD = (uint32_t)(ticks - 1UL);
10
11 		// 设置中断优先级
12 		NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
13
14 		// 设置当前数值寄存器
15 		SysTick->VAL = 0UL;
16
17 		// 设置系统定时器的时钟源为AHBCLK=72M
18 		// 使能系统定时器中断
19 		// 使能定时器
20 		SysTick->CTRL = 	SysTick_CTRL_CLKSOURCE_Msk |
21	 						SysTick_CTRL_TICKINT_Msk |
22 							SysTick_CTRL_ENABLE_Msk;
23 		return (0UL);
24 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.2.2.2 配置SysTick中断优先级

  1. 在SysTick_Config() 库函数还调用了固件库函数NVIC_SetPriority()

    • 配置系统定时器的中断优先级
    • 该库函数也在core_m3.h 中定义
  2. 函数流程

    • 首先先判断形参IRQn 的大小
      • 如果是小于0,则表示这个是系统异常,系统异常的优先级由内核外设SCB 的寄存器SHPRx 控制
      • 如果大于0,则是外部中断,外部中断的优先级由内核外设NVIC 中的IPx 寄存器控制。
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
		if ((int32_t)IRQn < 0) {
				SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] =
				(uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
		} else {
				NVIC->IP[((uint32_t)(int32_t)IRQn)] =
				(uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
		}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 因为SysTick 属于内核外设,跟普通外设的中断优先级有些区别,并没有抢占优先级和子优先级的说法。
  • 在STM32F103 中,内核外设的中断优先级由内核SCB 这个外设的寄存器:SHPRx(x=1.2.3)来配置。
    • 有关SHPRx 寄存器的详细描述可参考《Cortex-M3 内核编程手册》4.4.8 章节。
  • 下面我们简单介绍下SHPRx 寄存器
    • SPRH1-SPRH3 是一个32 位的寄存器
      • 但是只能通过字节访问
      • 每8 个字段控制着一个内核外设的中断优先级的配置
    • 在STM32F103 中,只有位7:4 这高四位有效,低四位没有用到
      • 所以内核外设的中断优先级可编程为:0~15,只有16 个可编程优先级,数值越小,优先级越高
      • 如果软件优先级配置相同,那就根据他们在中断向量表里面的位置编号来决定优先级大小,编号越小,优先级越高
        在这里插入图片描述
  • 修改内核外设的优先级,只需要修改下面三个寄存器对应的某个字段即可
  • SHPR1
    在这里插入图片描述
  • SHPR2
    在这里插入图片描述
  • SHPR3
    在这里插入图片描述
  1. 系统定时器中,配置优先级为(1UL « __NVIC_PRIO_BITS) - 1UL),其中宏__NVIC_PRIO_BITS为4,那计算结果就等于15,可以看出系统定时器此时设置的优先级在内核外设中是最低的,如果要修改优先级则修改这个值即可,范围为:0~15。
// 设置系统定时器中断优先级
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
  • 1
  • 2
  • 外设在设置中断优先级的时候,首先要分组,然后设置抢占优先级和子优先级。
  • systick 这类内核的外设在配置的时候,只需要配置一个寄存器即可,取值范围为0~15
  • 如何区分两者的优先级
    • 如配置一个外设的中断优先级分组为2,抢占优先级为1,子优先级也为1
    • systick 的优先级为固件库默认配置的15
    • 当比较内核外设和片上外设的中断优先级的时候,只需要抓住NVIC 的中断优先级分组不仅对片上外设有效,同样对内核的外设也有效
    • 把systick 的优先级15 转换成二进制值就是1111(0b),又因为NVIC 的优先级分组2,那么前两位的11(0b) 就是3,后两位的11(0b) 也是3。无论从抢占还是子优先级都比我们设定的外设的优先级低
    • 如果当两个的软件优先级都配置成一样,那么就比较他们在中断向量表中的硬件编号,编号越小,优先级越高

3.2.2.3 SysTick 初始化函数

  1. SysTick 初始化函数
  • SysTick 初始化函数由用户编写
    • 里面调用了SysTick_Config() 这个固件库函数
    • 通过设置该固件库函数的形参
    • 决定系统定时器经过多少时间就产生一次中断
1 /**
2 * @brief 启动系统滴答定时器SysTick
3 * @param 无
4 * @retval 无
5 */
6 void SysTick_Init(void)
7 {
8 		/* SystemFrequency / 1000 1ms 中断一次
9 		* SystemFrequency / 100000 10us 中断一次
10 		* SystemFrequency / 1000000 1us 中断一次
11 		*/
12 		if (SysTick_Config(SystemCoreClock / 100000)) {
13 		/* Capture error */
14 		while (1);
15 		}
16 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. SysTick 中断时间的计算
  • SysTick 定时器的计数器是向下递减计数的
  • 计数一次的时间TDEC=1/CLKAHB
  • 当重装载寄存器中的值VALUELOAD 减到0 的时候,产生中断
  • 可知中断一次的时间TINT=VALUELOAD * TDEC=VALUELOAD/CLKAHB,其中CLKAHB =72MHZ
    • 如果设置VALUELOAD 为72,那中断一次的时间TINT=72/72M=1us
    • 不过1us 的中断没啥意义,整个程序的重心都花在进出中断上了,根本没有时间处理其他的任务
  • SysTick_Config()的形参我们配置为SystemCoreClock / 100000=72M/100000=720
    • 形参的值最终是写到重装载寄存器LOAD 中
    • 现在把SysTick 定时器中断一次的时间TINT=720/72M=10us
SysTick_Config(SystemCoreClock / 100000)
  • 1
  1. SysTick 定时时间的计算
    当设置好中断时间TINT 后,可以设置一个变量t,用来记录进入中断的次数,变量t 乘以中断的时间TINT 可以计算出需要定时的时间

  2. SysTick 定时函数

  • 定义一个微秒级别的延时函数,形参为nTime
  • 用这个形参乘以中断时间TINT 就得出我们需要的延时时间,其中TINT 我们已经设置好为10us
  • 函数Delay_us() 中我们等待TimingDelay 为0,当TimingDelay 为0 的时候表示延时时间到
  • 变量TimingDelay 在中断函数中递减,即SysTick 每进一次中断即10us 的时间TimingDelay 递减一次
/**
* @brief us 延时程序,10us 为一个单位
* @param
* @arg nTime: Delay_us( 1 ) 则实现的延时为1 * 10us = 10us
* @retval 无
*/
void Delay_us(__IO u32 nTime)
{
		TimingDelay = nTime;
		while (TimingDelay != 0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  1. SysTick 中断服务函数
void SysTick_Handler(void)
{
		TimingDelay_Decrement();
}
  • 1
  • 2
  • 3
  • 4

中断复位函数调用了另外一个函数TimingDelay_Decrement(),原型如下:

  • TimingDelay 的值等于延时函数中传进去的nTime 的值,比如nTime=100000,则延时的时间等于100000*10us=1s。
/**
* @brief 获取节拍程序
* @param 无
* @retval 无
* @attention 在SysTick 中断函数SysTick_Handler() 调用
*/
void TimingDelay_Decrement(void)
{
		if (TimingDelay != 0x00) {
				TimingDelay--;
		}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.2.2.4 主函数

主函数

  • 主函数中初始化了LED 和SysTick
  • 然后在一个while 循环中以1s 的频率让LED 闪烁。
int main(void)
{
		/* LED 端口初始化*/
		LED_GPIO_Config();
		/* 配置SysTick 为10us 中断一次, 时间到后触发定时中断,
		* 进入stm32fxx_it.c 文件的SysTick_Handler 处理,通过数中断次数计时
		*/
		SysTick_Init();
		while (1) {
				LED_ON;
				Delay_us(100000); // 10000 * 10us = 1000ms
				LED2_ON;
				Delay_us(100000); // 10000 * 10us = 1000ms
				LED3_ON;
				Delay_us(100000); // 10000 * 10us = 1000ms
		}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.2.2.5 更简洁的定时编程

  • systick 的counter 从reload 值往下递减到 0 的时候,CTRL 寄存器的位16:countflag 会置1,且读取该位的值可清0
  • 所以我们可以使用软件查询的方法来实现延时
  • 具体代码见代码如下

微秒级延时

1 void SysTick_Delay_Us( __IO uint32_t us)
2 {
3 		uint32_t i;
4 		SysTick_Config(SystemCoreClock/1000000);
5
6 		for (i=0; i<us; i++) {
7 		// 当计数器的值减小到0 的时候,CRTL 寄存器的位16 会置1
8 		while ( !((SysTick->CTRL)&(1<<16)) );
9 		}
10		// 关闭SysTick 定时器
11 		SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
12 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

毫秒级延时

1 void SysTick_Delay_Ms( __IO uint32_t ms)
2 {
3 		uint32_t i;
4 		SysTick_Config(SystemCoreClock/1000);
5
6 		for (i=0; i<ms; i++) {
7 		// 当计数器的值减小到0 的时候,CRTL 寄存器的位16 会置1
8 		// 当置1 时,读取该位会清0
9 		while ( !((SysTick->CTRL)&(1<<16)) );
10 		}
11 		// 关闭SysTick 定时器
12 		SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
13 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这两个微秒和毫秒级别的延时函数中,我们还是调用了SysTick_Config 这个固件库函数

  • 其中SystemCoreClock 是一个宏,大小为72000000,如果不想使用这个宏,也可以直接改成数字
1 // 这个固件库函数在core_cm3.h 中
2 static __INLINE uint32_t SysTick_Config(uint32_t ticks)
3 {
4 		// reload 寄存器为24bit,最大值为2^24
5 		if (ticks > SysTick_LOAD_RELOAD_Msk) return (1);
6
7 		// 配置reload 寄存器的初始值
8 		SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;
9
10 		// 配置中断优先级为1<<4 -1 = 15,优先级为最低
11 		NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
12
13 		// 配置counter 计数器的值
14 		SysTick->VAL = 0;
15
16 		// 配置systick 的时钟为72M
17 		// 使能中断
18 		// 使能systick
19 		SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
20 						SysTick_CTRL_TICKINT_Msk |
21 						SysTick_CTRL_ENABLE_Msk;
22 		return (0);
23 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/942252
推荐阅读
相关标签
  

闽ICP备14008679号