【问题标题】:Issue with global variable while making 32-bit counter制作 32 位计数器时出现全局变量问题
【发布时间】:2018-01-30 02:43:19
【问题描述】:

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

现在要制作32-bit 计数器,我使用了一个16-bit 计数器,并在其over/under flow interrupt 中增加/减少了一个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 计时器可能已变为@ 987654333@。这可能会导致总的错误值。

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

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

不同成员寻求的详细信息:

  1. 我的电机可以向前或向后移动,并相应地计数器递增/递减。

  2. 1234563 /p>
  3. 在 ISR 中修改的变量声明为 volatile

  4. 多任务意味着我使用的 RTOS 内核包含大约 6 个任务(大部分是 3 个并行运行的任务)。

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

【问题讨论】:

  • 代码需要在uint32_t current_count = timer_over_flow; 发生时防止短时间的中断。解决方案取决于实现。真的需要minimal reproducible example 才能得到一个好的答案。
  • 不可能的。因为我们将丢失运动数据。
  • 我期望的唯一问题是当定时器溢出被读取为零然后发生溢出时,硬件寄存器计数再次从零开始。因此,我们最终会读取一个非常低的值,而不是读取更高的值。
  • 仍然需要 minimal reproducible example 。需要查看quad_enc_mov_forward, timer_over_flow, quad_enc_mov_backward, timer_16bit_count 的声明及其所有用途和初始化。
  • @chux:不,我们不需要 MVCE。这个问题已经得到了充分的描述并且是经典的。

标签: c embedded microcontroller


【解决方案1】:

一种解决方案是:

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 的硬件计数器可能会发生变化,而修改timer_over_flow 的中断服务例程 (ISR) 将不会运行,如果中断是禁用。如果在此期间发生换行,则 ISR 必须中断此代码。

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

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

如果不能发生两次换行的情况,则可以消除循环:

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

【讨论】:

  • 给定 OP 的 ISR,timer_16bit_count 可能会溢出(调用 ISR)而不更改 timer_over_flow。多次读取的想法是合理的,但在这种情况下需要更多。
  • @chux:如果ISR没有改变timer_over_flow,则reads没有不一致;读取的timer_16bit_count 的任何值都可以与timer_over_flow 的任一读取(具有相同的值)正确配对。我不明白他们为什么不希望 ISR 朝一个方向或另一个方向调整,但这是另一回事。
  • 关于“可能与任一读取正确配对”的好点 - 似乎很奇怪。
  • 能不能做成宏。这样我就可以在代码中的任何地方进行单行分配。比如 32_bit_count = 宏;
  • 请注意,根据您的具体情况,此解决方案可能是错误的/不足的。 1) timer_over_flow 是在 ISR 中更新的变量,但 timer_16bit_count 是硬件寄存器,这两个不会在一瞬间发生原子性变化(研究 great 详细介绍 ISR 时序等,如果它对你有用,请三思而后行) 2)如果您的编码器可以上下移动,那么您可能已经在 ISR 中遇到了大问题(如果在处理 ISR 之前该值将在零/最大值之间波动几次)。 quad_enc_mov_forward/backward 的确切定义很高兴知道。
【解决方案2】:

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

调用者和 ISR 之间或不同 ISR 之间共享的任何变量都必须受到保护,以免出现竞争条件。为了防止某些编译器做不正确的优化,这些变量也应该声明为volatile


不了解以上内容的人没有资格编写包含 ISR 的代码。或者包含多个进程或线程的程序。没有意识到上述情况的程序员将总是编写非常微妙、非常难以捕捉的错误。

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

  • 访问期间临时禁用特定中断。
  • 在访问期间临时禁用所有可屏蔽的中断(粗略方式)。
  • 原子访问,在机器代码中验证。
  • 互斥量或信号量。单核MCU:不能依次中断中断的,可以use a bool as "poor man's mutex"

【讨论】:

  • 变量确实是易变的。很明显,这就是我没有明确提及的原因。
  • @Vinodkumar 你没有显示你的变量声明,也没有提到你是如何保护它的,所以我假设你有一些错误。
  • 我希望看到您的第一条诫命被限制为原子地可访问的变量,这会导致领域 POSIX 和/或实现定义的行为。好的,你在下面提到它,但我认为它和volatile 一样重要。
  • 在这种情况下,互斥锁或信号量是不合适的。如果 ISR 尝试获取由线程上下文锁定的互斥锁,您将陷入死锁 - 在大多数系统中,锁定互斥锁或在 ISR 中获取信号量都会失败。
  • @Clifford 术语互斥锁,尤其是信号量,不一定是某些操作系统提供的项目。如果您发明自己的位标志以保护与 ISR 共享的变量,那么位标志就是一个信号量。早在多任务操作系统发明之前,汇编程序员就一直在使用这个术语。无论如何,保护与 ISR 共享的变量与保护与另一个进程/线程/回调共享的变量时的原则是相同的。
【解决方案3】:

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

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

cli
ld [low_byte]
sei
ld [high byte]

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

另一种方法是在每次上下文切换时保存共享的 TEMP 寄存器。您的操作系统可能已经执行此操作(不确定是否可能),但请务必检查。即便如此,您仍需要确保 ISR 不会发生冲突访问。

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多