一、Systick
1. Systick系统滴答
1.1 SysTick 系统嘀嗒概述
- SysTick 是 ARM Cortex - M 内核芯片内部设计的系统定时器。
- 内置一个 24 位递减计数器,最大数值范围是 0x0 ~ 0xFFFFF 。
- SysTick 响应速度很快,基本可以认为 SysTick 单次计数周期时间是当前 MCU 的 1 / 频率,例如 72MHz STM32F103,单次时间为 13.8888ns,从而提供标准的定时器时间标准。
1.2 SysTick 核心作用
- 为操作系统 OS 提供心跳时钟,精准时间参考,操作系统有 RTT (RT_Thread)、FreeRTOS、Hi3861 中 lite - OS 。
- 实现代码的精准延时,相较于_nop () 操作,计时精度更高 。
- 作为同样 TIM 定时器的参考依据 。
1.3****定时器的基本实现图例

1.4 SysTick****相关寄存器内容
1.4.1 SysTick重载值寄存器(SYST_RVR)


1.4.2 SysTick重载值寄存器(SYST_RVR)

1.4.3 SysTick当前值寄存器(SYST_CVR)


1.5系统内核针对于SysTick_Config****操作函数
cpp
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
// 检查重载值是否超过 24 位计数器的最大值(0x00FFFFFF)
if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
// 设置 SysTick 定时器的重载值(自动重装载寄存器),并做掩码处理后减 1(因计数器从 reload 值递减到 0 触发中断,实际计数次数是 reload + 1 次 )
SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */
// 配置 SysTick 中断的优先级,这里将优先级设为最低(数值越大优先级越低,(1 << __NVIC_PRIO_BITS) - 1 通常是最大可设置值 )
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 定时器:
// - SysTick_CTRL_CLKSOURCE_Msk:选择时钟源(一般可选内核时钟或外部参考时钟,这里选内核时钟 )
// - SysTick_CTRL_TICKINT_Msk:使能 SysTick 中断,计数到 0 时触发中断
// - SysTick_CTRL_ENABLE_Msk:使能 SysTick 定时器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
// 配置成功,返回 0
return (0); /* Function successful */
}

1.6****精准延时控制函数
1.6.1 SysTick_Handler****函数注释

1.6.2****代码实现
systick.h:
cpp
#ifndef __SYSTICK_H
#define __SYSTICK_H
#include "stm32f10x.h"
// 声明系统时钟频率,需根据实际配置修改(通常为72000000即72MHz)
#define SYS_CLK_FREQ 72000000
extern u16 SysTick_Count;
// 函数声明
void SysTick_Init(void);
void SysTick_Delay_us(u32 us);
void SysTick_Delay_ms(u32 ms);
void SysTick_Handler(void);
#endif
systick.c:
cpp
#include "systick.h"
// 全局计数变量,用于记录SysTick中断次数
u16 SysTick_Count = 0;
/**
* @brief 微秒级延时函数
* @param us: 延时的微秒数
* @retval 无
*/
void SysTick_Delay_us(u32 us)
{
u32 temp;
// 计算1us对应的重装载值 (系统时钟频率/1000000 - 1)
u32 reload_value = SYS_CLK_FREQ / 1000 / 1000 - 1;
// 检查是否超过24位计数器的最大值(0xFFFFFF)
if (reload_value > SysTick_LOAD_RELOAD_Msk)
{
reload_value = SysTick_LOAD_RELOAD_Msk;
}
// 设置重装载值
SysTick->LOAD = reload_value;
// 清零当前计数器
SysTick->VAL = 0;
// 配置SysTick: 内核时钟源 + 使能中断 + 使能定时器
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
// 循环等待指定的微秒数
for (u32 i = 0; i < us; i++)
{
// 等待COUNTFLAG标志置位(计数器到0)
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
}
// 读取CTRL寄存器清除COUNTFLAG标志
temp = SysTick->CTRL;
// 关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
/**
* @brief 毫秒级延时函数
* @param ms: 延时的毫秒数
* @retval 无
*/
void SysTick_Delay_ms(u32 ms)
{
u32 temp;
// 计算1ms对应的重装载值 (系统时钟频率/1000 - 1)
u32 reload_value = SYS_CLK_FREQ / 1000 - 1;
// 检查是否超过24位计数器的最大值(0xFFFFFF)
if (reload_value > SysTick_LOAD_RELOAD_Msk)
{
reload_value = SysTick_LOAD_RELOAD_Msk;
}
// 设置重装载值
SysTick->LOAD = reload_value;
// 清零当前计数器
SysTick->VAL = 0;
// 配置SysTick: 内核时钟源 + 使能中断 + 使能定时器
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
// 循环等待指定的毫秒数
for (u32 i = 0; i < ms; i++)
{
// 等待COUNTFLAG标志置位(计数器到0)
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
}
// 读取CTRL寄存器清除COUNTFLAG标志
temp = SysTick->CTRL;
// 关闭SysTick定时器
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}
/**
* @brief SysTick中断服务函数
* @retval 无
*/
void SysTick_Handler(void)
{
// 每次中断触发,计数加1
SysTick_Count += 1;
}
/**
* @brief 初始化SysTick定时器
* @retval 无
*/
void SysTick_Init(void)
{
// 1. 清除所有配置,关闭SysTick
SysTick->CTRL = 0;
// 2. 设置时钟源为内核时钟
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
// 3. 使能SysTick中断
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
// 4. 初始化计数器和重装载值
SysTick->VAL = 0;
SysTick->LOAD = SysTick_LOAD_RELOAD_Msk - 1; // 设置最大重装载值
// 设置SysTick中断优先级为最高
NVIC_SetPriority(SysTick_IRQn, 0);
// 开启SysTick定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
二、基础定时器TIM6的TIM7
2.1****基础定时器概述
TIM6 和 TIM7 定时器的主要功能包括:
- 16 位自动重装载累加计数器
- 16 位可编程(可实时修改)预分频器,用于对输入的时钟按系数为 1 ~ 65536 之间的任意数值分频
- 触发 DAC 的同步电路
- 在更新事件(计数器溢出)时产生中断 / DMA 请求
2.2****基础定时器开发流程
2.2.1****开发流程概述
- 定时器时钟使能,案例使用 TIM6
- 需要配置 PSC 预分频器数据
- 自动重装载寄存器数据
- 计数器累加或者递减规则
- 注册 IRQn 和实现对应的 TIM6_IRQHandler 中断函数
2.2.2 TIMx_PSC****预分频倍数寄存器
2.2.3 TIMx_ARR****自动重装载寄存器

2.2.4 TIMx_CR1****控制寄存器

**2.2.5 TIMx_DIER DMA/**中断使能寄存器

2.2.6 TIMx_SR****定时器状态寄存器

3.代码案例
tim6.c:
cpp
#include "tim6.h"
void TIM6_Init(u16 psc, u16 arr)
{
// 1. 定时器 TIM6 时钟使能
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
/*
2. 【定时器关闭】避免之前定时器任务的冲突
因为定时器开启状态下,无法对 PSC 和 ARR 进行有效修改
*/
TIM6->CR1 &= ~(0x01);
/*
3. PSC 预分配寄存器和 ARR 自动重装载寄存器配置
寄存器存储数据是真实期望数据 - 1
假设 PSC 预分配寄存器配置倍数为 1,
需要提供给 PSC 寄存器数据为 1 - 1 ==> 0
假设 ARR 自动重装载寄存器位 7200
需要提供给 PSC 寄存器数据为 7200 - 1 ==> 7199
【注意】
PSC 和 ARR 都是 16 位寄存器,对应数据有效范围是 1 ~ 65536
*/
TIM6->PSC = psc - 1;
TIM6->ARR = arr - 1;
/*
4. 中断使能控制
TIM6->DIER 对应 UIE [位0] 控制为 1
*/
TIM6->DIER |= 0x01;
/*
5. 定时器开启
*/
TIM6->CR1 |= 0x01;
/*
6. 利用 NVIC 注册中断内容
6.1 利用 EnableIRQ 注册对应的中断请求编号
6.2 给予当前 TIM6 定时器中断优先级配置
*/
NVIC_EnableIRQ(TIM6_IRQn);
NVIC_SetPriority(TIM6_IRQn, 5);
}
u16 TIM6_IT_Count = 0;
void TIM6_IRQHandler(void)
{
/*
根据 TIM6 状态寄存器 SR 判断当前中断是否触发
*/
if (TIM6->SR & 0x01)
{
// 清除中断标志位
TIM6->SR = 0;
if (TIM6_IT_Count % 2)
{
Led0_Ctrl(1);
Led1_Ctrl(0);
}
else
{
Led0_Ctrl(0);
Led1_Ctrl(1);
}
TIM6_IT_Count += 1;
}
}
tim6.h:
cpp
#ifndef _TIM6_H
#define _TIM6_H
#include "stm32f10x.h"
#include "led.h"
/**
* @brief TIM6 定时器初始化,要求提供的参数对应
* 预分配器倍数和对应的自动重装载值
*
* @param psc 预分配器倍数
* @param arr 自动重装载值 (Auto-Reload Register)
*/
void TIM6_Init(u16 psc, u16 arr);
#endif
main.c:
cpp
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "beep.h"
#include "usart1.h"
#include "adc.h"
#include "systick.h"
#include "tim6.h"
int main(void)
{
Led_Init();
Beep_Init();
NVIC_SetPriorityGrouping(2);
/*
PSC 7200 ==> 100 us
ARR 10000
根据当前 MCU 的时钟 72 MHz 情况下,对应的时间为 1 s
【注意】
PSC 和 ARR 数据都不可以超过 65536
*/
TIM6_Init(7200, 10000);
while (1)
{
Beep_Alarm();
}
}