Raspberry Pi Pico W and Watchdog

Hello,

I have a project where I wish to have a watchdog to reset the Raspberry Pi Pico if communication is lost.

My project takes RS-485 data, converts it to TTL, feeds it into the Raspberry Pi Pico W, then the Pico translates the data and publishes it wirelessly through MQTT.

It works! ... until it doesn't... :frowning: and I have to go and reset the Pico.

I wish to have a watchdog reset the Pico - how do I implement this?

I'm using Arduino IDE 2.2.1, Raspberry Pi RP2040 Boards(2.5.2).

1 Like

There is a good chance that your code has a bug that is causing the breakdown of the communication.

Anyway there is no standard-way of writing a watchdog-timer. Your own code will have to pat the watchdog and for doing this it would be good if you post your complete sketch as a code-section.

best regards Stefan

Not a demo-code but give some hints

https://arduino-pico.readthedocs.io/en/latest/rp2040.html#void-rp2040-wdt-begin-uint32-t-delay-ms

If you at least describe how your communication is done

example
Pi Pico gets a request to send data and is answering the request

Never worked with MQTT.
If it is important to have always data you should add some kind of acknowledgement that the MQTT-server is sending back to the Pi Pico
then you would check in your Pi-Pico-code if an aknowledgement arrived withing a certain timelimit. If it has not arrived reboot the pi-pico

I have read that the maximum time the watchdogtimer waits is limited to 8.3 seconds.
I guess this is too short for your application anyway.

best regards Stefan

I am unsure if this question is directed at me, but I needed an immediate reboot. Here is my code to do so:

For the Arduino Nano RP2040, connect:
watchdog_reboot(0, 0, 0);
watchdog_enable(100, 0);

This is documented here: https://cec-code-lab.aps.edu/robotics/resources/pico-c-api/group__hardware__watchdog.html

And for the Pico W using the Arudino core for the RP2040:
rp2040.reboot();

Documented here: RP2040 Helper Class — Arduino-Pico 3.6.0 documentation

I highly expect that my code is failing at some point - hence the reason I'd like to add a watchdog reset.

My code:

/*  Resol VBus Decoder and MQTT WiFi Transmitter for Resol DeltaSol BS/4 (https://www.resol.de/Produktdokumente/48005962_DeltaSol_BS4_V2.monen.pdf)
    
      For: Raspberry Pi Pico W 
      
           Decodes temperature data from a Resol Solar Hot Water System Controller then transmits the decoded temperatures by MQTT using wireless.

      Written using Arduino IDE 2.2.1
      Version 1.0 October 2023
      Author: ER

    Based on: RESOL VBus Protocol Specification Date: 11.10.2007 (RESOL – Elektronische Regelungen GmbH)
              VBus Protocol Specification Last updated: 27.01.2011
              http://danielwippermann.github.io/resol-vbus/#/md/docs/vbus-specification
              RS-485-TTL logic translator using an 817 optocoupler (https://esphome.io/components/vbus.html)

    The Resol VBus signal is a modified RS-485 signal, level shifted via logic translator.
    The VBus message 'packet' comprises a START (170), Header (10 bytes long), and data Frames (each 6 bytes long).
    The 1st byte of a 'packet' includes the START byte (Header is 10 bytes). The last byte of every packet is a checksum (byte 9 of the Header, byte 6 for each Frame).*/


#include "definitions.h"                // Definitions.h contains general information often repeated in my projects
#include <WiFi.h>
#include <AsyncMqtt_Generic.h>
#include <Ticker.h>                     // https://github.com/sstaub/Ticker

const char *PubTopic  = "SHWS";         // Topic to publish
int status = WL_IDLE_STATUS;
bool connectedWiFi  = false;
bool connectedMQTT  = false;
unsigned long heartBeatTime = millis(); // Rate for onboard LED flash when running
bool heartBeatState = true;             // Onboard LED state
byte packets[packetsLength];            // VBus raw data array
float sensors[numberOfSensors];         // Sensors temperature data array
//char temp[3];
AsyncMqttClient mqttClient;
void connectToMqtt();
void connectToMqttLoop();
Ticker connectToMqttTicker(connectToMqttLoop, MQTT_CHECK_INTERVAL_MS, 0, MILLIS); // Repeat forever, millis() resolution


void setup()
{
  memset(packets, 0, packetsLength);     // Zeroise the 'packets' array.
  memset(sensors, 0, numberOfSensors*4); // Set the 'sensor' array to a non-physical number (is a float so needs 4 bytes).
  Serial.begin(115200);                  // Start the Serial1 bus (for basic information to aid debugging)
  while (!Serial && millis() < 5000);    // Wait a bit...
  Serial.print(String(SEPARATIONLINE) + "\n\rStarting FullyFeature_RP2040W on " + String(BOARD_NAME) + " using " + String(ASYNC_MQTT_GENERIC_VERSION));
  Serial1.setFIFOSize(packetsLength);
  Serial1.begin(9600);                   // Start the Serial bus (for receiving translated VBus data)

  pinMode(LED_BUILTIN, OUTPUT);          // For heartBeat()
  connectToWifi();                       
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setCredentials(MQTT_USER, MQTT_PASSWORD);
  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.onPublish(onMqttPublish);
  connectToMqttTicker.start();           // Start the ticker.
  connectToMqtt();
}


void loop()
{
  bool progress = false;          // Flag for setting success of each step        
  byte reportedFrames = 0;        // This will provide the ability to process the reported frames rather than the entire 'packets' array.
  progress = readVBusData();      // Attempt to see if valid data exists on Serial1 bus

  if (progress)                   // Valid data has been received, so attempt to decode it
  {
    reportedFrames = packets[8];  // Extract the number of Frames (data elements) as reported by the Resol controller.
   
    if (reportedFrames > 5)       // This sketch only obtains temperatures from sensors S1, S2, and S3 (frames 1 and 2) 
    {
      reportedFrames = 5;
    }
  }

  if (progress)
  {
    progress = verifyData(reportedFrames); // Attempt to verify the received data
  }

  if (progress)                            // Attempt to unpack the received data
  {
    progress = unpackData(reportedFrames);
  }

  if (progress)
  {
    progress = buildTempData();            // Attempt to interpret the data into temperatures
  }
  connectToMqttTicker.update();            // Update the ticker
  heartBeat();                             // Flash the onboard LED
}


bool readVBusData() // Read the VBus data
{
  bool readVBusDataSuccess = false;
  byte chars = Serial1.available(); // Find how many characters are in the Serial1 buffer

  if (chars > (packetsLength-20))   // If we have enough characters in the buffer...
  {
    if (Serial1.peek() == 170)      // Look for the START of the frame (170)...
    {
      packets[0] = Serial1.read();  // ... and store the START frame.
      
      if (Serial1.peek() == 16)     // We have the 170...16 indicating a START and frames of data, so read the buffer.
      {
        for (int i = 1; i < chars; i++)
        {
          packets[i] = Serial1.read();
        }
        readVBusDataSuccess = true; // Data has been read from the Serial1 buffer successfully.
      }
      else Serial1.read();          // A 170 but not 170...16, so read on.
    }
    else Serial1.read();            // Not a 170 so read on.
  }
  return readVBusDataSuccess;
}


bool verifyData(byte NumDataBlocks) // Based on "vBus_CalcCrc" from "VBus Protocol Specification"
{
  bool verifyDataSuccess = false;

  for (byte thisBlock = 0; thisBlock < NumDataBlocks; thisBlock++)
  {
    byte offset = 4 + (thisBlock * 6);
    byte blockLength = 5;
    
    if (thisBlock == 0)
    {
      offset = 1;
      blockLength = 8;
    }
    
    byte checkSumLocation = 9 + (thisBlock * 6); // Determine the location of the checkSum for this block of this packet
    byte Crc = 0x7F;
    
    for (byte blockElement = 0; blockElement < blockLength; blockElement++)
    {
      Crc = (Crc - packets[offset + blockElement]) & 0x7F;
    }
    if (Crc == packets[checkSumLocation])
    {
      verifyDataSuccess = true;
    }
  }
  return verifyDataSuccess;
}


bool unpackData(byte numberOfFrames)
{
  bool unpackDataSuccess = false;
  byte startOfThisFrame = 0;
  byte septettLocation = 0;
  byte Septett = 0;

  for (byte thisFrame = 1; thisFrame < numberOfFrames; thisFrame++)
  {
    startOfThisFrame = (4 + (thisFrame * 6));
    septettLocation = 8 + (thisFrame * 6);
    Septett = packets[septettLocation];

    for (byte element = 0; element < 4; element++)
    {
      if (Septett & (1 << element))
      {
        packets[startOfThisFrame + element] |= 0x80;
        unpackDataSuccess = true;
      }
    }
  }
  return unpackDataSuccess;
}


bool buildTempData()
{
  bool buildTempDataSuccess = false;

  sensors[0] = ((packets[10] + (packets[11] * 256))/10.0); // Rooftop manifold temperature
  sensors[1] = ((packets[12] + (packets[13] * 256))/10.0); // Lower tank temperature
  sensors[2] = ((packets[16] + (packets[17] * 256))/10.0); // Upper tank temperature

  for (byte i = 0; i < 3; i++)              // Check for valid temparature ranges
  {
    if (sensors[i] < 0 || sensors[i] > 250) // Valid temperature range should be greater than 4°C (use 0°C here) and less than 250°C (rooftop collector gets above 175°C).
    {
      buildTempDataSuccess = false;
      return buildTempDataSuccess;          // In the event of any temperature out of bounds, end the data checking and return a failure.
    }
    else buildTempDataSuccess = true;
  }
  return buildTempDataSuccess;
}


void printWifiStatus()
{
  Serial.print("\n\rConnected to SSID: "); // Print the SSID of the connected network.
  Serial.print(WiFi.SSID());

  IPAddress ip = WiFi.localIP();           // Print board's IP address.
  Serial.print("\n\rLocal IP Address: ");
  Serial.print(ip);

  long rssi = WiFi.RSSI();                 // Print received signal strength.
  Serial.print("\n\rSignal strength (RSSI): ");
  Serial.print(rssi);
  Serial.print(" dBm");
}


void heartBeat() // Flash the onboard LED (this approach doesn't use any delay)
{
  digitalWrite(LED_BUILTIN, heartBeatState);
  if (heartBeatTime < millis())
  {
    heartBeatState = !heartBeatState; // Change state
    heartBeatTime = millis() + 1000;  // Update to 1 second from now
  }
}


bool connectToWifi()
{
  if (WiFi.status() == WL_NO_MODULE) // check for the WiFi module:
  {
    Serial.print("\n\rCommunication with WiFi module failed!");
    while (true);                    // don't continue unitl WiFi reconnects
  }

  Serial.print("\n\rConnecting to SSID: " + String(WIFI_SSID));

  uint8_t numWiFiConnectTries = 0;

  while ((status != WL_CONNECTED) && (numWiFiConnectTries++ < MAX_NUM_WIFI_CONNECT_TRIES_PER_LOOP)) // attempt to connect to WiFi network
  {
    status = WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    delay(500);
  }

  if (status != WL_CONNECTED) // Restart for Portenta as something is very wrong
  {
    Serial.println("Resetting. Can't connect to any WiFi");
    NVIC_SystemReset();
  }

  printWifiStatus();
  connectedWiFi = (status == WL_CONNECTED);

  return (connectedWiFi);
}


bool isWiFiConnected()
{
  static uint8_t theTTL = 10; // You can change longer or shorter depending on your network response; Shorter => more responsive, but more ping traffic

  if (WiFi.ping(WiFi.gatewayIP(), theTTL) == theTTL) // Use ping() to test TCP connections
  {
    return true;
  }
  return false;
}


void connectToMqttLoop()
{
  if (isWiFiConnected())
  {
    if (!connectedMQTT)
    {
      mqttClient.connect();
    }

    if (!connectedWiFi)
    {
      Serial.println("WiFi reconnected");
      connectedWiFi = true;
    }
  }
  
  else
  {
    if (connectedWiFi)
    {
      Serial.print("\n\rWiFi disconnected. Reconnecting");
      connectedWiFi = false;
      connectToWifi();
    }
  }
}


void connectToMqtt()
{
  Serial.print("\n\rConnecting to MQTT...");
  mqttClient.connect();
}


void onMqttConnect(bool sessionPresent)
{
  connectedMQTT = true;

  Serial.print("\n\rTempSensor1 = " + String(sensors[0]));
  mqttClient.publish("SHWS/TempSensor1", 0, true, String(sensors[0]).c_str());
  Serial.print("\n\rTempSensor2 = " + String(sensors[1]));
  mqttClient.publish("SHWS/TempSensor2", 0, true, String(sensors[1]).c_str());
  Serial.print("\n\rTempSensor3 = " + String(sensors[2]));
  mqttClient.publish("SHWS/TempSensor3", 0, true, String(sensors[2]).c_str());
  Serial.print(SEPARATIONLINE);

  memset(sensors, 0, numberOfSensors*4);
}


void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
  (void) reason;
  connectedMQTT = false;
  Serial.print("\n\r        **** Disconnected from MQTT! ****");
}


void onMqttPublish(const uint16_t& packetId)
{
  Serial.print("\n\rPublish acknowledged - packetId: " + String(packetId));
}
/***************************************************************************************************************************************
  definitions.h
 ***************************************************************************************************************************************/

#ifndef definitions_h
#define definitions_h

  #if !( defined(ARDUINO_RASPBERRY_PI_PICO_W) )
    #error For RASPBERRY_PI_PICO_W only
  #endif

  // Debug Level from 0 to 4
  #define _ASYNCTCP_RP2040W_LOGLEVEL_         1
  #define _ASYNC_MQTT_LOGLEVEL_               1

  #if (_ASYNC_MQTT_LOGLEVEL_ > 3)
    #warning Using RASPBERRY_PI_PICO_W with CYC43439 WiFi
  #endif

  #define MQTT_CHECK_INTERVAL_MS                300000
  #define MAX_NUM_WIFI_CONNECT_TRIES_PER_LOOP   20
  #define WIFI_SSID                             "SSID"        // your network SSID (name)
  #define WIFI_PASSWORD                         "Password"  // your network password (use for WPA, or use as key for WEP), length must be 8+
  #define MQTT_HOST                             "192.168.188.102"       // Broker address
  #define MQTT_PORT                             1883
  #define MQTT_USER                             "mqtt-user"
  #define MQTT_PASSWORD                         "mqtt-password"
  #define SEPARATIONLINE                        "\n\r_______________________________________________________________"
  #define packetsLength 128  // Maximum length of valid message is less than 64 bytes
  #define numberOfSensors 3 // There are 4 Temperature Sensors (S1, S2, S3 and S4) although only S1, S2, and S3 are used here.

#endif

Aha. Hm the WiFi-functions of the Pi pico W are very different from an ESP32
anyway your code is well structured into functions.

So for analysing where exactly the code fails I would insert a lot of serial printing.

My first interest would be to check if the WIFi-connection is still functioning.

You could do this by adding to loop() something like

if (WiFi.status() != WL_CONNECTED) {
  Serial.print(" WIFi-Status is not connected value of status=");
  Serial.println(WiFi.status());

and maybe there is even dokumenation to print the real status instead of just a number

It is very likely that the MQTT protocol has functions to check

  • connected to server
  • sended a valid message
  • etc.

Simply restarting your Pi pico means to keep the error inside and hidden.

What do you do if the error occurs as soon as you re-start?
Will you place two dozens of Pi picos in parallel hoping for on one of them the error might not occur?

There are plenty of things to check if this is causing the malfunction.

by the way with the ESP32 one reason for failing is insufficient power-supply.

You have to use a power-supply whic can deliver minimum 500 mA and you should add a 2200µF capacitor as close as possible to 3.3V and ground.

I have multiple ESP32 up and running permanently for years now sending an UDP-message every 20 seconds another one sending UDP-messages every second and they run without a flaw.

best regards Stefan

"So for analysing where exactly the code fails I would insert a lot of serial printing" - I did that in order to create the code.

I cannot locate the error (it doesn't always occur) - so I want to provide a higher level of robustness in the operation by using a watchdog.

My original question is "how do I use a watchdog with a Raspberry Pi Pico and the Arduino IDE?".

https://arduino-pico.readthedocs.io/en/latest/rp2040.html

Your code needs to set up the watchdog, and continually reset it within the timeout interval. If some code hangup blocks the reset, the watchdog will reset the Pico.

are all these serial printings still in the code that you posted in post # 5 ?

If yes I see a lot of places where you could add more serial printing for a more detailed analysis.

If it does happen only from time to time it would be interesting to log the serial printing into a textfile for hours and days to catch the moment when communication fails.

I inserted markings like Serial-print at each place I would add debug-output

void loop()
{
  bool progress = false;          // Flag for setting success of each step        
  byte reportedFrames = 0;        // This will provide the ability to process the reported frames rather than the entire 'packets' array.
  progress = readVBusData();      // Attempt to see if valid data exists on Serial1 bus

  Serial-print  the raw VBusData

  if (progress)                   // Valid data has been received, so attempt to decode it
  {
    Serial-print "readDatasucessfully"
    reportedFrames = packets[8];  // Extract the number of Frames (data elements) as reported by the Resol controller.
    Serial-print  reportedFrames
   
    if (reportedFrames > 5)       // This sketch only obtains temperatures from sensors S1, S2, and S3 (frames 1 and 2) 
    {
      reportedFrames = 5;
      Serial-print  "limited frames to 5"
    }
  }

  if (progress)
  {
    progress = verifyData(reportedFrames); // Attempt to verify the received data
  }

  if (progress)                            // Attempt to unpack the received data
  {
    Serial-print  "verifying data successfully"
    progress = unpackData(reportedFrames);
  }

  if (progress)
  {
    Serial-print  "unpacking data successfully"
    progress = buildTempData();            // Attempt to interpret the data into temperatures
  }
  Serial-print  "right before connectToMqttTicker.update()"
  connectToMqttTicker.update();            // Update the ticker
  Serial-print  "right after connectToMqttTicker.update()"
  heartBeat();                             // Flash the onboard LED
}

and then I would add printing the reason in this function

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
  (void) reason;
  connectedMQTT = false;
  Serial.print("\n\r        **** Disconnected from MQTT! ****");
}

this function has a parameter reason
but you just print disconnected from MQTT without printing the reason
and maybe printing the reason will shine a light on what the communication-fail is.

best regards Stefan

1 Like

Thank you - I will look into that further!

Okay - I have worked out how to implement the Watchdog. I had compile errors attempting use examples, so can't work out what I was doing wrong before, however the following works as a test:

int counter = 0;

void setup()
{
  delay(5000);                                // Provide enough time to clear the Serial Monitor text.
  Serial.begin(115200);                       // Begin Serial bus for data over Serial Monitor.
  Serial.print("\n\rInitialising Watchdog!"); // Indicate Watchdog has started.
  rp2040.wdt_begin(8300);                     // Commence Watchdog with maximum duration (8,300 milliseconds).
}

void loop()
{
  randomSeed(analogRead(1));                                             // Introduce more randomness than would otherwise exist.
  rp2040.wdt_reset();                                                    // Reset the Watchdog.
  Serial.print("\n\r" + String(counter) + " loops since resetting!");    // Provide successful loop count since Watchdog last triggered.
  long number = random(12000);                                           // Generate random number between 0 and 12,000 milliseconds for waiting.
  Serial.print("    This delay = " + String(number) + " milliseconds."); // Show the random delay time.
  delay(number);                                                         // Wait for the random delay time.
  counter++;                                                             // Increment the number of successful loops since resetting the Watchdog.
}

next step will be to add code that will detect if communication failed

@ dukbilt

OK, thanks for the example. (I'm a newbee in raspeberry and RP2040, and C/C++ definitely makes more sense to me than python)

a note: in order to "randomize the random", to my knowledge randomSeed(analogRead(1));
once, in the setup, is enough
(edit : confirmed. Other thing : take care to chose an analog pin, "1" is'nt)

but above all, a question: in my case, every reset, software or hardware (pin "run"), causes the loss of the serial. There's only one new download to initialize it...
Can this be fixed?

Hello Simon,

glad to help - I too, however, am an amateur at this and therefore can't assist much further.

Thanks for the heads up on the randomisation aspect - I will amend accordingly!

As for the loss of serial - it's expect to occur that way, as resetting the device completely resets the device... What you're seeking is some kind of lower-level reset without being a full system reset. This is beyond what the Watchdog is there for.

Ultimately, though, if you can make your code robust enough then you shouldn't even need a Watchdog. The Watchdog is there for 'all other' failure events that you haven't accounted for. I have to confess - I used the Watchdog out of poor programming, and laziness!

Hello !
regarding the watchdog, I haven't tried it in your code, but another very effective form of laziness would be to call the setup (yes yes, inside the loop... it's not forbiden !)

As for the loss of the serial... that would be due to the USB management, which is tricky as it seems.
Anyway, for my part, I'm giving up on the pi pico...

  • this serial issue not solved
  • but much more annoyingly, it's impossible to get out of a deep sleep using a pin interrupt (at least without using an external RTC !!...). It's simply not implemented in either micropython or circuitpython. Even less so in "Philhower's" work (he explains it in github).

I am having trouble with my pico w (acting as server) crashing as well. This thread looks very informative. I am trying to run a sparkfun temp/humidity/pressure sensor (BME280) to get the outdoor temperature and submit that to my computer indoors via wifi. The pico is plugged into a usb charger, so Serial.print commands dont output anything. Right now I am going to try writing to EEPROM. In my main loop I have a check for wifi being connected and send to a connectToWifi method if not connected. In that method I have the following code:

while(statusIdle == true){
  if (WiFi.status() == WL_CONNECTED) { statusIdle = false;}
  else if(WiFi.status() == WL_CONNECT_FAILED) {
    EEPROM.write(10,WL_CONNECT_FAILED);
    statusIdle = false;
    }
  else{
    delay(500);
    Serial.print("status: ");
    Serial.println(WiFi.status());
  }
 }

I am also implementing the watchdog, as described in the thread. Hope this works

oh so sorry I didn't see your post sooner!
welcome here !

wifi does indeed solve the serial problem elegantly. Have you succeeded?

Do you take sensor readings 24/24? in which case the watchdog alarm is suitable. But for pauses that exceed the watchdog's capacity, you'll run into the same problem with the pico, can't be woken up by an interrupt... argh

Hi Simon,
Thanks for your interest.

The project seems to be working well right now.

I think the problem is that my wifi service sometimes cuts out and the Arduino was not re-connecting. I am doing a measurement every min. The one min is counted down by a RPi, which queries the Arduino. Since the Arduino was offline, the whole thing was breaking down. My workaround was to compare the current time (millis()) to the time when the Arduino last responded to the RPi. If the difference exceeds 10 min, the Arduino restarts.I also put in a test where the Arduino restarts if the millis() query exceeds one week. That way the millis counter doesnt roll over, screwing up the first test.
Best,
Jack

hello jack,
I understand your solution; it's clever but perhaps cumbersome...
I haven't implemented it myself, so I can't guarantee anything, but at least I know that there's a library that seems to provide an arduino "inboard" solution :
wifi_status

Hi Jack,
speaking of bmp280s, what's your experience?
with the idea of checking the accuracy of my thermoPro hygrometer, I bought three ATH+BMP modules from Ali and here's the result... pretty dismal.

(at the same time t)
Module 1
Temperature : AHT20 16.27 BMP280 20.62
Humidity : 45.05 Pressure : 1012.88
Module 2
Temperature : AHT20 16.44 BMP280 20.57
Humidity : 0.00 Pressure : 1034.78
Module 3
Temperature : AHT20 16.70 BMP280 17.17
Humidity : 44.87 Pressure : 1012.41

humm !