STM32学习(MCU控制)(SysTick and TIM)
文章目录
- SysTick 和 TIM
- 1. SysTick 系统嘀嗒
- 1.1 SysTick 系统嘀嗒概述
- 1.2 SysTick 核心作用
- 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 操作函数
- 1.6 精准延时控制函数
- 1.6.1 SysTick_Handler 函数注释
- 1.6.2 代码实现
- 2. 基础定时器 TIM6 和 TIM7
- 2.1 基础定时器概述
- 2.2 基础定时器开发流程
- 2.2.1 开发流程概述
- 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 定时器状态寄存器
- 2.2.7 TIM6 定时器案例
- 3. 通用定时器
- 3.1 通用定时器概述
- 3.2 通用定时器框图分析
- 3.3 通用定时器基本功能
- 3.4 PWM 实现呼吸灯
- 3.4.1 PWM 概述
- 3.4.2 AFIO 控制定时器对应输出通道重映射
- 3.4.3 TIM3CH2 寄存器分析
- 3.4.5 核心代码实现
- 4. 作业
- 4.1 作业要求
- 4.2 SG90 舵机相关参数
- 4.3 相关寄存器分析
- 4.4 高阶实现
SysTick 和 TIM
1. SysTick 系统嘀嗒
1.1 SysTick 系统嘀嗒概述
- SysTick 是 ARM Cortex-M 内核芯片内部设计的系统定时器。
- 内置一个 24 位递减计数器,最大数值多少 0x0 ~ 0xFF FFFF
- 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)

| 位域 | 名称 | 功能描述 |
|---|---|---|
| 16 | COUNTFLAG | 计数标志位 (只读) • 当计数器从1递减到0时,该位会被硬件自动置1。 • 读取该寄存器后,该位会自动清零。也可以通过向 SYST_CVR 寄存器写入任何值来清零。 |
| 2 | CLKSOURCE | 时钟源选择位 (读写) • 0: 使用外部参考时钟(AHB/8,具体取决于芯片设计)。 • 1: 使用处理器内核时钟(AHB,即 HCLK)。 注意:通常为了获得更精确的定时,会选择内核时钟( CLKSOURCE=1)。 |
| 1 | TICKINT | 中断使能位 (读写) • 0: 计数器减到0时不产生SysTick异常(中断)请求。 • 1: 计数器减到0时产生SysTick异常(中断)请求(异常号15)。注意:如果使用SysTick做操作系统心跳,此位必须置1。如果仅用于查询式延时,则可以置0。 |
| 0 | ENABLE | 计数器使能位 (读写) • 0: 关闭SysTick计数器。 • 1: 开启SysTick计数器。 注意:开启后,计数器立即从 SYST_RVR 装载值并开始递减。 |
1.4.2 SysTick重载值寄存器 (SYST_RVR)

| 位域 | 名称 | 功能描述 |
|---|---|---|
| 23:0 | RELOAD | 重载值 (读写) • 当计数器减到0时,下一次时钟到来时, SYST_CVR 的值会被自动重载为此值,并继续递减。 • 写入一个新值到该寄存器后,它不会立即影响当前的计数周期。当前周期结束后,新的值才会被加载。 • 计算公式: 定时时间 = (RELOAD + 1) * (1 / CLK频率) • Example: HCLK=72MHz, 要产生1ms中断,则 RELOAD = (72,000,000 / 1000) - 1 = 71999。 |
1.4.3 SysTick当前值寄存器 (SYST_CVR)

| 位域 | 名称 | 功能描述 |
|---|---|---|
| 23:0 | CURRENT | 当前计数值 (读写) • 读操作:返回当前计数器的瞬时值。 • 写操作:向该寄存器写入任何值,都会将计数器清0,同时清除 SYST_CSR 中的 COUNTFLAG 状态位。写入操作不会触发重载。 注意:在调试时读取此寄存器可以知道还剩多少计数。在初始化时,通常通过写入此寄存器来将其清零,以确保第一个定时周期的准确性。 |
1.5 系统内核针对于 SysTick_Config 操作函数
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */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->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */return (0); /* Function successful */
}

1.6 精准延时控制函数
1.6.1 SysTick_Handler 函数注释
请注意保存!!!

1.6.2 代码实现
#include "systick.h"u16 SysTick_Count = 0;void SysTick_Delay_us(u32 us)
{u32 temp;/*1us 对应的 reload_value 为 72 - 1 ==> 715us 对应 72 * 5 - 1 = 360 - 1 = 359*/u32 reload_value = SYS_CLK_FREQ / 1000 / 1000 - 1;/*判断是否超出最大重装载值*/if (reload_value > SysTick_LOAD_RELOAD_Msk){reload_value = SysTick_LOAD_RELOAD_Msk;}// 修改 SysTick 对应的重装载值SysTick->LOAD = reload_value;// 这是当前计数器初始化为 0SysTick->VAL = 0;SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk |SysTick_CTRL_ENABLE_Msk;/*利用 SysTick->CTRL 寄存器中 COUNTFLAG 计数器标志位如果当前计数器到 0, COUNTFLAG 标志灯对应 1如果当前计数器未到 0, COUNTFLAG 标志灯对应 0 */for (u32 i = 0; i < us; i++) {while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));}/*需要对应当前 SysTick->CTRL 寄存器中 COUNTFLAG 标志位进行清除 0 操作。*/temp = SysTick->CTRL;/*当前系统嘀嗒延时任务已经完成,关闭当前 SysTick*/SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;}void SysTick_Delay_ms(u32 ms)
{u32 temp;/*1ms 对应的 reload_value 为 72000 - 1 ==> 719995ms 对应 72000 * 5 - 1 = 360000 - 1 = 359999*/u32 reload_value = SYS_CLK_FREQ / 1000 - 1;/*判断是否超出最大重装载值*/if (reload_value > SysTick_LOAD_RELOAD_Msk){reload_value = SysTick_LOAD_RELOAD_Msk;}// 修改 SysTick 对应的重装载值SysTick->LOAD = reload_value;// 这是当前计数器初始化为 0SysTick->VAL = 0;SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk |SysTick_CTRL_ENABLE_Msk;/*利用 SysTick->CTRL 寄存器中 COUNTFLAG 计数器标志位如果当前计数器到 0, COUNTFLAG 标志灯对应 1如果当前计数器未到 0, COUNTFLAG 标志灯对应 0for 循环控制当前的总延时时间,当前延时函数单位对应 1mswhile 控制当前 SysTick 定时器重复工作*/for (u32 i = 0; i < ms; i++) {while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));}/*需要对应当前 SysTick->CTRL 寄存器中 COUNTFLAG 标志位进行清除 0 操作。*/temp = SysTick->CTRL;/*当前系统嘀嗒延时任务已经完成,关闭当前 SysTick*/SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}/*
以上延时函数都有对应的中断触发,中断是当前 SysTick 完成定时任务之后
触发中断内容,在当前中断中,完成变量累加操作。
*/
void SysTick_Handler(void)
{/*系统嘀嗒之外完成一次任务,SysTick_Count += 1系统嘀嗒计数器到 0 SysTick_Count += 1*/SysTick_Count += 1;
}void SysTick_Init(void)
{// 1. 清理整了个 SysTick->CTRL 所有相关配置// 关闭当前 SysTick 系统嘀嗒SysTick->CTRL = 0;// 2. 设置当前 SysTick 对应的时钟源为内部使用SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;// 3. 设置当前 SysTick 对应的 Tick Interrupt 开启SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;// 4. 对当前计数器和对应重载数据进行初始化赋值// 分别 VAL = 0 LOAD 为当前重加载最大值。SysTick->VAL = 0;// SysTick_LOAD_RELOAD_Msk ==> 0xFFFFFFul SysTick->LOAD = SysTick_LOAD_RELOAD_Msk - 1;/*SysTick_IRQn 对应的优先级是最高优先级*/NVIC_SetPriority(SysTick_IRQn, 0);/*开启 SysTick*/SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
2. 基础定时器 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 定时器状态寄存器

2.2.7 TIM6 定时器案例
#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;}}
main.c
#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();}}
3. 通用定时器
3.1 通用定时器概述

3.2 通用定时器框图分析
通用定时器核心模块

通用定时器TIM比较捕获输出控制框图

通用定时器输入端口概述

通用定时器在目前的 STM32F103ZET6,对应 TIM2 ~ TIM5,每一个定时器对应 4 通道(Channel),可以进入输入捕获,也可以进行输出控制。
3.3 通用定时器基本功能
操作流程和基础定时器 TIM6 和 TIM7 一致
#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;}
}
3.4 PWM 实现呼吸灯
3.4.1 PWM 概述
- PWM 核心参数
- 频率 : 1 秒钟,PWM 输出波形的次数。需要根据当前设备场景进行分析。
- 占空比:一个 PWM 周期内对应的高电平时间/整个 PWM 周期时间。
- 需要利用 TIM2 ~ 5 通用定时器完成 PWM 输出

3.4.2 AFIO 控制定时器对应输出通道重映射
- 主要针对不同的 TIM 定时对外输出通道引脚映射关系。

- TIM3 定时器通道对应的复用引脚关系

3.4.3 TIM3CH2 寄存器分析
TIM3 CCMR 输入输出模式控制寄存器

TIM3 CCER 控制捕获比较使能极性寄存器


TIMx_CCR2 捕获比较寄存器数据寄存器

3.4.5 核心代码实现
TIM3_CH2_PB5_PWM 和 TIM3_CH2_PB5_CCR
void TIM3_CH2_PB5_PWM(u16 psc, u16 arr)
{/*1. 时钟使能1.1 TIM3 使能1.2 AFIO 使能,因为需要控制 TIM3_CH2 通道的重映射操作需要 AFIO 进行配合1.3 PB5 使能*/RCC->APB2ENR |= (RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPBEN);RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;/*【关闭当前定时器】防止之前存在定时器任务无法修改 PSC 和 ARR 数据*/TIM3->CR1 = 0;/*2. PB5 设置工作模式为 GPIO 复用推挽输出模式,速度为 50 MHz【复用推挽输出模式】因为当前 GPIO 的输出模式收到外设影响同时需要满足可以提供高低电平的模式,选择复用推挽输出模式*/GPIOB->CRL &= ~(0x0F << 20);GPIOB->CRL |= (0x0B << 20);/*3. AFIO IO复用功能开启,同时设置 TIM3_CH2 重映像需要设置 TIM3 部分重映射,设置完成 CH2 ==> PB5 引脚对应 DS0 LED 灯*/AFIO->MAPR &= ~(0x03 << 10);AFIO->MAPR |= 0x02 << 10;/*4. 定时器基础功能控制PSC 预分配倍数ARR 自动重装载寄存器*/TIM3->PSC = psc - 1;TIM3->ARR = arr - 1;/*5. TIM3_CH2 配置为对外输出,要求1. 对应输出模式 PWM12. 随时可以修改 CCR2 数据内容*/TIM3->CCMR1 &= ~(0xFF << 8);TIM3->CCMR1 |= (0x64 << 8);/*6. 当前通过 PWM 控制 DS0 LED对应计数器是向下规则CNT > CCR2 输出低电平,文档中对应 无效电平CNT < CCR2 输出高电平,文档中对应 有效电平设置有效电平为高电平*/TIM3->CCER &= ~(0x03 << 4);TIM3->CCER |= (0x01 << 4);/*7. 开启 TIM3 定时器*/TIM3->CR1 |= 0x01;
}void TIM3_CH2_PB5_CCR(u16 ccr)
{TIM3->CCR2 = ccr;
}
main.c
#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"
#include "tim3.h"int main(void)
{Led_Init();Beep_Init();// Led1_Ctrl(1);/*PSC = 72ARR = 1000当前定时器执行任务周期为 1 ms*/TIM3_CH2_PB5_PWM(72, 1000);u16 duty_value = 0;u8 flag = 0;while (1){TIM3_CH2_PB5_CCR(duty_value);if (flag){duty_value -= 10;if (duty_value <= 0){flag = 0;}}else{duty_value += 10;if (duty_value >= 1000){flag = 1;}}SysTick_Delay_ms(10);}}
4. 作业
4.1 作业要求
- 实现定时器输出 PWM 控制舵机
- 利用按键 KEY_2 KEY_1 KEY_0
- KEY_2 实现顺时针旋转
- KEY_1 实现舵机停止
- KEY_0 实现逆时针旋转
- 定时器可以选择 TIM2 TIM3 TIM4 TIM5 注意接线和供电
4.2 SG90 舵机相关参数
- 要求 PWM 对应频率是 50 Hz
- 占空比 7.5 % 舵机通知状态
- 占空比 2.5 % ~ 7.5% 顺时针转动
- 占空比 7.5% ~ 12.5% 逆时针转动
4.3 相关寄存器分析
根据手册选择 TIM2_CH4 通道,在未重映像情况下对应已经是 PA3
PA3 工作模式要求为 GPIO 复用推挽模式
PWM 需求是 50 Hz ,对应的时间周期为 20 ms
- PSC => 7200 - 1 ==> 计数器累加/递减一次时间周期是 100 us
- ARR ==> 200 - 1 ==> 重装载计数器对应的 199 的情况下,每一个 TIM 定时器周期周期控制在 20 ms
TIM2_CH4 端口选择输出,对应的输出模式选择 PWM1 或者 PMW2 工作模式。
定时器中,控制占空比的比较寄存器数据可以随时修改,从而控制舵机工作。
根据 ARR 数据分析, ARR数据为 200 - 1
占空比 比较寄存器数据范围 CCR4 7.5% 15 2.5% ~ 7.5% 5 ~ 15 7.5% ~ 12.5% 15 ~ 25
4.4 高阶实现
- 按键 KEY_UP 用于切换当前舵机的旋转方向 + 停止
- KEY_2 舵机旋转加速
- KEY_1 舵机旋转停止旋转
- KEY_0 舵机旋转减速
