我是嵌入式系统编程的新手,虽然我在学习期间完成了课程,但实际编程还有一段距离。
问题在于:我必须在没有操作系统的NXP LPC2103微控制器(基于ARM 7)上编写一个小型系统。它有一个看门狗定时器,需要定期更新。该系统具有嵌入了TCP / IP堆栈的GPRS调制解调器,并且初始化这需要比看门狗需要超时的时间更长的时间。当我调用初始化函数时,系统会重置。
我和一位经验丰富的同事交谈过,他建议我需要退出并重新输入相同的初始化函数,这些函数来自我将看门狗定时器咬了很长时间,直到函数完成执行。这个想法听起来不错,但我还想听听其他一些经历。此外,参考(书籍或网站)也可能有用,因为我找不到任何特定的内容。
我不想从初始化函数调用看门狗定时器,我发现这不好。
答案 0 :(得分:6)
我不想从初始化函数调用看门狗定时器,我发现这不好。
对于这种情况可能有点过头了,但是我用于长时间运行操作的一般技术是让你可能想要执行其他工作的是让长时间运行的函数接受将定期调用的回调函数指针。我通常使用的模式是有一个回调原型,可能看起来像:
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()
函数来处理阻止监视程序重置的问题。但是,如果您发现自己需要经常为监视程序依赖此类事物,那么您可能需要考虑您的任务如何进行交互并将其分解得更多或使用更加复杂的监视程序方案来管理监视程序通过自己的任务,其他任务与之交互,以保持监视计时器间接满意。
答案 1 :(得分:5)
一般来说,我采用了两种方法来处理这种情况。
第一个与您的同事建议的一样:在作为主循环的一部分调用的状态机中实现初始化例程,然后停止调用初始化例程并开始调用主例程。
这是一个简单而干净的功能,但在涉及特殊的长进程(如启动低频振荡器)时可能会有点尴尬。
如果您有'systick'或等效中断,还有另一种选择,例如每1 ms触发一次中断。在这种情况下,您可以考虑每隔50次中断调用(例如)馈送看门狗,但限制看门狗被馈送的次数等于初始化例程完成的最大允许时间。然后通常需要(如果你有,我认为应该是一个窗口看门狗)在初始化结束时有一个短的同步循环,以确保在达到最小窗口时间之前没有馈送看门狗,但这很难实现。
这是一个非常干净的解决方案(因为它不会将初始化例程转换为不必要的状态机)并处理初始化例程挂起的问题。但是,强制执行ISR中看门狗呼叫的限制非常重要。
两者都有其优点和缺点,但是针对不同的要求采用不同的方法是有用的。我倾向于选择后一种解决方案,其中我有一些低频振荡器(可能需要一段时间才能启动),因为它避免了过度复杂的初始化程序,这可能足够复杂!
我相信其他人也会提供其他选择......
答案 2 :(得分:3)
LPC2103中的Watchdog可高度自定义。 你有很多选择来控制它:
在初始化序列结束之前,您无法启用它。
您可以将Feed之间的时间段延长很长时间。
问题是您使用看门狗的是什么?
如果用于检查您的软件是否运行良好而且没有冻结,我不会看到AI的ISR选项如何帮助您(即使您的程序被卡住,ISR也能继续工作)。
有关看门狗选项的详细信息,请参阅MCU用户手册中的看门狗定时器(WDT)章节(17)。 http://www.nxp.com/documents/user_manual/UM10161.pdf
答案 3 :(得分:2)
看门狗很棒,但当你的程序或系统不适合它时,后面也会很痛苦。如果您的代码看起来(通常)如下,则效果最佳:
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值,最终可能导致看门狗不应该咬人。
答案 4 :(得分:1)
您可能会重新考虑WD代码服务的代码在哪里。
WD计时器通常需要在空闲时间(空闲循环或空闲任务)和最低级驱动程序中进行维护(例如,当您从/向GPRS调制解调器读取/写入时,或者用于TCP / IP连接的MAC)等等。)。
如果这还不够,您的固件也可能只会在延迟例程中烧掉CPU周期。可以在这里添加WD计时器服务,但您可能需要调整延迟计时器以计算WD服务时间。
如果您的应用程序只是执行一些长时间的CPU密集型任务,这些任务需要花费更多时间来执行WD计时器周期允许的时间,您可以考虑使WD计时器间隔更长一些。这可能并不总是可行,但我喜欢将WD定时器参考保留在固件的上层之外,以使应用层尽可能便携。 WD定时器通常依赖于硬件,因此代码中的任何WD定时器引用都很少可移植。低级驱动程序很少是便携式的,所以这通常是维护WD计时器的更好地方。