[AT32]AT32配置delay函数

这里所有的内容,仅针对AT32,如果你是STM32芯片,仅供参考,反正都差不多!

SYSTICK简介

系统嘀嗒定时器是一个 24 位递减计数器,递减至零可自动重载计数初值。可产生周期性异常,用作嵌入式操作系统的多任务调度计数器,或对于无嵌入式操作系统,可用于调用需周期性执行的任务。系统嘀嗒定时器校准值固定值 9000,当系统嘀嗒时钟设定为 9MHz,产生 1ms 时间基准。

这里我们使用SYSTICK定时器来完成我们的delay函数。

AT32时钟树

大多数外设使用系统时钟 HCLK、PCLK1 或 PCLK2 时钟。个别外设还有专用时钟。

系统嘀嗒定时器(SysTick)使用 CPU FCLK(HCLK)或 CPU systick(HCLK 的 8 分频)作为时钟。

ADC 使用 HCLK 时钟的 2、3、4、5…17 分频作为时钟。

定时器使用 APB1/2 作为时钟,特别地,当 APB 预分频系数是 1 时,定时器的时钟频率等于 APB1/2 的时钟频率;当 APB 预分频系数不为 1 时,定时器的时钟频率等于 APB1/2 时钟频率的 2 倍。

USB 时钟可在 HICK 和 PLL 分频时钟之间切换。当选 HICK 时钟源时,需配置 USB 时钟为 48MHz 时钟;当选 PLL 分频时钟时,USB 分频器提供 48MHz 的 USBCLK 时钟,PLL 需设置为 48*N*0.5 MHz(N=2,3,4,5…)。

ERTC 的时钟源有:HEXT 振荡器分频时钟,LEXT 振荡器时钟 LICK 振荡器时钟。ERTC 的时钟源一旦选择后就不可再更改,只有将电池供电域复位后才能重新配置 ERTC 时钟源。当 VDD 掉电时,ERTC 使用 LEXT 作为时钟的话,ERTC 可以继续工作,但 ERTC 使用 HEXT 或 LICK 作为时钟源时,由于 HEXT和 LICK 均掉电,会导致 ERTC 状态不定。

看门狗使用 LICK 振荡器时钟作为时钟源。硬件选项或软件开启看门狗后,将强制打开 LICK 振荡器,LICK振荡器稳定后,才给看门狗提供时钟。

SYSTICK配置

SysTick定时器不是自动配置好的。需要手动配置和使能。通常需要以下步骤:

  • 选择时钟源:可以选择使用HCLK、HCLK/8。
  • 设置重装载值(LOAD寄存器):根据需要的定时周期计算并设置。
  • 清零当前值(VAL寄存器):设置为0。
  • 配置并启用SysTick:通过控制寄存器启用SysTick和中断(如果需要)。
  • 完成这些配置后,SysTick定时器就会正常工作。
在C语言中,1000000U中的U表示这是一个无符号整数常量。这样可以确保在计算中使用无符号整数,避免潜在的符号相关错误。

 

static __IO uint32_t fac_us;
static __IO uint32_t fac_ms;

void delay_init()
{
  /* configure systick */
  systick_clock_source_config(SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV);
  fac_us = system_core_clock / (1000000U);
  fac_ms = fac_us * (1000U);
}

配置 SysTick 定时器的时钟源为AHB总线时钟,并计算微秒和毫秒延时的倍乘因子。

delay函数

在写延时函数之前,需要了解一下Systick中的控制寄存器(CTRL):

CTRL的第0位,是SYSTICK使能位。

CTRL的第1位,是SYSTICK中断使能位,置0代表关闭systick中断,置1代表开启systick中断。

CTRL的第2位,是时钟源选择,置0代表HCLK/8,置1代表HCLK。

CTRL的第16位,是计数比较标志,如果计数器达到0,第16位会自动置1,被读取后自动置0。

微秒延时

值得注意的是,我们可以看到,这个延时函数重新设定了重载值(LOAD),这在裸机中是没有问题的,并且容易理解,但是如果是在RTOS中,这样重置LOAD值的做法是不可取的,会导致RTOS系统的崩溃。

void delay_us(uint32_t nus)
{
  uint32_t temp = 0;
  SysTick->LOAD = (uint32_t)(nus * fac_us); //重置LOAD
  SysTick->VAL = 0x00;//重置当前计数
  SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ;//启动 SysTick 计数器
  do
  {
    temp = SysTick->CTRL; //读取控制器的值
  }while((temp & 0x01) && !(temp & (1 << 16)));//只要计数器启用,且未达到计数结束,就继续等待。

  SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;//停止 SysTick 计数器。清除 ENABLE 位,停止计数器。
  SysTick->VAL = 0x00;//清零计数器的当前值。确保计数器停止后,VAL 寄存器为 0。
}

毫秒延时

由于 SysTick 计数器的最大值限制,如果一次性延时的时间过长(LOAD 值超过 0xFFFFFF),会导致无法实现精确的延时。
通过将延时分为多个不超过 STEP_DELAY_MS(50ms)的段,逐段完成延时,可以实现任意长度的毫秒延时。
选择 50ms 作为单次最大延时,是为了在保证准确性的前提下,尽可能减少循环次数。

#define STEP_DELAY_MS 50

void delay_ms(uint16_t nms)
{
  uint32_t temp = 0;
  while(nms)
  {
    if(nms > STEP_DELAY_MS)
    {
      SysTick->LOAD = (uint32_t)(STEP_DELAY_MS * fac_ms);
      nms -= STEP_DELAY_MS;
    }
    else
    {
      SysTick->LOAD = (uint32_t)(nms * fac_ms);
      nms = 0;
    }
    SysTick->VAL = 0x00;
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    do
    {
      temp = SysTick->CTRL;
    }while((temp & 0x01) && !(temp & (1 << 16)));

    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    SysTick->VAL = 0x00;
  }
}

 秒延时

为什么不直接调用 delay_ms(1000)?

可能考虑到 delay_ms() 在处理较长时间延时时,需要分段处理,调用两次 delay_ms(500) 可以减少分段次数,提高延时精度。

void delay_sec(uint16_t sec)
{
  uint16_t index;
  for(index = 0; index < sec; index++)
  {
    delay_ms(500);
    delay_ms(500);
  }
}