STM32F072从零配置工程-实现delay功能

因为是使用SysTick来作为延时时钟,因此在这里给出SysTick时钟的寄存器;

CTRL:SysTick控制及状态寄存器

位段

名称

类型

复位值

描述

16

COUNTFLAG

R/W

0

如果在上次读取本寄存器后, SysTick 已经计到
了 0,则该位为 1。

2

CLKSOURCE

R/W

0

时钟源选择位,0=AHB/8,1=处理器时钟AHB

1

TICKINT

R/W

0

1=SysTick倒数计数到 0时产生 SysTick异常请
求,0=数到 0 时无动作。也可以通过读取COUNTFLAG标志位来确定计数器是否递减到0

0

ENABLE

R/W

0

SysTick 定时器的使能位

LOAD:SysTick 重装载数值寄存器

位段

名称

类型

复位值

描述

23:0

RELOAD

R/W

0

当倒数计数至零时,将被重装载的值

VAL:SysTick当前数值寄存器

 

位段

名称

类型

复位值

描述

23:0

CURRENT

R/W

0

读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG 标志

 

此处参考外设库的例子,程序结构和逻辑大致如下:

  通过SystemInit()配置好时钟;

  通过SysTick_Config(SystemCoreClock/1000)配置SysTick时钟;

  创建Delay(__IO uint32_t nTime)毫秒延时函数(设置执行几次中断,时间为nTime×中断时间);

  在SysTick中断SysTick_Handler()中按照配置的减小计数值并检测是否清零;

 

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime);

/**
  * @brief   Main program
  * @param  None
  * @retval None
  */
int main(void)
{
  /*!< At this stage the microcontroller clock setting is already configured, 
       this is done through SystemInit() function which is called from startup
       file (startup_stm32f0xx.s) before to branch to application main.
       To reconfigure the default setting of SystemInit() function, refer to
       system_stm32f0xx.c file
     */     

  /* Setup SysTick Timer for 1 msec interrupts.
     ------------------------------------------
    1. The SysTick_Config() function is a CMSIS function which configure:
       - The SysTick Reload register with value passed as function parameter.
       - Configure the SysTick IRQ priority to the lowest value (0x0F).
       - Reset the SysTick Counter register.
       - Configure the SysTick Counter clock source to be Core Clock Source (HCLK).
       - Enable the SysTick Interrupt.
       - Start the SysTick Counter.
    
    2. You can change the SysTick Clock source to be HCLK_Div8 by calling the
       SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8) just after the
       SysTick_Config() function call. The SysTick_CLKSourceConfig() is defined
       inside the misc.c file.

    3. You can change the SysTick IRQ priority by calling the
       NVIC_SetPriority(SysTick_IRQn,...) just after the SysTick_Config() function 
       call. The NVIC_SetPriority() is defined inside the core_cm0.h file.

    4. To adjust the SysTick time base, use the following formula:
                            
         Reload Value = SysTick Counter Clock (Hz) x  Desired Time base (s)
    
       - Reload Value is the parameter to be passed for SysTick_Config() function
       - Reload Value should not exceed 0xFFFFFF
   */
  if (SysTick_Config(SystemCoreClock / 1000))
  { 
    /* Capture error */ 
    while (1);
  }

  while (1)
  {
    /* Insert 100 ms delay */
    Delay(100);
  }
}

/**
  * @brief  Inserts a delay time.
  * @param  nTime: specifies the delay time length, in milliseconds.
  * @retval None
  */
void Delay(__IO uint32_t nTime)
{ 
  TimingDelay = nTime;

  while(TimingDelay != 0);
}

/**
  * @brief  Decrements the TimingDelay variable.
  * @param  None
  * @retval None
  */
void TimingDelay_Decrement(void)
{
  if (TimingDelay != 0x00)
  { 
    TimingDelay--;
  }
}

 

由于官方的例子中默认配置好了时钟,因此给出的SystemCoreClock是48000000;

  设置好重装值后,就可以确定发生一次中断需要的时间;

uint32_t SystemCoreClock    = 48000000;  

/** 

  * @brief  This function handles SysTick Handler. 

  * @param  None 

  * @retval None 

  */  

void SysTick_Handler(void)  

{  

  TimingDelay_Decrement();  

}  

 

从SysTick_Config()中可以看出配置SysTick的步骤:

       设置重装载寄存器LOAD的值;(设置的是中断时间)

       设置中断优先级;

       清楚当前数值寄存器VAL的值;

       配置控制与状态寄存器CTRL,包括时钟源、系统定时器中断、使能定时器;

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */

  SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

 

SysTick中断时间的计算:

       假设fAHB=180MHz,则计数值±1的时间为t=1/fAHB,中断一次的时间=LOAD×t;

       根据计算,中断1us的时间= LOAD×1/fAHB=180×(1/180M)=1us;

       若需要中断1ms的时间=180000×(1/180M)=1ms;

       因此,设置SystemCoreClock=180M/1000=180000=LOAD值;

       这样取LOAD值为180000就可以得到一次中断需要的时间为1ms;

 

由于库例子是默认设置好了时钟,因此这里涉及到一个时钟的配置

在SystemInit中重要任务是清除RCC中的时钟配置:

  将HSI作为默认时钟;

  且SYSCLK不分频、HCLK不分频、ADC时钟不分频、MCO时钟输出不分频;

  关闭HSE、CSS、PLL时钟;

  将外部HSE时钟输入旁路;

  清空PLL系数;

  清空USART1、I2C、CEC和ADC外设的时钟源;

  关闭HSI14时钟;

  关闭所有中断Int;

 

void SystemInit (void)
{    
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

#if defined(STM32F051)   
  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE and MCOSEL[2:0] bits */
  RCC->CFGR &= (uint32_t)0xF8FFB80C;
#else
  /* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */
  RCC->CFGR &= (uint32_t)0x08FFB80C;
#endif /* STM32F051 */
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */
  RCC->CFGR &= (uint32_t)0xFFC0FFFF;

  /* Reset PREDIV1[3:0] bits */
  RCC->CFGR2 &= (uint32_t)0xFFFFFFF0;

  /* Reset USARTSW[1:0], I2CSW, CECSW and ADCSW bits */
  RCC->CFGR3 &= (uint32_t)0xFFFFFEAC;

  /* Reset HSI14 bit */
  RCC->CR2 &= (uint32_t)0xFFFFFFFE;

  /* Disable all interrupts */
  RCC->CIR = 0x00000000;

  /* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */
  SetSysClock();
}

 

时钟配置的重点在函数SetSysClock()中;

       使能HSE并等待HSE初始化完成;

       若HSE初始化完成后用HSE配置PLL时钟,并将PLL输出时钟作为系统时钟SYSCLK;

/**
  * @brief  Configures the System clock frequency, AHB/APBx prescalers and Flash
  *         settings.
  * @note   This function should be called only once the RCC clock configuration
  *         is reset to the default reset state (done in SystemInit() function).
  * @param  None
  * @retval None
  */
static void SetSysClock(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

/******************************************************************************/
/*            PLL (clocked by HSE) used as System clock source                */
/******************************************************************************/
  
  /* SYSCLK, HCLK, PCLK configuration ----------------------------------------*/
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer and set Flash Latency */
    FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;
 
    /* HCLK = SYSCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK = HCLK */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;

    /* PLL configuration */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);
            
    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }

    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL)
    {
    }
  }
  else
  { /* If HSE fails to start-up, the application will have wrong clock 
         configuration. User can add here some code to deal with this error */
  }  
}

 

而在核心时钟HCLK发生变化时,需要调用SystemCoreClockUpdate()函数来更新SystemCoreClock值,以便调整使用了该参数的配置;

       如果SYSCLK源为HSI,则SystemCoreClock=HSI_VALUE;(HSI_VALUE默认为8MHz,实际值会随电压和温度变化而变化)

       如果SYSCLK源为HSE,则SystemCoreClock=HSE_VALUE;(HSE_VALUE需要确保和实际所使用的外接晶振频率相同,否则此功能可能会失效)

       如果SYSCLK源为PLL,则SystemCoreClock= HSE/HSI_VALUE与PLL因子的计算;

       如果SYSCLK源不确定,默认为HSI时钟源;

       最后考虑HCLK的分频系数;

void SystemCoreClockUpdate (void)
{
  uint32_t tmp = 0, pllmull = 0, pllsource = 0, prediv1factor = 0;

  /* Get SYSCLK source -------------------------------------------------------*/
  tmp = RCC->CFGR & RCC_CFGR_SWS;
  
  switch (tmp)
  {
    case 0x00:  /* HSI used as system clock */
      SystemCoreClock = HSI_VALUE;
      break;
    case 0x04:  /* HSE used as system clock */
      SystemCoreClock = HSE_VALUE;
      break;
    case 0x08:  /* PLL used as system clock */
      /* Get PLL clock source and multiplication factor ----------------------*/
      pllmull = RCC->CFGR & RCC_CFGR_PLLMULL;
      pllsource = RCC->CFGR & RCC_CFGR_PLLSRC;
      pllmull = ( pllmull >> 18) + 2;
      
      if (pllsource == 0x00)
      {
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
      }
      else
      {
        prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;
        /* HSE oscillator clock selected as PREDIV1 clock entry */
        SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; 
      }      
      break;
    default: /* HSI used as system clock */
      SystemCoreClock = HSI_VALUE;
      break;
  }
  /* Compute HCLK clock frequency ----------------*/
  /* Get HCLK prescaler */
  tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];
  /* HCLK clock frequency */
  SystemCoreClock >>= tmp;  
}

 

另一中延时函数的配置方法,是不利用中断来实现的

首先初始化SysTick时钟

       这里直接将SysTick时钟源设置为HCLK的8分频;

       由于这里SYSCLK=SystemCoreClock=168MHz,因此SysTick时钟为168MHz/8=21MHz;

       因此要延时1us=10^(-6)s=21/21M,此时的重装值为21=SYSCLK/8=fac_us;

       要延时1ms=10^(-3)=21k/21M,此时的重装值为21k=1000*fac_us= fac_ms;

 

void delay_init(u8 SYSCLK)
{
     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
    fac_us=SYSCLK/8;
    fac_ms=(u16)fac_us*1000;
}

 

延时1us到延时1ms的函数

       流程大致如下:将x*fac_us加载入重装寄存器LOAD;

       清空计数寄存器VAL;

       通过CTRL寄存器,开启SysTick时钟;

       等待计数完成,关闭SysTick时钟,清空计数寄存器VAL;

void delay_us(u32 nus)  
{         
    u32 temp;              
    SysTick->LOAD=nus*fac_us;  
    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;  
}  
  
void delay_xms(u16 nms)  
{                   
    u32 temp;            
    SysTick->LOAD=(u32)nms*fac_ms;  
    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;  
}   
  
void delay_ms(u16 nms)  
{          
    u8 repeat=nms/540;  
    u16 remain=nms%540;  
    while(repeat)  
    {  
        delay_xms(540);  
        repeat--;  
    }  
    if(remain)delay_xms(remain);  
}   

 

2019-7-18更新

从STM32Cube生成的代码已经默认包含了延时功能,但是仅用于实现1ms的延时,其实现是通过系统自带的配置:

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

然后延时的功能采用库函数实现HAL_Delay()来实现:

  可以实现延时1ms的功能;

/**
  * @brief This function provides accurate delay (in milliseconds) based 
  *        on variable incremented.
  * @note In the default implementation , SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals where uwTick
  *       is incremented.
  * @note ThiS function is declared as __weak to be overwritten in case of other
  *       implementations in user file.
  * @param Delay specifies the delay time length, in milliseconds.
  * @retval None
  */
__weak void HAL_Delay(__IO uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;
  
  /* Add a period to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
     wait++;
  }
  
  while((HAL_GetTick() - tickstart) < wait)
  {
  }
}

 

你可能感兴趣的:(STM32F072从零配置工程-实现delay功能)