当前位置: 首页 > article >正文

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

目录

  • 零、前言
  • 一、DWT
    • 1、DEMCR
    • 2、DWT_CTRL
    • 3、DWT_CYCCNT
  • 二、实现代码
  • 三、测试


零、前言

在 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 使能

二、实现代码

#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 的电平状态,再通过逻辑分析仪查看延时效果:

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 时钟计数器容易出现不运行的情况,而调试状态或者重新上电都不存在问题,使用的时候要注意。


http://www.kler.cn/a/381579.html

相关文章:

  • 浅谈ORACLE中间件SOA BPM,IDM,OID,UCM,WebcenterPortal服务器如何做迁移切换
  • 牛客周赛73B:JAVA
  • 关于埃斯顿机器人文件导出或者系统日志导出
  • RK3506开发板:智能硬件领域的新选择,带来卓越性能与低功耗
  • 一款5k star的 Redis 客户端!!简洁高效!
  • 打造高效租赁小程序让交易更便捷
  • 【Linux】IPC进程间通信System V:并发编程实战指南(二)
  • xcode更新完最新版本无法运行调试
  • Postman断言与依赖接口测试详解
  • 人工智能AI 产品经理与传统产品经理工作到底有什么不同?非常详细收藏我这一篇就够了
  • kubernetes部署rancher无法查看pod日志及通过execute shell进入pod解决办法
  • 【Android Wi-Fi 操作命令指南】
  • pdf添加目录标签python(手动配置)
  • 【大数据学习 | kafka】producer之拦截器,序列化器与分区器
  • 数论——约数(完整版)
  • 动态避障-图扑自动寻路 3D 可视化
  • 使用Python简单实现客户端界面
  • 数据结构(8.7_2)——败者树
  • 苹果iOS 18.4将允许欧盟地区的iPhone用户设置默认地图和翻译应用
  • Excel 个人时间管理工具
  • 一文带您了解SonarScanner的原理和使用方法(包括maven构建和命令行执行)
  • 面试题:Vue生命周期
  • 【python】OpenCV—Connected Components
  • sheng的学习笔记-tidb框架原理
  • angular实现dialog弹窗
  • CentOS—OpenEulerOS系统联网指南