Hi all,
I need some expert help in identifying a potential memory leak. The problem appears, when I enable the OLED routine, to show frequently status of what is happening.
The entire program is written in somewhat of a state-machine, where the OLED logic is executed, every ~ 100ms. If this routine is enabled, It takes a random number of minutes, sometimes seconds,
before the Arduino hangs up and nothing "appears" to be happening anymore.
Following hardware in use:
Arduino Uno (SMD uC)
some push buttons
I2C OLED 128x64 using Adafruit libs
Dev environment is VS Code with github integration and platformIO for library management etc.
Done plenty of other projects in this set-up, usually no issues with env.
no buttons are pressed and nothing else needs to happen, and it still crashed / hangs.
Here the function that is causing me grief. As long as I comment it out, all good and happy, the program runs for days, without crashing, which I can indicate by the blinking led.
That tells me, something about this part of the program is causing memory leaks, or something the like. The program is using interrupts, however, the problem exists whether interrupts are enabled or disabled.
Below is the trouble-some code, full source below that.
// ------------------------------------------------------
// Service Routine[2]: ????
#if SR02_OLED_EN == 1
if ((t-tTime[2]) >= (1000 / OLED_UPD_FREQ))
{
// general settings
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
// Display Step-#
display.setCursor(0, 0);
display.setTextSize(1);
display.print(F("Current Step: "));
display.println((processStep));
// Display OP-Mode
display.setCursor(115, 0);
if MANUAL_MODE
{
display.print(F("M"));
}
else
{
display.print(F("A"));
}
// Display Step-time
display.setCursor(0, 10);
display.print(F("Remaining Time: "));
if (processStep == 30)
{
display.println(curentStepTimeout-pulseRCVD_Timeout);
}
else
{
display.println(curentStepTimeout-curentStepTimer);
}
// Display current tool position & homed-status
if (homed)
{
display.setTextSize(4);
display.setCursor(0, 20);
display.println(toolPosition_actual);
// show moving to arrow
if ((processStep >= 40) && (processStep <= 60))
{
//
display.setCursor(50, 20);
display.write(175);
}
// show target position
if (pulseCount != 0)
{
display.setCursor(70, 20);
display.print(pulseCount);
}
//
display.setTextSize(2);
display.setCursor(0, 50);
display.println(F("Homed"));
}
else
{
display.setTextSize(4);
display.setCursor(0, 20);
display.println(F("UNK"));
// show moving to arrow
if (processStep == 10)
{
//
display.setCursor(50, 20);
display.write(175);
}
// show target position
if (toolPosition_target != 0)
{
display.setCursor(70, 20);
display.print(F("H"));
}
//
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F("--?--"));
}
// show interrupt1 status
display.setTextSize(1);
display.setCursor(80, 55);
if (int1State)
{
display.write(174);
int1State = !int1State;
}
else
{
display.write(255);
}
// show interrupt1 status
if (int2State)
{
display.write(175);
int2State = !int2State;
}
else
{
display.write(255);
}
// show heartbeat
display.setTextSize(1);
display.setCursor(120, 55);
if (heartbeatState)
{
display.write(221);
}
else
{
display.write(222);
}
heartbeatState = !heartbeatState;
// show all content
display.display(); // Show initial text
tTime[2] = t;
}
#endif
// ---------------------------------------------------------------------------------------------------------------------------------
And here now the entire source code
// --------------------------------------------------------
// LIBRARIES
// --------------------------------------------------------
#include <Stepper.h>
#include <Arduino.h>
#include "../include/config.h"
#include <Adafruit_SSD1306.h>
// --------------------------------------------------------
// DEBUG
// --------------------------------------------------------
#define DEBUG_EN 0 // 0=disables , 1= enabled
#if DEBUG_EN
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
// --------------------------------------------------------
// SERVICE ROUTINE FREQUENCIES
// --------------------------------------------------------
#define DEBUG_LED_FREQ F_500ms // DEBUG_LED pulse rate in Hz
#define TOOLCH_SR_FREQ F_10ms // TOOLCHG_SR_FREQ rate in Hz
#define ISR_DEBOUNCE_FREQ F_50ms // ISR_DEBOUNCE_FREQ rate in Hz
#define MANUAL_FUNC_FREQ F_100ms // MANUAL_FUNC_FREQ rate in Hz
#define OLED_UPD_FREQ F_250ms // OLED_UPD_FREQ rate in Hz
// --------------------------------------------------------
// CONSTANTS
// --------------------------------------------------------
#define STEPS_PER_REV 7670
#define MAG_SLOTS 15
#define DIRECTION 8
#define PULSE 9
#define F_10ms 100
#define F_20ms 50
#define F_50ms 20
#define F_100ms 10
#define F_250ms 4
#define F_500ms 2
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO: A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO: 2(SDA), 3(SCL), ...
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
// --------------------------------------------------------
// INPUTS
// --------------------------------------------------------
#define PULSE_IN 2
#define MODE_SW 10
#define HOME_SW 3
// --------------------------------------------------------
// OUTPUTS
// --------------------------------------------------------
#define LED_PIN 13
#define DROP_PIN 5 // red led
#define UNCLAMP_PIN 6 // yellow led
#define CYCLE_START_PIN 7 // blue led (was 3)
#define HOME_SW 3
// --------------------------------------------------------
// COMMUNICATION
// --------------------------------------------------------
#define BAUD_RATE 9600
// --------------------------------------------------------
// CLEARTEXT
// --------------------------------------------------------
#define START_SERIAL (Serial.begin(BAUD_RATE))
#define STARTUP (bFirstScan == true)
#define PUSHBUTTON_IS_PRESSED (sensorVal == HIGH)
#define AUTO_MODE (digitalRead(MODE_SW) == LOW)
#define MANUAL_MODE (digitalRead(MODE_SW) == HIGH)
#define LED_ON (digitalWrite(LED_PIN, HIGH))
#define LED_OFF (digitalWrite(LED_PIN, HIGH))
#define MAG_ON (digitalWrite(DROP_PIN, HIGH))
#define MAG_OFF (digitalWrite(DROP_PIN, LOW))
#define UNCLAMP (digitalWrite(UNCLAMP_PIN, HIGH))
#define CLAMP (digitalWrite(UNCLAMP_PIN, LOW))
#define CYCLE_START_1 (digitalWrite(CYCLE_START_PIN, HIGH));
#define CYCLE_START_0 (digitalWrite(CYCLE_START_PIN, LOW));
#define S0_RESET 0
#define S10_HOMING 10
#define S20_IDLE 20
#define S30_RECEIVING_PULSES 30
#define S40_UNCLAMP 40
#define S50_MAG_ON 50
#define S60_INDEX 60
#define S70_MAG_OFF 70
#define S80_CLAMP 80
#define S90_CYCLE_START 90
#define S100_RESET 100
#define S500_ERROR 500
// Step timeout values [ms]
#define S30_TIMEOUT 2000
#define S40_TIMEOUT 2000
#define S50_TIMEOUT 1000
#define S60_TIMEOUT 5000
#define S70_TIMEOUT 3000
#define S80_TIMEOUT 1000
#define S90_TIMEOUT 500
// --------------------------------------------------------
// GLOBAL VARIABLES
// --------------------------------------------------------
int sensorVal = 0;
int pulseCount = 0;
bool bFirstScan = true;
static uint32_t tTime[11];
bool ledState = false;
bool heartbeatState = false;
bool int1State = false;
bool int2State = false;
bool isr0_countFlag = false;
bool isr0_flagResetRequired = false;
int pulseRCVD_Timeout = 0;
int processStep = 0;
int curentStepTimer = 0;
int curentStepTimeout = 0;
bool S60_Indexed = false;
int toolPosition_actual = 12;
int toolPosition_target = 0;
// int stepsToTarget = 0;
bool manualModePriority = false; // false=cycle finishes first, true=stop cycle immediately
bool homingIncremental = false; // false=decremental (CW rotation), true=incremental (CCW rotation)
bool homed = true; // shouldn't need explaining :-)
bool bToDo = false;
// --------------------------------------------------------
// OBJECTS & INSTANCES
// --------------------------------------------------------
Stepper myStepper(STEPS_PER_REV, DIRECTION, PULSE);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --------------------------------------------------------
// FUNCTION PROTOTYPES
// --------------------------------------------------------
void check_interface(void);
void IRS_0(void); // Interrupt Service Routine 0 [Pulses]
void IRS_1(void); // Interrupt Service Routine 1 [Home_SW]
int calcStepTimeout(int);
int calcTarget(int);
void show_stepNumber(void);
void show_opMode(void);
void show_stepTime(void);
void show_posInfo(void);
void show_intStatusa(void);
void show_heartbeat(void);
// --------------------------------------------------------
// SET-UP
// --------------------------------------------------------
void setup()
{
#if DEBUG_EN
START_SERIAL;
#else
#endif
debugln("Set-up complete!");
//-------------------------------------------------------
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
debugln(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.clearDisplay();
// display.setTextSize(1); // Draw 2X-scale text
// display.setTextColor(SSD1306_WHITE);
// display.setCursor(0, 0);
// display.println(F("Init"));
display.display(); // Show initial text
delay(100);
// pre-set object parameters
myStepper.setSpeed(100);
// all I/O
pinMode(LED_PIN, OUTPUT);
pinMode(DROP_PIN, OUTPUT);
pinMode(UNCLAMP_PIN, OUTPUT);
pinMode(PULSE,OUTPUT);
pinMode(DIRECTION,OUTPUT);
pinMode(CYCLE_START_PIN, OUTPUT);
pinMode(PULSE_IN, INPUT);
pinMode(HOME_SW, INPUT);
pinMode(MODE_SW, INPUT);
attachInterrupt(0, IRS_0, FALLING);
attachInterrupt(1, IRS_1, RISING);
// Here we will read the SD card file, but for now just set to step 20
processStep = S20_IDLE;
}
// --------------------------------------------------------
// MAIN LOOP
// --------------------------------------------------------
void loop()
{
uint32_t t = millis();
if STARTUP
{
bFirstScan = false;
debugln("First Scan");
}
// ------------------------------------------------------
// Service Routine[0]: DEBUG
#if SR00_DEBUG_EN == 1
if ((t-tTime[0]) >= (1000 / DEBUG_LED_FREQ))
{
blinkDebugLED();
tTime[0] = t;
}
#endif
// ------------------------------------------------------
// Service Routine[1]: TOOLCHG
#if SR01_TOOLCHG_EN == 1
if ((t-tTime[1]) >= (1000 / TOOLCH_SR_FREQ))
{
switch (processStep)
{
case S0_RESET:
debugln("S00");
break;
// --------------------------------------------------
case S10_HOMING:
debugln("S10");
while (!homed)
{
if (homingIncremental)
{
myStepper.step(STEPS_PER_REV);
}
else
{
myStepper.step(-STEPS_PER_REV);
}
}
break;
// --------------------------------------------------
case S20_IDLE:
debugln("S20");
delay(2);
break;
// --------------------------------------------------
case S30_RECEIVING_PULSES:
//debugln("S30");
curentStepTimeout = calcStepTimeout(S30_TIMEOUT);
if ((isr0_countFlag == true) && (pulseRCVD_Timeout < curentStepTimeout))
{
pulseRCVD_Timeout++;
}
if ((isr0_countFlag == true) && (pulseRCVD_Timeout >= curentStepTimeout))
{
// start next step, reset step timer!
// in case target equals current throw a hissi-fit and goto S90
if (bToDo)
{
// yeah here's something left to DO!
}
processStep = S40_UNCLAMP;
curentStepTimer = 0;
}
debug("S30 "); debug(tTime[1]); debug(" - ");
debug(pulseCount); debug(" - "); debugln(pulseRCVD_Timeout);
break;
// --------------------------------------------------
case S40_UNCLAMP:
// debugln("S40");
curentStepTimeout = calcStepTimeout(S40_TIMEOUT);
if (curentStepTimer < curentStepTimeout)
{
UNCLAMP;
curentStepTimer++;
}
if (curentStepTimer >= curentStepTimeout)
{
// start next step, reset step timer!
processStep = S50_MAG_ON;
curentStepTimer = 0;
}
// debug("S40 "); debug(tTime[1]); debug(" - ");
// debug(curentStepTimer); debug(" - "); debugln(curentStepTimeout);
break;
// --------------------------------------------------
case S50_MAG_ON:
// debugln("S50");
curentStepTimeout = calcStepTimeout(S50_TIMEOUT);
if (curentStepTimer < curentStepTimeout)
{
MAG_ON;
curentStepTimer++;
}
if (curentStepTimer >= curentStepTimeout)
{
// start next step, reset step timer!
processStep = S60_INDEX;
curentStepTimer = 0;
}
// debug("S50 "); debug(tTime[1]); debug(" - ");
// debug(curentStepTimer); debug(" - "); debugln(curentStepTimeout);
break;
// --------------------------------------------------
case S60_INDEX:
// debugln("S60");
curentStepTimeout = calcStepTimeout(S60_TIMEOUT);
if ((curentStepTimer < curentStepTimeout) && !(S60_Indexed))
{
S60_Indexed = true;
// calculate distance and direction
toolPosition_target = calcTarget(pulseCount);
if ((toolPosition_target != 0) && (toolPosition_target != 99))
{
myStepper.step(toolPosition_target * STEPS_PER_REV);
// curentStepTimer++;
}
else if (toolPosition_target == 99)
{
// Received error code. Terminate
processStep = S500_ERROR;
curentStepTimer = 0;
break;
}
}
else{
curentStepTimer++;
debugln("from - to - index: ");
debug(toolPosition_actual); debug(" "); debug(pulseCount); debug(" "); debugln(toolPosition_target);
}
if (curentStepTimer >= curentStepTimeout)
{
// update current position
toolPosition_actual = pulseCount;
pulseCount = 0;
// start next step, reset step timer!
processStep = S70_MAG_OFF;
curentStepTimer = 0;
}
// debug("S60 "); debug(tTime[1]); debug(" - ");
// debug(curentStepTimer); debug(" - "); debug(curentStepTimeout); debug(" PulseCount: "); debugln(pulseCount);
break;
// --------------------------------------------------
case S70_MAG_OFF:
debugln("S70");
curentStepTimeout = calcStepTimeout(S70_TIMEOUT);
if (curentStepTimer < curentStepTimeout)
{
MAG_OFF;
curentStepTimer++;
}
if (curentStepTimer >= curentStepTimeout)
{
// start next step, reset step timer!
processStep = S80_CLAMP;
curentStepTimer = 0;
}
break;
// --------------------------------------------------
case S80_CLAMP:
debugln("S80");
curentStepTimeout = calcStepTimeout(S80_TIMEOUT);
if (curentStepTimer < curentStepTimeout)
{
CLAMP;
curentStepTimer++;
}
if (curentStepTimer >= curentStepTimeout)
{
// start next step, reset step timer!
processStep = S90_CYCLE_START;
curentStepTimer = 0;
}
break;
// --------------------------------------------------
case S90_CYCLE_START:
debugln("S90");
curentStepTimeout = calcStepTimeout(S90_TIMEOUT);
if (curentStepTimer < curentStepTimeout)
{
CYCLE_START_1;
curentStepTimer++;
}
if (curentStepTimer >= curentStepTimeout)
{
// start next step, reset step timer!
processStep = S100_RESET;
curentStepTimer = 0;
}
break;
// --------------------------------------------------
case (S100_RESET):
debugln("S100");
// Reset the Signals
pulseCount = 0;
isr0_countFlag = false;
pulseRCVD_Timeout = 0;
S60_Indexed = false;
CYCLE_START_0;
processStep = S0_RESET;
processStep = S20_IDLE;
curentStepTimer = 0;
curentStepTimeout = 0;
toolPosition_target = 0;
break;
default:
break;
} // end of switch / case
if MANUAL_MODE
{
// service manual functions
}
tTime[1] = t;
}
#endif
// ------------------------------------------------------
// Service Routine[2]: ????
#if SR02_OLED_EN == 1
if ((t-tTime[2]) >= (1000 / OLED_UPD_FREQ))
{
// general settings
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
// Display Step-#
display.setCursor(0, 0);
display.setTextSize(1);
display.print(F("Current Step: "));
display.println((processStep));
// Display OP-Mode
display.setCursor(115, 0);
if MANUAL_MODE
{
display.print(F("M"));
}
else
{
display.print(F("A"));
}
// Display Step-time
display.setCursor(0, 10);
display.print(F("Remaining Time: "));
if (processStep == 30)
{
display.println(curentStepTimeout-pulseRCVD_Timeout);
}
else
{
display.println(curentStepTimeout-curentStepTimer);
}
// Display current tool position & homed-status
if (homed)
{
display.setTextSize(4);
display.setCursor(0, 20);
display.println(toolPosition_actual);
// show moving to arrow
if ((processStep >= 40) && (processStep <= 60))
{
//
display.setCursor(50, 20);
display.write(175);
}
// show target position
if (pulseCount != 0)
{
display.setCursor(70, 20);
display.print(pulseCount);
}
//
display.setTextSize(2);
display.setCursor(0, 50);
display.println(F("Homed"));
}
else
{
display.setTextSize(4);
display.setCursor(0, 20);
display.println(F("UNK"));
// show moving to arrow
if (processStep == 10)
{
//
display.setCursor(50, 20);
display.write(175);
}
// show target position
if (toolPosition_target != 0)
{
display.setCursor(70, 20);
display.print(F("H"));
}
//
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F("--?--"));
}
// show interrupt1 status
display.setTextSize(1);
display.setCursor(80, 55);
if (int1State)
{
display.write(174);
int1State = !int1State;
}
else
{
display.write(255);
}
// show interrupt1 status
if (int2State)
{
display.write(175);
int2State = !int2State;
}
else
{
display.write(255);
}
// show heartbeat
display.setTextSize(1);
display.setCursor(120, 55);
if (heartbeatState)
{
display.write(221);
}
else
{
display.write(222);
}
heartbeatState = !heartbeatState;
// show all content
display.display(); // Show initial text
tTime[2] = t;
}
#endif
}
//-----------------------------------------------------------------------------
// END OF MAIN LOOP
//-----------------------------------------------------------------------------
// --------------------------------------------------------
// INTERRUPT SERVICE ROUTINES
// --------------------------------------------------------
// IRS_0 [Pulse_Counter]
void IRS_0() {
noInterrupts();
int1State = true;
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
// If interrupts come faster than 200ms, assume it's a bounce and ignore
if ((interrupt_time - last_interrupt_time > 200) && (pulseRCVD_Timeout < 200))
{
processStep = S30_RECEIVING_PULSES;
pulseCount++;
isr0_countFlag = true;
pulseRCVD_Timeout = 0;
}
last_interrupt_time = interrupt_time;
interrupts();
}
// --------------------------------------------------------
// IRS_1 [Home_SW]
void IRS_1() {
noInterrupts();
int2State = true;
// static unsigned long last_interrupt_time = 0;
// unsigned long interrupt_time = millis();
// // If interrupts come faster than 200ms, assume it's a bounce and ignore
// if ((interrupt_time - last_interrupt_time > 200) && (pulseRCVD_Timeout < 200))
// {
// processStep = S30_RECEIVING_PULSES;
// pulseCount++;
// isr0_countFlag = true;
// pulseRCVD_Timeout = 0;
// }
// last_interrupt_time = interrupt_time;
interrupts();
}
// --------------------------------------------------------
// FUNCTIONS
// --------------------------------------------------------
// Calculate Target Direction and Distance
int calcTarget(int toolIndex) {
// int toolPosition_actual = 0;
// int toolPosition_target = 0;
// STEPS_PER_REV
// SANITY CHECK
if ((toolIndex < 0) || (toolIndex > MAG_SLOTS))
{
// error. should never get here. Terminate program
processStep = S500_ERROR;
return (99);
}
// check if target different from actual
if (toolIndex > toolPosition_actual)
{
if ((toolIndex - toolPosition_actual) >= ((MAG_SLOTS / 2) + (MAG_SLOTS %2)))
{
// overflow in + direction
return ((toolPosition_actual - toolIndex + MAG_SLOTS) * -1);
}
// else if ((toolIndex - toolPosition_actual) < ((MAG_SLOTS / 2) + (MAG_SLOTS %2)))
else
{
// return (toolPosition_actual - toolIndex);
return (toolIndex - toolPosition_actual);
}
}
else if (toolIndex < toolPosition_actual)
{
if ((toolPosition_actual - toolIndex) >= ((MAG_SLOTS / 2) + (MAG_SLOTS %2)))
{
return (MAG_SLOTS - (toolPosition_actual - toolIndex));
}
else
{
return ((toolPosition_actual - toolIndex) * -1);
}
}
else if (toolIndex == toolPosition_actual)
{
return (0); // nothing to do in this case
}
return (0);
}
// --------------------------------------------------------
// Step Timeout Calculation
int calcStepTimeout(int timeoutValue) {
return ((timeoutValue * TOOLCH_SR_FREQ) / 1000);
}
// --------------------------------------------------------
// CHECK_INTERFACE
void check_interface(void) {
//read the pushbutton value into a variable
sensorVal = digitalRead(PULSE_IN);
debugln(sensorVal);
}
// --------------------------------------------------------
void blinkDebugLED() {
// if the LED is off turn it on and vice-versa:
// if (ledState == LOW) {
// ledState = HIGH;
// } else {
// ledState = LOW;
// }
// set the LED with the ledState of the variable:
digitalWrite(LED_PIN, !ledState);
ledState = !ledState;
// debug("ledState: "); debugln(ledState);
}
// --------------------------------------------------------
// DISPLAY FUNCTIONS
void show_stepNumber(){
// to be done
}
void show_opMode(){
// to be done
}
void show_stepTime(){
// to be done
}
void show_posInfo(){
// to be done
}
void show_intStatusa(){
// to be done
}
void show_heartbeat(){
// to be done
}