在有看门狗定时器的情况下编程

发布于 2024-09-07 14:46:08 字数 363 浏览 2 评论 0原文

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

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

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

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

I am new to embedded systems programming, although I have done courses during studies, practical programming is still a bit further away.

Here is the problem: I have to program a small system on NXP LPC2103 microcontroller (ARM 7 based), without an operating system. It has a watchdog timer which needs to be regularly updated. The system has a GPRS modem with TCP/IP stack embedded, and initializing this takes time longer than the watchdog needs to timeout. When I call the initialization function, the system resets.

I spoke to a more experienced colleague and he suggested that I need to exit and reenter the same initialization function from the main function in which I bite the watchdog timer so long until the function finishes executing. The idea sounds good, but I would like to also hear some other experiences. Also, a reference (book or website) could be also useful, because I couldn't find anything specific to this.

I wouldn't like to call watchdog timer from the initialization function, I don't find this good.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

幽梦紫曦~ 2024-09-14 14:46:08

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

对于这种情况,这可能有点过分了,但是我用于长时间运行的操作(您可能想要执行其他工作)的通用技术是让长时间运行的函数接受将定期调用的回调函数指针。我通常使用的模式是拥有一个可能如下所示的回调原型:

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() 函数来允许它们处理防止看门狗重置的问题。但是,如果您发现自己经常需要依赖这种类型的东西,特别是看门狗,那么您可能需要考虑您的任务如何交互,要么更多地分解它们,要么使用更复杂的看门狗方案来管理看门狗通过其自己的任务与其他任务交互以间接保持看门狗计时器的状态。

I wouldn't like to call watchdog timer from the initialization function, I don't find this good.

It might be overkill for this one situation, but a general technique I've used for long running operations where you might want to perform other work is to have the long running function accept a callback function pointer that will be periodically called. The pattern that I usually use is to have a callback prototype that might look like:

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

The long running function will periodically call the callback, with some information that indicates it's progress (how that progress is represented to what it means is dependent on the details of the particular function) and with a context value that the original caller passed in along with the callback pointer (again - what that parameter means and how it's interpreted is entirely up to the callback). generically, the return value of the callback function might be used to indicate that the 'long running thing' should cancel or otherwise change behavior.

This way, your initialization function can take a callback pointer with a context value, and just periodically call it. Obviously, in your situation, those callbacks would have to occur often enough to keep the watchdog happy.

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

One nice thing about this pattern is that it can be used in different situations - you might have a function that reads or writes a large amount of data. The callback pattern might be used to have something display the progress.

Note that if you find that you have other long-running functions, the same watchdog_callback() function could be used to allow them to deal with preventing the watchdog from reseting. However, if you find yourself needing to rely on this type of thing often for the watchdog in particular, then you might need to consider how your tasks are interacting and either break them down more or use a more complex watchdog scheme that has the watchdog managed by its own task that other tasks interact with to keep the watchdog timer happy indirectly.

千纸鹤带着心事 2024-09-14 14:46:08

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

状态机初始化

第一个就像您的同事所建议的那样:在作为主循环一部分调用的状态机中实现初始化例程,然后停止调用初始化例程并开始调用主例程。

这是一个简单而干净的函数,但是当涉及到特定的长过程(例如启动低频振荡器)时可能会有点尴尬。

限时 ISR 看门狗处理

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

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

讨论

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

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

Generally, there are two approaches that I have adopted for this situation.

State Machine Initialisation

The first is much as your colleague has suggested: implemented the initialisation routines in a state machine called as part of the main loop and then stop calling the initialisation routines and start calling the main routines.

This is a simple and clean function, but can be a little awkward when it comes to particular long processes such as starting up a low frequency oscillator.

Time-Limited ISR Watchdog Handling

There is another alternative if you have a 'systick' or equivalent interrupt, for example an interrupt that is fired every 1 ms. In this situation, you can consider feeding the watchdog (e.g.) every 50 calls of the interrupt, but limiting the number of times the watchdog is fed to equate to the maximum allowable time for the initialisation routines to complete. It is then generally necessary (if you have, as you should in my opinion, a windowed watchdog) to have a short synchronisation loop at the end of the initialisation to ensure that the watchdog isn't fed before the minimum window time is reached, but this is trivial to implement.

This is quite a clean solution (as it doesn't make the initialisation routines into an unnecessary state machine) and deals with the issues of an initialisation routine hanging. It is, however, very important that the limit on watchdog calls in the ISR is enforced.

Discussion

Both have their advantages and disadvantages, but it's useful to have different approaches for different requirements. I tend to prefer the latter solution where I have things like a low frequency oscillator (which can take a while to start) as it avoids over-complicating the initialisation routines, which can be complicated enough on their own!

I'm sure others will offer other alternatives ideas as well...

听闻余生 2024-09-14 14:46:08

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

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

您可以将喂食之间的时间间隔延长至很长一段时间。

问题是你用看门狗做什么?

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

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

The Watchdog in LPC2103 is highly customizable.
You have many options to control it:

You can to not enable it until your initialization sequence is over.

You can extend the period between feeds to very long time.

The question is for what are you using the watchdog?

If it used to check if your software running well and not freezing, I don't see how the ISR option from AI will help you (ISR can continue work even your program is stuck).

For details about Watchdog options see WatchDog Timer (WDT) chapter (17) in the User manual for your MCU.
http://www.nxp.com/documents/user_manual/UM10161.pdf

拥抱影子 2024-09-14 14:46:08

看门狗很棒,但当您的程序或系统无法轻松适应时,它也会带来麻烦。当您的代码(通常)看起来像这样时,效果最好:

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 值,这最终可能会阻止看门狗在应该执行的情况下执行操作。

Watchdogs are great, but also a pain in the rear when your program or system does not fit it easily. The work best when you have code that looks (generally) like:

Watchdog_init();

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

forever {
   Watchdog_tickle();

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

Very often you can design your program in such a way that this works, and generally it is very fool proof (but not totally).

But in cases like yours this does not work so well. You end up having to design and create (or possibly use a library) a framework that has various conditions that must be met that control if/when the watchdog get petted. This can be very tricky, though. The complexity of this code could itself introduce its own errors. You could very well write a perfect application except for the watchdog framework and your project may reset a lot, or all of your code might be bad and just continually pet the watchdog, causing it to never reset.

One good way to change the above code to handle more complicated situations would be to change the subsystemX_work functions to keep up with state. This can be done with static variables in the functions or by using function pointers rather than functions and change the actual function that is executed to reflect the current state of that subsystem. Each subsystem becomes a state machine.

Another way to go about working around long intentional waits with a quick biting watchdog is to break up the long running function into shorter pieces. Rather than:

slow_device_init();
Watchdog_tickle();

You could do:

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

And then extend this to stretch the watchdog timer by doing:

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

Even still it can get more and more complicated. Often you end up having to create a watchdog delegate which just checks for conditions to be met and does or does not pet the watchdog based on these conditions. This begins to get very complicated. It can be implemented by making an object for each of your subsystems that has some method/function to call to test the subsystem's health. The health methods could be very complex and could even change as the state of that subsystem changes, though it should be as simple as possible so that it is as easy as possible to verify that the code is correct, and also because changes to how the subsystem works will require changes to how you measure health.

If you can ensure that some code runs at regular intervals then you could just have an integer for each subsystem that acts as the subsystem's local watchdog. Some code (maybe in a timer interrupt handler, but not necessarily) will decrement and test each subsystem's variable. If it reaches 0 for any subsystem then the watchdog is not tickled.

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

Then each subsystem can tickle its own count_down for varying amounts of time by setting it's count_down to a positive value.

You should also notice that this is really just a software watchdog, even though it may make use of the hardware watchdog to do the actual reset.

You should also note that the more complicated the watchdog framework the more oppurtunity there is for errors in it as well as oppurtunity for errors in other code to cause it to work improperly. For instance a pointer error such as:

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

could result in setting some subsystem's count_down value, which could end up keeping the watchdog from biting when it should.

往事风中埋 2024-09-14 14:46:08

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

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

如果这还不够,您的固件可能除了在延迟例程中消耗 CPU 周期之外什么也不做。可以在此处添加 WD 计时器服务,但您可能需要调整延迟计时器以考虑 WD 服务时间。

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

You might reconsider where in code the WD timer is serviced.

Typically the WD timer needs to be service during idle time (idle loop or idle task) and in the lowest level drivers (e.g. when you are reading/writing from/to the GPRS modem or the MAC for your TCP/IP connection, etc.).

If this is not sufficient, your firmware may also be doing nothing but burning up CPU cycles in a delay routine. Its fine to add a WD timer service here but you may have to adjust your delay timer to account for the WD service time.

If your application simply has some long, CPU intensive tasks that take more time to execute that the WD timer period allows, you might consider making the WD timer interval a bit longer. This may not always be possible but I like to keep WD timer references out of the upper layers of the firmware to keep the application layer as portable as possible. WD timers are typically hardware dependent, therefore any WD timer references in your code are rarely portable. The low-level drivers are rarely portable anyway so this typically is a better place to service a WD timer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文