STM32使用SysTick定时器延时

STM32使用SysTick定时器延时

最近在学习Stm32 HAL库相关教程(之前都是xjb缝合,缺少点原理的理解,在看到TM1640(一个可以方便在数码管上显示内容的模块)驱动编写的过程需要用到us 级别的延时,而HAL 库的 Hal_Dealy() 的函数是ms 级别的,所以需要自己手动编写一个更精准的延时函数。看教程里几行不知所云,在搜索过程中大概了解到了几种写发,但是感觉比较优雅的是使用 SysTick定时器 进行延时。但是几篇教程在原理处比较简略,所以根据个人理解学习了一下。

参考目录

《ARM Cortex-M3 权威指南》第3版

STM32延时函数的四种方法 - Fireflycjd - 博客园 (cnblogs.com)

STM32的SysTick延时方法 - 代码争霸 - 博客园 (cnblogs.com)

STM32学习心得九:Systick滴答定时器和延时函数解读_天亮继续睡的博客-CSDN博客

stm32的systick(滴答定时器)实现精准延时_haha690的博客-CSDN博客_stm32精准定时

STM32延时函数的四种方法:普通延时(2种)、SysTick 定时器延时(2种)_魏波-的博客-CSDN博客_delayms延时函数用法

关于STM32单片机延时微妙(delay_us)函数-hal库_好奇龙猫的博客-CSDN博客_delay_us

STM32延时函数的四种方法_strongercjd的博客-CSDN博客_stm32延时函数

STM32高精度延时实验 (stmicroelectronics.cn)

几种延时方法

第一种比较粗暴,就是让单片机做一些无关紧要的工作来打发时间,比如写点循环;

第二种是用汇编指令,和第一种大同小异;

第三种是使用SysTick滴答定时器,但是是采取 中断 的方式;

第四种是使用SysTick滴答定时器,但是是采取 查询 的方式,也就是 本次使用的方法

……(前三种可以在参考目录找到具体实现

SysTick 定时器

简介

详细资料可以阅读 《ARM Cortex-M3 权威指南》第3版 9.5 SysTick 定时器。

SysTick 定时器是 Cortex-M 处理器内部集成的一个小型定时器,属于NVIC 的一部分,可以产生SysTick 异常。SysTick 为简单的向下计数的24位计数器,可以使用处理器时钟或者外部时钟。

SysTick 定时器可用作简单的定时器外设,用以产生周期性中断,延时或时间测量。

SyTick 定时器的寄存器

地址 CMSIS-Core 符号 寄存器
0xE000E010 SysTick -> CTRL SysTick 控制和状态寄存器
0xE000E014 SysTick -> LOAD SysTick 重装值寄存器
0xE000E018 SysTick -> VAL SysTick 当前值寄存器
0xE000E01C SysTick -> CALIB SysTick 校准值寄存器

SysTick 控制和状态寄存器(SysTick -> CTRL

位段 名称 类型 复位值 描述
16 COUNTFLAG RO 0 当SYSTICK 定时器计数到0时,该位变成1,读取寄存器或清除计数器当前值会被清零
2 CLKSOURCE R/W 0 0 = 外部参考时钟(STCLK);
1 = 使用内核时钟
1 TOCKINT R/W 0 1 = SYSTICK 定时器计数减至0时产生异常
0 = 不产生异常
0 ENABLE R/W 0 SYSTICK 定时器使能

SysTick 重装值寄存器(SysTick -> LOAD

名称 类型 复位值 描述
23: 0 RELOAD R/W 未定义 定时器为0时的重装载值

SysTick 当前值寄存器(SysTick -> VAL

名称 类信 复位值 描述
23: 0 CURRENT R/Wc 0 读出值位SYSTICK 定时器的当前数值。写入任何值都会清除寄存器,SYSTICK 控制和状态寄存器中的 COUNTFLAG 也会清零

SysTick 校准值寄存器(SysTick -> CALIB

名称 类型 复位值 描述
31 NOREF R - 1 = 没有外部参考时钟(STCLK 不可用)
0 = 有外部参考时钟可供使用
30 SKEW R - 1= 校准值并非精准的 10ms
0 = 校准值准确
23: 0 TENMS R/W 0 10毫秒校准值。芯片设计者应通过 Cortex-M3 的输入信号提供该数值,若读出为0,则表示校准值不可用。

使用 SysTick 定时器

主要流程

本次使用查询的方式。

  1. 将0写入 SysTick -> CTRL 禁止 SysTick 定时器,防止之前 SysTick 定时器在之前被使能过;
  2. 将新的重加载值写入 SysTick -> LOAD ,重加载值应为周期数减1(因为是倒数到0);
  3. 将任何数值写入 SysTick 当前值寄存器 SysTick -> VAL ,该寄存器会被清零;
  4. 写入 SysTick 控制和状态寄存器 SysTick -> CTRL 启动寄存器

延时原理

利用SysTick 控制和状态寄存器 SysTick -> CTRL 中的 计数标志位 来确定定时器合适变为0.可以设置 SysTick -> LOAD 的值,然后等待计数标志位变为0,以此实现延时。

示例代码

//禁止 SysTick 定时器
SysTick -> CTRL = 0;

//设置计数周期为256 (255~0)
SysTick -> LOAD = 0XFF;

// 清零当前值寄存器
SysTick -> VAL = 0;

// 5 = b'101
// 位0 = 1 为使能SYSTICK定时器
// 位2 = 1 表示使用内核时钟
SysTick -> CTRL = 5;

// 0x00010000 = 1<<16
// 通过查询 SysTick -> CTRL 第16位标志位判断计数器是否计数到0
while((SysTick -> CTRL & 0x00010000) == 0);

//禁止 SysTick 定时器
SysTick -> CTRL = 0;

进行 us 级别延时

关于时钟

本次选用外部时钟。

对于STM32,外部时钟源是HCLK(AHB总线时钟)的1/8,内核时钟是HCLK时钟

)可以翻出来 CubeMX 的时钟树看一眼

以f103 为例,这里HCLK 为72MHz,那么SYSTICK 的时钟为9MHz,即 SYSTICK定时器以9MHz的频率递减

计算

如果要获取 Nus的延迟,那么我们需要计算出 SysTick -> LOAD ,即重加载值

容易得到:

$1s = 10^6us$

$$Nus=T\times LOAD=\frac{LOAD}{SYSTICK}\times 10^6$$

$$LOAD=Nus\times SysTick \times 10^{-6}$$

已知有:

$HCLK=72MHz$

$SYSTICK=9MHz$

代入具体数值可以得到:

$LOAD=Nus\times9\times 10^6\times 10^{-6}$

最后得到

$$LOAD=Nus\times9$$

72M主频代码

)博客里面xjb找了一段

us级延时
/*采用SysTick定时器位16标志位读取方式做延时(程序起始处)*/

void delay_us(uint32_t nus) //us级延时
{
    uint32_t temp;
    SysTick->CTRL=0x00;
    SysTick->LOAD=nus*9;
    SysTick->VAL=0x00;//清空计数器
    SysTick->CTRL=0x01;//使能,减到零时无动作,采用外部时钟源
    // 当计数器的值减小到 0 的时候, CRTL 寄存器的位 16 会置 1
    // 当置 1 时,读取该位会清 0
    do{
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));	// 等待时间到达
    SysTick->CTRL=0x00;//关闭计数器
    SysTick->Val=0x00;//清空计数器
}

/*采用SysTick定时器位16标志位读取方式做延时(程序结束处)*/
ms级延时

循环1000次即可

void delay_ms(uint16_t nms) {
    uint32_t i;
    for(i=0;i<1000;++i) {
        delay_us(nms);
    }
}

更通用一点的代码

上面那个 SysTick->LOAD=nus*9; 的9是手动算出来的,一般情况下主频是很容易知道的,那直接用主频自动算出这个数字就更方便了

再次回到上面的式子

记系统时钟 $SYSCLK \ Mhz$

$SYSTICK=SYSCLK\div9 \ Mhz$

$$Nus=T\times LOAD=\frac{LOAD}{SYSTICK\times10^6}\times 10^6=\frac{LOAD}{SYSTICK}$$

$$LOAD=Nus\times SYSTICK$$

这样可以在代码中定义一个延时倍乘数 fac_us

$fac_\ us=SYSCLK\div8$

代码

代码分为两步

  1. 配置 SYSTICKSYSCLK 的$\frac{1}{8}$ ,并计算出延时倍乘数 fac_us
  2. 采用SysTick定时器位16标志位读取方式做延时

还要记得在头文件包含 stm32f1xx.h (以f103为例)

dealy.c

static uint8_t  fac_us=0;//us延时倍乘数

// 初始化延迟函数
// SYSTICK的时钟固定为AHB时钟的1/8
// SYSCLK:系统时钟频率
void delay_init(uint8_t SYSCLK) {
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8);//配置SYSTICK 
	fac_us=SYSCLK/8;
}

//采用SysTick定时器位16标志位读取方式做延时(程序起始处)
//us级延时
void delay_us(uint32_t nus) {
    uint32_t temp;
    SysTick->CTRL=0x00;
    SysTick->LOAD=nus*fac_us;
    SysTick->VAL=0x00;//清空计数器
    SysTick->CTRL=0x01;//使能,减到零时无动作,采用外部时钟源
    // 当计数器的值减小到 0 的时候, CRTL 寄存器的位 16 会置 1
    // 当置 1 时,读取该位会清 0
    do{
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));	// 等待时间到达
    SysTick->CTRL=0x00;//关闭计数器
    SysTick->VAL=0x00;//清空计数器
}
//采用SysTick定时器位16标志位读取方式做延时(程序结束处)
//ms级延时
void delay_ms(uint16_t nms) {
    uint32_t i;
    for(i=0;i<1000;++i) {
        delay_us(nms);
    }
}

delay.h

#include "stm32f1xx.h"
void delay_init(uint8_t SYSCLK);
void delay_us(uint32_t nus);
void delay_ms(uint16_t nms);

函数 HAL_SYSTICK_CLKSourceConfig()

/**
  * @brief  Configures the SysTick clock source.
  * @param  CLKSource specifies the SysTick clock source.
  *          This parameter can be one of the following values:
  *             @arg SYSTICK_CLKSOURCE_HCLK_DIV8: AHB clock divided by 8 selected as SysTick clock source.
  *             @arg SYSTICK_CLKSOURCE_HCLK: AHB clock selected as SysTick clock source.
  * @retval None
  */
void HAL_SYSTICK_CLKSourceConfig(uint32_t CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(CLKSource));
  if (CLKSource == SYSTICK_CLKSOURCE_HCLK)
  {
    SysTick->CTRL |= SYSTICK_CLKSOURCE_HCLK;
  }
  else
  {
    SysTick->CTRL &= ~SYSTICK_CLKSOURCE_HCLK;
  }
}

总结

最后看下来就是 设置重装载值,使能,检查标志位 这三个步骤。

还有就是书上讲的是比较清楚详细的。