【STM32】通过 DWT 实现毫秒级延时

目录


零、前言

在 FreeRTOS 中,SysTick 被用于作为调度器的一部分进行任务调度,那么如果我需要使用软件模拟通信,例如软件 I2C,需要使用 delay,就无法使用 SysTick 实现的 delay。

因此,这里提供一种基于 DWT 实现的 delay。

一、DWT

在实现我们的代码之前,如果你没有了解过 DWT,那就先来看一下:

这里只介绍待会儿会用到的延时相关的内容



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

它有一个 32 位的寄存器叫 CYCCNT,它是一个向上的计数器,记录的是内核时钟运行的个数,内核时钟跳动一次,该计数器就加 1。

它的精度非常高,取决于内核的频率是多少,如果是 F103 系列,内核时钟是 72M,那精度就是 1 / 72 M = 14 n s 1/72M = 14ns 1/72M=14ns,而程序的运行时间都是微秒级别的,所以 14ns 的精度是远远够的。最长能记录的时间为: 60 s = 2 32 / 72000000 60s = 2^{32}/72000000 60s=232/72000000 (假设内核频率为72M,内核跳一次的时间大概为 1 / 72 M = 14 n s 1/72M=14ns 1/72M=14ns),而如果是 H7 这种 400M 主频的芯片,那它的计时精度高达 2.5ns( 1 / 400000000 = 2.5 1/400000000 = 2.5 1/400000000=2.5)。

CYCCNT 溢出之后,会清 0 重新开始向上计数。

要实现延时的功能,总共涉及到三个寄存器:DEMCRDWT_CTRLDWT_CYCCNT,分别用于开启 DWT 功能、开启 CYCCNT 及获得系统时钟计数值。下面就来看一下这几个寄存器吧。

1、DEMCR

参照权威指南:

配置的时候,将 TRCENA 设置为 1 就行了。

2、DWT_CTRL

CYCCNTENA 使能位置 1 即可。

3、DWT_CYCCNT

使用 DWT_CYCCNT 寄存器之前,先清 0。

综上所述,要使用 DWT 的 CYCCNT 配置步骤如下:

  1. 使能 DWT 外设,这个由内核调试寄存器 DEMCR 的位 24 TRCENA 控制,写 1 使能
  2. 使能 CYCCNT 寄存器之前,先清 0。
  3. 使能 CYCCNT 寄存器,这个由 DWT 控制寄存器的 CYCCNTENA 位控制,也就是 DWT 控制寄存器的位 0 控制,写 1 使能

二、实现代码

c 复制代码
#define  DWT_CYCCNT  *(volatile unsigned int *)0xE0001004
#define  DWT_CR      *(volatile unsigned int *)0xE0001000
#define  DEM_CR      *(volatile unsigned int *)0xE000EDFC

#define  DEM_CR_TRCENA               (1 << 24)
#define  DWT_CR_CYCCNTENA            (1 <<  0)

/******************************************************************************
 * @brief  初始化 DWT
 * @return none
******************************************************************************/
void bsp_dwt_init(void)
{
    DEM_CR         |= (unsigned int)DEM_CR_TRCENA;   /* Enable Cortex-M4's DWT CYCCNT reg.  */
	DWT_CYCCNT      = (unsigned int)0u;
	DWT_CR         |= (unsigned int)DWT_CR_CYCCNTENA;
}

/******************************************************************************
 * @brief      
 * @param[in]  _delay_time    :    延时时间  
 * @return     none
******************************************************************************/
void bsp_dwt_delay(uint32_t _delay_time)
{
	uint32_t cnt, delay_cnt;
	uint32_t start;
		
	cnt = 0;
	delay_cnt = _delay_time;  /* 需要的节拍数 */ 		      
	start = DWT_CYCCNT;       /* 刚进入时的计数器值 */
	
	while(cnt < delay_cnt)
	{
		cnt = DWT_CYCCNT - start; /* 求减过程中,如果发生第一次32位计数器重新计数,依然可以正确计算 */	
	}
}

/******************************************************************************
 * @brief      这里的延时采用CPU的内部计数实现,32位计数器
 *             OSSchedLock(&err);
 *			   bsp_DelayUS(5);
 *			   OSSchedUnlock(&err); 根据实际情况看看是否需要加调度锁或选择关中断
 * @param[in]  _delay_time    :    延迟长度,单位1 us
 * @return     none
 * @note       1. 主频168MHz的情况下,32位计数器计满是2^32/168000000 = 25.565秒
 *                建议使用本函数做延迟的话,延迟在1秒以下。  
 *             2. 实际通过逻辑分析仪测试,微妙延迟函数比实际设置实际多运行0.25us左右的时间。
 *             3. 测试硬件:STM32F407VET6
******************************************************************************/
void bsp_delay_us(uint32_t _delay_time)
{
	uint32_t cnt, delay_cnt;
	uint32_t start;
		
	start = DWT_CYCCNT;                                     /* 刚进入时的计数器值 */
	cnt = 0;
	delay_cnt = _delay_time * (SystemCoreClock / 1000000);	 /* 需要的节拍数 */ 		      

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

/******************************************************************************
 * @brief      为了让底层驱动在带RTOS和裸机情况下有更好的兼容性
 *             专门制作一个阻塞式的延迟函数,在底层驱动中ms毫秒延迟主要用于初始化,并不会影响实时性。 
 * @param[in]  _delay_time    :    延迟长度,单位1 ms
 * @return     none
******************************************************************************/
void bsp_delay_ms(uint32_t _delay_time)
{
	bsp_delay_us(1000 * _delay_time);
}

三、测试

下面通过一个简单的 demo 测试一下 us 级的延时函数,通过翻转 PC0 的电平状态,再通过逻辑分析仪查看延时效果:

c 复制代码
int main(void)
{
	/******
	 * 系统及外设初始化函数
	 *****/
	
	bsp_dwt_init();
	
	/* 使用PC0测试时间 */
	{
		GPIO_InitTypeDef GPIO_InitStructure;
			
		RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);

		GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	/* 输出类型为推挽 */
		GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;	/* 内部上拉电阻使能 */
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;	/* 复用模式 */
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOC, &GPIO_InitStructure);		
		
	}

	while (1)
	{
		GPIOC->BSRR = (uint32_t)GPIO_Pin_0;
		bsp_delay_us(1);
		GPIOC->BSRR = (uint32_t)GPIO_Pin_0 << 16;
		bsp_delay_us(1);
	}

	return 0;
}

bsp_delay_us(10)

由于本人使用的逻辑分析仪精度较低,只能看个大概数据,大致要比实际设置的时间多运行 0.25 us

bsp_delay_us(1)

注意事项

在烧录运行程序的时候,由于下载器的问题,早期用的 D 版 JLINK,不能正常复位 DWT。所以 DWT 时钟计数器容易出现不运行的情况,而调试状态或者重新上电都不存在问题,使用的时候要注意。

相关推荐
网易独家音乐人Mike Zhou2 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
PegasusYu5 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
文弱书生6569 小时前
输出比较简介
stm32
黑客呀12 小时前
[系统安全]Rootkit基础
stm32·单片机·系统安全
小A15912 小时前
STM32完全学习——使用SysTick精确延时(阻塞式)
stm32·嵌入式硬件·学习
楚灵魈12 小时前
[STM32]从零开始的STM32 HAL库环境搭建
stm32·单片机·嵌入式硬件
小A15912 小时前
STM32完全学习——使用标准库点亮LED
stm32·嵌入式硬件·学习
code_snow14 小时前
STM32--JLINK使用、下载问题记录
stm32·单片机·嵌入式硬件
youcans_16 小时前
【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
stm32·单片机·嵌入式硬件·电机控制·foc
YuCaiH20 小时前
【STM32】MPU6050简介
笔记·stm32·单片机·嵌入式硬件