Saturday, 30 January 2016

Arduino Due and the Watchdog

A watchdog is used to detect when a system has gone off with the fairies; you have to reset the watchdog timer ("pat the watchdog") every so often or it takes some recovery action. In the case of the Arduino Due, with the ATSAM3X8E MCU, the recovery action is to reset the CPU.

In the ATSAM3X8E (and probably other Atmel MCUs, too), the watchdog timeout defaults to 16 seconds, and the default hardware state is that the watchdog is enabled! (the default in the Atmel Software Framework is to disable the watchdog unless you have defined CONF_BOARD_KEEP_WATCHDOG_AT_INIT).

It can be quite hard to detect a watchdog timeout! I first noticed it because an LED that was supposed to remain on for 5 seconds would sometimes only remain on for a considerably shorter time. Of course, if you're driving something more complex than an LED it might be more obvious that something was wrong.

It took a lengthy debugging session and some inspiration to find out what was wrong. I now like to extend my standard Heartbeat task, which flashes the on-board LED once a second, so that it flashes five times in the first second:

task body Heartbeat is
   --  The on-board LED is pin PB27.
   use Registers.ATSAM3X8.Peripheral_Identifiers;
   use Registers.ATSAM3X8.PMC;
   use Registers.ATSAM3X8.PIO;
   use type Ada.Real_Time.Time;
begin
   --  Enable PIOB
   PMC.PCER0 := (PIOB_IRQ => 1, others => 0);
   --  Enable PB27 ..
   PIOB.PER := (27 => 1, others => 0);
   --  .. as output.
   PIOB.OER := (27 => 1, others => 0);

   --  flash for 1 second at startup
   for J in 1 .. 5 loop
      PIOB.CODR := (27 => 1, others => 0);
      delay until Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (100);
      PIOB.SODR := (27 => 1, others => 0);
      delay until Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (100);
   end loop;

   --  flash every second while running
   loop
      PIOB.CODR := (27 => 1, others => 0);
      delay until Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (900);
      PIOB.SODR := (27 => 1, others => 0);
      delay until Ada.Real_Time.Clock + Ada.Real_Time.Milliseconds (100);
   end loop;
end Heartbeat;

The ATSAM3X8E datasheet (Rev C, 03/2015) describes the watchdog function in section 15.5.4. The Watchdog Timer User Interface consists of three registers; to disable it the Watchdog Mode Register (WDT_MR) bit 15 (WDDIS) must be set.

WDT_MR is "write-once"; it can't be rewritten until processor reset.

In Cortex GNAT RTS, which runs over FreeRTOS, I couldn't find a way of starting FreeRTOS tasking before entering the main program (at least, not without considerable change to gnatbind); the user has to start tasking by calling Start_FreeRTOS_Scheduler at the end of the main program. With this feature, the simplest way to give the user control if required seemed to be to declare Start_FreeRTOS_Scheduler as

procedure Start_FreeRTOS_Scheduler (Disable_Watchdog : Boolean := True)
with
  No_Return;

implemented as

procedure Start_FreeRTOS_Scheduler (Disable_Watchdog : Boolean := True) is
   WDT_MR : Interfaces.Unsigned_32
     with
       Import,
       Convention => Ada,
       Volatile,
       Address => System'To_Address (16#400E1A54#);
   WDT_MR_WDDIS : constant Interfaces.Unsigned_32
     := Interfaces.Shift_Left (1, 15);  -- bit 15
begin
   if Disable_Watchdog then
      WDT_MR := WDT_MR_WDDIS;
   end if;
   System.FreeRTOS.Tasks.Start_Scheduler;
   raise Program_Error with "Start_Scheduler returned";
end Start_FreeRTOS_Scheduler;

This way, if you want to control the watchdog yourself, call as

Start_FreeRTOS_Scheduler (Disable_Watchdog => False);

and do your own setup, either during elaboration (which takes place before this call) or when you prefer (within the first 16 seconds!)

Note that the STMicroelectronics' STM32F4 series does not enable its watchdog by default.

No comments:

Post a Comment