Reason behind Hard Faults

Is there any possibility for a Hard Fault to occur due to the modem (Boron 404X) and its inability to maintain a stable connection with the tower?

Unlikely, unless you are also almost out of free RAM.

The most common reason for a hard fault is a programming error, such as:

  • Heap corruption, such as from overwriting the end of a buffer
  • Stack corruption, such as from overwriting the end a stack allocated variable or overflowing the stack
  • Not returning a value from a function that is non-void
  • Deleting a block of memory twice using delete or free
  • Not checking the result of a memory allocation using new, malloc, calloc, strdup, etc. and having insufficient memory for the allocation
  • Passing an unexpectedly invalid structure to a function that's expecting something else, causing one of the things above to occur
2 Likes

Thanks Rick.
Any chance it can be caused by "hardware" such as a drop in voltage from grid power while the battery is also connected or some other "dirty" power issue.

It's possible that power issues could cause corruption of RAM, which could cause a hard fault. But a software issue is generally more likely.

I reviewed my code many times and I'm not seeing anything that would fall into the most common reasons for a hard fault. I'm including it below if anyone sees a red flag please let me know.


#define DELAY_BEFORE_REBOOT (15 * 1000)

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);

int pin = D4;
int powerStatus = 1;

// EEPROM Locations
int setPhoneEEPROMLocation = 1;
int setEmailEEPROMLocation = 100;
int setAlertEEPROMLocation = 200;
int setAlertEmailPowerEEPROMLocation = 500;
int setAlertPhonePowerEEPROMLocation = 700;

char dev_name[40] = "Not Set";
char phone_str[40] = "Not Set";
char email_str[40] = "Not Set";
char alertPowerSms[256] = "0";
char alertPowerEmail[256] = "0";

int A = 0;
int PP = 0;
int EP = 0;

int CR(String command);
int RS = 0;
int UT = 0;
int day = 0;
int hour = 0;
int minutes = 0;
int seconds = 0;

double voltage = 0.0;
double power = 0.0;
double charging = 0.0;

//Rebooting the device delay
unsigned int rebootDelayMillis = DELAY_BEFORE_REBOOT;
unsigned long rebootSync = millis();

bool resetFlag = false;

int SG = 0;
double batterySoc = System.batteryCharge();
int powerSource = System.powerSource();

unsigned long previousMillis = 0;        // will store last time updated
const unsigned long interval = 43200000; // Check every 5 minutes

unsigned long previousMillisBattery = 0;    // will store last time updated
const unsigned long intervalBattery = 2000; // Check every 2 seconds

double tempF = 0;
bool chargeEnabled = true;

const unsigned long POWER_CHECK_INTERVAL_MS = 5000;
unsigned long lastPowerCheck = 0;
PMIC pmic;

char fjson[256];
char sjson[256];

const unsigned long intervalCell = 30000; // wait 10 seconds after Particle.connected
bool initialCellCheck = false;
bool waitingForCell = false;
unsigned long waitingMillis = 0;

// Initialize previous values of PP and EP
int prev_PP = -1;
int prev_EP = -1;

void setup()
{

  // The device has booted, reconnect the battery.
  pmic.enableBATFET();
  pinMode(pin, OUTPUT);
  Particle.variable("FData", fjson);
  Particle.variable("SData", sjson);
  Particle.variable("SPS", phone_str);
  Particle.variable("SES", email_str);
  Particle.function("SES", setEmail);
  Particle.function("SPS", setPhone);
  Particle.function("EP", enableEmailPowerAlerts);
  Particle.function("PP", enablePhonePowerAlerts);
  Particle.variable("A", A);
  Particle.variable("EP", EP);
  Particle.variable("PP", PP);
  Particle.variable("SG", SG);
  Particle.variable("BAT", batterySoc);
  Particle.variable("PWR", powerSource);
  Particle.function("RT", cloudResetFunction);
  Particle.function("CR", CR);
  Particle.variable("RS", RS);
  Particle.variable("UT", UT);
  Particle.variable("TMP", tempF);
  // Now that the battery is connected, connect to the cloud.
  Particle.connect();
  getStoredInfo();
  Particle.subscribe("particle/device/name", handler);
  waitFor(Particle.connected, 600000);
  Particle.publish("particle/device/name");
}

void loop()
{
  UT = System.uptime();
  batterySoc = System.batteryCharge();
  powerSource = System.powerSource();
  RS = digitalRead(pin);
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval)
  {
    previousMillis += interval;
    system_display_rssi();
    internal_temp();
  }

  if (initialCellCheck == false)
  {
    if (Cellular.ready() == true)
    {
      if (Particle.connected && waitingForCell == false)
      {
        waitingMillis = millis();
        waitingForCell = true;
      }
      unsigned long currentMillisInitail = millis();
      if (currentMillisInitail - waitingMillis >= intervalCell)
      {
        system_display_rssi();
        initialCellCheck = true;
        internal_temp();
      }
    }
  }

  JSONBufferWriter writer(fjson, sizeof(fjson));
  writer.beginObject();
  writer.name("email").value(email_str);
  writer.name("phone").value(phone_str);
  writer.name("alert").value(A);
  writer.name("EP").value(EP);
  writer.name("PP").value(PP);
  writer.name("SG").value(SG);
  writer.name("BAT").value(batterySoc);
  writer.name("PWR").value(powerSource);
  writer.name("RS").value(RS);
  writer.name("UT").value(UT);
  writer.name("TMP").value(tempF);
  writer.endObject();
  writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
  JSONBufferWriter shortwriter(sjson, sizeof(sjson));
  shortwriter.beginObject();
  shortwriter.name("alert").value(A);
  shortwriter.name("PWR").value(powerSource);
  shortwriter.name("RS").value(RS);
  shortwriter.endObject();
  shortwriter.buffer()[std::min(shortwriter.bufferSize(), shortwriter.dataSize())] = 0;

  if (millis() - lastPowerCheck >= POWER_CHECK_INTERVAL_MS)
  {
    lastPowerCheck = millis();
    if ((!pmic.isPowerGood()) && (batterySoc < 5.0))
    {
      // Disconnect from the cloud and power down the modem.
      Particle.disconnect();
      Cellular.off();
      delay(30000);
      // Disabling the BATFET disconnects the battery from the PMIC. Since there
      // is no longer external power, this will turn off the device.
      pmic.disableBATFET();
      // This line should not be reached. When power is applied again, the device
      // will cold boot starting with setup().
      // However, there is a potential for power to be re-applied while we were in
      // the process of shutting down so if we're still running, enable the BATFET
      // again and reconnect to the cloud. Wait a bit before doing this so the
      // device has time to actually power off.
      delay(2000);
      pmic.enableBATFET();
      Cellular.on();
      Particle.connect();
    }
  }

  if (currentMillis - previousMillisBattery >= intervalBattery)
  {
    previousMillisBattery += intervalBattery;
    if (powerSource == 5)
    {
      if (powerStatus != 0)
      {
        if ((A == 1) && (PP == 1))
        {
          // Power Lost SMS
          snprintf(alertPowerSms, sizeof(alertPowerSms), "{\"N\": \"%s\", \"T\": \"%s\"}", dev_name, phone_str);
          Particle.publish("PS_SMS", alertPowerSms, 60, PRIVATE);
        }

        if ((A == 1) && (EP == 1))
        {
          // Power Lost EMAIL
          snprintf(alertPowerEmail, sizeof(alertPowerEmail), "{\"N\": \"%s\", \"T\": \"%s\"}", dev_name, email_str);
          Particle.publish("E_PS", alertPowerEmail, 60, PRIVATE);
        }
        powerStatus = 0;
      }
    }
    else if (powerSource < 5)
    {
      if (powerStatus != 1)
      {
        if ((A == 1) && (PP == 1))
        {
          snprintf(alertPowerSms, sizeof(alertPowerSms), "{\"N\": \"%s\", \"T\": \"%s\"}", dev_name, phone_str);
          Particle.publish("PR_SMS", alertPowerSms, 60, PRIVATE);
        }

        if ((A == 1) && (EP == 1))
        {
          snprintf(alertPowerEmail, sizeof(alertPowerEmail), "{\"N\": \"%s\", \"T\": \"%s\"}", dev_name, email_str);
          Particle.publish("E_PR", alertPowerEmail, 60, PRIVATE);
        }
        powerStatus = 1;
      }
    }
  }

  if ((resetFlag) && (millis() - rebootSync >= rebootDelayMillis))
  {
    delay(1000);
    System.reset();
  }

 if ((PP != prev_PP) || (EP != prev_EP)) {
    if ((PP == 0) && (EP == 0)) {
        enableAlerts("0");
    }
    else if ((PP == 1) || (EP == 1)) {
        enableAlerts("1");
    }
}

// Update previous values
prev_PP = PP;
prev_EP = EP;
}

int enableAlerts(String command)
{

  if (command.equalsIgnoreCase("1"))
  {
    EEPROM.put(setAlertEEPROMLocation, 1);
    A = 1;
    return 1;
  }
  else
  {
    EEPROM.put(setAlertEEPROMLocation, 0);
     A = 0;
    return 0;
  }
}

int enableEmailPowerAlerts(String command)
{
  if (command.equalsIgnoreCase("1"))
  {
    EP = 1;
    EEPROM.put(setAlertEmailPowerEEPROMLocation, 1);
    return 1;
  }
  else
  {
    EP = 0;
    EEPROM.put(setAlertEmailPowerEEPROMLocation, 0);
    return 0;
  }
}

int enablePhonePowerAlerts(String command)
{
  if (command.equalsIgnoreCase("1"))
  {
    PP = 1;
    EEPROM.put(setAlertPhonePowerEEPROMLocation, 1);
    return 1;
  }
  else
  {
    PP = 0;
    EEPROM.put(setAlertPhonePowerEEPROMLocation, 0);
    return 0;
  }
}

int setPhone(const char *data)
{
  if(strlen(data) >= sizeof(phone_str))
  {
    // Data is too long, return an error code
    return -1;
  }
  
  strlcpy(phone_str, data, sizeof(phone_str));
  EEPROM.put(setPhoneEEPROMLocation, phone_str);
  return strlen(phone_str);
}

int setEmail(const char *data)
{
   if(strlen(data) >= sizeof(email_str))
  {
    // Data is too long, return an error code
    return -1;
  }
  strlcpy(email_str, data, sizeof(email_str));
  EEPROM.put(setEmailEEPROMLocation, email_str);
  return strlen(email_str);
}

void getStoredInfo()
{
  EEPROM.get(setPhoneEEPROMLocation, phone_str);
  EEPROM.get(setEmailEEPROMLocation, email_str);
  EEPROM.get(setAlertEEPROMLocation, A);
  EEPROM.get(setAlertEmailPowerEEPROMLocation, EP);
  EEPROM.get(setAlertPhonePowerEEPROMLocation, PP);
  prev_PP = PP;
  prev_EP = EP;
}

void handler(const char *topic, const char *data)
{
  if(strlen(data) > 0) {
    strncpy(dev_name, data, sizeof(dev_name) - 1);
  }
}

int cloudResetFunction(String command)
{
  resetFlag = true;
  rebootSync = millis();
  return 0;
}

int CR(String command)
{
  if (command.equalsIgnoreCase("1"))
  {
    digitalWrite(pin, HIGH);
    return 1;
  }
  if (command.equalsIgnoreCase("0"))
  {
    digitalWrite(pin, LOW);
    return 1;
  }

  if (command.equalsIgnoreCase("Reset"))
  {
    digitalWrite(pin, HIGH);
    for (uint32_t ms = millis(); millis() - ms < 10000; Particle.process());
    digitalWrite(pin, LOW);
    return 1;
  }
  return 0;
}

void system_display_rssi()
{
  CellularSignal sig = Cellular.RSSI();
  SG = sig.getStrength();
}

void internal_temp()
{
  int32_t temp;
  uint32_t res = sd_temp_get(&temp);
  if (res == NRF_SUCCESS)
  {
    float tempC = (float)temp / 4;
    tempF = tempC * 9 / 5 + 32;
  }
  else
  {
    tempF = 999;
  }
  if ((tempF < 40 || tempF > 113) && chargeEnabled)
  {
    PMIC pmic(true);
    pmic.disableCharging();
    chargeEnabled = false;
  }
  else if ((tempF >= 40 && tempF <= 113) && !chargeEnabled)
  {
    PMIC pmic(true);
    pmic.enableCharging();
    chargeEnabled = true;
  }
}

void setPowerConfig()
{
  SystemPowerConfiguration conf;
  System.setPowerConfiguration(SystemPowerConfiguration()); // To restore the default configuration
  conf.powerSourceMaxCurrent(500);                          // Set maximum current the power source can provide (applies only when powered through VIN)
  //   int res = System.setPowerConfiguration(conf); // returns SYSTEM_ERROR_NONE (0) in case of success
  //   return res;
}

Could it be that the JSONBufferWriter is writing to a buffer 1 too large? The documentation mentions it needs to be - 1 to leave room for the null terminator. If it fills up maybe it's overwriting.
JSONBufferWriter writer(buf, sizeof(buf) - 1);

2 Likes

Subtracting 1 from writer.bufferSize() to "leave room for the null terminator" is not necessary. The writer.bufferSize() already accounts for the null-terminator. Subtracting 1 would actually lead to incorrect behavior by placing the null-terminator one position before the end of the buffer.

JSONBufferWriter() does not leave room for a null terminator. It's designed to work with buffer and length.

If you need null termination, you do need to pass the -1 factor to the constructor as in this example because when you execute this:

writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
  • dataSize() will be actual data size if it fit
  • bufferSize() is what you passed to the constructor
  • If the data didn't fit, you overwrite the end of the buffer by 1 byte if the JSON exceeded the buffer size without the -1 factor in the constructor when passing in sizeof().
2 Likes

Sounds good. Thank you for the clarification.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.