gpt4 book ai didi

c - 更新 : SAM3X problems with systick handler and accessing systick value for microsecond resolution

转载 作者:太空宇宙 更新时间:2023-11-04 05:57:29 24 4
gpt4 key购买 nike

我目前正在努力处理一段非常简单的代码,它表明 ARM GCC 的 1 级优化器以某种方式破坏了一个简单的公式。
这使用标准编译器设置 (O1) 在最新的 Atmel 6.2 Studio 上运行。
Atmel 工具链\ARM GCC\Native\4.8.1426\arm-gnu-toolchain

代码非常少:

volatile uint32_t g_timing_tick_ms=0;
void SysTick_Handler(void)
{
g_timing_tick_ms++;
}
inline uint32_t get_millis()
{
return g_timing_tick_ms;
}

uint32_t get_micros()
{
return (g_timing_tick_ms * 1000 + (1000 - SysTick->VAL/84));
}

uint8_t timer_expired(timer_ *t)
{
uint32_t cur_us = get_micros();

uint32_t dt = cur_us - t->last_systick_us;

t->last_systick_us = cur_us;
if (t->elapsed <= dt)
{
// <--------- dt is regularly a huge value (around 0xfffffe00)
// this happens because t->last_systick_us sometimes is bigger than cur_us (overflow)
// however get_micros() is without such an error, cur_us ALWAYS increases and the
// variables are not modified outside this function which is called every 500us.
t->elapsed = t->interval;
return 1;
}
t->elapsed -= dt;
return 0;
};

get_millis 从每毫秒调用一次的 Systick 计时器返回毫秒数。
系统定时器为24位,以84mhz的速率递减计数。
get_micros() 使用此 systick 值并计算自上次重置以来经过的微秒,然后加上毫秒 *1000。
这工作得很好,我找不到更快的方法来获取当前微秒作为时间戳。

第三个函数显示了一个偶发问题,有时存储在 t->last_systick_us 中的值(直接来自 get_micros() )比它应该的大。确切地说,最后三个十进制值总是 986 (20065986,1000986)。
该值大约 1000us 太高,十进制数末尾始终带有 986。
每隔一些电话就会发生这种情况。

解决方法:
1)改变:

uint32_t dt = cur_us - t->last_systick_us; ---> 
volatile uint32_t dt = cur_us - t->last_systick_us;

将此变量更改为 volatile 解决了这个问题,这导致了编译器以一种糟糕的方式处理它的想法。该变量不是静态的,它是本地变量,没有任何东西可以从外部对其进行修改,volatile 是一种浪费,但可以解决数学问题。2)改变

uint32_t get_micros() ----->内联 uint32_t get_micros()这也解决了这个问题,但是这也不是一个好的解决方法,因为编译器不必将其内联。所以这可能会在未来的某个时候适得其反。

3)在值更改之前将任何调试写入或类似内容添加到计时器函数中也可以修复它,具体取决于代码。

这似乎是 gcc-ARM 核心编译器中的错误,优化器以某种方式破坏了数学。我可以提供 asm,我不知道 ARM ASM,但我注意到它在靠近 get_micro() 公式的部分删除了一个“sub”。
我不认为我这里有代码错误,它太简单了(而且效果很好)。此外,解决方案表明这不是编码错误,在函数中添加或删除内联除了优化之外应该没有任何区别。

也许有人知道该怎么做,经历过/解决了类似的行为。我正处于完全删除优化器的边缘,但这可能会花费相当多的性能。

更新

当我意识到可能的原因并且我认为是这种情况时,我正准备准备 asm 差异(并通读)。

我认为这是一个竞争条件,Systick 的中断还没有触发,但是 systick 定时器溢出了。
结果是大约 1000us 的误差(随着定时器每 84ns 滴答一次,误差会小一些。
这将导致错误,不可预测,并且通过更改代码,循环会发生变化,通过更改循环,它可能会以导致竞争条件稍后出现的方式对齐代码。

我进行了调试,并且可以验证问题在 Systick 重新加载后不久就发生了。

很抱歉在编译器错误中做出过快的猜测。

最佳答案

问题是由于竞争条件引起的,编译器没有错误。我不完全确定,但我认为这是 SAM3x8e ARM 实现(或一般的 Cortex M3)中的弱点,或者他们没有考虑使用 IRQ 和 Systick 值的人。

无论我尝试过什么修复或代码,我总是遇到以下两种情况之一:在 get_micros() 计算期间触发中断Systick 在 get_micros() 期间溢出,但没有触发中断。

get_micros() 读取一个旧的毫秒/Systick 值和一个新的 systick/毫秒变量,导致近 1 毫秒的错误。

有人可能会考虑添加 NVIC_DisableIRQ(SysTick_IRQn);一开始有帮助。它没有,它在 ASF 中没有记录,但 NVIC 不处理 Systick 启用/禁用,IRQn 为负,不会对异常产生任何影响。
有趣的是,NVIC 用于在 Atmels 驱动程序代码中设置优先级,但可能也没有效果。
另一个有趣的方面是 atmel 在它自己的一些源代码示例中使用了这个调用。(在浪费了 6 个小时之后就没那么有趣了)

我尝试用 __disable_irq() 保护代码但没有产生积极影响,同样的竞争条件发生了(定时器被改变但 Systick 值还没有超过)

我试过这个:

if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) micro_us+=1000;

这会读取系统控制寄存器并检查自上次读取后计数器是否溢出。
根据 atmel sam3x8ek 数据表,它应该这样做。
但是,读取此寄存器的两个未记录的副作用:
1) 它启动另一个 Systick 中断!
2) 它将计数标志值重置为 0
数据表中对这两个操作只字未提,2) 没用但不会成为问题 1) 是个阻碍。

禁用 IRQ 的唯一方法是在系统处理程序中,SCB->SHCSR。
但是,如果发生这种情况(硬故障),则会导致崩溃。

可能的解决方案:禁用 systick 的时钟,等待挂起的 IRQ 发生,然后继续。这将确保读取值和读取中断同步,并且会引入一个小的定时错误并在函数本身中花费额外的时间。

经过大约 4-5 小时的调试和与错误或未记录的功能作斗争后,我想出的最佳解决方案是以下代码:

  uint32_t get_micros()
{
//__disable_irq(); // does not affect systick
static uint32_t last_value;
volatile uint32_t timestamp = g_timing_tick_ms; // set to volatile to make sure the compiler does not optimize here
volatile uint32_t val = SysTick->VAL;

uint32_t micro_us = (timestamp * 1000 + (1000 - val/84));
if (last_value > micro_us) micro_us+=1000; // Hack: race condition only causes a 1ms delay, this solves it
last_value = micro_us;
//if (SysTick->VAL > val ) micro_us+=1000; // undocmented, causes VAL reset to 0
//if (NVIC_GetPendingIRQ(SysTick_IRQn)) micro_us+=1000; // asf undocumented, does not handle systick (system handler)
//if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) micro_us+=1000; // triggerd undocumented systick interrupt


return micro_us; // hardcoded auf 84mhz
}

我希望这可以节省别人我不得不花费的时间。
它引入了一个新变量并保留了最后一个值的运行副本,如果时间开始倒流,它会将值增加 1 毫秒(误差始终为 1 毫秒)。

如果这看起来不够干净:
我能想到的唯一精益解决方案是停止系统时钟并改用计时器。
定时器有更好的文档记录(至少对于基本使用而言)并且它们工作可靠。SAM3 带有 9 个定时器。

关于c - 更新 : SAM3X problems with systick handler and accessing systick value for microsecond resolution,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24592145/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com