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

时间:2018-01-30 02:43:20

标签: c embedded microcontroller

我正在尝试使用 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时,可能会出现溢出和{{ 1}}计时器可能已成为16bit。这可能导致总错误值。

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

是否有办法防止此数据损坏并获得32位值的工作模型。

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

  1. 我的电机可以向前或向后移动,因此计数器递增/递减。

  2. 如果是ISR,在启动电机之前,我要设置全局变量(zero&amp; quad_enc_mov_forward),以便在出现溢出/下溢时{{ 1}}会相应更改。

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

  4. 多个任务意味着我使用RTOS Kernel执行大约6个任务(大多数3个并行运行的任务)。

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

4 个答案:

答案 0 :(得分:3)

一个解决方案是:

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

答案 1 :(得分:3)

#1基础嵌入式系统编程FAQ:

必须保护呼叫者与ISR之间或不同ISR之间共享的任何变量,以防止竞争条件。为防止某些编译器进行不正确的优化,此类变量也应声明为volatile

不理解上述内容的人没有资格编写包含ISR的代码。或者包含多个进程或线程的程序。没有意识到上述内容的程序员将始终编写非常微妙,非常难以捕获的错误。

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

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

答案 2 :(得分:2)

如果你没有正确处理它,在多线程代码中读取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位值时禁用中断。

答案 3 :(得分:0)

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