Hello,
I am working on a project that uses an Arduino Nano in order to control Peltier modules, fans and pumps so as to cool of a polymer solution so as to c<hange its viscosity. For the interface I use an encoder and an OLED SPI screen (SSH1106) that I control using the U8g2 library. My temperature sensor is a MAX31865 with the Adafruit breakout board (and thus I use the Adafruit library). Apart from this, I don't use any libraries, and no Strings. Here is the schematic:
Everything works fine both regarding hardware and software, however, this only stays true for a bunch of minutes.
After which the Arduino completely freezes/crashes and needs to be rebooted manually.
I would like to try and debug my code, but I don't have any debugging programmers.
I did try to look for over/underflows and memory leaks in my code, but I can't find nor think of any. I am thus wondering if it could be caused by one of the libraries I use.
Here is my code:
#include <Arduino.h>
#include <U8x8lib.h>
#include <Adafruit_MAX31865.h>
#include <SPI.h>
// Comment out to use On-Off Control
//#define USE_PID
#define CONTROL_SKIPS 128 // Wait for x loop() iterations to update control
#define SET_BIT(p,n) ((p) |= (0x1 << (n)))
#define CLR_BIT(p, n) ((p) &= ~(0x1 << (n)))
#define GET_BIT(p, n) (((p) & (0x1 << (n))) >> (n))
#define OLED_CS 2
#define OLED_DC 9
#define OLED_RST 7
#define RTD_CS 10
#define ENCODER_A 6
#define ENCODER_B 5
#define ENCODER_P 4
#define FP_MOSFET 8 // Fans & pumps mosfet
#define PELT_MOSFET 3 // Peltier modules mosfet (PWM pin)
#define R_REF 430.0 // PT100 R reference
#define R_NOMINAL 100.0 // PT100 R at 0°C
#ifdef USE_PID
// Comment out if necessary, for example KI and KD for solely proportional control
// Propotional constant
#define KP 2
// Integral constant
//#define KI 5
// Derivative constant
//#define KD 7
#else
// Regulation will start when temp is outside of [desTemp - RANGE; desTemp + RANGE]
#define RANGE 1
// Hysteresis for the control. Regulation will stop when temp is inside of [desTemp - RANGE + HIST_RANGE; desTemp + RANGE - HISTRANGE]
#define HIST_RANGE 0.5
#endif
U8X8_SH1106_128X64_NONAME_4W_HW_SPI u8x8(OLED_CS, OLED_DC, OLED_RST);
Adafruit_MAX31865 rtd = Adafruit_MAX31865(RTD_CS);
// Bitmaps
const byte logo[] PROGMEM = {...};
const byte okay[] PROGMEM = {...};
const byte regu[] PROGMEM = {...};
const byte moon[] PROGMEM = {...};
const int timer = 49911; // For preloading timer interruption, 65536 - 16MHz/256/4Hz
byte desTemp = 15;
float temp = 20.00;
byte reg = 8; // 0 for no regulation, 1 for regulation, 2 for sleep, 8 for not yet initialized
/*
Encoder data byte, uses bitwise operation (from right to left) so as to minimize the number of variables.
Bits:
- 0: Last state wheel
- 1: Current state wheel
- 2: Direction (0 for CW and 1 for CCW)
- 3: Last state button
- 4: Current state button
- 5 to 7: No use
*/
byte encoder = 0;
ISR(TIMER1_OVF_vect) {
TCNT1 = timer;
updateScreen();
}
void setup(void)
{
// Encoder pins
pinMode(ENCODER_A ,INPUT);
pinMode(ENCODER_B, INPUT);
pinMode(ENCODER_P, INPUT);
// MOSFETs pins
pinMode(FP_MOSFET, OUTPUT);
pinMode(PELT_MOSFET, OUTPUT);
// Encoder init
digitalRead(ENCODER_A) ? SET_BIT(encoder, 0) : CLR_BIT(encoder, 0);
digitalRead(ENCODER_P) ? SET_BIT(encoder, 3) : CLR_BIT(encoder, 3);
// Timer interrupt
noInterrupts();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = timer;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
interrupts();
// RTD init
rtd.begin(MAX31865_3WIRE);
// OLED init
u8x8.begin();
u8x8.setPowerSave(0);
// OLED interface
drawImg(logo, 3, 5, 0, 2);
u8x8.setFont(u8x8_font_profont29_2x3_r);
u8x8.drawString(8, 3, "C");
u8x8.setFont(u8x8_font_8x13B_1x2_r);
u8x8.drawString(5, 0, "RoboMix");
u8x8.setFont(u8x8_font_7x14B_1x2_r);
u8x8.drawString(7, 2, "o");
u8x8.setFont(u8x8_font_5x8_r);
u8x8.drawString(0, u8x8.getRows() - 1, "Mesure:");
u8x8.drawGlyph(u8x8.getCols() - 1, 2, 'E');
u8x8.drawGlyph(u8x8.getCols() - 1, 3, 'T');
u8x8.drawGlyph(u8x8.getCols() - 1, 4, 'A');
u8x8.drawGlyph(u8x8.getCols() - 1, 5, 'T');
updateScreen();
// For security reasons, initially set to sleep mode
reg = 2;
}
void loop(void) {
static byte cnt = 0;
cnt++;
updateEncoder();
if (cnt > CONTROL_SKIPS) {
temp = rtd.temperature(R_NOMINAL, R_REF);
updateControl();
cnt = 0;
}
delay(1);
}
#ifdef USE_PID
void updateControl() {
static unsigned long previousTime = 0;
static unsigned long lastDelta = 0;
if (reg == 2) {
digitalWrite(PELT_MOSFET, LOW);
digitalWrite(FP_MOSFET, LOW);
} else {
if (! previousTime) previousTime = millis();
float delta = desTemp - temp;
float res = KP * delta; // PID output
#ifdef KI || KD
unsigned int elapsedTime = millis() - previousTime;
#endif
#ifdef KI
res += KI * error * elapsedTime;
#endif
#ifdef KD
res += KD * (delta - lastDelta) / elapsedTime;
#endif
byte pwm = (int) res;
lastDelta = delta;
previousTime = currentTime;
if (pwm < 16) {
digitalWrite(FP_MOSFET, LOW);
reg = 0;
} else {
analogWrite(FP_MOSFET, pwm);
reg = 1;
}
}
}
#else
void updateControl() {
if (reg == 2) {
digitalWrite(PELT_MOSFET, LOW);
digitalWrite(FP_MOSFET, LOW);
} else {
float delta = temp - desTemp;
if (delta < 0) delta = -delta;
if (delta > RANGE) {
digitalWrite(PELT_MOSFET, HIGH);
reg = 1;
} else if (delta < RANGE - HIST_RANGE) {
digitalWrite(PELT_MOSFET, LOW);
reg = 0;
}
digitalWrite(FP_MOSFET, HIGH);
}
}
#endif
void updateEncoder() {
digitalRead(ENCODER_A) ? SET_BIT(encoder, 1) : CLR_BIT(encoder, 1); // Set current state wheel
if (GET_BIT(encoder, 1) && GET_BIT(encoder, 1) != GET_BIT(encoder, 0)) {
if (digitalRead(ENCODER_B) != GET_BIT(encoder, 1)) {
if (desTemp <= 25)
desTemp++;
CLR_BIT(encoder, 2);
} else {
if (desTemp > 0)
desTemp--;
SET_BIT(encoder, 2);
}
}
digitalRead(ENCODER_P) ? SET_BIT(encoder, 4) : CLR_BIT(encoder, 4); // Press button state
if (GET_BIT(encoder, 4) && GET_BIT(~encoder, 3)) { // Encoder button passed from 0 to 1, toggle sleep mode
if (reg == 2)
reg = 0;
else
reg = 2;
}
GET_BIT(encoder, 1) ? SET_BIT(encoder, 0) : CLR_BIT(encoder, 0); // Last = current wheel
GET_BIT(encoder, 4) ? SET_BIT(encoder, 3) : CLR_BIT(encoder, 3); // Last = current button
}
void updateScreen() {
static byte last = 0;
u8x8.setFont(u8x8_font_profont29_2x3_r);
if (desTemp < 10) {
u8x8.drawGlyph(3, 3, '0');
u8x8.setCursor(5, 3);
} else {
u8x8.setCursor(3, 3);
}
u8x8.print(desTemp);
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.setCursor(8, u8x8.getRows() - 1);
u8x8.print(temp);
if (last != reg) {
switch (reg) {
case 0:
drawImg(okay, 4, 4, 11, 2);
break;
case 1:
drawImg(regu, 4, 4, 11, 2);
break;
default:
drawImg(moon, 4, 4, 11, 2);
break;
}
last = reg;
}
}
void drawImg(const byte bitmap[], byte tWidth, byte tHeight, byte sx, byte sy) {
byte tmp[8];
for (byte x = 0; x < tWidth; x++) {
for (byte y = 0; y < tHeight; y++) {
for (byte i = 0; i < 8; i++) tmp[i] = pgm_read_byte(&bitmap[8 * tHeight * x + tHeight * i + y]);
u8x8.drawTile(sx + x, sy + y, 1, tmp);
}
}
}
I should note that all of the different bitmaps can be shown multiples times without issues before the crash, temperature variations etc as well... I can change my desired temperature etc... So like I said everything works but only for a period of time. Which is why I am thinking more of a memory leak than an overflow.
But I would be glad to get opinions from other people.
So thank you very much in advance!





