(总结)STM32中系统嘀嗒定时器(Systick)的原理和应用
!!!鸣谢轩哥的笔记!!!
一,系统滴答定时器(Systick)
1,基本概念
2,基本应用
3,时钟分析
如果使用Systick定时器进行定时,则需要弄清楚Systick定时器的时钟频率,而定时器是挂载在总线下的,不同的总线的频率是不同的,而总线的频率又是由时钟来提供的,而时钟的提供者又各不相同,所以必须要了解各时钟源的区别
3.1 HSI 高速内部时钟
3.2 HSE 高速外部时钟
(笔试题常考)
3.3 LSE 低速外部时钟
(笔试题常考)
3.4 LEI 低速内部时钟
3.5 PLL 倍频锁相环
注意:
- 通过阅读ST公司提供的函数库的帮助手册,可以知道ST公司默认使用的高速外部时钟的频率是25MHZ,并且PLL_M = 25,PLL_N = 336,PLL_P = 2, 芯片主频 = HSE / M * N / P = 168M
- PLL可以理解为是一个加工器,负责放大或者缩小频率,其本身是没有时钟频率的
4,时钟树分析
4.1 LSI 低速内部时钟(常用于看门狗)
4.2 LSE 低速外部时钟(常用于RTC实时时钟,如日历)
4.3 HSI 高速内部时钟(低功耗时使用,一般不使用)
4.4 HSE 高速外部时钟
巧记:M(8) N(336) P(2)
———————————————————————————————————————————
注意:如果使用的是STM32F4xx系列,需要修改倍频锁相环参数
开发板默认使用的HSE时钟源的频率是8MHZ,为了能够计算出168MHZ的芯片主频,所以需要修改PLL_M的值,所以需要修改system_stm32f4xx.c文件,并且还需要修改stm32f4xx.h头文件中HSE_VALUE宏定义的值。
这两个位置修改完成后,PLL倍频锁相环的参数才是正确的,所以计算出来的频率就是正确的。
———————————————————————————————————————————
5,时钟选择
通过M3内核文档可以知道Systick定时器有2个时钟源,一个是内部时钟(FCLK,指的是MCU的自由运行时钟,就是168MHZ),一个是外部时钟(STCLK,目前STM32F407ZET6这颗MCU是把AHB总线的时钟频率进行8分频,就是168MHZ /8 = 21MHZ)
5.1 内部时钟
如果选择使用内部时钟(168MHZ)作为系统嘀嗒定时器的时钟源,则嘀嗒定时器的计数周期:1000000us生成168000000个脉冲,意味着 1us可以生成168个脉冲,所以计数周期等于1/168us。
5.2 外部时钟
如果选择使用外部时钟(21MHZ)作为系统嘀嗒定时器的时钟源,则嘀嗒定时器的计数周期:1000000us生成21000000个脉冲,意味着 1us可以生成21个脉冲,所以计数周期等于1/21us。
二,Systick的控制方式
因为Systick 是内嵌在NVIC中的,所以ST公司并没有编写对应的示例代码,所以需要参考内核文档(Cortex M3/M4权威指南)
三,如何使用Systick定时器
1,中断定时方式
(注意必须采用168MH作为时钟源)
代码实现:
/*
******************************************************************************
* @file main.c
* @author
* @version V1.0
* @date 2024/09/25
* @brief 学习内核中的系统滴答定时器的定时方式,本案例采用的是Systick的中断
方式实现延时,采用该方式必须使用168MHZ作为Systick的时钟源,该方式
可以实现周期性的延时,会周期性的触发Systick异常
******************************************************************************
*/
#include "stm32f4xx.h" //必须包含
/* Private typedef 用于记录用户自定义的一些数据类型的别名-------------------*/
/* Private define 用于记录用户自定义的类型,比如结构体、共用体、枚举-------*/
/* Private macro 用于记录用户自定义的宏定义-------------------------------*/
/* Private variables 用于记录用户自定义的全局变量-----------------------------*/
volatile int Cnt = 0;
/* Private function prototypes 用于记录用户自定义的函数声明-------------------*/
/* Private functions 用于记录用户自定义的函数原型-----------------------------*/
/**
* @brief LED的初始化
* @param None
* @retval None
*/
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//打开外设的时钟 LED --- PF9
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//配置引脚的参数
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//初始化GPIO端口
GPIO_Init(GPIOF, &GPIO_InitStructure);
}
void delay(uint32_t nms)
{
Cnt = nms; //把要延时的时间放入变量
while(Cnt != 0);
}
/**
* @brief 程序的入口
* @param None
* @retval None
*/
int main(void)
{
//硬件的初始化
LED_Init();
SysTick_Config(168*1000); //中断的触发频率是1KHZ
while (1)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
delay(1000); //延时单位是ms
}
}
//Systick中断服务函数
void SysTick_Handler(void)
{
if(Cnt != 0)
{
Cnt--;
}
}
/********************** (C) COPYRIGHT Your Name xxxx@126.com***END OF FILE****/
注意:
2,非中断定时方式
代码实现:
/**
* @brief 延时微秒
* @param
@nus :待延时的微秒 注意:不能超过798915us
* @retval None
*/
void delay_us(uint32_t nus)
{
SysTick->CTRL = 0; // 关闭定时器
SysTick->LOAD = nus * 21 - 1; // 设置重载值 nus * 21 - 1
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = 1; // 打开定时器并且使用参考时钟 168MHZ/8 = 21MHZ
while ((SysTick->CTRL & 0x00010000)==0);// 等待计数值递减到0
SysTick->CTRL = 0; // 关闭定时器
}
/**
* @brief 延时毫秒
* @param
@nms :待延时的毫秒
* @retval None
*/
void delay_ms(uint32_t nms)
{
while(nms--)
{
SysTick->CTRL = 0; // 关闭定时器
SysTick->LOAD = 21*1000 - 1; // 设置重载值 nus * 21 - 1
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = 1; // 打开定时器并且使用参考时钟 168MHZ/8 = 21MHZ
while ((SysTick->CTRL & 0x00010000)==0);// 等待计数值递减到0
SysTick->CTRL = 0; // 关闭定时器
}
}
/**
* @brief 程序的入口
* @param None
* @retval None
*/
int main(void)
{
//硬件的初始化
LED_Init();
while (1)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);
delay_ms(1000); //每隔1s,LED的电平翻转一次
}
}
步骤总结:
- 关闭定时器
- 设置重载值
- 清空当前值
- 打开定时器
- 等待计数完成
- 关闭定时器
作者:100 Miles485