STM32F1xx -- Systick 系统滴答定时器

1. SysTick 是一个向 CPU 提供定时中断信号的计数器,其计数速率是由 Cortex-M 系列处理器的系统时钟频率和 SysTick 计数器的重载值共同决定的。

1.1 Systick 时钟来源之一,Systick 一般设置为1ms 中断一次,为系统任务调度提供服务,24-bit reload寄存器,只能向下计数 Download

STM32F1xx -- Systick 系统滴答定时器_第1张图片

2. 查阅Cortex-M3/M4权威指南手册第138 页,表8.9 ,Systick的三个主要寄存器介绍,没列出来的bit位是保留位 reserve

STM32F1xx -- Systick 系统滴答定时器_第2张图片

3. Systick的地址、定义

core_cm3.h

/*
 * CMSIS_CM3_SysTick
 * memory mapped structure for SysTick
 */
typedef struct
{
  __IO uint32_t CTRL;                         /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;                         /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;                          /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;                        /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

#define SCS_BASE            (0xE000E000)                              /*!< System Control Space Base Address */
#define SysTick_BASE        (SCS_BASE +  0x0010)                      /*!< SysTick Base Address              */
#define SysTick             ((SysTick_Type *)       SysTick_BASE)     /*!< SysTick configuration struct      */

4. Systick 初始化,1ms一个Systick 中断

static u8  fac_us = 0; // us延时倍乘数, 即1us数了多少个数 (1us对应多少个时钟周期)
static u16 fac_ms = 0; // ms延时倍乘数, 在FreeRTOS下, 代表每个节拍的ms数, 即:1ms数了多少个数 (1ms 对应多少个时钟周期)

/*
 * System Clock Source Config
 */
#ifdef SYSCLK_FREQ_HSE
    uint32_t SystemCoreClock = SYSCLK_FREQ_HSE;    // HSE
#elif defined SYSCLK_FREQ_24MHz
    uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz; // PLL - 24Mhz
#elif defined SYSCLK_FREQ_36MHz
    uint32_t SystemCoreClock = SYSCLK_FREQ_36MHz; // PLL - 36Mhz
#elif defined SYSCLK_FREQ_48MHz
    uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz; // PLL - 48Mhz
#elif defined SYSCLK_FREQ_56MHz
    uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz; // PLL - 56Mhz
#elif defined SYSCLK_FREQ_72MHz
    uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz; // PLL - 72Mhz
#else
    uint32_t SystemCoreClock = HSI_VALUE; // HSI
#endif
/*

#define SysTick_CTRL_TICKINT_Pos    1
#define SysTick_CTRL_TICKINT_Msk    (1ul << SysTick_CTRL_TICKINT_Pos) // Enable Systick interrupt
#define SysTick_CTRL_ENABLE_Pos    0
#define SysTick_CTRL_ENABLE_Msk    (1ul << SysTick_CTRL_ENABLE_Pos) // Enable Systick

#define configTICK_RATE_HZ    (1000)    // 时钟节拍频率,这里设置为1000,周期就是1ms

 * SYSTICK 的时钟配置为 AHB 时钟
 * 这里为了兼容 FreeRTOS, 所以将 SYSTICK 的时钟频率改为 AHB 的频率!
 * SYSCLK: 系统时钟频率
 * reload 为 24-bit 寄存器, 最大值: 16777216 (0xFF FFFF + 1), 在72M下, 约合 0.233s左右 (1 / 72M * 16777216)
 * 延时时间 T = reload / systick_clock (s) = 72000 / 72000 000 (s) = 0.001s = 1ms
 */
void delay_init()
{
    u32 reload;

    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);

    fac_us = SystemCoreClock / 1000000;

    reload = SystemCoreClock / 1000000;
    reload *= 1000000 / configTICK_RATE_HZ;

    fac_ms = 1000 / configTICK_RATE_HZ;

    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
    SysTick->LOAD = reload;
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}

5. 延时 n us

/*
 * decription: delay n (us)
 * @para nus: delay n us, n[0 ~ 204522252], max: 2^32 / fac_us @fac_us = 168
 * return value: none
 * Blocking function, occupying CPU
 */
void delay_us(u32 nus)
{
    u32 ticks;
    u32 told = 0;
    u32 tnow = 0;
    u32 tcnt = 0; // 统计Systick 数了多少个数
    u32 reload = 0;
    
    reload = SysTick->LOAD; // Systick 计数的当前值,数到哪了
    ticks = nus * fac_us; // 延时n us需要数 ticks 次
    told = SysTick->VAL; // 从哪个数开始数,起始值

    while (1)
    {
        tnow = SysTick->VAL; // 数到哪了,一直算数到了哪了,然后下面再统计数了多少次了
        if (tnow != told)
        {
            if (tnow < told)
            {
                tcnt += told - tnow; // 统计Systick 数了多个数了, 这里注意一下 SYSTICK 是一个只能递减的计数器就可以了
            }
            else
            {
                tcnt += (reload - tnow) + (told - 0); // 统计Systick 数了多个数了
            }
            told = tnow;
            if (tcnt >= ticks) // 当Systick 数了ticks 个数后,延时时间到,表示延时了 n us 时间
            {
                break;
            }
        }
    }
}

6. 延时n ms

6.1 跑实时操作系统FreeRTOS

/*
 * decription: delay n (ms)
 * @para nus: delay n ms, n[0 ~ 65535]
 * return value: none
 * Blocking function, occupying CPU
 * 会引起系统任务调度
 */
void delay_ms(u32 nms)
{
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) // 系统已经运行
    {
        if (nms >= fac_ms) // 延时的时间大于OS的最少时间周期
        {
            vTaskDelay(nms / fac_ms); // FreeRTOS延时
        }
        nms %= fac_ms; // OS已经无法提供这么小的延时了, 采用普通方式延时
    }
    delay_us((u32)(nms * 1000)); // 普通方式延时
}

6.2 没有跑操作系统,nuo奔

/*
 * decription: delay n (ms)
 * @para nus: delay n ms
 * return value: none
 * Blocking function, occupying CPU
 * 不会引起任务调度
 */
void delay_xms(u32 nms)
{
	  u32 i = 0;
    for (i = 0; i < nms; i++)
    {
        delay_us(1000);
    }
}

7.在 ARM Cortex-M 系列处理器中,SysTick 中断的入口地址是由 NVIC(Nested Vectored Interrupt Controller,嵌套式向量中断控制器)模块自动进行管理和保存的,因此我们无法直接跳转到 SysTick 中断的入口地址。

当 SysTick 计数器减到 0 时,会自动触发 SysTick 中断,并将控制权交给 NVIC 模块,由 NVIC 根据优先级和其他相关配置信息,自动切换到 SysTick 中断服务程序。

在编写代码时,我们只需要提供一个 SysTick 中断服务程序的函数,名称为 SysTick_Handler。当 SysTick 中断被触发时,CPU 会自动跳转到该函数的入口地址,并执行相应的中断处理代码。因此,我们可以在 SysTick_Handler 函数中编写处理中断的代码,例如更新计时器、控制 I/O 口、执行任务等。

需要注意的是,在 Cortex-M 处理器中,中断服务程序的入口地址必须对齐 4 个字节,即地址的最低两位必须为 0。因此,在定义 SysTick_Handler 函数时,需要确保其函数属性或链接器脚本中指定的起始地址符合对齐要求。

你可能感兴趣的:(stm32,单片机,嵌入式硬件,mcu,arm开发)