1

写在前面:

本文代码基于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个字节)功能
CTRL0xE000E010SysTick 控制和状态寄存器 
LOAD0xE000E014SysTick 自动重装载除值寄存器 
VAL0xE000E018SysTick 当前值寄存器 
CALIB0xE000E01CSysTick 校准值寄存器

4个寄存器简介

在uVision运行中的寄存器值举例

上图为程序运行时寄存器的实际状态

控制和状态寄存器CTRL

CTRL

默认情况下采用内核时钟(FCLK)

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

CTRL

重装载数值寄存器LOAD

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

LOAD

LOAD

当前值寄存器VAL

VAL

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对应毫秒级,\( \frac{1*16}{16000}=1ms \)

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

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

下面两张图为STM32CubeMx中Clock的设置部分:

image.png

image.png

中断

使用NVIC,需要做三件事:

  1. 首先为我们使用的中断源配置中断向量表
  2. 配置NVIC寄存器来启用NVIC中断和设置NVIC终端的优先级
  3. 使能中断:

    1. 与向量表里名称相对应的ISR(中断服务程序)
    void Systick_Handler(void){
    ...
    }
    1. 有了中断向量表配置和ISR原型定义,我们可以配置NVIC来处理Systick定时器中断

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

时钟源

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

该函数从未被调用

image.png

image.png

初始值、中断与控制

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

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

image.png

Systick应用

us(微秒级)延时

image.png

HAL_Delay

image.png

实例

法1:利用HAL_Delay()

main.c函数中增加下列代码(注意代码的书写位置严格按照注释,否则可能报错!)

image.png

image.png

stm32g4xx_it.c中,增加下列代码

image.png

image.png

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

image.png

image.png

法3:利用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 就是回调函数。
  • 回调函数可用于通知机制

image.png

image.png

法4(法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();
}

ysji
2 声望4 粉丝

在遍地六便士的街上,他抬头看见了月亮