STM32之Systick与基础定时器

一、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();
	}
	
}

https://github.com/0voice

相关推荐
易享电子1 小时前
基于单片机步进电机控制电机正反转加减速系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·51单片机·proteus·智能家居
易享电子2 小时前
基于单片机空调温度控制测温ds18b20系统Proteus仿真(含全部资料)
单片机·嵌入式硬件·51单片机·proteus·智能家居
点灯小铭2 小时前
基于51单片机汽车自动照明灯超声波光敏远近光灯设计
单片机·嵌入式硬件·汽车·毕业设计·51单片机·课程设计
j_xxx404_3 小时前
数据结构:单链表的应用(力扣算法题)第一章
c语言·数据结构·算法·leetcode
小刘爱玩单片机3 小时前
【stm32简单外设篇】-4×4 薄膜键盘
stm32·单片机·计算机外设
小白嘎嘎4 小时前
ESP8266:Arduino学习
单片机·嵌入式硬件
姜太小白6 小时前
【VSCode】VSCode为Java C/S项目添加图形用户界面
java·c语言·vscode
爱睡觉的王宇昊7 小时前
GD32和STM32的区别在哪里?
stm32·单片机·嵌入式硬件
代码总长两年半9 小时前
STM32物联网项目---ESP8266微信小程序结合OneNET平台MQTT实现STM32单片机远程智能控制---AT固件篇(二)
stm32·单片机·物联网