DWT基础应用与获取程序运行时间Debug练习(上)

前置介绍

DWT (Data Watchpoint and Trace 数据观察点及跟踪)简介
  1. DWT 是 一个内核外设
  2. DWT 用于系统调试和跟踪
  3. DWT 中的寄存器 CYCCNT 记录内核时钟运行个数
  4. CYCCNT 精度非常高(可以达到纳秒, HAL_Delay 是毫秒级, TIM 定时器是微秒级)
  5. 其几乎不受任何的中断和代码影响 (因为依赖的是硬件计数器)
DWT 精度
  1. 主频越高, DWT 精度越高
  2. 当 CYCCNT 溢出之后, 会清零重新开始向上计数.
DWT 相关寄存器
DEMCR
DWT_CYCCNT

可以根据某一代码运行前后两次的数值求一个差值, 就能够得到这段代码运行的具体时间是多少.

但是在 HAL 库中没有对 DWT 的封装, 因为 HAL 库主要封装的都是偏向外设的.

而 DWT 属于 内核的一种外设, 所以需要我们自己去封装一些操作函数.

CYCCNTENA
  1. CYCCNTENA 是 DWT 控制寄存器 (DWT_CTRL) 的第一位
  2. 通过向该寄存器写 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);

}

程序现象

每一秒亮灯, 熄灯.

相关推荐
西梅汁2 小时前
C++ 设计模式三大类型理解
笔记
我要成为嵌入式大佬2 小时前
正点原子MP157--问题详解--五(beep编写报错端口繁忙)
stm32·嵌入式硬件·学习
振浩微433射频芯片3 小时前
433MHz在智能家居中的应用大全(一):智能窗帘篇——为什么稳定比花哨更重要?
网络·单片机·嵌入式硬件·物联网·智能家居
野指针YZZ3 小时前
XV6操作系统:proc机制学习笔记
笔记·学习
Hammer_Hans3 小时前
DFT笔记37
笔记
鸟电波3 小时前
硬件笔记——Allegro绘制器件封装和过孔
笔记·嵌入式硬件·智能硬件
西梅汁3 小时前
C++ 策略模式
笔记
咸鱼嵌入式3 小时前
【AutoSAR】详解CANIF模块
单片机·mcu·车载系统·autosar
小小的代码里面挖呀挖呀挖3 小时前
恒玄BES蓝牙耳机开发--IIC接口应用
笔记·单片机·物联网·学习·iot