FreeRTOS中实现精确的us级延时

目录标题

  • 前言
  • 1、利用SysTick(滴答)定时器
    • 1.1、滴答定时器简介
    • 1.2、裸机中使用SysTick实现延时
    • 1.3、在FreeRTOS中使用SysTick实现延时
  • 2、利用硬件定时器实现延时函数
  • 3、利用DWT实现延时函数
    • 3.1、DWT简介
    • 3.2、延时功能使用到的DWT寄存器
    • 3.2、具体实现方法
    • 3.3、DWT延时优缺点

前言

在使用通讯协议来驱动外设的时候需要遵循严格的时序逻辑,往往用到微秒(us)级别的延时,在裸机编程中可以使用SysTick定时器、软件堵塞等方法来实现;但是在FreeRTOS中,SysTick定时器则是用来作为FreeRTOS系统时钟的,并且它提供的API仅能实现毫秒级别的延时,所以在网上查找资料总结了几个在FreeRTOS上运行的,可以实现us级别延时的几个方案。

1、利用SysTick(滴答)定时器

1.1、滴答定时器简介

SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号15)。滴答中断对操作系统尤其重要。例如,操作系统可以为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。FreeRTOS的节拍器就是使用的SysTick(系统滴答)定时器。
SysTick-系统定时器是CM3内核中的一个外设,内嵌在NVIC中,是一个24位向下递减的计数器,所有基于CM3内核的单片机都具有这个系统定时器,当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复!
有四个寄存器来控制SysTick定时器,如下图所示:
FreeRTOS中实现精确的us级延时_第1张图片
FreeRTOS中实现精确的us级延时_第2张图片

1.2、裸机中使用SysTick实现延时

操作步骤:
a.根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
b.将该数值加载到重装载寄存器中;
c.将当前值寄存器清零,打开定时器开始计数;
d.等待控制及状态寄存器的位16变为1;
e.关闭定时器,退出。

程序代码:

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233015
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值    (系统滴答定时器SysTick:当加入操作系统时可利用其提供定时以完成任务的切换)
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

1.3、在FreeRTOS中使用SysTick实现延时

在FreeRTOS中,系统滴答定时器作为操作系统的时基是一直开启的,并且重装载值是固定的,所以向上面裸机的方法操作就不可行了,我们可以这样做:

操作步骤:
a.根据延时时间和定时器所选时钟频率,计算出定时器要计数的时间数值;
b.获取当前数值寄存器的数值;
c.以当前数值为基准开始计数;
d.当所计数值等于(大于)需要延时的时间数值时退出。
注:计数时间值的计算,我们以延时10us,时钟频率为72MHZ的STM32F103C8T6来计算,
        计数值 = 延时时间/1S × 时钟频率 = 0.000 01/1 *72 000 000 =  720
        
void delay_us(u32 nus)
{ 
       u32 ticks;
       u32 told,tnow,reload,tcnt=0;
       if((0x0001&(SysTick->CTRL)) ==0)    //定时器未工作
              vPortSetupTimerInterrupt();  //初始化定时器
 
       reload = SysTick->LOAD;                     //获取重装载寄存器值
       ticks = nus * (SystemCoreClock / 1000000);  //计数时间值
       
       vTaskSuspendAll();//阻止OS调度,防止打断us延时
       told=SysTick->VAL;  //获取当前数值寄存器值(开始时数值)
       while(1)
       {
              tnow=SysTick->VAL; //获取当前数值寄存器值
              if(tnow!=told)  //当前值不等于开始值说明已在计数
              {         
                     if(tnow<told)  //当前值小于开始数值,说明未计到0
                          tcnt+=told-tnow; //计数值=开始值-当前值
 
                     else     //当前值大于开始数值,说明已计到0并重新计数
                            tcnt+=reload-tnow+told;   //计数值=重装载值-当前值+开始值  (
                                                      //已从开始值计到0) 
 
                     told=tnow;   //更新开始值
                     if(tcnt>=ticks)break;  //时间超过/等于要延迟的时间,则退出.
              } 
       }  
       xTaskResumeAll();	//恢复OS调度		   
} 
//SystemCoreClock为系统时钟(system_stmf4xx.c中),通常选择该时钟作为
//systick定时器时钟,根据具体情况更改

2、利用硬件定时器实现延时函数

暂时不写了,比较浪费开发板资源。和裸机情况下使用系统滴答定时器来延时类似。

3、利用DWT实现延时函数

3.1、DWT简介

Corex-M中的DWT外设本身是用于系统调试和追踪的。

跟踪组件:数据观察点与跟踪(DWT)

  • 它包含了 4 个比较器,可以配置成在发生比较匹配时,执行如下动作:
    • 硬件观察点(产生一个观察点调试事件,并且用它来调用调试模式,包括停机模式和调试监视器模式
    • ETM 触发,可以触发 ETM 发出一个数据包,并汇入指令跟踪数据流中
    • 程序计数器(PC)采样器事件触发
    • 数据地址采样器触发
    • 第一个比较器还能用于比较时钟周期计数器(CYCCNT),用于取代对数据地址的比较
  • 作为计数器,DWT 可以对下列项目进行计数
    • 时钟周期(CYCCNT)
    • 被折叠(Folded)的指令
    • 对加载/存储单元(LSU)的操作
    • 睡眠的时钟周期
    • 每指令周期数(CPI)
    • 中断的额外开销(overhead)
  • 以固定的周期采样 PC 的值
  • 中断事件跟踪

我们在使用其完成延时功能时用到的正是DWT对时钟周期计数的功能。

3.2、延时功能使用到的DWT寄存器

要实现延时的功能,总共涉及到三个寄存器:DEMCR 、DWT_CTRL、DWT_CYCCNT,它们的地址分别为:0xE000EDFC、0xE0001000、0xE0001004,分别用于开启DWT功能、开启CYCCNT及获得系统时钟计数值。
FreeRTOS中实现精确的us级延时_第3张图片
FreeRTOS中实现精确的us级延时_第4张图片

3.2、具体实现方法

我们需要按以下步骤进行操作:
a.对DEMCR寄存器的位24控制,写1使能DWT外设。
b.对于DWT的CYCCNT寄存器清0。
c.对DWT控制寄存器的位0控制,写1使能CYCCNT寄存器。
d.通过编写函数获取DWT_CYCCNT的值,并通过判断是否到达新的DWT_CYCCNT值来实现延时。
由此我们可以得到一个32位向上累加的计数器,溢出会自动清零并累加,频率是系统主频。

具体配置函数:

#define  DEM_CR      *(volatile u32 *)0xE000EDFC
#define  DWT_CR      *(volatile u32 *)0xE0001000
#define  DWT_CYCCNT  *(volatile u32 *)0xE0001004
#define  DEM_CR_TRCENA                   (1 << 24)
#define  DWT_CR_CYCCNTENA                (1 <<  0)

void DWT_Init()
{
    DEM_CR  |=  DEM_CR_TRCENA; /*对DEMCR寄存器的位24控制,写1使能DWT外设。*/
    DWT_CYCCNT = 0;/*对于DWT的CYCCNT计数寄存器清0。*/
    DWT_CR  |=  DWT_CR_CYCCNTENA;/*对DWT控制寄存器的位0控制,写1使能CYCCNT寄存器。*/
}

void DWT_DelayUS(uint32_t _ulDelayTime)
{
    uint32_t tCnt, tDelayCnt;
    uint32_t tStart;
           
    tStart = DWT_CYCCNT; /* 刚进入时的计数器值 */
    tCnt = 0;
    tDelayCnt = _ulDelayTime * (SystemCoreClock / 1000000);
    /* 需要的节拍数 */    /*SystemCoreClock :系统时钟频率*/                 

    while(tCnt < tDelayCnt)
      {
        tCnt = DWT_CYCCNT - tStart; 
        /* 求减过程中,如果发生第一次32位计数器重新计数,依然可以正确计算 */       
      }
}

void DWT_DelayMS(uint32_t _ulDelayTime)
{
        bsp_DelayUS(1000*_ulDelayTime);
}

3.3、DWT延时优缺点

优点:方便移植,经过测试在M3、M4、M7内核的MCU上都可以使用。
缺点:和定时器一样,都有一个延时的最大时间,测量代码运行时间的最大值。

你可能感兴趣的:(STM32,单片机,嵌入式硬件)