写在前面:
本文代码基于STM32G431Rx开发板,G4系列基本通用

本文主要内容:

  • Systick计时器的特点
  • 相关的寄存器
  • 代码中主要调用的库函数
  • 软件运行配置
  • 自定义延迟时长的4种代码实现

Systick定时器简介

Systick定时器是一个简单的定时器,对于ST的CM3,CM4,CM7内核芯片,都有Systick定时器(没有差别)

特点

  • 常用来做延时,或者实时系统的心跳时钟(心跳时钟用在实时操作系统中进行调度)

    • 可以节省MCU资源,不用浪费一个定时器

    • 比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟

  • Systick定时器就是系统滴答定时器,一个24位的倒计数定时器

    • 计到0时,将从RELOAD寄存器(初始值存储在这个寄存器中,默认值为2^24)中自动重装载定时初值

    • 只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作

  • SysTick定时器被捆绑在NVIC(是一个嵌入式的中断向量控制寄存器)中,用于产生SYSTICK异常(异常号:15,异常级别最低,异常号越小中断优先级越高)

    • 优先级可以被设置

    • 单片机系统分为前台和后台:前台平时不做事,只有有任务的时候被后台唤醒

相关寄存器

冯诺依曼结构中,寄存器地址编号同时包含了RAM和ROM的地址

寄存器 地址(相差4个字节) 功能
CTRL 0xE000E010 SysTick 控制和状态寄存器
LOAD 0xE000E014 SysTick 自动重装载除值寄存器
VAL 0xE000E018 SysTick 当前值寄存器
CALIB 0xE000E01C SysTick 校准值寄存器

在这里插入图片描述
在这里插入图片描述
上图为程序运行时寄存器的实际状态

控制和状态寄存器CTRL

在这里插入图片描述
默认情况下采用内核时钟(FCLK)

调用HAL_SYSTICK_CLKSourceConfig()函数,传递参数为1——采用内核时钟(传递参数为0则采用外部时钟源)

在这里插入图片描述

重装载数值寄存器LOAD

32位寄存器,但只用了24位

在这里插入图片描述
在这里插入图片描述

当前值寄存器VAL

在这里插入图片描述
在这里插入图片描述

Systick库函数

HAL库中的Systick相关函数

  1. stm32fGxx_hal_cortex.h文件中
    HAL_SYSTICK_CLKSourceConfig ()用于Systick时钟源选择(需要用户自己调用)

  2. core_cm4.h文件中
    SysTick_Config (uint32_t ticks)初始化Systick时钟为HCLK,并开启中断(传递参数为滴答时钟的计数值,按照毫秒计数一次)

  3. void HAL_IncTick(void):在滴答定时器中断里面被调用,全局变量uwTick每毫秒加1

  4. void HAL_Delay(uint32_t Delay):阻塞式延迟,默认单位是ms

  5. uint32_t HAL_GetTick(void):用于获取全局变量uwTick当前的计数

  6. uint32_t HAL_GetTickPrio(void):获取滴答时钟优先级

  7. HAL_StatusTypeDef HAL_SetTickFreq(uint32_t Freq):设置中断频率

  8. uint32_t HAL_GetTickFreq(void):获取时钟中断频率

  9. void HAL_SuspendTick(void):挂起滴答定时器

  10. void HAL_ResumeTick(void):恢复滴答定时器

Systick中断服务函数

  1. void SysTick_Handler (void);
    每毫秒响一次(默认情况下滴答时钟的中断服务号为15,也就是Systick时钟的优先级为15)

配置

STM32CubeMx: Clock

MHz对应微秒级,KHz对应毫秒级

1∗1616000=1ms\frac{1*16}{16000}=1ms16000116=1ms

系统刚复位的时候,即外部时钟没有生效时,要先使用内部时钟(HSI RC,16MHz),此时系统时钟SYSCLK为16MHz(16000000Hz = 16000KHz)

外部时钟稳定后(即不震荡)才能被使用

在这里插入图片描述
在这里插入图片描述

中断

使用NVIC,需要做三件事:

  1. 首先为我们使用的中断源配置中断向量表

  2. 配置NVIC寄存器来启用NVIC中断和设置NVIC终端的优先级

  3. 使能中断:

  4. 与向量表里名称相对应的ISR(中断服务程序)

void Systick_Handler(void){
...
}
  1. 有了中断向量表配置和ISR原型定义,我们可以配置NVIC来处理Systick定时器中断

总结:通常情况下,需要做两件事——设置中断的优先级,然后使能中断源。NVIC寄存器位于系统控制空间

时钟源

HAL_SYSTICK_CLKSourceConfig函数(在stm32g4xx_hal_cortex.c头文件中)

该函数从未被调用

在这里插入图片描述
在这里插入图片描述

初始值、中断与控制

SysTick_Config函数(在core_cm4.h头文件中——放到头文件里作为内联函数使用)

在该函数中改变了CTRL寄存器的值

在这里插入图片描述

Systick应用

us(微秒级)延时

在这里插入图片描述

HAL_Delay

在这里插入图片描述

实例

法1:利用HAL_Delay()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

法2:利用SysTick_Handler ()中断服务

在这里插入图片描述
在这里插入图片描述

函数指针

  • 每个函数都占用一段内存单元,他们有一个入口地址(起始地址)

  • 在C语言中,函数名代表函数的入口地址

  • 可以定义一个指针变量,接收函数的入口地址,让他指向函数,这就是指向函数的指针,也称为函数指针

  • 通过函数指针可以调用函数,它也可以作为函数的参数

定义
  • 函数指针定义的格式为:类型名(*变量名)(参数类型表);

    • 类型名指定函数返回值的类型,变量名时指向函数的指针变量的名称
  • 例如:int (*funptr) (int, int);

    • 定义一个函数指针funptr,它可以指向有两个整型参数且返回值类型为int的函数
调用函数
  • 通过函数指针调用函数的一般格式为:(*函数指针名)(参数表)

  • 例如:

int fun (int x, int y);
int (*funptr) (int, int); 
funptr = fun;
(*funptr) (3, 5); // 变量的内容发生改变
//(*funptr)是一个整体,指向函数fun
回调函数
  • 回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数

    **来自Stack Overflow某位大神简洁明了的表述:**A “callback” is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。

  • 回调函数可用于通知机制

法3:利用SysTick_Handler()中断服务和回调函数

在这里插入图片描述
在这里插入图片描述

法4:利用SysTick_Handler ()中断服务和回调函数(法3简化版)

在这里插入图片描述
在这里插入图片描述
代码实现:

variable.h头文件中,添加下列结构体声明

typedef struct SystickHandler{
	uint32_t u32Counter;
	uint32_t u32Period;
	void (*pInit)(void);
	void (*pCallBack)(void);
}stSystickHandler;

main.c中的Private variables部分,添加下列代码,实现对结构体内变量的初始化

void LedFlash(void);

// 初始化结构体内的变量
stSystickHandler sSystickHandler = {
    .u32Counter = 0, // 计数器
    .u32Period = 1000, // 设置周期为1000,即1s闪烁一次
    .pCallBack = LedFlash, // 回调函数
};
	
// 回调函数
void LedFlash(void){
    // 当计数器的值大于周期时,LED闪烁一次,并将计数器清零
    if(sSystickHandler.u32Counter > sSystickHandler.u32Period - 1){
        HAL_GPIO_TogglePin(TickLED_GPIO_Port, TickLED_Pin);
        sSystickHandler.u32Counter = 0;
    }
    // 计数器+1,不闪烁
    else{
        sSystickHandler.u32Counter ++;
    }
}

stm32g4xx_it.c中的External variables部分,添加下列外部变量声明

extern stSystickHandler sSystickHandler;

stm32g4xx_it.c中的void SysTick_Handler(void)函数中,添加下列代码,调用回调函数

if(sSystickHandler.pCallBack){
    sSystickHandler.pCallBack();
}

实验结果:开发板对应的LED灯每秒闪烁一次,通过修改.u32Period的值可以更改闪烁的频率

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐