系列文章目录

Github开源地址
从头开始写STM32F103C8T6驱动库(一)——STM32CubeMX创建并调整工程结构
从头开始写STM32F103C8T6驱动库(二)——编写系统初始化程序,配置时钟树
从头开始写STM32F103C8T6驱动库(三)——编写GPIO驱动
从头开始写STM32F103C8T6驱动库(四)——编写延时函数,详解Systick



前言

上一章我们编写了GPIO驱动函数,但是如果我们想使用GPIO来实现一个流水灯的话还需要一个延时函数,今天我们就来写一下使用Systick实现的系统延时函数


1.Systick定时器介绍

Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的Cortex‐M3芯片都带有这个定时
器,软件在不同 CM3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,
CM3上的自由运行时钟),或者是外部时钟( CM3处理器上的STCLK信号)。 Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

2.Systick定时器使用方法

在这里插入图片描述
Systick定时器的使用方法也十分简单,稍微介绍一下就能理解,首先Systick定时器共有四个寄存器:

  1. SysTick控制及状态寄存器(SysTick->CTRL):该寄存器负责控制Systick定时器的相关功能
  2. SysTick重装载数值寄存器(SysTick->LOAD):定时器所要计数的值
  3. SysTick当前数值寄存器(SysTick->VAL):定时器当前的数值
  4. SysTick校准数值寄存器(SysTick->CALIB):定时器校准相关的寄存器

(1)所以想要使用SysTick定时器实现延时函数就是先选择一个时钟源,这里我们使用默认的外部时钟源(此外部时钟非彼外部时钟,是Systick定时器外的时钟并非单片机芯片的外部时钟)
在这里插入图片描述

该定时器的时钟源可以是内部时钟(FCLK,
CM3上的自由运行时钟),或者是外部时钟( CM3处理器上的STCLK信号)。不过, STCLK的
具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视
芯片的器件手册来决定选择什么作为时钟源。
——————————————————————————————节选自Cortex‐M3 权威指南

而STM32上所指的STCLK是

RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick
控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。
——————————————————————————————节选自STM32F10xxx参考手册
在这里插入图片描述

(2)第二步载入定时器所要计数的值
(3)第三步清空定时器当前的数值
(4)第四步使能定时器
(5)第五步循环等待SysTick控制及状态寄存器(SysTick->CTRL)字段16置1

3.编写微秒延时程序(delay_us)

和第三章同样的方法我们新建delay.c和delay.h文件并添加到工程当中,这里就不再赘述了,详细教程可见第三章:
从头开始写STM32F103C8T6驱动库(三)——编写GPIO驱动

  1. 编写函数名

首先我们需要了解一下计时器所能计的最大值是多少,这涉及到我们函数声明时传入参数的数据类型
在这里插入图片描述
从上图可以看出SysTick重装载数值寄存器所能写入的最大值为2^24因为没有"uint24"这个数据类型所以我们需要用uint32类型来声明传入参数,但是输入范围必须限制在 0 ~ 2^24

void delay_us(uint32 us)
  1. 对输入参数做单边限幅
us = (1<<25) <= us ? (1<<25) - 1 : us;
  1. 载入定时器所要计数的值
    现在有一个问题就是我们需要向重装载数值寄存器写入多少才是我们想要的时间呢?
    回顾刚才我们所说的,我们使用Systick默认的外部时钟作为时钟源,而在STM32中RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。
    这里还需要回顾一下第二章我们所配置的时钟树,详情可见:
    从头开始写STM32F103C8T6驱动库(二)——编写系统初始化程序,配置时钟树
    在这里插入图片描述
    在第二章中我们配置的AHB时钟频率为72MHz所以经过8分频后也就是Systick的时钟频率为9MHz。
    也就是说当前Systick计一个数需要1/9,000,000秒。
    所以:
    LOAD(载入数值)=t(计时时间,单位:秒) x T(频率,单位:Hz)

我们现在common.h文件中将各个外设桥频率宏定义,方便我们后期更改

//系统各外设桥主频
#define 		AHB_freq			72000000UL
#define 		APB1_freq			36000000UL
#define 		APB2_freq			72000000UL

数字后面字母的含义
U:unsigned
L:long / double
F:float

也就是表示无符号长整型的意思。

然后再在delay_us函数中编写

SysTick->LOAD = us * (AHB_freq / 8000000);
  1. 清空当前数值寄存器的值
SysTick->VAL = 0;
  1. 使能定时器
SysTick->CTRL |= 0x01<<SysTick_CTRL_ENABLE_Pos;
  1. 循环等待计数值为0
while(!(SysTick->CTRL & SysTick_CTRL_CLKSOURCE_Msk));
  1. 关闭定时器
SysTick->CTRL &= ~(0x01<<SysTick_CTRL_ENABLE_Pos);
  1. 完整代码如下:
/**
	* @name		delay_us
  * @brief  微秒延时函数
	* @param  us	延时微秒数   0 ~ 2^24
	* @return void
	* @Sample delay_us(2)
  */
void delay_us(uint32 us)
{
	us = (1<<25) <= us ? (1<<25) - 1 : us;
	//LOAD(载入数值)= t(计时时间,单位:秒) x T(频率,单位:Hz)
	SysTick->LOAD = us * (AHB_freq / 8000000);
	SysTick->VAL = 0;
	SysTick->CTRL = 0x01;
	while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
	SysTick->CTRL &= ~(0x01<<SysTick_CTRL_ENABLE_Pos);
}

4.编写毫秒延时程序(delay_ms)

ms延时要注意计数器数值范围问题,我们可以算一下最大计时时间
2^24 = 16,777,216 / 9,000,000Hz = 1.864135s
也就是说毫秒延时最大为1864ms
具体方法和微妙延时大同小异,代码如下:

/**
	* @name		delay_ms
  * @brief  毫秒延时函数
	* @param  ms	毫秒微秒数   0 ~ 1864
	* @return void
	* @Sample delay_ms(100)
  */
void delay_ms(uint16 ms)
{
	ms = 1864 <= ms ? 1864 : ms;
	SysTick->LOAD = ms * (AHB_freq / 8000);
	SysTick->VAL = 0;
	SysTick->CTRL = 0x01;
	while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
	SysTick->CTRL &= ~(0x01<<SysTick_CTRL_ENABLE_Pos);
}

5.编写测试程序

我们来简单写一个闪灯程序,发现小灯是可以已10Hz频率在闪烁,说明我们的延时数运行正确。

int main(void)
{
	gpio_init(PC13, GPO, 0, GPO_PUSH_PULL, GPIO_SPEED_50MHZ);
 	while (1)
  	{
		delay_ms(100);
		gpio_reverse(PC13);
  	}
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐