2

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

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

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

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

4

9 回答 9

1

我认为您在这里有一些相互冲突/未经过深思熟虑的要求。您说您不能从计时器 ISR 调用此代码,因为它需要很长时间才能运行(这意味着它的优先级低于其他会延迟的东西),但是您受到了以下事实的打击当您从前台路径(“程序循环”)运行它时,本来应该是较低优先级的 else 会延迟它。

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

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

于 2008-10-17T10:12:33.143 回答
1

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

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

这是它的工作原理。

1/ 从零开始一个值。

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

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

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

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

如果由于某种原因,您的函数在一次迭代中延迟了 20 毫秒,则该值已经是 520,因此您的函数会将其设置为 20,这意味着它只会在下一次迭代之前等待 480 毫秒。

在我看来,这似乎是实现您想要的最佳方式。

我已经很多年没有接触过 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 */
        }
    }
}
于 2008-10-17T12:09:46.480 回答
1

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

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

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

您的要求如下:

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

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

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

以下是为 CSC 460 类创建的报告和代码:实时操作系统(维多利亚大学)。

于 2008-10-18T03:20:40.540 回答
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();
      }
   }
 }
于 2008-10-24T17:04:18.240 回答
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 毫秒的任务提供服务方面落后了,那么这将使它们排队,这可能不是您想要的。

于 2008-10-17T12:09:15.153 回答
0

一种直接的解决方案是设置一个在 500 毫秒触发的定时器中断……
如果您的硬件设计具有一定的灵活性,您可以将一个定时器的输出级联到第二级计数器,以获得较长的时基。我忘记了,但我隐约记得能够在 x51 上级联计时器。

于 2008-10-21T23:21:37.680 回答
0

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

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

如果具有精确的 500 毫秒(或其他)间隔的目的是让信号在特定时间间隔发生变化,那么您最好使用快速 ISR,它根据先前的计算输出新信号,然后设置一个标志,将导致新计算在 ISR 之外运行。

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


根据评论添加:

如果您可以确保服务例程中的时间是可预测的持续时间,您可能会错过计时器中断发布......

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

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

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

于 2008-10-21T23:27:54.563 回答
0

啊,另一种可供考虑的选择——x51 架构允许两个级别的中断优先级。如果您有一定的硬件灵活性,您可以通过定时器 ISR 以 500ms 的间隔触发外部中断引脚之一,然后让每 500ms 代码的较低级别的中断处理发生。

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

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

于 2008-10-21T23:38:33.293 回答
0

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

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

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

第二种选择 - 你真的需要每 10ms 处理一次 10ms 中断吗?除了计时,它还有什么作用吗?如果不是,并且如果您的硬件允许您确定在处理 500ms 操作时已经过去的 10ms 滴答数(即不使用中断本身),那么您可以在 10ms 中断内启动 500ms 操作并处理完成后您错过的 10 毫秒滴答声。

第三种选择:按照 Justin Tanner 的回答,听起来您可以生成自己的抢先式多任务内核来满足您的需求,而不会遇到太多麻烦。听起来您只需要两项任务-一项用于主超级循环,一项用于您的 500ms 任务。

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

(我想严格来说,这是抢占式和协作式多任务处理的混合体,但现在这并不重要)


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

于 2008-10-24T06:21:23.933 回答