[野火]一种Cortex-M内核中的精确延时方法-ns级别-DWT

看书 对书里面内容写文字

原创: 杰杰 物联网IoT开发 2018-11-10

https://mp.weixin.qq.com/s/1ruKZ2uJFaWqQn-tnGQp6A

前言

为什么要学习这种延时的方法?

  1. 很多时候我们跑操作系统,就一般会占用一个硬件定时器——SysTick,而我们一般操作系统的时钟节拍一般是设置100-1000HZ,也就是1ms——10ms产生一次中断。很多裸机教程使用延时函数又是基于SysTick的,这样一来又难免产生冲突。

  2. 很多人会说,不是还有定时器吗,定时器的计时是超级精确的。这点我不否认,但是假设,如果一个系统,总是进入定时器中断(10us一次/1us一次/0.5us一次),那整个系统就会经常被打断,线程的进行就没办法很好运行啊。此外还消耗一个硬件定时器资源,一个硬件定时器可能做其他事情呢!

  3. 对应ST HAL库的修改,其实杰杰个人觉得吧,ST的东西什么都好,就是出的HAL库太恶心了,没办法,而HAL库中有一个HAL_Delay(),他也是采用SysTick延时的,在移植操作系统的时候,会有诸多不便,不过好在,HAL_Delay()是一个弱定义的,我们可以重写这个函数的实现,那么,采用内核延时当然是最好的办法啦(个人是这么觉得的)当然你有能力完全用for循环写个简单的延时还是可以的。

  4. 可能我说的话没啥权威,那我就引用Cortex-M3权威指南中的一句话——“DWT 中有剩余的计数器,它们典型地用于程序代码的“性能速写”(profiling)。通过编程它们,就可以让它们在计数器溢出时发出事件(以跟踪数据包的形式)。最典型地,就是使用 CYCCNT寄存器来测量执行某个任务所花的周期数,这也可以用作时间基准相关的目的(操作系统中统计 CPU使用率可以用到它)。”

Cortex-M中的DWT

在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace),是用于系统调试及跟踪,

[野火]一种Cortex-M内核中的精确延时方法-ns级别-DWT_第1张图片

 

它有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,内核时钟跳动一次,该计数器就加1,精度非常高,决定内核的频率是多少,如果是F103系列,内核时钟是72M,那精度就是1/72M = 14ns,而程序的运行时间都是微秒级别的,所以14ns的精度是远远够的。最长能记录的时间为:60s=2的32次方/72000000(假设内核频率为72M,内核跳一次的时间大概为1/72M=14ns),而如果是H7这种400M主频的芯片,那它的计时精度高达2.5ns(1/400000000 = 2.5),而如果是 i.MX RT1052这种比较牛逼的处理器,最长能记录的时间为: 8.13s=2的32次方/528000000 (假设内核频率为528M,内核跳一次的时间大概为1/528M=1.9ns) 。当CYCCNT溢出之后,会清0重新开始向上计数。

 

 

m3、m4、m7[需要注意有锁]实测可用 m0不可用。 
精度:1/内核频率(s)。

要实现延时的功能,总共涉及到三个寄存器:DEMCR 、DWT_CTRL、DWT_CYCCNT,分别用于开启DWT功能、开启CYCCNT及获得系统时钟计数值。

 

 

综上所述

想要使用DWT的CYCCNT步骤:

  1. 先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能

  2. 使能CYCCNT寄存器之前,先清0。

  3. 使能CYCCNT寄存器,这个由DWT的CYCCNTENA 控制,也就是DWT控制寄存器的位0控制,写1使能

代码实现

注:此代码全部解释权归【®野火】所有

  1/**
  2  ******************************************************************
  3  * @file    core_delay.c
  4  * @author  fire
  5  * @version V1.0
  6  * @date    2018-xx-xx
  7  * @brief   使用内核寄存器精确延时
  8  ******************************************************************
  9  * @attention
 10  *
 11  * 实验平台:野火 STM32开发板  
 12  * 论坛    :http://www.firebbs.cn
 13  * 淘宝    :https://fire-stm32.taobao.com
 14  *
 15  ******************************************************************
 16  */
 17
 18#include "./delay/core_delay.h"   
 19
 20/*
 21**********************************************************************
 22*         时间戳相关寄存器定义
 23**********************************************************************
 24*/
 25/*
 26 在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace),
 27 该外设有一个32位的寄存器叫CYCCNT,它是一个向上的计数器,
 28 记录的是内核时钟运行的个数,最长能记录的时间为:
 29 10.74s=2的32次方/400000000
 30 (假设内核频率为400M,内核跳一次的时间大概为1/400M=2.5ns)
 31 当CYCCNT溢出之后,会清0重新开始向上计数。
 32 使能CYCCNT计数的操作步骤:
 33 1、先使能DWT外设,这个由另外内核调试寄存器DEMCR的位24控制,写1使能
 34 2、使能CYCCNT寄存器之前,先清0
 35 3、使能CYCCNT寄存器,这个由DWT_CTRL(代码上宏定义为DWT_CR)的位0控制,写1使能
 36 */
 37
 38
 39#define  DWT_CR      *(__IO uint32_t *)0xE0001000
 40#define  DWT_CYCCNT  *(__IO uint32_t *)0xE0001004
 41#define  DEM_CR      *(__IO uint32_t *)0xE000EDFC
 42
 43
 44#define  DEM_CR_TRCENA                   (1 << 24)
 45#define  DWT_CR_CYCCNTENA                (1 <<  0)
 46
 47
 48/**
 49  * @brief  初始化时间戳
 50  * @param  无
 51  * @retval 无
 52  * @note   使用延时函数前,必须调用本函数
 53  */
 54HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
 55{
 56    /* 使能DWT外设 */
 57    DEM_CR |= (uint32_t)DEM_CR_TRCENA;                
 58
 59    /* DWT CYCCNT寄存器计数清0 */
 60    DWT_CYCCNT = (uint32_t)0u;
 61
 62    /* 使能Cortex-M DWT CYCCNT寄存器 */
 63    DWT_CR |= (uint32_t)DWT_CR_CYCCNTENA;
 64
 65    return HAL_OK;
 66}
 67
 68/**
 69  * @brief  读取当前时间戳
 70  * @param  无
 71  * @retval 当前时间戳,即DWT_CYCCNT寄存器的值
 72  */
 73uint32_t CPU_TS_TmrRd(void)
 74{        
 75  return ((uint32_t)DWT_CYCCNT);
 76}
 77
 78/**
 79  * @brief  读取当前时间戳
 80  * @param  无
 81  * @retval 当前时间戳,即DWT_CYCCNT寄存器的值
 82  */
 83uint32_t HAL_GetTick(void)
 84{        
 85  return ((uint32_t)DWT_CYCCNT/SysClockFreq*1000);
 86}
 87
 88
 89/**
 90  * @brief  采用CPU的内部计数实现精确延时,32位计数器
 91  * @param  us : 延迟长度,单位1 us
 92  * @retval 无
 93  * @note   使用本函数前必须先调用CPU_TS_TmrInit函数使能计数器,
 94            或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION
 95            最大延时值为8秒,即8*1000*1000
 96  */
 97void CPU_TS_Tmr_Delay_US(uint32_t us)
 98{
 99  uint32_t ticks;
100  uint32_t told,tnow,tcnt=0;
101
102  /* 在函数内部初始化时间戳寄存器, */  
103#if (CPU_TS_INIT_IN_DELAY_FUNCTION)  
104  /* 初始化时间戳并清零 */
105  HAL_InitTick(5);
106#endif
107
108  ticks = us * (GET_CPU_ClkFreq() / 1000000);  /* 需要的节拍数 */      
109  tcnt = 0;
110  told = (uint32_t)CPU_TS_TmrRd();         /* 刚进入时的计数器值 */
111
112  while(1)
113  {
114    tnow = (uint32_t)CPU_TS_TmrRd();  
115    if(tnow != told)
116    { 
117        /* 32位计数器是递增计数器 */    
118      if(tnow > told)
119      {
120        tcnt += tnow - told;  
121      }
122      /* 重新装载 */
123      else 
124      {
125        tcnt += UINT32_MAX - told + tnow; 
126      } 
127
128      told = tnow;
129
130      /*时间超过/等于要延迟的时间,则退出 */
131      if(tcnt >= ticks)break;
132    }  
133  }
134}
135
136/*********************************************END OF FILE**********************/
 1#ifndef __CORE_DELAY_H
 2#define __CORE_DELAY_H
 3
 4#include "stm32h7xx.h"
 5
 6/* 获取内核时钟频率 */
 7#define GET_CPU_ClkFreq()       HAL_RCC_GetSysClockFreq()
 8#define SysClockFreq            (218000000)
 9/* 为方便使用,在延时函数内部调用CPU_TS_TmrInit函数初始化时间戳寄存器,
10   这样每次调用函数都会初始化一遍。
11   把本宏值设置为0,然后在main函数刚运行时调用CPU_TS_TmrInit可避免每次都初始化 */  
12
13#define CPU_TS_INIT_IN_DELAY_FUNCTION   0  
14
15
16/*******************************************************************************
17 * 函数声明
18 ******************************************************************************/
19uint32_t CPU_TS_TmrRd(void);
20HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority);
21
22//使用以下函数前必须先调用CPU_TS_TmrInit函数使能计数器,或使能宏CPU_TS_INIT_IN_DELAY_FUNCTION
23//最大延时值为8秒
24void CPU_TS_Tmr_Delay_US(uint32_t us);
25#define HAL_Delay(ms)     CPU_TS_Tmr_Delay_US(ms*1000)
26#define CPU_TS_Tmr_Delay_S(s)       CPU_TS_Tmr_Delay_MS(s*1000)
27
28
29#endif /* __CORE_DELAY_H */

注意事项:

使用者如果不是在HAL库中使用,注释掉:

1uint32_t HAL_GetTick(void)
2{        
3  return ((uint32_t)DWT_CYCCNT/SysClockFreq*1000);
4}

同时建议重新命名HAL_InitTick()函数。

按照自己的平台重写以下宏定义:

1/* 获取内核时钟频率 */
2#define GET_CPU_ClkFreq()       HAL_RCC_GetSysClockFreq()
3#define SysClockFreq            (218000000)

后记

其实在ucos-iii 源码中,有一个功能是测量关中断时间的功能,就是使用STM32的时间戳,即记录程序运行的某个时刻,如果记录下程序前后的两个时刻点,即可以算出这段程序的运行时间。
 

你可能感兴趣的:([野火]一种Cortex-M内核中的精确延时方法-ns级别-DWT)