Wiki:知识&笔记 Wiki:知识&笔记
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
  • 基础

  • 底层&寄存器

    • 寄存器操作基础
    • 时钟配置
    • 滴答定时器
      • 什么是滴答定时器
      • HAL库的配置
        • 初始化与设定频率
        • 设定函数
      • 正点原子的delay.c
      • delay_init
      • delay_us
    • GPIO工作原理
    • GPIO位带操作
  • HAL库外设

  • STM32
  • 底层&寄存器
2020-11-18
什么是滴答定时器
HAL库的配置
初始化与设定频率
设定函数
正点原子的delay.c
delay_init
delay_us

滴答定时器

# 系统滴答定时器

# 什么是滴答定时器

SysTick 是一个 24 位定时器,属于 Cortex内核中的一个外设,类似 NVIC。用于提供时间基准,多为操作系统所使用,常用于对时间要求严格的情况。SysTick 的输入源可以是 HCLK 或者 HCLK 分频8倍(好像这是一个bug,时钟源仅仅只是 HCLK 而已)

由于是 24 位,所以滴答定时器每次最多可以提供 个时钟脉冲,这里我们称这些滴答定时器提供的脉冲为“节拍”。这个脉冲计数值保存在当前计数值寄存器 STK_VAL(SysTick current value register) 中,只能向下计数,也就是倒计数。每接收到一个 HCLK 脉冲,STK_VAL 的值就会向下减 1,当减到 0 时,硬件会自动将重装载寄存器 STK_LOAD(可以设定,跟 STK_VAL 初始值相等) 中保存的数值加载到 STK_VAL,使其重新计数。并且,系统滴答定时器就产生一次中断,以此循环往复,只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息

# HAL库的配置

# 初始化与设定频率

HAL 库对于滴答定时器的处理与更新其实非常巧妙,具体怎样巧妙请接着看:

在系统初始化函数 HAL_Init() 中,系统在时钟为初始化前调用 HAL_InitTick() 首先初始化了滴答定时器,令其为 1ms 产生一个中断,且设置中断优先级为最高

而在系统时钟初始化后,也就是在 HAL_RCC_ClockConfig() 的最后,重新用新的时钟参数初始化了滴答定时器,令其中断产生的频率始终维持在 1ms 产生一次

在使用函数 HAL_SetTickFreq() 对滴答定时器中断频率进行修改时也是通过更改 HAL_InitTick() 中使用的全局变量 uwTickFreq 后再次执行 HAL_InitTick() 实现的

# 设定函数

前面提到的系统初始化与时钟初始化后调用的函数 HAL_InitTick() 就是初始化滴答定时器的函数,它能够使中断频率始终维持在 1ms 一次,我们来看一下它的定义:

/**
  * @brief This function configures the source of the time base.
  *        The time source is configured  to have 1ms time base with a dedicated
  *        Tick interrupt priority.
  * @note This function is called  automatically at the beginning of program after
  *       reset by HAL_Init() or at any time when clock is reconfigured  by HAL_RCC_ClockConfig().
  * @note In the default implementation, SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals.
  *       Care must be taken if HAL_Delay() is called from a peripheral ISR process,
  *       The SysTick interrupt must have higher priority (numerically lower)
  *       than the peripheral interrupt. Otherwise the caller ISR process will be blocked.
  *       The function is declared as __weak  to be overwritten  in case of other
  *       implementation  in user file.
  * @param TickPriority Tick interrupt priority.
  * @retval HAL status
  */
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  /* Configure the SysTick to have interrupt in 1ms time basis*/
  if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
  {
    return HAL_ERROR;
  }

  /* Configure the SysTick IRQ priority */
  if (TickPriority < (1UL << __NVIC_PRIO_BITS))
  {
    HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
    uwTickPrio = TickPriority;
  }
  else
  {
    return HAL_ERROR;
  }

  /* Return function status */
  return HAL_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

第一个 if 语句的参数中的函数 HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) 就是关键所在,其中的参数 SystemCoreClock 是关键中的关键,我们来看一下库对其定义:

/* This variable is updated in three ways:
      1) by calling CMSIS function SystemCoreClockUpdate()
      2) by calling HAL API function HAL_RCC_GetHCLKFreq()
      3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency 
         Note: If you use this function to configure the system clock; then there
               is no need to call the 2 first functions listed above, since SystemCoreClock
               variable is updated automatically.
*/
uint32_t SystemCoreClock = 16000000;
1
2
3
4
5
6
7
8
9

在注释中写到的三种出发条件其中一种发生时,这个变量的值将会被更新,而每次我们更新时钟频率后,均会触发条件 3 致使这个值更新,更新后的值一定为最新的 HCLK 时钟频率,这个更新函数 SystemCoreClockUpdate() 定义在 system_stm32f1xx.c 中

而函数 HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) 设定了滴答定时器两次中断之间的节拍数,也就相当于 STK_LOAD 寄存器中的参数。其中 uwTickFreq 默认值为 1 (代表 1KHz,为了配合 uwTickFreq 算出中断频率设为 1)

例如 HCLK 频率为 72MHz,SystemCoreClock 括号内的值计算结果为 也就是 1 秒内 HCLK 发送了 个脉冲给滴答定时器,滴答定时器清零 1000 次,产生了 1000 个中断,即每 1ms 产生一次中断

# 正点原子的delay.c

目前对于此文件研究较浅(由于还没有在开发中应用操作系统)

# delay_init

这个函数 SYSTICK 参数是系统时钟的频率 nMHz 中的数值 n,没有支持系统时有效语句只有:

HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); //SysTick频率为 HCLK
fac_us = SYSCLK;    //不论是否使用 OS,fac_us都需要使用
1
2

第一个语句就是将滴答定时器的时钟源设置为 HCLK,但是其实默认也就是 HCLK

第二个语句中:fac_us 这个参数实际上是在后面用到的乘以需要的延迟的微秒数的乘数,最终才会得到延迟相应的微秒数所需要的节拍数

# delay_us

这是一个采用了时钟摘取法的微秒级的延时函数,实际上思路就使刚刚提到的使用滴答定时器产生节拍的频率数 fac_us 乘以需要延迟的微秒数 tick 得出一共需要的节拍数。函数内的计数器向下计数,技术结束后结束。

为什么 呢?可能有这个疑问,这是因为 fac_us 的值就是前面提到的 SYSTICK,它由于是 nMHz 中的 n 所以可以视为一个“微秒频率 “。

这样就达到了毫秒级的延迟!

编辑 (opens new window)
上次更新: 2020/11/19, 10:11:00
时钟配置
GPIO工作原理

← 时钟配置 GPIO工作原理→

Theme by Vdoing | Copyright © 2020-2025 Jack :) | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式