DWT基础应用与获取程序运行时间Debug练习(上)
前置介绍
DWT (Data Watchpoint and Trace 数据观察点及跟踪)简介
- DWT 是 一个内核外设
- DWT 用于系统调试和跟踪
- DWT 中的寄存器 CYCCNT 记录内核时钟运行个数
- CYCCNT 精度非常高(可以达到纳秒, HAL_Delay 是毫秒级, TIM 定时器是微秒级)
- 其几乎不受任何的中断和代码影响 (因为依赖的是硬件计数器)
DWT 精度
- 主频越高, DWT 精度越高
- 当 CYCCNT 溢出之后, 会清零重新开始向上计数.
DWT 相关寄存器
DEMCR
DWT_CYCCNT
可以根据某一代码运行前后两次的数值求一个差值, 就能够得到这段代码运行的具体时间是多少.
但是在 HAL 库中没有对 DWT 的封装, 因为 HAL 库主要封装的都是偏向外设的.
而 DWT 属于 内核的一种外设, 所以需要我们自己去封装一些操作函数.
CYCCNTENA
- CYCCNTENA 是 DWT 控制寄存器 (DWT_CTRL) 的第一位
- 通过向该寄存器写 1 使能, 以启用 CYCCNT 计数器
项目配置
请直接 Copy 上一节手动建立工程模板所创建的模板, 并进行如下修改
创建对应目录和文件
User/ ├── dwt/ # DWT 模块文件 │ ├── bsp_dwt.h │ └── bsp_dwt.c │ ├── led/ # LED 模块文件 │ ├── bsp_led.h │ └── bsp_led.c │ └── ... # 其他文件在 User (用户代码) 目录下创建对应模块目录 dwt, led
分别在其中创建对应的 头文件和 C 文件
附: BSP 为 Board Support Package 板级支持包
Keil 中修改项目名, 添加对应文件
代码部分
#ifndef __MAIN_H #define __MAIN_H /* Includes ------------------------------------------------------------------*/ #include "stm32f1xx_hal.h" /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ /* Exported macro ------------------------------------------------------------*/ /* Exported functions ------------------------------------------------------- */ #endif /* __MAIN_H *//** * @brief DWT_BASE */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "led/bsp_led.h" #include "dwt/bsp_dwt.h" /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ uint64_t begin = 0; // 记录计时起始值 uint64_t end = 0; // 记录计时结束值 uint64_t duration = 0; // 记录程序运行所花的时间 (单位: DWT 计数器周期) uint64_t task_us = 0; // 记录程序运行所花的时间 (单位: DWT 计数器周期) /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* Private functions ---------------------------------------------------------*/ int main(void) { uint8_t a = 10; HAL_Init(); // 初始化 HAL 库 SystemClock_Config(); // 配置系统时钟, 设置为 72MHz DWT_Init(); // 启动 DWT 计数器, 用于精确测量程序运行时间 LED_GPIO_Config(); // 初始化 LED 引脚 while (1) { begin = DWT_GetTick(); // 记录第一段程序开始的时间 LED_RGB_ALL_ON(); // 打开 RGB 灯, 模拟任务运行 DWT_DelayMs(1000); // 精确延时 1 秒 LED_RGB_ALL_OFF(); // 关闭 RGB 灯, 模拟任务结束 DWT_DelayMs(1000); // 精确延时 1 秒 end = DWT_GetTick(); // 记录结束时间 duration = end - begin; // 计算任务耗时 (单位位 DWT 周期) DWT_TickToMicrosecond(duration, SystemCoreClock); // 转为微秒 } } /** * @brief System Clock Configuration * The system Clock is configured as follow : * System Clock source = PLL (HSE) * SYSCLK(Hz) = 72000000 * HCLK(Hz) = 72000000 * AHB Prescaler = 1 * APB1 Prescaler = 2 * APB2 Prescaler = 1 * HSE Frequency(Hz) = 8000000 * HSE PREDIV1 = 1 * PLLMUL = 9 * Flash Latency(WS) = 2 * @param None * @retval None */ void SystemClock_Config(void) { RCC_ClkInitTypeDef clkinitstruct = {0}; RCC_OscInitTypeDef oscinitstruct = {0}; /* Enable HSE Oscillator and activate PLL with HSE as source */ oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; oscinitstruct.HSEState = RCC_HSE_ON; oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; oscinitstruct.PLL.PLLState = RCC_PLL_ON; oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK) { /* Initialization Error */ while(1); } /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */ clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1; clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1; clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK) { /* Initialization Error */ while(1); } }#ifndef __BSP_DWT_H #define __BSP_DWT_H /* Includes ------------------------------------------------------------------*/ #include "main.h" // 引用 main.h 是因为需要用到很多的 HAL 库和定义. 所以也建议后续添加的每一个 .h 文件中都要引入 main.h /* 在 Cortex_M 里面有一个外设叫 DWT (Data Watchpoint and Trace), 该外设有一个32位的寄存器叫 CYCCNT, 它是一个向上的计数器, 记录的使内核时钟运行的个数, 假设内核频率为 72M, 内核跳一次的时间大概为 1/72M = 14ns (最长能记录的时间为: 60s = 2 的 32 次方 / 72 000 000) . 当 CYCCNT 溢出之后, 会清0重新开始向上计数 使能 CYCCNT 计数的操作步骤: 1. 先使能 DWT 外设设备, 这个由另外内核调试寄存器 DEMCR 的 位 24 控制, 写 1 使能 2. 使能 CYCCNT 寄存器之前, 先清0 3. 使能 CYCCNT 计数器, 这个由 DWT_CTRL (代码上宏定义为 DWT_CR) 的位0控制, 写 1 使能 4. CYCCNT 和 us 互相转换: 1 / SystemCoreCLock * CYCCNT (S) = 1000 / SystemCoreClock * CYCCNT (MS) = 1000000 / SystemCoreClock * CYCCNT (US) = (X)US */ /* DWT时间戳相关寄存器定义 */ #define DEMCR *(uint32_t *) (0xE000EDFC) #define DWT_CTRL *(uint32_t *) (0xE0001000) #define DWT_CYCCNT *(uint32_t *) (0xE0001004) #define DEMCR_TRCENA (1<<24) #define DWT_CTRL_CYCCNTENA (1<<0) void DWT_Init(void); uint32_t DWT_GetTick(void); uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency); void DWT_DelayUs(uint32_t time); void DWT_DelayMs(uint32_t time); void DWT_DelayS(uint32_t time); #endif /* __BSP_DWT_H *//** * @file bsp_dwt.c * @brief 使用内核定时器函数接口 */ #include "dwt/bsp_dwt.h" /** * @brief 初始化DWT计数器s * @param 无 * @retval 无 * @note 使用延时函数前, 必须调用本函数 */ void DWT_Init(void) { /* 使能 DWT 外设 */ DEMCR |= (uint32_t)DEMCR_TRCENA; /* DWT CYCNT 寄存器计数清0 */ DWT_CYCCNT = (uint32_t)0U; // 使能 CYCCNT 寄存器之前, 先清0 /* 使能 Cortex-M DWT CYCCNT 寄存器 */ DWT_CTRL |= (uint32_t)DWT_CTRL_CYCCNTENA; } /** * @brief 读取当前时间戳 * @param 无 * @retval 当前时间戳, 即 DWT_CYCCNT 寄存器的值 */ uint32_t DWT_GetTick(void) { return ((uint32_t)DWT_CYCCNT); } /** * @brief 节拍数转化时间间隔 (微秒单位) * @param tick: 需要转换的节拍数 * @param frequency: 内核时钟频率 * @retval 当前时间戳 (微秒单位) */ uint32_t DWT_TickToMicrosecond(uint32_t tick, uint32_t frequency) { return (uint32_t)(1000000.0 / frequency * tick); } /** * @brief DWT计数器实现精确延时, 32位计数器 * @param time: 延迟长度, 单位: us * @retval 无 */ void DWT_DelayUs(uint32_t time) { /* 将微秒转化为对应的时钟计数值 */ uint32_t tick_duration = time * (SystemCoreClock / 1000000); uint32_t tick_start = DWT_GetTick(); /* 刚进入时的计数器值 */ while(DWT_GetTick() - tick_start < tick_duration); } /** * @brief DWT计数器实现精确延时, 32位计数器 * @param time: 延迟长度, 单位: ms */ void DWT_DelayMs(uint32_t time) { for (uint32_t i = 0; i < time; i++) { DWT_DelayUs(1000); } } /** * @brief DWT计数器实现精确延时, 32位计数器 * @param time: 延迟长度, 单位: s */ void DWT_DelayS(uint32_t time) { for (uint32_t i = 0; i < time; i++) { DWT_DelayMs(1000); } }#ifndef __BSP_LED_H__ #define __BSP_LED_H__ /* Includes ------------------------------------------------------------------*/ #include "main.h" /* ----------------------------- LED 引脚定义 ----------------------------- */ // 红灯 #define LED_R_Pin GPIO_PIN_1 // 绿灯 #define LED_G_Pin GPIO_PIN_2 // 蓝灯 #define LED_B_Pin GPIO_PIN_3 // 所有灯都连接在 GPIOA #define LED_Port GPIOA /* ----------------------------- 函数声明 ----------------------------- */ void LED_GPIO_Config(void); /* ----------------------------- LED控制宏 ----------------------------- */ // 红灯控制 #define LED_R_ON() HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_RESET); // 点亮红灯 #define LED_R_OFF() HAL_GPIO_WritePin(LED_Port, LED_R_Pin, GPIO_PIN_SET); // 熄灭红灯 #define LED_R_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_R_Pin); // 翻转红灯状态 // 绿灯控制 #define LED_G_ON() HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_RESET); // 点亮绿灯 #define LED_G_OFF() HAL_GPIO_WritePin(LED_Port, LED_G_Pin, GPIO_PIN_SET); // 熄灭绿灯 #define LED_G_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_G_Pin); // 翻转绿灯状态 // 蓝灯控制 #define LED_B_ON() HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_RESET); // 点亮蓝灯 #define LED_B_OFF() HAL_GPIO_WritePin(LED_Port, LED_B_Pin, GPIO_PIN_SET); // 熄灭蓝灯 #define LED_B_TOGGLE() HAL_GPIO_TogglePin(LED_Port, LED_B_Pin); // 翻转蓝灯状态 /* ----------------------------- LED 组合控制宏 ----------------------------- */ // 三色灯全亮 #define LED_RGB_ALL_ON() LED_R_ON(); LED_G_ON(); LED_B_ON() // 三色灯全灭 #define LED_RGB_ALL_OFF() LED_R_OFF(); LED_G_OFF(); LED_B_OFF() // 仅亮红灯 #define LED_R_ON_ONLY() LED_R_ON(); LED_G_OFF(); LED_B_OFF() // 仅亮绿灯 #define LED_G_ON_ONLY() LED_R_OFF(); LED_G_ON(); LED_B_OFF() // 仅亮蓝灯 #define LED_B_ON_ONLY() LED_R_OFF(); LED_G_OFF(); LED_B_ON() #endif /* __BSP_LED_H__ *//** * @file bsp_led.c * @brief LED灯函数接口 */ #include "led/bsp_led.h" /** * @brief 初始化控制LED的GPIO引脚 * @note 配置为推挽输出, 默认全灭 */ void LED_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 使能GPIOA端口时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 初始化时关闭所有LED (低电平点亮, 默认全设为高电平) */ HAL_GPIO_WritePin(GPIOA, LED_R_Pin|LED_G_Pin|LED_B_Pin, GPIO_PIN_SET); /* 设置GPIO引脚 : LED_R_Pin LED_G_Pin LED_B_Pin */ GPIO_InitStruct.Pin = LED_R_Pin|LED_G_Pin|LED_B_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉不下拉 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速输出 HAL_GPIO_Init(LED_Port, &GPIO_InitStruct); }程序现象
每一秒亮灯, 熄灯.
