[APM32F4]

APM32F407 不显眼的DWT计数器

[复制链接]
807|1
手机看帖
扫描二维码
随时随地手机跟帖
kai迪皮|  楼主 | 2024-7-30 22:25 | 显示全部楼层 |阅读模式
1 背景

APM32F407是极海一款基于ARM Cortex-M4F处理器内核的MCU。有丰富的外设资源的同时,很多时候我们会忽略该内核自带的一些组件。今天我给大家带来的数据观察和跟踪(Data Watchpoint and Trace, DWT)模块。DWT模块提供了一种高效的方法用于性能分析、调试和事件跟踪。

2 技术文档

image-20240730220138211.png

image-20240730220249129.png

注:以上资料可见《arm_cortexm4_processor_trm_100166_0001_04_en》。

根据文档,我们可以得知的DWT模块的主要功能有:

1.  周期计数器(CYCCNT):用于测量从系统复位以来或从某个时刻起到当前时刻所经历的时钟周期数。

2.  比较器(Comparator):用于设置特定地址的监视点,可以在数据读写或代码执行时触发。

3.  事件计数器(Event Counter):用于计数特定事件的发生次数,如指令周期数、CPU周期数、睡眠周期数等。

本文章主要关注的一个关键寄存器是:**CYCCNT寄存器**

CYCCNT寄存器是DWT模块中最常用的寄存器之一。它用于记录从启动以来的时钟周期数。通过读取这个寄存器,可以精确地测量代码块的执行时间。以下是该寄存器的一些关键点:

   1. 寄存器宽度:CYCCNT寄存器是一个32位的寄存器。

   2. 启用和重置:需要手动启用CYCCNT寄存器,可以在每次测量前重置它。

   3. 最大可测时间:最大可测时间取决于处理器的时钟速度和寄存器的宽度。

3 DWT配置和使用

要使用DWT模块,需要进行一些基本配置,包括启用DWT模块和CYCCNT寄存器。以下是一个配置DWT模块的示例代码:

#ifndef SYSTEM_M4_DWTMEASURE_H
#define SYSTEM_M4_DWTMEASURE_H

#include <stdint.h>
#include "core_cm4.h"  // 包含CMSIS核心头文件

// 宏定义以启用或禁用DWT测量
#ifndef ENABLE_DWT_MEASURE
#define ENABLE_DWT_MEASURE 1 // 如果用户未定义,则默认启用
#endif

// 宏定义以启用或禁用DWT测量期间的中断禁用
#ifndef DISABLE_INTERRUPTS_WHILE_MEASURE
#define DISABLE_INTERRUPTS_WHILE_MEASURE 1 // 如果用户未定义,则默认启用
#endif

// 定义dwtCycleCounts数组的大小
#ifndef DWT_CYCLE_COUNTS_SIZE
#define DWT_CYCLE_COUNTS_SIZE 30 // 如果用户未定义,则默认大小
#endif

#if ENABLE_DWT_MEASURE

// 启用DWT计数器
#define ENABLE_DWT_CYCCNT()  do { CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; } while (0)

// 重置DWT计数器
#define RESET_DWT_CYCCNT()   do { DWT->CYCCNT = 0; } while (0)

// 获取DWT周期计数的宏定义
// cycle_count: 用于存储DWT周期计数的变量
// code_to_run: 需要测量DWT周期计数的代码块

#if DISABLE_INTERRUPTS_WHILE_MEASURE
    #define GET_DWT_CYCLE_COUNT(cycle_count, code_to_run) \
        do { \
            __disable_irq(); \
            ENABLE_DWT_CYCCNT(); \
            RESET_DWT_CYCCNT(); \
            code_to_run; \
            cycle_count = DWT->CYCCNT; \
            __enable_irq(); \
        } while (0)
#else
    #define GET_DWT_CYCLE_COUNT(cycle_count, code_to_run) \
        do { \
            ENABLE_DWT_CYCCNT(); \
            RESET_DWT_CYCCNT(); \
            code_to_run; \
            cycle_count = DWT->CYCCNT; \
        } while (0)
#endif

// 获取当前DWT周期计数的宏定义
#define GET_CURRENT_DWT_CYCLE_COUNT(cycle_count) \
    do { \
        cycle_count = DWT->CYCCNT; \
    } while (0)

#else // ENABLE_DWT_MEASURE

// 如果禁用DWT测量,提供空操作宏
#define ENABLE_DWT_CYCCNT()  // 无操作
#define RESET_DWT_CYCCNT()   // 无操作
#define GET_DWT_CYCLE_COUNT(cycle_count, code_to_run) code_to_run; cycle_count = 0;
#define GET_CURRENT_DWT_CYCLE_COUNT(cycle_count) cycle_count = 0;

#endif // ENABLE_DWT_MEASURE

#endif // SYSTEM_M4_DWTMEASURE_H

3.1 代码解读

1. 启用DWT模块

    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;

此行代码启用了DWT和ITM模块。

2. 启用CYCCNT寄存器

    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

这行代码启用了周期计数器。

3. 重置CYCCNT寄存器

    DWT->CYCCNT = 0;

重置周期计数器以开始新的测量。

4. 获取周期计数

    cycle_count = DWT->CYCCNT;

读取周期计数器的值。

5.  获取当前DWT周期计数: 增加了一个新宏 `GET_CURRENT_DWT_CYCLE_COUNT`,用于在任意时刻获取当前的周期计数值。这对于需要在多个代码段中测量周期数的情况非常有用。

3.2 如何使用

使用上述宏定义,可以轻松地测量任意代码块的执行时间,甚至可以在不同位置获取当前计数值。以下是一些示例:

volatile uint32_t DWTCycleCount = 0;
volatile uint32_t DWTtime = 0;

/** @defgroup GPIO_Toggle_Functions Functions
  @{
  */

/*!
* [url=home.php?mod=space&uid=247401]@brief[/url]     Main program
*
* @param     None
*
* @retval    None
*/
int main(void)
{
    APM_TINY_LEDInit(LED2);
    APM_TINY_LEDInit(LED3);

    /* USART Initialization */
    USART_Config_T usartConfigStruct;

    /* USART configuration */
    USART_ConfigStructInit(&usartConfigStruct);
    usartConfigStruct.baudRate = 115200;
    usartConfigStruct.mode = USART_MODE_TX_RX;
    usartConfigStruct.parity = USART_PARITY_NONE;
    usartConfigStruct.stopBits = USART_STOP_BIT_1;
    usartConfigStruct.wordLength = USART_WORD_LEN_8B;
    usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;

    /* COM1 init*/
    APM_TINY_COMInit(COM1, &usartConfigStruct);

    SystemCoreClockUpdate();
   
    /* Init delay function */
    Delay_Init();

    printf("DWT Measurement Demo\r\n");
    printf("The current MCU frequency is %d Hz\r\n",SystemCoreClock);

    while (1)
    {
        GET_DWT_CYCLE_COUNT(DWTCycleCount,  \
        APM_TINY_LEDOff(LED2);             \
        APM_TINY_LEDOff(LED3);             \
         Delay_ms(500);                  \
         APM_TINY_LEDOn(LED2);          \
         APM_TINY_LEDOn(LED3);          \
        );

        DWTtime = DWTCycleCount/(SystemCoreClock/1000000);
        printf("DWTCycleCount: %d\r\n",DWTCycleCount);
        printf("DWTtime: %d us\r\n\r\n",DWTtime);
        
        Delay_ms(2000);

    }
}


逻辑分析仪抓取的波形:

d6399062-3375-4535-bc74-da0186b8a9e6.png

可以发现逻辑分析仪抓取的波形高电平时间基本为500ms。

使用串口打印是本demo的一个通用做法:
image-20240730222331686.png

很多时候我们并不需要使用串口打印,可直接在仿真界面读取哦。

image-20240730215027083.png
4 使用注意

4.1 最大可测时间

最大可测时间取决于处理器的时钟速度和DWT\_CYCCNT寄存器的宽度。对于32位宽的DWT\_CYCCNT寄存器:

1. 如果处理器时钟速度为100 MHz,最大可测时间大约为42.94秒(2^32 / 100,000,000)。

2. 如果处理器时钟速度为250 MHz,最大可测时间大约为17.18秒(2^32 / 250,000,000)。

公式计算最大可测时间为:

    最大可测时间 = 2^32 / 处理器时钟速度

4.2 中断的影响

中断的发生可能会影响DWT周期计数的准确性。当中断发生时,处理器可能会切换到执行中断服务程序(ISR),导致DWT\_CYCCNT寄存器中计入额外的周期数。为了确保测量的准确性,可以在测量期间禁用中断。

不过,禁用中断可能不适用于所有应用,特别是那些具有时间关键性中断的应用。可以使用 `DISABLE_INTERRUPTS_WHILE_MEASURE` 宏定义来控制测量期间是否禁用中断。

4.3 时间计算

为了将测量的时钟周期转换为实际时间,可以使用以下公式:

    time_in_seconds = cycle_count / processor_clock_speed

例如,如果处理器时钟速度为250 MHz,而测量的周期数为5000000,则执行时间为:

    time_in_seconds = 5000000 / 250,000,000 = 0.02秒

代码请见 APM32F4_不显眼的DWT计数器_code.zip (768.47 KB)

使用特权

评论回复
chenjun89| | 2024-8-14 08:28 | 显示全部楼层
这种深度测试代码性能时需要用到

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

31

主题

212

帖子

11

粉丝