【问题标题】:Get 64bit timestamps from a 32bit timer从 32 位计时器获取 64 位时间戳
【发布时间】:2021-02-09 18:25:30
【问题描述】:

在我的 stm32wb55 上,我使用 32 位定时器“tim2”从系统启动后的 32 位寄存器“CNT”读取时间。通过预缩放,我在腻子控制台上显示时间(以微秒为单位),效果非常好。但是现在,我需要记住更高的值。所以我想用 64 位整数来记忆时间。

有人知道这样做的简单方法吗?

【问题讨论】:

  • tim2 的分辨率是 32 位,如果你想要一个 64 位的计数器,那么你应该在溢出时设置一个中断0xffffffff,然后增加一个 32 位的变量。这样你就得到了一个 64 位分辨率的计数器。
  • 不幸的是,我对中断一无所知。有没有几行代码的解决方案?
  • 或者你能简单描述一下我如何通过中断来做到这一点吗?
  • 在指南中,您将了解如何使用定时器中断和中断处理程序。设置你的定时器中断并在中断处理程序中增加一个uint32_t 变量。要获得全新的 64 位分辨率,只需执行此操作。 (uint64_t)((var << 32) & current_timer_value)

标签: android c timer stm32


【解决方案1】:

tim2 定时器是一个 32 位分辨率的定时器,你想要一个 64 位的分辨率。有两种方法可以模拟 64 位计数器,以跟踪您的正常运行时间。

  1. 每次达到要跟踪的时间单位时,都会增加一个变量。但这将是极其低效的,因为微控制器会进行大量持续的上下文切换。

  2. 第二种方法是使用 32 位变量扩展计时器。然后在溢出时增加此类变量。

MSB                           LSB
+--------------+ +--------------+
|  32bit uint  | |  32bit timer |
+--------------+ +--------------+

它的工作方式是,在定时器达到0xffffffff(这是 32 位无符号计数器的最大值)后,定时器将溢出并从 0 开始。如果在第 32 位之后还有另一个位,它将翻转(与递增相同)。你可以做的是通过增加一个变量来模拟这种精确的行为。

首先,设置你的计时器。

static TIM_HandleTypeDef s_TimerInstance = { 
    .Instance = TIM2
};

void setup_timer()
{
    __TIM2_CLK_ENABLE();
    s_TimerInstance.Init.Prescaler = ##; //Chose the correct value that fits your needs
    s_TimerInstance.Init.CounterMode = TIM_COUNTERMODE_UP;
    s_TimerInstance.Init.Period = 0xffffffff; //Chose the correct value that fits your needs
    s_TimerInstance.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //Also choose this value
    s_TimerInstance.Init.RepetitionCounter = 0;
    HAL_TIM_Base_Init(&s_TimerInstance);
    HAL_TIM_Base_Start(&s_TimerInstance);
}

你的处理程序,每次你的计时器到达0xffffffff时都必须调用它

extern void TIM2_IRQHandler();
void TIM2_IRQHandler()
{
    HAL_TIM_IRQHandler(&s_TimerInstance);
}

uint32_t extension;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    extension++; //Increment
}

结合扩展变量和计时器值。每次要获取扩展器计数器值时使用此函数。您可以将其设为内联以避免额外调用,也可以将其设为宏。

uint64_t get_time()
{
    return (extension << 32) & (__HAL_TIM_GET_COUNTER(&s_TimerInstance));
}

现在把所有东西粘在一起

int main(void)
{
    HAL_Init(); //Initialize HAL library
    InitializeTimer(); //Initialize timer
    
    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);

    while(1);
}

注意,现在 tim2 将被使用直到它溢出。不应更改,否则以下代码将不起作用。此外,设置除法器,使计时器按您之前所述每微秒递增一次。

此外,您可以使用计时器来计算秒数,然后计算微秒。如果您计算秒数,则最多可以数到 2^32 秒,即 4294967296。一年大约有 31536000 秒。使用 32 位计数器 (4294967296/31536000),您最多可以计算 136.19252 年的正常运行时间。然后通过将正常运行时间除以 1000000 (uptime/1000000) 来获得微秒。我不知道你打算用微控制器做什么,但数秒对我来说听起来更有意义。

如果您真的想要精确,您仍然可以通过计算秒数来做到这一点,您可以将计时器计数器值添加到微秒计数中,您可以通过将秒数降低到微秒来获得,这样您就可以抵消没有的微秒' t 被添加到第二个计数中。

【讨论】:

  • 到目前为止非常感谢!不幸的是,通过在我的代码中添加此方法,我有一个语法错误:extern "C" void TIM2_IRQHandler() { HAL_TIM_IRQHandler(&amp;s_TimerInstance); } 是否需要任何进一步的操作才能使用它?我必须说,我以前从未使用过这种类型的函数......
  • 另外我想在main的while循环中频繁调用时间计数器的值。但是如果我把get_time()方法放在while(1)里面,我直接用64bits得到时间,虽然我的值低于0xffffffff,对吧?
  • 或者在代码中有一个特殊的地方我必须把方法extern "C" void TIM2_IRQHandler() { HAL_TIM_IRQHandler(&amp;s_TimerInstance); } ?
  • 哦,我的错,你不需要extern "C",因为你正在使用C。
  • " 如果我​​把get_time()方法放在while(1)中,我直接用64bits获取时间,即使我的值低于0xffffffff,对吧?" 是的,你会得到一个64bits价值。
【解决方案2】:

如果您只从非 ISR [非中断服务] 上下文中访问它,那非常简单。

如果您有 ISR,则基本级别需要锁定/解锁中断处理。 ISR 确实不必须与定时器中断相关。它可以用于任何 ISR(例如 tty、磁盘、SPI、视频/音频等)。

这里是一个简单的半裸机实现的一些代表性代码 [这类似于我在一些 R/T 商业产品中所做的,特别是在 Xilinx FPGA 内的 microblaze 中]:

typedef unsigned int u32;
typedef unsigned long long u64;

volatile int in_isr;                    // 1=inside an ISR

volatile u32 oldlo;                     // old LSW timer value
volatile u32 oldhi;                     // MSW of 64 bit timer

// clear and enable the CPU interrupt flag
void cli(void);
void sti(void);

// tmr32 -- get 32 bit timer/counter
u32 tmr32(void);

// tmrget -- get 64 bit timer value
u64
tmrget(void)
{
    u32 curlo;
    u32 curhi;
    u64 tmr64;

    // base level must prevent interrupts from occurring ...
    if (! in_isr)
        cli();

    // get the 32 bit counter/timer value
    curlo = tmr32();

    // get the upper 32 bits of the 64 bit counter/timer
    curhi = oldhi;

    // detect rollover
    if (curlo < oldlo)
        curhi += 1;

    oldhi = curhi;
    oldlo = curlo;

    // reenable interrupts
    if (! in_isr)
        sti();

    tmr64 = curhi;
    tmr64 <<= 32;
    tmr64 |= curlo;

    return tmr64;
}

// isr -- interrupt service routine
void
isr(void)
{

    // say we're in an ISR ...
    in_isr += 1;

    u64 tmr = tmrget();
    // do stuff ...

    // leaving the ISR ...
    in_isr -= 1;
}

// baselevel -- normal non-interrupt context
void
baselevel(void)
{

    while (1) {
        u64 tmr = tmrget();
        // do stuff ...
    }
}

如果tmrget 的调用频率足够高,以至于它可以捕获每个 32 位计时器值的翻转,则此方法可以正常工作。

【讨论】:

  • 为什么不使用标准 C 类型 uint32_t、uint64_t 等?
  • @0___________ 因为 linux 内核使用它们。而且,我的原始代码使用了它们。而且,就个人而言,我认为(例如)u32 优于 uint32_t [让我休息一下],因为它简单明了并且更短 [并且它早于 uint32_t]。 typedef uint32_t u32; 会更好吃吗?
  • 确实省了不少墨
猜你喜欢
  • 2012-09-17
  • 2014-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-18
  • 2011-12-16
  • 2011-01-01
相关资源
最近更新 更多