STM32 HAL库创建滴答定时器(systick定时器)

2019-07-20 16:09发布

本帖最后由 急速的蜗牛 于 2017-2-21 11:15 编辑

以前使用老版固件库进行STM32的开发,现在使用新版HAL库进行
例程是正点原子的STM32F429例程,发现滴答定时器的写法和以前老版本的完全不同
对其代码分析,发现,在初始化定时器时和定时函数中并没有赋予装载值load
后来发现其实在main函数的最开始进行了全局初始化,函数为:
  1. HAL_Init();
复制代码函数定义:

  1. HAL_StatusTypeDef HAL_Init(void)
  2. {
  3.   /* Configure Flash prefetch, Instruction cache, Data cache */
  4. #if (INSTRUCTION_CACHE_ENABLE != 0)
  5.    __HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
  6. #endif /* INSTRUCTION_CACHE_ENABLE */

  7. #if (DATA_CACHE_ENABLE != 0)
  8.    __HAL_FLASH_DATA_CACHE_ENABLE();
  9. #endif /* DATA_CACHE_ENABLE */

  10. #if (PREFETCH_ENABLE != 0)
  11.   __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
  12. #endif /* PREFETCH_ENABLE */

  13.   /* Set Interrupt Group Priority */
  14.   HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//ÖD¶ÏóÅÏ輶·Ö×é2

  15.   /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  16.   HAL_InitTick(TICK_INT_PRIORITY);
  17.   
  18.   /* Init the low level hardware */
  19.   HAL_MspInit();
  20.   
  21.   /* Return function status */
  22.   return HAL_OK;
  23. }
复制代码说明有对滴答定时器的初始化操作,打开这个函数来分析
  1. __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
  2. {
  3.   /*Configure the SysTick to have interrupt in 1ms time basis*/
  4. HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
  5.   /*Configure the SysTick IRQ priority */
  6.   HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0);

  7.   /* Return function status */
  8.   return HAL_OK;
  9. }
复制代码继续下一步查找重点函数:
  1. uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
  2. {
  3.   SysTick_Config(TicksNumb);</font>
  4. }
复制代码继续:
  1. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
  2. {
  3.   if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
  4.   {
  5.     return (1UL);                                                   /* Reload value impossible */
  6.   }

  7.   SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  8.   NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  9.   SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  10.   SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
  11.                    SysTick_CTRL_TICKINT_Msk   |
  12.                    SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */
  13.   return (0UL);                                                     /* Function successful */
  14. }
复制代码至此到达重点,在前面提到这个函数,如下:
  1. HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
复制代码这里可以看出函数参数是通过HAL_RCC_GetHCLKFreq() 函数来获取当前时钟频率的
  1. uint32_t HAL_RCC_GetHCLKFreq(void)
  2. {
  3.   SystemCoreClock = HAL_RCC_GetSysClockFreq() >> APBAHBPrescTable[(RCC->CFGR & RCC_CFGR_HPRE)>> POSITION_VAL(RCC_CFGR_HPRE)];
  4.   return SystemCoreClock;
  5. }
复制代码在这里, HAL_RCC_GetSysClockFreq()  函数是用来获取当前系统的时钟频率里面的实现方法暂不介绍,这里只提一个重点
  1. PSITION_VAL(RCC_CFGR_HPRE)
复制代码这个函数的意义我研究了好久,最后通过模拟来确认的,网上资料确实不多
首先这个函数原型是一个宏定义
  1. #define POSITION_VAL(VAL)         (__CLZ(__RBIT(VAL)))     
复制代码这里重点的就是__CLZ(__RBIT(VAL)) 这个函数
__RBIT(VAL)这个函数的含义是,将val按照32bit格式翻转180° 比如:原本二进制为:0000 0000 0000 0000 0000 1111 1100 0000
通过这个函数返回的值为:0000 0011 1111 0000 0000 0000 0000 0000
函数实现如下:
  1. uint32_t __RBIT(uint32_t value)
  2. {
  3.   uint32_t result;
  4.   int32_t s = 4 /*sizeof(v)*/ * 8 - 1; /* extra shift needed at end */

  5.   result = value;                      /* r will be reversed bits of v; first get LSB of v */
  6.   for (value >>= 1U; value; value >>= 1U)
  7.   {
  8.     result <<= 1U;
  9.     result |= value & 1U;
  10.     s--;
  11.   }
  12.   result <<= s;                        /* shift when v's highest bits are zero */
  13.   return(result);
  14. }
复制代码
重点:
前面的__CLZ()看网上的介绍,说是前导零计算个数,就是二进制数第一个1之前到最高位0的个数 比如说
第31位为1 那么计算结果为0 第30位为1,31位为0 计算结果为1。这里听的不明代的建议在网上再搜索一下。
这样两个函数加起来的意思就明显了,前导零的个数是VAL指的180°翻转后前面零的个数,这样,我们就可以
知道原来的值在32bit中的具体位置.
比如之前的例子二进制为:0000 0000 0000 0000 0000 1111 1100 0000,我们可以得出这个32bit型的整数
右移6位就得到111111 这个值。即十进制63 ,有什么具体作用呢?
HAL库中,很多寄存器的值读出来的都是32bit的,里面包含了很多寄存器,如果我们知道所在寄存器的位置
比如说第7-11位为一个寄存器,那么将这个32bit的值&上0000 0000 0000 0000 0000 1111 1000 0000就可以得到
当前寄存器中第7-11实际的值。然后通过__CLZ(__RBIT(VAL))换算知道后面有7个零 这样再将&后的值右移7位,然后就得到
当前寄存器的实际值了,在HAL库中非常实用。

实际上到这里我们就可以计算出当前系统的时钟频率,回到这个函数:
  1. __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
复制代码
  1. SysTick->LOAD  = (uint32_t)(ticks - 1UL);
复制代码可以知道如果频率不大于24bit最大值得情况下
加载值就是当前频率的1/1000。即1ms需要这么多个时钟周期

(比如时钟频率为180MHZ 这个意思是1S种可以跑180M个时钟周期,除以1000即
1ms可以跑180000个周期)
写入重装载值中
如果控制位不设为0便永不停息
所以,滴答定时器函数中无需再重复设置加载值,便于HAL_Delay也能正常运行
下面便是新版的延时函数(没有复制OS支持的代码部分[mw_shl_code=c,true]void delay_init(u8 SYSCLK)
{
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
fac_us=SYSCLK;        
}        [/mw_shl_code]实际上可以直接宏定义fac_us 180 选择时钟来源已经在HAL_Init()中选择了。
  1. #define  fac_us  180
复制代码这样的话,就可以直接写延时函数就可以了,不用在main中写初始化了。
  1. void delay_us(u32 nus)
  2. {               
  3.      u32 ticks;
  4.      u32 told,tnow,tcnt=0;
  5.      u32 reload=SysTick->LOAD;
  6.      ticks=nus*fac_us;
  7.      told=SysTick->VAL;
  8.      while(1)
  9.     {
  10.      tnow=SysTick->VAL;        
  11.           if(tnow!=told)
  12.           {            
  13.                 if(tnow<told)tcnt+=told-tnow;
  14.                else tcnt+=reload-tnow+told;            
  15.                told=tnow;
  16.                if(tcnt>=ticks)break;
  17.           }  
  18.     }
  19. }
复制代码

  1. void delay_ms(u16 nms)
  2. {
  3. u32 i;
  4. for(i=0;i<nms;i++) delay_us(1000);
  5. }
复制代码亦可以改为
  1. void delay_ms(u16 nms)
  2. {
  3.       HAL_Delay(nms);
  4. }
复制代码或者不用这个函数就好直接用HAL_Delay();






友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。