STM32F4 计数器设计

要在STM32 F4上 测试算法 性能 (重点原子的开发板 180MHz 的频率)
1 要精确到us 不是 ms,裸机程序
2 在不加RTC的情况下如何记录系统运行的时间。一个32位 uint32_t (2^32=4,294,967,296) (大约可以表示1.19小时)
曾经用过定时器,每中断一次计算器加1. 1ms 中断一次程序可运行,但需要精确到us,如果是1us中断一次,程序就没法跑了,不停中断


volatile uint32_t T = 0;
volatile uint32_t T1 = 0;
volatile uint32_t T2 = 0;
volatile uint32_t reload = 0;
.....

reload = SysTick->LOAD;
T1 = SysTick->VAL;
algo();
T2 = SysTick->VAL;
if (T1 != T2)
{
       if (T2 < T1)
    {
        T = T1 - T2;
    }
    else
    {
        T = reload - T2 + T1;
    }
}

当需要在没有RTC的情况下记录系统运行时间时,我们可以使用STM32的系统滴答定时器(SysTick)来实现。

系统滴答定时器是一个24位的计数器,它可以按照预设的时间间隔(通常为1ms)自动递减,当计数器减为0时,就会产生一次SysTick中断。在中断服务程序中,我们可以记录系统的运行时间,并进行相应的处理。

为了精确到us,我们需要根据SysTick定时器的时钟频率来计算每个SysTick定时器中断所表示的时间。在STM32F4上,SysTick定时器的时钟频率是AHB总线时钟频率(通常等于系统时钟频率)的1/8。因此,如果我们的系统时钟频率是180MHz,则SysTick定时器的时钟频率是180/8=22.5MHz。

计算每个SysTick中断所表示的时间(即SysTick定时器的计数周期)的方法如下:

由于SysTick定时器是一个24位的计数器,因此它的计数周期是2^24 = 16,777,216。
SysTick定时器的时钟频率是22.5MHz,因此每个SysTick中断所表示的时间是16,777,216/22.5MHz ≈ 0.744ms。
在中断服务程序中,我们可以记录系统的运行时间(以us为单位)的代码如下

volatile uint32_t systick_count = 0;  // 系统运行时间(以us为单位)

void SysTick_Handler(void)
{
    systick_count += 744;  // 每个SysTick中断表示的时间是744us
}

uint32_t get_system_time_us(void)
{
    uint32_t ticks;
    uint32_t cnt;
    do {
        ticks = systick_count;
        cnt = SysTick->VAL;
    } while (ticks != systick_count);
    return ticks + 744 - cnt / (SystemCoreClock / 22500000);
}


在上面的代码中,我们定义了一个全局变量systick_count来记录系统运行的时间(以us为单位),并在SysTick中断服务程序中每次增加744。这个数值是根据上面的计算得到的每个SysTick中断所表示的时间(即0.744ms)除以1000得到的,即744us。

在get_system_time_us()函数中,我们先读取当前的systick_count值和SysTick定时器的当前计数值(cnt),如果在读取过程中SysTick中断发生了,我们就再次读取,直到两次读取得到的systick_count值相同。然后我们将这两个值相加,并减去cnt/(SystemCoreClock/22500000)的值,得到系统运行的时间(以us为单位)。

这个差值的计算方法是根据SysTick计数器的减法计算方法得到的。当SysTick计数器减为0时,它会自动重载
使用DWT(Dat Watchpoint and Trace)寄存器进行计时
DWT寄存器是专门用来进行调试的寄存器,其中包括一个32位的计数器CYCCNT,可以实现精确计时。使用DWT寄存器需要开启调试功能,并且在编译时需要将调试信息加入代码中。

具体的使用方法如下:

在main函数中开启DWT寄存器,代码如下:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;


在需要进行计时的地方使用DWT_CYCCNT寄存器获取当前计数值,代码如下:

uint32_t start = DWT->CYCCNT;
algo();
uint32_t end = DWT->CYCCNT;
uint32_t cycles = end - start;


这样就可以使用DWT寄存器实现精确计时。

需要注意的是,DWT寄存器只能在调试模式下使用,而且在初始化时需要先开启调试模式才能使用DWT寄存器,这会对系统的性能产生一定影响。

总结:

以上是几种常用的方法,在STM32 F4上实现精确计时。不同的应用场景可以选择不同的方法来实现计时。需要根据实际情况选择合适的方法,权衡计时的精度和系统的性能。

为了实现更精确的时间记录,可以使用STM32F4的SysTick定时器。SysTick是一个24位定时器,可以以CPU时钟的速度自动递减。使用SysTick的计数器值,可以记录系统从启动以来的时间。可以通过以下步骤实现:

在启动代码中初始化SysTick定时器,并启用SysTick中断。在每个SysTick中断处理程序中将一个计数器加1,并保存SysTick计数器的当前值。
系统启动后,在任何需要记录时间的地方,读取SysTick计数器的当前值。这将给出系统启动以来经过的CPU时钟周期数。通过除以CPU时钟频率,可以得到经过的时间,以微秒为单位。
以下是一个伪代码示例,展示如何在不加RTC的情况下使用SysTick定时器记录系统运行时间,以1微秒的精度:

volatile uint32_t systick_counter = 0;

void systick_handler() {
    systick_counter++;
}

void main() {
    // Initialize SysTick timer with 1us period
    SysTick_Config(SystemCoreClock/1000000);

    while(1) {
        // Read SysTick counter value
        uint32_t current_time = systick_counter;

        // Perform algorithm
        algo();

        // Read SysTick counter value again
        uint32_t elapsed_time = systick_counter - current_time;

        // elapsed_time now contains the time taken to run algo(), in microseconds
    }
}

在此示例中,systick_handler() 是 SysTick 中断处理程序。它每次SysTick中断时会将 systick_counter 计数器加1。在 main() 中,我们读取 systick_counter 的当前值,并在 algo() 函数执行后再次读取该值。将这两个值相减,即可得到 algo() 函数的执行时间,以微秒为单位。

https://blog.csdn.net/Joyerx/article/details/126531106

你可以使用 STM32 F4 的内部时钟来测量系统运行的时间,它可以提供微秒级别的准确度。你可以使用 STM32F4 的 SysTick 定时器来记录系统运行的时间,它有一个 24 位计数器,最大可以表示 16777215us (16.77s)。

用毫秒就行,算法可以循环几千几万遍来测试

可以使用STM32的内部时钟,这个可以提供比定时器更高的精度,可以达到微秒级别。

为了精确记录程序运行的时间并测试算法性能,可以使用STM32 F4的定时器功能。下面是可能的解决方案.
使用系统滴答定时器 SysTick。SysTick 模块是 Cortex-M3/M4 处理器内置的计数器,可以用于实现延迟、定时等功能。SysTick 可以通过配置重载值(LOAD)和中断优先级来实现不同的定时精度。
在 SysTick 的中断处理函数中,使用一个全局变量(例如 uint32_t counter)来记录程序运行的时间。每次 SysTick 中断发生时,将 counter 加上 SysTick 的重载值(LOAD)即可。
如果需要更精确的时间记录,可以使用定时器外设来代替 SysTick。定时器可以配置成一定的计数精度,并且在计数到一定值时产生中断。在定时器中断处理函数中,更新计数器变量的值。定时器的计数精度可以通过调整预分频器、计数器值等参数来实现。
可以使用 STM32F4 提供的性能分析工具(如 STM32CubeProfiler)来分析算法的性能。这些工具可以帮助您分析程序在 CPU、存储器、外设等方面的资源利用率,找到瓶颈并进行优化。
以下是使用 SysTick 来记录程序运行时间的示例代码,其中 SysTick 的重载值(LOAD)为 SystemCoreClock / 1000000 - 1,即每隔 1us 触发一次中断。代码中,每次调用 getTimeUs() 函数都会返回程序运行的时间(单位为 us)。

volatile uint32_t counter = 0;

void SysTick_Handler(void)
{
    counter += 1;
}

void initSysTick(void)
{
    // 配置 SysTick
    SysTick_Config(SystemCoreClock / 1000000 - 1);
}

uint32_t getTimeUs(void)
{
    return counter;
}

int main(void)
{
    initSysTick();

    // 在这里进行算法测试

    uint32_t startTime = getTimeUs();
    algo();
    uint32_t endTime = getTimeUs();

    printf("程序运行时间为 %lu us\n", endTime - startTime);
}

如果需要更精确的时间记录,可以使用定时器外设。以下是使用 TIM2 定时器来记录程序运行时间的示例代码,其中定时器的计数精度为 1us。

volatile uint32_t counter = 0;

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        counter += 1;
        TIM2->SR &= ~TIM_SR_UIF;  // 清除中断标志
    }
}

void initTimer(void)
{
    // 配置 TIM2 定时器
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    TIM2->PSC = SystemCoreClock / 1000000 - 1;  // 预分频器,时钟周期为 1us
    TIM2->ARR = 0xFFFFFFFF;  // 自动重载值,定时器溢出时间为 4294.967295 s
    TIM2->DIER |= TIM_DIER_UIE;  // 使能更新中断
    NVIC_EnableIRQ(TIM2_IRQn);
    TIM2->CR1 |= TIM_CR1_CEN;  // 启动定时器
}

uint32_t getTimeUs(void)
{
    return counter;
}

int main(void)
{
    initTimer();

    // 在这里进行算法测试

    uint32_t startTime = getTimeUs();
    algo();
    uint32_t endTime = getTimeUs();

    printf("程序运行时间为 %lu us\n", endTime - startTime);
}

请注意,由于算法测试本身可能会干扰程序运行时间的精确记录,因此需要进行多次测试并取平均值以获得更准确的结果。此外,如果算法的运行时间非常短(例如几百纳秒),则可能需要使用更高精度的计时方法,例如使用 DMA 计时器等。

为了在STM32 F4上精确地记录时间,可以使用SysTick定时器,该定时器是一个基于硬件的定时器,可以在裸机程序中使用。您可以使用SysTick->LOAD和SysTick->VAL寄存器来获取SysTick定时器的当前值,并通过比较前后两个值的差来计算经过的时间。以下是一个示例代码片段,可以帮助您开始记录时间:

volatile uint32_t T = 0;
volatile uint32_t T1 = 0;
volatile uint32_t T2 = 0;
volatile uint32_t reload = 0;

reload = SysTick->LOAD;
T1 = SysTick->VAL;
// 执行您要测试的算法
algo();
T2 = SysTick->VAL;
if (T1 != T2)
{
    if (T2 < T1)
    {
        T = T1 - T2;
    }
    else
    {
        T = reload - T2 + T1;
    }
}

请注意,使用SysTick定时器需要谨慎操作,并且需要在代码中禁用所有其他定时器和中断,以确保计时的准确性。此外,由于SysTick定时器的最大值为2^24-1,您可能需要定期重新加载定时器以避免溢出。

可以使用SysTick定时器来实现精确到us的计时功能,以及记录系统运行时间。具体实现方式如下:

SysTick_Config(SystemCoreClock / 1000000); // 设置SysTick定时器中断为每1us触发一次

volatile uint32_t g_sys_time = 0; // 记录系统运行时间,单位为us

void SysTick_Handler(void)
{
    g_sys_time++;
}
volatile uint32_t T1 = 0; // 代码段开始时的SysTick计数器值
volatile uint32_t T2 = 0; // 代码段结束时的SysTick计数器值

T1 = g_sys_time; // 记录代码段开始时的SysTick计数器值
// 执行需要测试的代码段
T2 = g_sys_time; // 记录代码段结束时的SysTick计数器值
uint32_t elapsed_time = T2 - T1; // 计算代码段的运行时间,单位为us

#include "core_cm4.h" // 包含CMSIS Cortex-M4库

volatile uint32_t g_sys_time = 0; // 记录系统运行时间,单位为us

void SysTick_Handler(void)
{
    __disable_irq(); // 关中断,确保读写g_sys_time是原子的
    g_sys_time++;
    __enable_irq(); // 开中断
}



#include "main.h"
#include "stm32f4xx_hal.h"

uint32_t SysTickCounter = 0;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();

  HAL_SYSTICK_Config(SystemCoreClock / 1000000);
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(SysTick_IRQn);

  while (1)
  {

  }
}

void SysTick_Handler(void)
{
  SysTickCounter++;
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 360;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1
                              |RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}


要在STM32 F4上测试算法性能,可以使用SysTick定时器计算程序运行时间。在不加RTC的情况下,可以通过SysTick定时器的计数器和重载值计算出程序运行时间。由于SysTick定时器的时钟频率与系统时钟频率相同,因此可以通过计算SysTick定时器的计数器值和重载值之间的差值来计算程序的运行时间。为了达到微秒级别的精度,可以将SysTick定时器的时钟分频系数设置为1,使其每微秒产生一次中断。例如,将SysTick定时器的重载值设置为179,即每1微秒产生一次中断,然后在每次中断时更新计数器的值,最后根据计数器的值计算程序运行时间。

首先选择基础定时器即可,配置定时器参数,主要关注预分频器寄存器TIMx_PSC,它的大小决定了延时的最小单位,由于TIM2的时钟为84MHz,所有TIM2_PSC设置为84,即每一次计数对应1us。
不要1us去中断,又要1us精度,好像没办法吧

STM32F4有两个32位的通用定时器,TIM2和TIM512,它们可以用来测量输入信号的脉冲长度或者产生输出波形。你可以利用这两个定时器来实现高精度的时间测量,分辨率可以达到1us。你可以通过设置定时器的预分频器(PSC)和自动重载寄存器(ARR)来控制定时器的计数周期。

如果你想要记录系统运行的时间,你可以使用一个32位的变量来存储定时器的计数值,每次定时器溢出时,就将变量加一。这样,你就可以得到系统运行的时间,单位为us。一个32位的变量可以表示的最大时间为2^32 us,约等于1.19小时。如果你需要更长的时间,你可以使用一个64位的变量,或者使用两个32位的变量来拼接成一个64位的变量。

如果你不想要使用定时器中断,你也可以使用定时器的更新事件(UEV)来触发DMA传输,将定时器的计数值传输到内存中。这样,你就可以避免中断的开销,提高程序的效率

不知道你这个问题是否已经解决, 如果还没有解决的话:

如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

STM32F4的SysTick定时器是一个24位定时器,足够你的us级别计时使用了,至于多久中断一次,就看你的需求量,确实没必要1us中断一次。

在不加RTC的情况下记录系统运行时间,可以使用STM32芯片内置的SysTick定时器,该定时器可以产生中断,实现计时的功能。以下是一个示例程序:

#include "stm32f4xx.h"

volatile uint32_t systime_us = 0;

void SysTick_Handler(void)
{
    systime_us++;
}

int main()
{
    // 配置SysTick定时器
    SysTick_Config(SystemCoreClock / 1000000);

    // 循环中运行测试算法
    while (1)
    {
        uint32_t t_start = systime_us;

        // 运行测试算法

        uint32_t t_end = systime_us;

        uint32_t time_used = t_end - t_start;
    }
}

在上述示例中,SysTick定时器每计数一次就会产生一个中断,中断处理函数会将系统运行时间systime_us加1。在主函数中运行测试算法,先记录下开始时间t_start,运行结束后记录下结束时间t_end,计算出运行时间time_used。注意,由于systime_us是uint32_t类型,它最大能表示的时间为4294967296微秒,即约为1.19小时,超过这个时间后systime_us会溢出重新计数。如果要在更长的时间内记录系统运行时间,需要使用外部RTC模块进行时间记录。

方案来自 梦想橡皮擦 狂飙组基于 GPT 编写的 “程秘”


对于需要精确到微秒的定时任务,裸机程序中通常使用定时器和外部中断来完成。一般可以使用 SysTick 定时器实现定时器中断。SysTick 定时器是 Cortex-M 内核中内置的系统定时器,可以产生精确的定时中断。

首先需要设置 SysTick 定时器的时钟源和中断时间间隔。在 STM32F4 系列的芯片中,SysTick 定时器的时钟源是 CPU 的时钟,可以使用以下代码设置 SysTick 定时器中断时间间隔为 1 微秒:

SysTick_Config(SystemCoreClock / 1000000);

在 SysTick 定时器中断服务函数中,可以记录系统运行的时间,例如:

void SysTick_Handler(void)
{
    T++;
}

在算法函数中,记录开始时间和结束时间,然后计算算法执行的时间。根据 SysTick 定时器的中断时间间隔,可以将时间转换为微秒。例如:

volatile uint32_t start_time = 0;
volatile uint32_t end_time = 0;
volatile uint32_t elapsed_time_us = 0;

start_time = T;
algo(); // 执行算法
end_time = T;

elapsed_time_us = (end_time - start_time) % (1 << 24); // 模 2^24 转换为微秒

需要注意的是,由于 SysTick 定时器的计数器是 24 位的,只能计数 2^24 个时钟周期,约为 16777216 微秒,大约是 4.66 小时。如果需要测试更长的时间,可以使用 RTC 定时器来记录系统时间。可以使用 RTC 定时器来定时触发外部中断,然后在中断服务函数中记录系统运行时间。

要精确到us,可以使用STM32 F4的定时器和外部中断来实现。下面是一些实现思路和代码示例:

1.使用定时器来计时
可以使用STM32 F4的定时器模块来进行精确计时。具体来说,可以配置一个定时器来产生1us的中断,然后在中断服务函数中更新系统运行时间。下面是一个代码示例:

volatile uint32_t system_time_us = 0;

void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF) {
        TIM2->SR &= ~TIM_SR_UIF;  // clear interrupt flag
        system_time_us++;  // increment system time in microseconds
    }
}

void configure_timer()
{
    // enable clock for TIM2
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

    // configure TIM2 to generate an interrupt every 1us
    TIM2->PSC = 71;  // set prescaler to 72
    TIM2->ARR = 1;  // set auto-reload register to 1
    TIM2->DIER |= TIM_DIER_UIE;  // enable update interrupt

    // enable TIM2 interrupt in NVIC
    NVIC_EnableIRQ(TIM2_IRQn);

    // start TIM2
    TIM2->CR1 |= TIM_CR1_CEN;
}

在程序中调用configure_timer()函数来配置定时器。然后在主循环中,可以使用system_time_us变量来记录系统运行时间。

2.使用外部中断来计时
除了定时器,还可以使用STM32 F4的外部中断模块来进行计时。具体来说,可以连接一个外部时钟信号到一个GPIO引脚,然后在GPIO的外部中断服务函数中更新系统运行时间。下面是一个代码示例:

volatile uint32_t system_time_us = 0;

void EXTI0_IRQHandler(void)
{
    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;  // clear interrupt flag
        system_time_us++;  // increment system time in microseconds
    }
}

void configure_gpio_interrupt()
{
    // enable clock for GPIOA and SYSCFG
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

    // configure PA0 as input with pull-up
    GPIOA->MODER &= ~GPIO_MODER_MODE0;
    GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0;

    // configure EXTI0 to trigger on rising edge
    EXTI->IMR |= EXTI_IMR_MR0;
    EXTI->RTSR |= EXTI_RTSR_TR0;

    // enable EXTI0 interrupt in NVIC
    NVIC_EnableIRQ(EXTI0_IRQn);
}
在程序中调用configure_gpio_interrupt()函数来配置GPIO外部中断。然后在主循环中,可以使用system_time_us变量来记录系统运行时间。