Embedded 使用定时器计时会中断嵌入式微控制器

Embedded 使用定时器计时会中断嵌入式微控制器,embedded,interrupt,microchip,pic18,Embedded,Interrupt,Microchip,Pic18,这个问题是关于编程没有操作系统的小型微控制器。特别是,我现在对图片感兴趣,但问题是一般性的 我已经多次看到以下计时模式: 计时器中断代码(假设计时器每秒触发一次): 主线代码(非中断): 主线代码可能重复,将计数器设置为各种值(不仅仅是秒),等等 在我看来,当主线代码中对秒计数器的赋值不是原子性的时,这里似乎存在竞争条件。例如,在PIC18中,赋值被转换为4条ASM语句(此时加载每个字节,并在此之前从内存库中选择正确的字节)。如果中断代码出现在中间,则最终值可能被损坏。 奇怪的是,如果赋值小于2

这个问题是关于编程没有操作系统的小型微控制器。特别是,我现在对图片感兴趣,但问题是一般性的

我已经多次看到以下计时模式:

计时器中断代码(假设计时器每秒触发一次):

主线代码(非中断):

主线代码可能重复,将计数器设置为各种值(不仅仅是秒),等等

在我看来,当主线代码中对
秒计数器
的赋值不是原子性的时,这里似乎存在竞争条件。例如,在PIC18中,赋值被转换为4条ASM语句(此时加载每个字节,并在此之前从内存库中选择正确的字节)。如果中断代码出现在中间,则最终值可能被损坏。

奇怪的是,如果赋值小于256,则赋值是原子的,因此没有问题

这个问题我说得对吗? 您使用什么模式来正确实现这种行为?我看到几个选择:

  • 在每次分配到sec_计数器之前禁用中断,在分配到sec_计数器之后启用中断-这不太好
  • 不要使用中断,而是使用一个单独的计时器,该计时器启动后进行轮询。这是干净的,但使用了整个计时器(在前一种情况下,1秒触发计时器也可用于其他目的)

还有其他想法吗?

写下值,然后检查它是否是所需的值似乎是最简单的选择

do {
 sec_counter = value;
} while (sec_counter != value);
顺便说一句,如果使用C,应该使变量可变

如果需要读取该值,则可以读取两次

do {
    value = sec_counter;
} while (value != sec_counter);

那么,比较汇编代码是什么样子的呢


考虑到它是向下计数的,并且它只是一个零比较,如果它首先检查MSB,然后检查LSB,应该是安全的。可能会有腐败现象,但如果它在0x100和0xFF之间,那么损坏的比较值是0x1FF,则不重要。

在设置计数器之前,您确实需要禁用中断。尽管它可能很丑,但它是必要的。在配置影响ISR方法的硬件寄存器或软件变量之前,最好始终禁用中断。如果在C中编写,则应将所有操作视为非原子操作。如果您发现必须多次查看生成的程序集,那么最好放弃汇编中的C和程序。根据我的经验,这种情况很少发生

关于讨论的问题,我的建议如下:

ISR:
if (countDownFlag)
{
   sec_counter--;
}
以及设置计数器:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;
您需要一个额外的变量,最好将所有内容封装在函数中:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

通过这种方式,您可以抽象出起始方法(如果需要,还可以隐藏丑陋之处)。例如,你可以很容易地改变它来启动一个硬件计时器而不影响该方法的呼叫者。

现在你使用计时器的方式,无论如何它不会计算整个秒,因为你可以在一个周期中间改变它。 所以,如果你不在乎的话。在我看来,最好的方法是读取值,然后比较差异。它需要更多的操作,但没有多线程问题。(因为计时器具有优先级)


如果您对时间值要求更严格,我会在计时器倒计时到0时自动禁用计时器,并在需要时清除计时器的内部计数器并激活它

PIC架构是原子化的。它确保对内存文件的所有读修改写操作都是“原子”操作。尽管执行整个读-修改-写操作需要4个时钟,但所有4个时钟都在一条指令中消耗,下一条指令使用下一个4个时钟周期。这是管道的工作方式。在8个时钟中,有两条指令在管道中


如果该值大于8位,则会成为一个问题,因为PIC是一台8位机器,较大的操作数在多条指令中处理。这将引入原子问题。

将main()上的代码部分移动到适当的函数中,并让ISR有条件地调用它


此外,为了避免任何形式的延迟或丢失信号,请选择此定时器ISR作为高优先级中断(PIC18有两个级别)。

因为对sec_计数器变量的访问不是原子的,如果需要确定性行为,在访问主线代码中的这个变量之前以及在访问之后恢复中断状态之前,确实无法避免禁用中断。这可能比为这项任务指定一个硬件计时器更好(除非您有多余的计时器,在这种情况下,您最好使用一个)。

如果您下载Microchip的免费TCP/IP堆栈,其中有一些例程使用计时器溢出来跟踪经过的时间。特别是“tick.c”和“tick.h”。只需将这些文件复制到您的项目中


在这些文件中,你可以看到它们是如何做到的。

对于少于256次的移动是原子的,这并不奇怪——移动一个8位的值就是一个操作码,所以它是原子的


在PIC这样的微控制器上,最好的解决方案是在更改计时器值之前禁用中断。您甚至可以在更改主循环中的变量时检查中断标志的值,并根据需要进行处理。使其成为一个可以更改变量值的函数,您甚至可以从ISR调用它。

一种方法是让中断保留一个字节变量,并让其他东西至少每256次计数器被调用一次;做一些类似于:

// ub==unsigned char; ui==unsigned int; ul==unsigned long ub now_ctr; // This one is hit by the interrupt ub prev_ctr; ul big_ctr; void poll_counter(void) { ub delta_ctr; delta_ctr = (ub)(now_ctr-prev_ctr); big_ctr += delta_ctr; prev_ctr += delta_ctr; } //ub==无符号字符;ui==无符号整数;ul==无符号长 ub now_ctr;//这个被打断了 ub prev_ctr; ul big_ctr; void poll_计数器(void) { 乌兰巴托三角洲中心; delta_ctr=(ub)(现在为_ctr-prev_ctr); 大中心+=三角中心; prev_ctr+=delta_ctr; } 如果你不介意的话,稍微改变一下
void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}
// ub==unsigned char; ui==unsigned int; ul==unsigned long ub now_ctr; // This one is hit by the interrupt ub prev_ctr; ul big_ctr; void poll_counter(void) { ub delta_ctr; delta_ctr = (ub)(now_ctr-prev_ctr); big_ctr += delta_ctr; prev_ctr += delta_ctr; } ul big_ctr; void poll_counter(void) { big_ctr += (ub)(now_ctr - big_ctr); }
do {
       t = timer;
 } while (t != timer);