6

我是嵌入式系统编程的新手,虽然我在学习期间完成了课程,但实际编程仍然有点远。

这就是问题所在:我必须在没有操作系统的情况下在 NXP LPC2103 微控制器(基于 ARM 7)上编写一个小型系统。它有一个看门狗定时器,需要定期更新。系统有一个嵌入了 TCP/IP 堆栈的 GPRS 调制解调器,初始化它所花费的时间比看门狗需要的超时时间长。当我调用初始化函数时,系统会重置。

我与一位更有经验的同事交谈,他建议我需要退出并从 main 函数重新进入相​​同的初始化函数,在该函数中我将看门狗定时器咬得太久,直到函数完成执行。这个想法听起来不错,但我也想听听其他一些经验。此外,参考资料(书籍或网站)也可能有用,因为我找不到任何具体的内容。

我不想从初始化函数中调用看门狗定时器,我觉得这不好。

4

5 回答 5

7

我不想从初始化函数中调用看门狗定时器,我觉得这不好。

对于这种情况,这可能有点矫枉过正,但是我在长时间运行的操作中使用的一种通用技术是让长时间运行的函数接受一个将定期调用的回调函数指针。我通常使用的模式是有一个可能看起来像这样的回调原型:

int (callback_t*)(void* progress, void* context);

长时间运行的函数将定期调用回调,其中包含一些指示其进度的信息(该进度如何表示为它的含义取决于特定函数的详细信息)以及原始调用者传入的上下文值以及回调指针(同样 - 该参数的含义以及如何解释它完全取决于回调)。通常,回调函数的返回值可能用于指示“长时间运行的事物”应该取消或以其他方式改变行为。

这样,您的初始化函数可以使用带有上下文值的回调指针,并定期调用它。显然,在您的情况下,这些回调必须经常发生才能让看门狗满意。

int watchdog_callback( void* progress, void* context)
{
    kick_the_watchdog();

    return 0;  // zero means 'keep going...'
}


void init_modem( callback_t pCallback, void* callback_context)
{
    // do some stuff

    pCallback( 0, callback_context);

    // do some other stuff

    pCallback( 1, callback_context);


    while (waiting_for_modem()) {
         // do work...

         pCallback( 2, callback_context);
    }    
}

这种模式的一个好处是它可以在不同的情况下使用——你可能有一个读取或写入大量数据的函数。回调模式可能用于显示进度。

请注意,如果您发现您有其他长时间运行的函数,watchdog_callback()可以使用相同的函数来允许它们处理防止看门狗重置的问题。但是,如果您发现自己需要经常依赖这种类型的东西,尤其是看门狗,那么您可能需要考虑您的任务是如何交互的,或者进一步分解它们,或者使用更复杂的看门狗方案来管理看门狗通过它自己的任务与其他任务交互以间接保持看门狗定时器快乐。

于 2010-06-29T17:32:56.257 回答
6

一般来说,我针对这种情况采用了两种方法。

状态机初始化

第一个与您的同事所建议的差不多:在称为主循环的一部分的状态机中实现初始化例程,然后停止调用初始化例程并开始调用主例程。

这是一个简单而干净的功能,但在涉及特别长的过程(例如启动低频振荡器)时可能会有些尴尬。

限时 ISR 看门狗处理

如果您有“systick”或等效中断,还有另一种选择,例如每 1 毫秒触发一次的中断。在这种情况下,您可以考虑每 50 次中断调用馈送看门狗(例如),但限制馈送看门狗的次数等于初始化例程完成的最大允许时间。然后通常有必要(如果你有,我认为你应该有一个窗口看门狗)在初始化结束时有一个短的同步循环,以确保在达到最小窗口时间之前看门狗不会被馈送,但这实现起来很简单。

这是一个非常干净的解决方案(因为它不会将初始化例程变成不必要的状态机)并处理初始化例程挂起的问题。然而,在 ISR 中强制执行对看门狗调用的限制非常重要。

讨论

两者都有其优点和缺点,但是针对不同的要求采用不同的方法很有用。我倾向于后一种解决方案,我有像低频振荡器(可能需要一段时间才能启动)之类的东西,因为它避免了初始化例程过于复杂,这本身就足够复杂了!

我相信其他人也会提供其他替代想法......

于 2010-06-29T10:09:33.620 回答
4

LPC2103 中的看门狗是高度可定制的。你有很多选择来控制它:

在初始化序列结束之前,您可以不启用它。

您可以将提要之间的时间间隔延长到很长的时间。

问题是您使用看门狗做什么?

如果它用于检查您的软件是否运行良好并且没有冻结,我看不出 AI 的 ISR 选项将如何帮助您(即使您的程序卡住,ISR 也可以继续工作)。

有关看门狗选项的详细信息,请参阅您的 MCU 用户手册中的看门狗定时器 (WDT) 第 (17) 章。 http://www.nxp.com/documents/user_manual/UM10161.pdf

于 2010-06-29T12:25:19.813 回答
3

看门狗很棒,但当您的程序或系统不能轻松适应它时,它也是一种痛苦。当您的代码(通常)看起来像这样时,效果最好:

Watchdog_init();

hardware_init();
subsystem1_init();
subsystem2_init();
subsystem3_init();
...
subsystemN_init();

forever {
   Watchdog_tickle();

   subsystem1_work();
   subsystem2_work();
   subsystem3_work();
   ...
   subsystemN_work();
}

很多时候,您可以以这样的方式设计您的程序,这通常是非常简单的(但不完全是)。

但在像你这样的情况下,这并不能很好地工作。您最终必须设计和创建(或可能使用库)一个框架,该框架具有必须满足的各种条件,以控制看门狗是否/何时被宠爱。不过,这可能非常棘手。此代码的复杂性本身可能会引入其自身的错误。除了看门狗框架之外,您可以很好地编写一个完美的应用程序,并且您的项目可能会重置很多,或者您的所有代码可能都是坏的并且只是不断地抚摸看门狗,导致它永远不会重置。

更改上述代码以处理更复杂情况的一种好方法是更改​​subsystemX_work 函数以跟上状态。这可以通过函数中的静态变量或使用函数指针而不是函数来完成,并更改执行的实际函数以反映该子系统的当前状态。每个子系统都成为一个状态机。

使用快速咬人的看门狗解决长时间有意等待的另一种方法是将长时间运行的函数分解为较短的部分。而不是:

slow_device_init();
Watchdog_tickle();

你可以这样做:

slow_device_init_begin();
Watchdog_tickle();
slow_device_init_finish();
Watchdog_tickle();

然后通过执行以下操作扩展它以延长看门狗定时器:

slow_device_init_begin();
for ( i = SLOW_DEV_TRIES; i ; i--) {
   Watchdog_tickle();
   if (slow_device_init_done()) {
       break;
   }
}
Watchdog_tickle();

即使它仍然可以变得越来越复杂。通常,您最终不得不创建一个看门狗代表,它只检查要满足的条件,并根据这些条件是否宠爱看门狗。这开始变得非常复杂。它可以通过为每个子系统创建一个对象来实现,该对象具有一些方法/函数来调用以测试子系统的健康状况。健康方法可能非常复杂,甚至可能随着子系统状态的变化而变化,尽管它应该尽可能简单,以便尽可能容易地验证代码是否正确,并且因为子系统的工作需要改变你衡量健康的方式。

如果您可以确保某些代码定期运行,那么您可以为每个子系统设置一个整数,作为子系统的本地看门狗。一些代码(可能在定时器中断处理程序中,但不一定)将递减并测试每个子系统的变量。如果任何子系统的值为 0,则看门狗不会被触发。

Watchdog_periodic() {
   for_each subsustem in subsystem_list { // not C, but you get the idea
      if ( 0 > --(subsystem->count_down) ) {
           // Do something that causes a reset. This could be returning and not petting
           // the hardware watchdog, doing a while(1);, or something else
      }
   }
   Watchdog_tickle();
}

然后,每个子系统可以通过将其 count_down 设置为正值来在不同的时间段内对自己的 count_down 进行挠痒痒。

您还应该注意到,这实际上只是一个软件看门狗,尽管它可能会利用硬件看门狗来进行实际的复位。

您还应该注意,看门狗框架越复杂,出现错误的机会就越大,其他代码中的错误也就越容易导致其无法正常工作。例如指针错误,例如:

int x;
fscanf(input, "%i", x); // Passed uninitialized x rather than address of x

可能会导致设置某些子系统的 count_down 值,这最终可能会阻止看门狗在应该咬的时候。

于 2010-06-29T19:16:18.533 回答
1

您可能会重新考虑 WD 计时器在代码中的哪个位置提供服务。

通常,WD 定时器需要在空闲时间(空闲循环或空闲任务)和最低级别的驱动程序中提供服务(例如,当您从/向 GPRS 调制解调器或 TCP/IP 连接的 MAC 读取/写入时,等等。 )。

如果这还不够,您的固件也可能只是在延迟例程中消耗 CPU 周期。可以在此处添加 WD 计时器服务,但您可能必须调整延迟计时器以考虑 WD 服务时间。

如果您的应用程序只是有一些较长的 CPU 密集型任务,这些任务需要更多时间来执行,而 WD 计时器周期允许,您可能会考虑将 WD 计时器间隔设置得更长一些。这可能并不总是可能的,但我喜欢将 WD 计时器引用排除在固件的上层之外,以使应用层尽可能地便携。WD 计时器通常依赖于硬件,因此代码中的任何 WD 计时器引用都很少可移植。无论如何,低级驱动程序很少是可移植的,因此这通常是服务 WD 计时器的更好地方。

于 2010-06-29T15:56:28.723 回答