制作 32 位计数器时出现全局变量问题

Issue with global variable while making 32-bit counter

我正在尝试使用 atmel xmega avr 微控制器进行 正交解码。 Xmega 只有 16-bit 个计数器。此外,我已经用完了所有可用的计时器。

现在制作 32-bit 计数器我使用了一个 16-bit 计数器并且在它的 over/under flow interrupt 中我有 increment/decrement 一个 16 位全局变量,所以通过组合我们可以制作 32 位计数器。

ISR(timer_16bit)
{

   if(quad_enc_mov_forward)
    {
      timer_over_flow++;
    }

   else if (quad_enc_mov_backward)
    {
      timer_over_flow--;
    }
}

到目前为止一切正常。但是我需要在各种任务 运行 并行中使用这个 32 位值。我正在尝试读取 32 位值,如下所示

uint32_t current_count = timer_over_flow;
         current_count = current_count << 16;
         current_count = current_count + timer_16bit_count;
`timer_16_bit_count` is a hardware register.

现在我面临的问题是,当我在第一条语句中读取 timer_over_flowcurrent_count 时,当我添加 timer_16bit_count 时,可能会溢出并且16bit 计时器可能已变为 zero。这可能会导致采用完全错误的价值。

我正在尝试在多个任务中读取这个 32 位值。

有没有办法防止这种数据损坏并获得 32 位值的工作模型。

不同成员寻求的细节:

  1. 我的电机可以向前或向后移动并相应地计数器increments/decrements。

  2. 在 ISR 的情况下,在启动我的电机之前,我正在设置全局变量(quad_enc_mov_forward & quad_enc_mov_backward),这样如果有 overflow/underflow timer_over_flow 将相应更改。

  3. ISR中修改的变量声明为volatile.

  4. 多任务意味着我使用 RTOS 内核处理大约 6 个任务(主要是 3 个任务 运行 并行)。

  5. 在XMEGA中我直接读取TCCO_CNT寄存器的低字节。

一个解决方案是:

uint16_t a, b, c;
do {
    a = timer_over_flow;
    b = timer_16bit_count;
    c = timer_over_flow;
} while (a != c);
uint32_t counter = (uint32_t) a << 16 | b;

根据 user5329483 的评论,这不能在禁用中断的情况下使用,因为获取到 b 的硬件计数器可能会在修改 [=12] 的中断服务例程 (ISR) 时发生变化=] 不会 运行 如果中断被禁用。如果在此期间发生回绕,则 ISR 有必要中断此代码。

这将获取计数器并检查高位字是否已更改。如果是,此代码将重试。当循环退出时,我们知道低位字在读取期间没有回绕。 (除非有可能我们读取高字,然后包装低字,然后我们读取低字,然后以另一种方式包装,然后我们读取高字。如果您的系统中可能发生这种情况,另一种选择是添加一个标志,ISR 在高位字更改时设置。reader 将清除标志,读取定时器字,然后读取标志。如果设置了标志,它会再次尝试。)

请注意 timer_over_flowtimer_16bit_count 和标志(如果使用)必须是 volatile

如果wrap-two-times场景不会发生,那么你可以消除循环:

  • 如上阅读 abc
  • 比较 b0x8000
  • 如果 b 有一个高值,要么没有换行,它是在向上换行之前读取的(0xffff 到 0),或者是在向下换行之后读取的。使用 ac 中较小的一个。
  • 否则要么没有换行,b向上换行后读取,要么向下换行前读取。使用 ac 中较大的一个。

#1 基础嵌入式系统编程常见问题解答:

调用者和 ISR 之间或不同 ISR 之间共享的任何变量都必须防止出现竞争条件。为防止某些编译器进行不正确的优化,此类变量也应声明为 volatile.


不懂以上内容的人没有资格编写包含ISR的代码。或者包含多个进程或线程的程序。没有意识到以上几点的程序员将总是写出非常微妙、非常难以发现的错误。

一些防止竞争条件的方法可能是以下之一:

  • 访问期间临时禁用特定中断。
  • 在访问期间临时禁用所有可屏蔽中断(粗略的方式)。
  • 原子访问,在机器代码中验证。
  • 互斥量或信号量。在单核MCU:s无法轮流打断的情况下,可以.

在多线程代码中读取 TCCO_CNT 如果你没有正确处理它就是竞争条件。查看 XMega 手册中关于读取 16 位寄存器的部分。您应该先读取低字节(这可能会由编译器为您透明地处理)。当低位字节被读取时,高位字节被(原子地)复制到 TEMP 寄存器中。然后,读取高字节确实读取了 TEMP 寄存器,而不是计数器。以这种方式确保了 16 位值的原子读取,但是 仅当 在读取低字节和高字节之间没有访问 TEMP 寄存器时。

请注意,此 TEMP 寄存器在所有计数器之间共享,因此在正确(错误)时刻进行上下文切换可能会破坏其内容,从而破坏您的高字节。您需要禁用此 16 位读取的中断。因为XMega会在sei之后执行一条指令,并且禁止中断,所以最好的方法可能是:

cli
ld [low_byte]
sei
ld [high byte]

它禁用四个 CPU 周期的中断(如果我计算正确的话)。

另一种方法是在每次上下文切换时保存共享的 TEMP 寄存器。有可能(不确定是否可能)您的 OS 已经这样做了,但一定要检查一下。即便如此,您仍需要确保 ISR 不会发生访问冲突。

此预防措施应适用于代码中读取的任何 16 位寄存器。确保 TEMP 寄存器正确 saved/restored(或根本未被多个线程使用)或在 reading/writing 16 位值时禁用中断。

这个问题确实是一个很普遍也很棘手的问题。所有解决方案都会对较低优先级层中的时序约束提出警告。澄清一下:系统中优先级最高的功能是硬件计数器 - 它的响应时间定义了您最终可以采样的最大频率。解决方案中下一个优先级较低的是尝试跟踪位 2^16 的中断例程,最低的是尝试读取 32 位值的应用程序级代码。现在的问题是,您是否可以量化编码器 A 和 B 输入上两个电平变化之间的最短时间。最短时间通常不会发生在真实世界轴旋转的最高速度时,而是在某个位置停止时:通过最小的振动,编码器可以在两个增量之间双摆动,从而产生例如同一编码器输出的下降沿和上升沿短时间连续。 Iff(当且仅当)你可以保证你的中断处理时间比这个最小时间短(一定幅度)你可以使用这样的方法来虚拟地扩展你的坐标范围编码器。