STM32_06_Systick定时器

发布于:2025-09-15 ⋅ 阅读:(27) ⋅ 点赞:(0)

Systick定时器又称滴答定时器。

延时时间

一样的代码在不同的机器它的延时时间也会有轻微的区别,因此我们要通过Systick定时器来获取精确的延时。


Systick定时器

原理就是数数,假设一个周期=10ms,我们设个初始值y,然后从y自减到0,我们计算用了20个时钟周期,那这个时候我们所需的时间是20x10=200ms=0.2s,这样我们就能设置一个确定的时间了,初始值y就是200ms,用这个时间我们就能延时。 进入while()循环等待,y的值减至0,这个循环的时间就是200ms。

  • 如上图,如果我们初始值是 72
  • 000 时钟周期,那我们的延时时间就是1ms。
  • 再举个例子:如果初始值是 72 0000 时钟周期,那它的延时时间是?
    ------ 10ms
  • 思路:初始值 -> 时钟周期 -> 时间
               时间 -> 时钟周期 -> 配置初始值
  • 滴答定时器实际上是9MHz
    1s = 9 000 000 时钟周期           1ms = 9 000 时钟周期        1us = 9 时钟周期
    如果延时时间是30ms,时钟周期是 9000 * 30 = 27 0000,初始值就是270000

思考:

  1. 怎么去设置初始值?
    看第二个寄存器(LOAD),比如你的初始值想设置为 9 0000,那我们就把 9 0000 放到这个LOAD寄存器里就可以了。
  2. 你怎么知道你那个初始值最终是减到0了?
    判断这个寄存器的第16位。【16】= 1(减到0了);【16】 = 1(还没减到0)。

[0]: 一旦开启定时器,初始值就开始自减;一旦关闭定时器,初始值即停止自减。

LOAD寄存器:比如你的初始值想设置为 9 0000,那我们就把 9 0000 放到这个LOAD寄存器里就可以了。

VAL寄存器:假设LOAD寄存器当中放的是 9 0000,一旦打开定时器,我们就把这个 9 0000 加载到VAL寄存器当中,然后每个时钟周期-1.

当减到0的时候,CTRL【16】= 1,即等待 / 延时的时间就到期了。


Systick寄存器结构体

思考:为什么上面三个寄存器叫CTRL,LOAD,VAL呢?


初始化计数次数


微秒延时


毫秒延时


实战演练-掌控需求

代码示例:

systick.h:

// systick.h
#ifndef __SYSTICK_H__
#define __SYSTICK_H__

#include "stm32f10x.h"
extern void Systick_Init(void);
extern void delay_us(u32 n);
extern void delay_ms(u32 n);


#endif

systick.c:

// systick.c
#include "systick.h"

static u32 fac_us = 0;
static u32 fac_ms = 0;
//
// 定时器初始化
//
void Systick_Init(void){
	// 1.配置定时器时钟为9MHz
	// CTRL【2】 = 0
	SysTick->CTRL &= ~(1 << 2);
	// 2.1us计数周期
	fac_us = 9;
	// 3.1us计数周期
	fac_ms = 9000;
}
//
// 延时n微秒
//
void delay_us(u32 n){
	u32 temp;
	// 1.设置初始值
	SysTick->LOAD = n * fac_us;
	// 2.将VAL清空
	SysTick->VAL = 0;
	// 3.启动定时器
	SysTick->CTRL |= (1 << 0);
	// 启动了定时器,初始值会加载到VAL中,每个时钟周期减1
	// 直到0
	// 循环等待,直到初始值自减到0
	// 判断CTRL【16】
	// [16] = 1,时间到期,结束循环
	// xxxxxxxxxxxxxxxxxxxxxxxx	CTRL
	// 100000000000000000000000	& (1 << 16)
	// x00000000000000000000000
	do{
		temp = SysTick->CTRL;
	}while((temp & 0x01) && (!(temp & (1 << 16))));
	// 结束循环 - 时间到期
	// 关闭定时器
	SysTick->CTRL &= ~(1 << 0);
	SysTick->VAL = 0;
}

//
// 延时n毫秒
//
void delay_ms(u32 n){
	u32 temp;
	// 1.设置初始值
	SysTick->LOAD = n * fac_ms;
	// 2.将VAL清空
	SysTick->VAL = 0;
	// 3.启动定时器
	SysTick->CTRL |= (1 << 0);
		// 启动了定时器,初始值会加载到VAL中,每个时钟周期减1
	// 直到0
	// 循环等待,直到初始值自减到0
	// 判断CTRL【16】
	// [16] = 1,时间到期,结束循环
	// xxxxxxxxxxxxxxxxxxxxxxxx	CTRL
	// 100000000000000000000000	& (1 << 16)
	// x00000000000000000000000
	do{
		temp = SysTick->CTRL;
	}while((temp & 0x01) && (!(temp & (1 << 16))));
	// 结束循环 - 时间到期
	// 关闭定时器
	SysTick->CTRL &= ~(1 << 0);
	SysTick->VAL = 0;
}
}

main.c:

// main.c
#include "stm32f10x.h" 
#include "led.h"
#include "beep.h"
#include "system.h"
#include "systick.h"

// 时钟树配置
// 参数1:div	参数2:pllm
void RCC_HSE_Config(u32 div, u32 pllm){
	// 1.设置为默认值
	RCC_DeInit();
	// 2.打开HSE时钟
	RCC_HSEConfig(RCC_HSE_ON);
	// 3.等待HSE起振
	if(RCC_WaitForHSEStartUp() == SUCCESS){
		// 4.配置AHB  不分频
		RCC_HCLKConfig(RCC_SYSCLK_Div1);
		// 5.配置APB1 2分频
		RCC_PCLK1Config(RCC_HCLK_Div2);
		// 6.配置APB2 不分频
		RCC_PCLK2Config(RCC_HCLK_Div1);
		// 7.配置PLL时钟源:div 倍频系数:pllm
		//RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
		RCC_PLLConfig(div,pllm);
		// 8.使能PLL
		RCC_PLLCmd(ENABLE);
		// 9.循环判断PLL是否生效
		while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
		// 10.配置SYSCLK的时钟源为PLL
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
	}
}

// 位带只是对输入/输出做优化,和初始化无关
int main(void){
	// 配置时钟树
	// pll时钟源:HSE 倍频系数:9 PLLCLK=SYSCLK=72MHz
	RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
	
	// pll时钟源:HSE/2 倍频系数:9 PLLCLK=SYSCLK=36MHz
	//RCC_HSE_Config(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);
	
	// pll时钟源:HSE 倍频系数:9 PLLCLK=SYSCLK=128MHz
	// 长时间超频是不行的,系统不稳定
	//RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_16);
	
	// LED初始化
	LED_Init();
	// BEEP初始化
	BEEP_Init();
	// Systick初始化
	Systick_Init();
	
	while(1){
		// 循环开关灯 + 蜂鸣器
		LED0 = !LED0;// 1 - 0 - 1
		LED1 = !LED1;
		BEEP = !BEEP;
		// 延时1000ms
		delay_ms(1000);
	}
}  

实验现象见视频。