文章目录
-
- [SysTick 和 TIM](#SysTick 和 TIM)
-
-
- [1. SysTick 系统嘀嗒](#1. SysTick 系统嘀嗒)
-
- [1.1 SysTick 系统嘀嗒概述](#1.1 SysTick 系统嘀嗒概述)
- [1.2 SysTick 核心作用](#1.2 SysTick 核心作用)
- [1.3 定时器的基本实现图例](#1.3 定时器的基本实现图例)
- [1.4 SysTick 相关寄存器内容](#1.4 SysTick 相关寄存器内容)
-
- [1.4.1 SysTick重载值寄存器 (SYST_RVR)](#1.4.1 SysTick重载值寄存器 (SYST_RVR))
- [1.4.2 SysTick重载值寄存器 (SYST_RVR)](#1.4.2 SysTick重载值寄存器 (SYST_RVR))
- [1.4.3 SysTick当前值寄存器 (SYST_CVR)](#1.4.3 SysTick当前值寄存器 (SYST_CVR))
- [1.5 系统内核针对于 SysTick_Config 操作函数](#1.5 系统内核针对于 SysTick_Config 操作函数)
- [1.6 精准延时控制函数](#1.6 精准延时控制函数)
-
- [1.6.1 SysTick_Handler 函数注释](#1.6.1 SysTick_Handler 函数注释)
- [1.6.2 代码实现](#1.6.2 代码实现)
- [2. 基础定时器 TIM6 和 TIM7](#2. 基础定时器 TIM6 和 TIM7)
-
- [2.1 基础定时器概述](#2.1 基础定时器概述)
- [2.2 基础定时器开发流程](#2.2 基础定时器开发流程)
-
- [2.2.1 开发流程概述](#2.2.1 开发流程概述)
- [2.2.2 TIMx_PSC 预分频倍数寄存器](#2.2.2 TIMx_PSC 预分频倍数寄存器)
- [2.2.3 TIMx_ARR 自动重装载寄存器](#2.2.3 TIMx_ARR 自动重装载寄存器)
- [2.2.4 TIMx_CR1控制寄存器](#2.2.4 TIMx_CR1控制寄存器)
- [2.2.5 TIMx_DIER DMA/中断使能寄存器](#2.2.5 TIMx_DIER DMA/中断使能寄存器)
- [2.2.6 TIMx_SR 定时器状态寄存器](#2.2.6 TIMx_SR 定时器状态寄存器)
- [2.2.7 TIM6 定时器案例](#2.2.7 TIM6 定时器案例)
- [3. 通用定时器](#3. 通用定时器)
-
- [3.1 通用定时器概述](#3.1 通用定时器概述)
- [3.2 通用定时器框图分析](#3.2 通用定时器框图分析)
- [3.3 通用定时器基本功能](#3.3 通用定时器基本功能)
- [3.4 PWM 实现呼吸灯](#3.4 PWM 实现呼吸灯)
-
- [3.4.1 PWM 概述](#3.4.1 PWM 概述)
- [3.4.2 AFIO 控制定时器对应输出通道重映射](#3.4.2 AFIO 控制定时器对应输出通道重映射)
- [3.4.3 TIM3CH2 寄存器分析](#3.4.3 TIM3CH2 寄存器分析)
- [3.4.5 核心代码实现](#3.4.5 核心代码实现)
- [4. 作业](#4. 作业)
-
- [4.1 作业要求](#4.1 作业要求)
- [4.2 SG90 舵机相关参数](#4.2 SG90 舵机相关参数)
- [4.3 相关寄存器分析](#4.3 相关寄存器分析)
- [4.4 高阶实现](#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 操作函数
c
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 代码实现
c
#include "systick.h"
u16 SysTick_Count = 0;
void SysTick_Delay_us(u32 us)
{
u32 temp;
/*
1us 对应的 reload_value 为 72 - 1 ==> 71
5us 对应 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;
// 这是当前计数器初始化为 0
SysTick->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 ==> 71999
5ms 对应 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;
// 这是当前计数器初始化为 0
SysTick->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 循环控制当前的总延时时间,当前延时函数单位对应 1ms
while 控制当前 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 定时器案例
c
#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
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 一致
c
#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
c
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. 对应输出模式 PWM1
2. 随时可以修改 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
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 = 72
ARR = 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 舵机旋转减速