前置介绍
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);
}
程序现象

每一秒亮灯, 熄灯.