从 32 位定时器获取 64 位时间戳
Get 64bit timestamps from a 32bit timer
在我的 stm32wb55 上,我使用 32 位定时器“tim2”从系统启动后的 32 位寄存器“CNT”中读取时间。通过预缩放,我在 putty-console 上显示了以微秒为单位的时间,并且效果很好。但现在,我需要记住更高的价值。所以我想用64位整数来存储时间。
有人知道这样做的简单方法吗?
如果您仅从非 ISR [非中断服务] 上下文访问它,则非常简单。
如果您有 ISR,基础级别需要 lock/unlock 中断处理。 ISR not 必须与定时器中断相关。它可以用于 any ISR(例如 tty、磁盘、SPI、video/audio,等等)。
这里有一些简单的半裸机实现的代表性代码[这类似于我在一些 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 位计时器值的 each 次翻转,则此方法工作正常。
tim2
定时器是32位分辨率定时器,你要64位分辨率。有两种方法可以模拟 64 位计数器,以跟踪您的正常运行时间。
每当您达到要跟踪的时间单位时,就会递增一个变量。但这将是极其低效的,因为微控制器将进行大量持续的上下文切换。
第二种方法是用 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)
得到微秒。我不知道你打算用微控制器做什么,但数秒对我来说听起来更有意义。
如果你真的想要精度,你仍然可以通过计算秒来实现,你可以将定时器计数器值添加到微秒计数,你可以通过将秒细分为微秒来获得微秒,这样你就可以抵消微秒还没有被添加到第二个计数中。
在我的 stm32wb55 上,我使用 32 位定时器“tim2”从系统启动后的 32 位寄存器“CNT”中读取时间。通过预缩放,我在 putty-console 上显示了以微秒为单位的时间,并且效果很好。但现在,我需要记住更高的价值。所以我想用64位整数来存储时间。
有人知道这样做的简单方法吗?
如果您仅从非 ISR [非中断服务] 上下文访问它,则非常简单。
如果您有 ISR,基础级别需要 lock/unlock 中断处理。 ISR not 必须与定时器中断相关。它可以用于 any ISR(例如 tty、磁盘、SPI、video/audio,等等)。
这里有一些简单的半裸机实现的代表性代码[这类似于我在一些 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 位计时器值的 each 次翻转,则此方法工作正常。
tim2
定时器是32位分辨率定时器,你要64位分辨率。有两种方法可以模拟 64 位计数器,以跟踪您的正常运行时间。
每当您达到要跟踪的时间单位时,就会递增一个变量。但这将是极其低效的,因为微控制器将进行大量持续的上下文切换。
第二种方法是用 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)
得到微秒。我不知道你打算用微控制器做什么,但数秒对我来说听起来更有意义。
如果你真的想要精度,你仍然可以通过计算秒来实现,你可以将定时器计数器值添加到微秒计数,你可以通过将秒细分为微秒来获得微秒,这样你就可以抵消微秒还没有被添加到第二个计数中。