Arduino Freezing/Locking problem

Hi everyone, I am facing an issue where my project completely locks up after several minutes after turning it on.

Brief overview of my device:

  • SAM D21 mcu
  • SPBTLE-RF BLE chip (SPI)
  • 32KB FRAM (I2C)
  • MAX30105 Heart rate sensor (I2C)
  • MPU-9250 Accelerometer (I2C)
  • SSD1306 OLED Display (I2C)

The code is currently all working fine. It just breaks after a couple minutes. This problem does not happen when I do not call the functions: countSteps and calculateHR() in my main loop.

To fix this problem, I could simply implement a watchdog timer which will reset the mcu but I rather not and wish to solve the root of the problem.

EDIT: The device seems to stop alot at time: 4:27 (almost 4 minutes after startup)
Code:

#include <U8g2lib.h>
#include <SparkFunMPU9250-DMP.h>
#include <RTCZero.h>
#include "MAX30105.h"
#include "heartRate.h"
#include <SparkFunBLEMate2.h>
#include "Adafruit_FRAM_I2C.h"
#include "avdweb_SAMDtimer.h"
#include <SPI.h>
#include <STBLE.h>
#include "esperto.h"

extern "C" char *sbrk(int i);

// Define display I2C bus
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);

// Define FRAM
Adafruit_FRAM_I2C FRAM = Adafruit_FRAM_I2C();

// Bluetooth and Clock Variables
char dateBT[15]; // date info MM/DD/YYYY
char timeBT[15]; // time info HH:MM:SS MM
char callBT[20]; // caller number info
char textBT[20]; // text number info
RTCZero rtc; // instance of RTCZero class
bool isRTCInit; // determines if RTC has been initialized
uint8_t ble_rx_buffer[21];
uint8_t ble_rx_buffer_len = 0;
uint8_t ble_connection_state = false;
int connected = FALSE;
volatile uint8_t set_connectable = 1;
uint16_t connection_handle = 0;
uint16_t UARTServHandle, UARTTXCharHandle, UARTRXCharHandle;
uint8_t sendBuffer[21];
uint8_t sendLength = 0;
byte lastSecondDisplayUpdated; // last second which display was updated (only update display every second)

// MPU9250
MPU9250_DMP imu;
float iir_Av = 0; // IIR filter average
const int stepDiffMinThreshold = 20; // minimum difference between step max and min for considered step
int stepCount = 0; // total number of steps taken (combination of dmp and gyro step counts)
int dmpSteps = 0; // total number of high frequency steps calculated by DMP
int gyroSteps = 0; // total number of low frequency steps calculated by gyroscope
double stepMax = 0; // peak of gyration data
double stepMin = 0; // trough of gyration data
double gyroData[3]; // array storing recent gyration readings

// Heart Rate Variables
MAX30105 heartRateSensor; // instance of MAX30105 class
const byte arraySizeHR = 5; // size of array containing latest HR values
byte heartRates[arraySizeHR]; // array containing latest HR values
byte heartRateIndex = 0; // index latest value was inputted into array
long prevHeartBeat = 0; // time at which the last heart beat occurred
float heartRate; // current heart rate
int heartRateAvg = 0; // average heart rate which will we be displayed
long timeLastHRBeat = 0;

// FRAM Variables
uint16_t countFRAM = 0; // stores the address which is going to be used by the FRAM
volatile uint8_t FRAM_ISR_CTR = 0; // counter used in addition to timer to determine when memory is stored
// FRAM Timer ISR -- write to FRAM every minute when not connected to device
void ISR_timer3(struct tc_module *const module_inst) 
{ 
  FRAM_ISR_CTR = FRAM_ISR_CTR + 1;
  if(FRAM_ISR_CTR >= 60)
  {
    writeFRAM();
    FRAM_ISR_CTR = 0;
  }
}
SAMDtimer timer3_1Hz = SAMDtimer(3, ISR_timer3, 1e6); // FRAM timer interrupt

// Battery Variables
const uint8_t chargePin = 0; // TODO: needs to be implemented
const uint8_t batteryPin = 1; // analog 1: used to determine when battery is low/high
const float referenceVolts = 5.0; // the default reference on a 5-volt board

// Bootloader
void setup()
{
  Serial1.begin(9600);
  SerialUSB.begin(9600);
  Wire.begin();
  
  // Define displays I2C address and display boot screen
  u8g2.begin();
  u8g2.firstPage();
  do {
    u8g2.drawXBMP(32, 0, 64, 64, boot);
  } while ( u8g2.nextPage() );
  
  // setup MPU9250
  init_MPU9250();
  
  // initialize Real Time Clock
  rtc.begin();

  // Turn on and setup heart rate sensor
  heartRateSensor.begin(Wire, I2C_SPEED_STANDARD); // 100 KHz
  heartRateSensor.setup(); // configure sensor with default settings
  heartRateSensor.setPulseAmplitudeRed(0x0A); // red LED to low to indicate sensor is running
  heartRateSensor.setPulseAmplitudeGreen(0); // turn off Green LED

  // Initialize FRAM and associated timer interrupt
  timer3_1Hz.attachInterrupt(ISR_timer3); 
  FRAM.begin();

  // Initialize STPBTLE-RF
  BLEsetup();
}

// Function to update the display with latest information
void updateDisplay()
{
  // if there is an incoming phone call
  if (strlen(callBT) >= 10 && ble_connection_state == true)
  {
    u8g2.setFont(u8g2_font_profont11_tf);
    // print time top left corner
    u8g2.drawStr(0, 10, timeBT);

    u8g2.setFont(u8g2_font_profont22_tf);
    // display call text and phone number
    u8g2.setCursor(40, 38);
    u8g2.print("Call");
    u8g2.drawStr(0, 58, callBT);
  }
  // if there is an incoming text
  else if (strlen(textBT) >= 10 && ble_connection_state == true)
  {
    u8g2.setFont(u8g2_font_profont11_tf);
    // print time top left corner
    u8g2.drawStr(0, 10, timeBT);

    u8g2.setFont(u8g2_font_profont22_tf);
    // display text text and phone number
    u8g2.setCursor(40, 38);
    u8g2.print("Text");
    u8g2.drawStr(0, 58, textBT);
  }
  // if no incoming call or text
  else
  {
    u8g2.setFont(u8g2_font_profont11_tf);
    // display date
    if(ble_connection_state == true)
      u8g2.drawStr(0, 10, dateBT);
    else
    {
      u8g2.setCursor(0, 10);
      u8g2.print(String(rtc.getMonth()) + "/" + String(rtc.getDay()) + "/20" + String(rtc.getYear()));
    }

    // display heart rate
    u8g2.setCursor(14, 62);
    u8g2.print(String(heartRateAvg) + " bpm");
    u8g2.drawXBMP(0, 54, 10, 10, heart);

    // display steps
    u8g2.setCursor(78, 62);
    u8g2.print(String(stepCount*2) + " stp");
    u8g2.drawXBMP(64, 54, 10, 10, mountain);

    // display time
    u8g2.setFont(u8g2_font_profont22_tf);
    // if connected to Bluetooth
    if(ble_connection_state == true)
    {
      // center time on display
      if (strlen(timeBT) == 7)
        u8g2.drawStr(20, 38, timeBT);
      else if (strlen(timeBT) == 8)
        u8g2.drawStr(13, 38, timeBT);
    }
    // if not connected to Bluetooth
    else
    {
      u8g2.setCursor(13, 38);
      // time does not come with leading 0's, add to display string if needed
      if(rtc.getMinutes() < 10 && rtc.getSeconds() < 10)
        u8g2.print(String(rtc.getHours()) + ":0" + String(rtc.getMinutes()) + ":0" + String(rtc.getSeconds()));
      else if(rtc.getMinutes() < 10)
        u8g2.print(String(rtc.getHours()) + ":0" + String(rtc.getMinutes()) + ":" + String(rtc.getSeconds()));
      else if(rtc.getSeconds() < 10)
        u8g2.print(String(rtc.getHours()) + ":" + String(rtc.getMinutes()) + ":0" + String(rtc.getSeconds()));
      else
        u8g2.print(String(rtc.getHours()) + ":" + String(rtc.getMinutes()) + ":" + String(rtc.getSeconds()));
    }   
  }
  
  // draw BT logo only if connected
  if(ble_connection_state == true)
    u8g2.drawXBMP(118, 0, 10, 10, BT);

  // obtain and display battery status
  int battVoltRaw = analogRead(batteryPin);
  float battVolt = (battVoltRaw / 1023.0) * referenceVolts;
  // display full battery
  if (battVolt >= 3.6)
    u8g2.drawXBMP(105, 0, 10, 10, battHigh);
  // display low battery
  if (battVolt < 3.6)
    u8g2.drawXBMP(105, 0, 10, 10, battLow);
}

Second part of code:

void setRTCTime()
{
  byte setTimeDate;
  
  // set time (HH:MM PM)
  // determine how many bytes to read as format can be HH:MM or H:MM
  if(strlen(timeBT) == 7)
    setTimeDate = stringToByte(timeBT, 1);
  else
    setTimeDate = stringToByte(timeBT, 2);
  // determine if PM and add 12 hours if so
  if(memchr(timeBT, 'P', strlen(timeBT)) != NULL)
     rtc.setHours(setTimeDate + 12);
  else
    rtc.setHours(setTimeDate);
  // determine position of pointer (start of MM)as format can be HH:MM or H:MM
  if(strlen(timeBT) == 7)
    setTimeDate = stringToByte(timeBT+2, 2);
  else
    setTimeDate = stringToByte(timeBT+3, 2);
  rtc.setMinutes(setTimeDate);
  rtc.setSeconds(30);
  
  // set date (MM/DD/YYYY)
  setTimeDate = stringToByte(dateBT, 2);
  rtc.setMonth(setTimeDate);
  setTimeDate = stringToByte(dateBT+3, 2);
  rtc.setDay(setTimeDate);
  setTimeDate = stringToByte(dateBT+8, 2); // only get the last two digits of the year
  rtc.setYear(setTimeDate);
}

void writeFRAM()
{
  // Ensure memory is not full
  if(countFRAM < 32768)
  {
     FRAM.write8(countFRAM, heartRate);
     FRAM.write8(countFRAM+1, stepCount >> 8);
     FRAM.write8(countFRAM+2, stepCount);
     countFRAM+=4; // 4 byte alligned 
  }
}

void burstTransferFRAM()
{
  SerialUSB.println("Reading started");
  for(int i = 0; i < countFRAM; i+=4)
  {
    // TODO: BASED ON BLE TRANSFER RATE AND PACKAGE SIZE
    // Possibly transfer all in this function, block entire watch, and write to display
    int hrFRAM = FRAM.read8(i);
    int stepsFRAM = 0;
    stepsFRAM = (FRAM.read8(i+1) << 8);
    stepsFRAM |= FRAM.read8(i+2);
    SerialUSB.println(String(hrFRAM) + " " + String(stepsFRAM));
  }/*
    uint8_t bufferRand[50] = "12345678901234567890";
    sendLength = 20; // max 20 characters - not sure if null terminator is needed
    Write_UART_TX((char*)bufferRand, sendLength);
  */
  countFRAM = 0; // Go back to first FRAM address
}

void countSteps()
{
  float gyroX = imu.calcGyro(imu.gx);
  float gyroY = imu.calcGyro(imu.gy);
  float gyroZ = imu.calcGyro(imu.gz);
  
  float magGyration = sqrt(sq(gyroX) + sq(gyroY) + sq(gyroZ));
  float i = iirFilter((int) magGyration);
  
  // update recent quaternion values
  gyroData[0] = gyroData[1];
  gyroData[1] = gyroData[2];
  gyroData[2] = i; 
  
  // if a peak/max is found - compare min to 100 to remove high freq data
  if(gyroData[1] > gyroData[0] && gyroData[1] > gyroData[2] && stepMin < 100)
  {
    stepMax = gyroData[1];// update peak value      
    double maxMinDiff = stepMax - stepMin; 
    // if a step is detected
    if (maxMinDiff > stepDiffMinThreshold)
    {
        gyroSteps++;
        stepCount = gyroSteps;
    }
  }
  // if a trough/min is found - 100 compared to reduce high freq noise
  else if(gyroData[1] < gyroData[0] && gyroData[1] < gyroData[2])
  {
    stepMin = gyroData[1];
  }
}

// Function used to calculate users heart rate
void calculateHR()
{
  // obtain infrared value
  long irValue = heartRateSensor.getIR();

  // if heart beat was detected and valid IR value
  if (checkForBeat(irValue) == true && irValue > 50000)
  {
    // calculate time difference between 2 beats
    long heartBeatTimeDiff = millis() - prevHeartBeat;
    prevHeartBeat = millis();

    // use the difference to calculate the heart rate
    heartRate = 60 / (heartBeatTimeDiff / 1000.0); // 60 s in 1 min, 1000 ms in 1 s

    // only use valid heart rates
    if (heartRate < 120 && heartRate > 40)
    {
      // store heart rate
      heartRates[heartRateIndex++] = (byte)heartRate;
      heartRateIndex %= arraySizeHR; // use modulus op. to determine current index

      // calcute average heart rate
      heartRateAvg = 0; // reset
      for (int i = 0; i < arraySizeHR; i++)
        heartRateAvg += heartRates[i]; // add up all heart rates
      heartRateAvg /= arraySizeHR; // determine average by dividing

      timeLastHRBeat = millis();
    }
  }
}

void loop()
{
  aci_loop();//Process any ACI commands or events from BLE
  
  //Check if data is available
  if (ble_rx_buffer_len) 
  { 
    if(strncmp((const char*)ble_rx_buffer, "D:", 2) == 0)
      strcpy(dateBT, (const char*)ble_rx_buffer+2);
    else if(strncmp((const char*)ble_rx_buffer, "T:", 2) == 0)
      strcpy(timeBT, (const char*)ble_rx_buffer+2);
    else if(strncmp((const char*)ble_rx_buffer, "C:", 2) == 0)
      strcpy(callBT, (const char*)ble_rx_buffer+2);
    else if(strncmp((const char*)ble_rx_buffer, "M:", 2) == 0)
      strcpy(textBT, (const char*)ble_rx_buffer+2);
    ble_rx_buffer_len = 0;//clear afer reading

    // update display
    u8g2.firstPage();
    do {
      updateDisplay();
    } while ( u8g2.nextPage() );

    // If device was just disconnected
    if(isRTCInit == true)
    {
      burstTransferFRAM();
      isRTCInit = false;
      timer3_1Hz.enableInterrupt(0); // disable timer interrupt
    }
  }

  if(ble_connection_state == false && lastSecondDisplayUpdated != rtc.getSeconds())
  {
    // set time based off last Bluetooth connection
    if(isRTCInit == false)
    {
      setRTCTime();
      isRTCInit = true;
      timer3_1Hz.enableInterrupt(1); // enable FRAM timer interrupt
    }
    
    // update display
    u8g2.firstPage();
    do {
      updateDisplay();
    } while ( u8g2.nextPage() );
    
    // update time
    lastSecondDisplayUpdated = rtc.getSeconds();
  }

  // Check for new data in the MPU9250 FIFO
  if ( imu.fifoAvailable() )
  {
    // Update accel and gyro values
    if ( imu.dmpUpdateFifo() == INV_SUCCESS)
    {
        dmpSteps = imu.dmpGetPedometerSteps()*2;
        countSteps();
    }
  }
  
  // calculate heart rate
  if(millis()-timeLastHRBeat > 500)
  {
      calculateHR(); 
  }
  int i = freeRam();
  SerialUSB.println(i);
}

A possible cause for the symptom that you describe is inadverantly writing beyond the limits of an array and trashing memory that you do not "own". Sometime this is because the null that is automatically added to the end of character arrays is not accounted for.

Good point. However, I do not see where this could be happening.

Sprinkle some Serial prints around to monitor variables and program flow to try to see which function or section of code is causing the freeze.

Thank you for your help.

I seemed to have fixed the problem. I have an ISR for writing to my FRAM chip which was calling a function which takes much too long.

This resulted in the program freezing.