Embedded 函数调用的精确时间

Embedded 函数调用的精确时间,embedded,timer,Embedded,Timer,我使用的是一个以C51为核心的微控制器。我有一个相当耗时的大型子程序,需要每隔500毫秒调用一次。未使用RTOS 我现在这样做的方式是,我有一个10毫秒的现有定时器中断。我在主程序循环中每50次中断后设置一个标志,检查是否为真。如果该标志为true,则调用子例程。问题是,当程序循环开始维护标志时,它已经超过500毫秒,有时甚至在某些代码路径的情况下超过515毫秒。所花费的时间无法准确预测 显然,由于执行时间太长,无法从计时器中断内部调用该子例程。该子例程需要50毫秒到89毫秒,具体取决于各种条件

我使用的是一个以C51为核心的微控制器。我有一个相当耗时的大型子程序,需要每隔500毫秒调用一次。未使用RTOS

我现在这样做的方式是,我有一个10毫秒的现有定时器中断。我在主程序循环中每50次中断后设置一个标志,检查是否为真。如果该标志为true,则调用子例程。问题是,当程序循环开始维护标志时,它已经超过500毫秒,有时甚至在某些代码路径的情况下超过515毫秒。所花费的时间无法准确预测

显然,由于执行时间太长,无法从计时器中断内部调用该子例程。该子例程需要50毫秒到89毫秒,具体取决于各种条件


有没有办法确保每次调用子程序的时间精确到500毫秒?

我认为您在这里有一些相互冲突/未仔细考虑的要求。您说您不能从计时器ISR调用此代码,因为它运行时间太长(这意味着它的优先级低于其他将被延迟的代码),但是,当您从前台路径(“程序循环”)运行它时,您会遇到一个事实,即本应是较低优先级的其他代码正在延迟它

如果这项工作必须在精确的500毫秒时进行,那么从计时器例程运行它,并处理由此产生的误差。这实际上就是先发制人的RTO所要做的


如果希望它从“程序循环”运行,然后,您必须确保从该循环运行的任何操作都不会超过您所能容忍的最大延迟,这通常意味着将其他长时间运行的工作分解为状态机,这些状态机每次通过循环时都能做一点工作。

这能满足您的需要吗

#define FUDGE_MARGIN 2    //In 10ms increments

volatile unsigned int ticks = 0;

void timer_10ms_interrupt( void )  { ticks++; }

void mainloop( void )
{
    unsigned int next_time = ticks+50;

    while( 1 )
    {
        do_mainloopy_stuff();

        if( ticks >= next_time-FUDGE_MARGIN )
        {
            while( ticks < next_time );
            do_500ms_thingy();
            next_time += 50;
        }
    }
}
#以10ms为增量定义FUDGE_边距2//
易失性无符号整数刻度=0;
无效计时器\u 10ms\u中断(无效){ticks++}
void主循环(void)
{
unsigned int next_time=ticks+50;
而(1)
{
你主要做些无聊的事吗;
如果(滴答声>=下一次-模糊边缘)
{
while(滴答声<下一次);
做什么;
下一次+=50;
}
}
}

注意:如果你在完成每500毫秒一次的任务时落后了,那么这将使他们排队,这可能不是你想要的。

我认为没有办法保证这一点,但这个解决方案可能提供了一个可接受的替代方案

我是否可以建议不要设置标志,而是修改一个值

下面是它的工作原理

1/从零开始一个值

2/每10毫秒中断一次,在ISR(中断服务程序)中将该值增加10

3/在主循环中,如果值>=500,则从值中减去500,然后进行500毫秒活动

在修改值时,您必须小心观察计时器和主程序之间的竞争条件

这样做的优点是,无论延迟或持续时间如何,函数都尽可能靠近500毫秒边界运行

如果由于某种原因,您的函数在一次迭代中延迟20毫秒启动,那么该值将已经是520,因此您的函数将随后将其设置为20,这意味着它将在下一次迭代之前仅等待48毫秒

在我看来,这是实现你想要的最好方式

我已经很多年没有接触过8051了(假设这是C51的目标,根据您的描述,这似乎是一个安全的赌注),但它可能有一条指令,可以在不中断的情况下减去50。然而,我似乎记得该体系结构非常简单,因此在执行加载/修改/存储操作时,您可能必须禁用或延迟中断

volatile int xtime = 0;
void isr_10ms(void)  {
    xtime += 10;
}
void loop(void) {
    while (1) {
        /* Do all your regular main stuff here. */
        if (xtime >= 500) {
            xtime -= 500;
            /* Do your 500ms activity here */
        }
    }
}

一个好的选择是使用RTO或编写自己的简单RTO

要满足您的需求,RTO只需执行以下操作:

  • 安排定期任务
  • 安排循环任务
  • 预制上下文切换
您的要求如下:

  • 每500毫秒执行一次定期任务
  • 在执行循环任务之间的额外时间内(执行非时间关键型操作)
像这样的RTO将保证99.9%的几率您的代码能够按时执行。我不能说100%,因为您在ISR中所做的任何操作都可能会干扰RTO。对于一次只能执行一条指令的8位微控制器来说,这是一个问题

编写RTOS是一件棘手的事情,但确实是有能力的。以下是针对ATMEL的8位AVR平台的小型(900线)RTO示例


下面是为CSC 460创建的:实时操作系统(在维多利亚大学)。

< P>一个简单的解决方案是在50ms中触发一个定时中断……BR>
如果硬件设计有一定的灵活性,可以将一个定时器的输出级联到第二级计数器,以获得较长的时基。我忘了,但我隐约记得能够在x51上级联定时器。

为什么您有一个时间关键型例程需要运行这么长时间

我同意其他一些人的观点,这里可能存在架构问题

如果精确的500毫秒(或任何时间)间隔的目的是在特定的时间间隔内发生信号变化,则最好使用快速ISR,根据以前的计算输出新信号,然后设置一个标志,使新计算在ISR之外运行

您能更好地描述这个长时间运行的例程在做什么,以及特定间隔的需要是什么吗


根据评论添加:

如果您可以确保服务例程中的时间具有可预测的持续时间,那么您可能会错过计时器中断记录

以你为例,如果你
#define PREACTION_HOLD_TICKS (2)
#define TOTAL_WAIT_TICKS (10)

volatile unsigned char pre_action_flag;
volatile unsigned char trigger_flag;

static isr_ticks;
interrupt void timer0_isr (void) {
   isr_ticks--;
   if (!isr_ticks) {
      isr_ticks=TOTAL_WAIT_TICKS;
      trigger_flag=1;
   } else {
      if (isr_ticks==PREACTION_HOLD_TICKS)
          preaction_flag=1;
   }
}

// ...

int main(...) {


isr_ticks = TOTAL_WAIT_TICKS;
preaction_flag = 0;
tigger_flag = 0;
// ...

   while (1) {
      if (preaction_flag) {
          preaction_flag=0;
          while(!trigger_flag)
             ;
          trigger_flag=0;
          service_routine();
      } else {
          main_processing_routines();
      }
   }
 }