准确地调用函数

时间:2008-10-17 09:49:25

标签: embedded timer

我正在使用带有C51内核的微控制器。我有一个相当耗时的大型子程序需要每500ms调用一次。没有使用RTOS。

我现在正在做的方式是我有一个10 ms的现有定时器中断。我在每50个中断后设置一个标志,在主程序循环中检查是否为真。如果Flag为真,则调用子例程。问题是,当程序循环到达服务标志时,它已经超过500毫秒,有时甚至在某些代码路径的情况下> 515毫秒。所花费的时间不能准确预测。

显然,由于执行时间很长,子程序无法从定时器中断内部调用。子程序需要50ms到89ms,具体取决于各种条件。

有没有办法确保每次只用500毫秒调用子程序?

9 个答案:

答案 0 :(得分:1)

我认为你在这里有一些相互矛盾/没有思考的要求。你说你不能从计时器ISR中调用这段代码,因为它运行时间太长(暗示它的优先级低于其他可能被延迟的优先级),但是你会受到这样的事实的影响。当你从前台路径('program loop')运行它时,应该优先级较低的else就是延迟它。

如果这项工作必须恰好在500毫秒时发生,那么从计时器例程中运行它,并处理掉它的后果。这实际上是先发制人的RTOS无论如何都要做的事情。

如果你想让它从'程序循环'运行,那么你必须确保从该循环中运行的任何东西都不会超过你能容忍的最大延迟 - 通常这意味着打破你的其他长期 - 将工作分配到状态机中,每次循环都可以完成一些工作。

答案 1 :(得分:1)

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

我可以建议设置标志,而是修改值吗?

以下是它的工作原理。

1 /从零开始一个值。

2 /每10ms中断,在ISR(中断服务程序)中将此值增加10。

3 /在主循环中,如果值为> = 500,则从值中减去500并执行500ms活动。

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

这样做的好处是,无论延迟或持续时间如何,函数都尽可能接近500ms边界。

如果由于某种原因,你的函数在一次迭代中开始迟到20ms,那么该值已经是520,那么你的函数将把它设置为20,这意味着它只会在下一次迭代之前等待480ms。

在我看来,这是达到你想要的最佳方式。

我多年没有接触过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 */
        }
    }
}

答案 2 :(得分:1)

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

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

  • 安排定期任务
  • 安排循环任务
  • 预成型上下文切换

您的要求如下:

  • 每500毫秒执行一次定期任务
  • 执行循环任务(执行非时间关键操作)之间的额外时间

像这样的RTOS将保证99.9%的机会按时执行代码。我不能说100%,因为你在ISR中所做的任何操作都可能会干扰RTOS。这是8位微控制器的问题,一次只能执行一条指令。

编写RTOS很棘手,但可行。以下是针对ATMEL 8位AVR平台的小型(900线)RTOS示例。

以下是为CSC 460类创建的 Report and Code :实时操作系统(维多利亚大学)。

答案 3 :(得分:1)

您还可以使用两个标志 - “预执行”标志和“触发”标志(使用Mike F作为起点):

#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();
      }
   }
 }

答案 4 :(得分:0)

这会做你需要的吗?

#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;
        }
    }
}

注意:如果你为每500毫秒的任务提供服务,那么这会让他们排队,这可能不是你想要的。

答案 5 :(得分:0)

一个简单的解决方案是让定时器中断在500ms时触发......
如果您在硬件设计方面具有一定的灵活性,则可以将一个计时器的输出级联到第二级计数器,以获得较长的时间基础。我忘记了,但我依旧回忆起能够在x51上级联定时器。

答案 6 :(得分:0)

为什么你有一个时间紧迫的例程需要很长时间才能运行?

我同意其他一些人认为这里可能存在架构问题。

如果具有精确的500ms(或其他)间隔的目的是在特定时间间隔发生信号变化,那么根据先前的计算输出新信号的快速ISR可能会更好,然后设置将导致新计算在ISR之外运行的标志。

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


根据评论添加:

如果你能确保服务程序中的时间具有可预测的持续时间,那么你可能会错过定时器中断发布...

举个例子,如果您的定时器中断设置为10 ms,并且您知道您的服务程序将花费89ms,那么请继续计数41个定时器中断,然后执行89 ms活动并错过8个定时器中断(第42至第49位)。

然后,当您的ISR退出(并清除待处理的中断)时,下一轮500ms的“第一个”中断将在大约一个ms后发生。

鉴于您的“资源最大化”表明您还有其他计时器和中断源也在使用 - 这意味着依赖主循环来准确计时是不行的,因为那些其他中断消息来源可能会在错误的时刻触发。

答案 7 :(得分:0)

啊,还有一个需要考虑的替代方案 - x51架构允许两级中断优先级。如果你有一些硬件灵活性,你可以让定时器ISR以500ms的间隔引出一个外部中断引脚,然后让你的每500ms代码进行低级中断处理。

根据您的特定x51,您也可以在设备内部生成优先级较低的中断。

请参阅我在网上找到的本文件中的第11.2部分:http://www.esacademy.com/automation/docs/c51primer/c11.htm

答案 8 :(得分:0)

如果我正确地解释了你的问题,你有:

  • 主循环
  • 需要每500毫秒运行一次高优先级操作,持续时间长达89毫秒
  • 一个10毫秒的计时器,也执行少量操作。

我看到它有三个选项。 第一种是为您的500ms操作使用较低优先级的第二个计时器。您仍然可以处理10ms中断,并在完成后继续为500ms定时器中断服务。

第二个选项 - 你真的需要每10ms服务一次10ms的中断吗?除了计时之外,它还在做什么吗?如果没有,并且您的硬件将允许您确定处理500ms操作时已经过的10ms滴答数(即不使用中断本身),那么您可以在10ms中断内启动500ms操作并处理你完成时错过了10ms的时间。

第三种选择:继续Justin Tanner的回答,听起来你可以制作自己的抢先式多任务内核来满足你的要求而不会有太多麻烦。 听起来你只需要两个任务 - 一个用于主超级循环,一个用于500ms任务。

在两个上下文之间交换的代码(即所有寄存器的两个副本,使用不同的堆栈指针)非常简单,通常由一系列寄存器推送(以保存当前上下文),一系列注册pops(恢复新的上下文)和中断返回指令。完成500毫秒操作后,您将恢复原始上下文。

(我认为严格来说这是先发制人和合作多任务的混合体,但现在这并不重要)


编辑: 有一个简单的第四选择。在任何冗长的操作之前和之后,通过检查500ms是否已经过去,对你的主要超级循环充满信心。 不完全是500毫秒,但您可以将延迟降低到可容忍的水平。