The trouble with OLED SSD1306 and mc interruptions

Hello, dear friends.
First of all - sorry if I've chosen a wrong category.
Second - I've read already this topic OLED (SSD1322) display goes blank when using h/w interrupts?! - #7 by david_prentice This is not exactly my case.

So. This is a WaterSystem project for my house. A short intro.
It has 4 "bobber switchers" in the water tank, 2 switchers for 3 way valve (let's call it sensors).
At the moment I use with my Arduino Leonardo (ATmega32U4):
74HC165, 74HC595, OLED SSD1306_128x64
And the last one doesn't want to work with interruptions. And I will concentrate on this problem, putting aside the rest implementation.

There is my code ( As I said, I've deleted things have nothing to do with the problem)


#include <WaterSystem.h> // my lib for timers
#include "defines.h" // project's defines
#include "variables.h" // project's variables
#include <GyverOLED.h> // small OLED lib

// OLED DISPLAY
GyverOLED<SSD1306_128x64, OLED_BUFFER> display;
// TIMER
WaterTimer timerReadSensors("READ_SENSORS", READ_SENSORS_DELAY, "loop", true, ptrReadSensors);

// TIMERS
// Prototypes
void readSensors();
void (*ptrReadSensors)() = readSensors;

unsigned long currentMillis, previousMillis;

void setup()
{
  {
    // Timer0 
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
  }
  Serial.begin(9600);
  display.init();
  display.clear();
  display.home();
  display.setScale(1);
  display.println("Water system");
  display.update();
}
SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();
  timerBlink.timerCheckUp(currentMillis);
  timerReadSensors.timerCheckUp(currentMillis);
}

void loop()
{
}

void readSensors()
{
  if (shift.update())
  {
    byte registers = shift.read();
    switch (registers & B11110000)
    {
    case B0:
    {
      Serial.println("3WV is working");
      break;
    }
    case B10000:
    {
      Serial.println("3WV is closed");
      break;
    }
    case B100000:
    {
      Serial.println("3WV is open");
      break;
    }
    default:
    {
      Serial.println("3WV state error");
    }
    }

    switch (registers & B00001111)
    {
    case B0:
    {
      Serial.println("EMPTY");
      break;
    }
    case B1:
    {
      Serial.println("FIRST bobber");
      //draw_tank(1); - > troubles
      break;
    }
    case B11:
    {
      Serial.println("SECOND bobber");
      // draw_tank(2);- > troubles
      break;
    }
    case B111:
    {
      Serial.println("THIRD bobber");
      // draw_tank(3);- > troubles
      break;
    }
    case B1111:
    {
      Serial.println("OVERFLOW");
      break;
    }
    default:
    {
      //draw_tank(4);- > troubles
      Serial.print("ERROR ");
      Serial.println(registers, BIN);
    }
    }
  }
}

void draw_tank(byte state)
{
  display.clear();
  display.clearDisplay();
  display.drawRect(97, 0, 31, 64, OLED_FILL); 
   switch (state)
   {
   case 1:
   {
     display.fillRect(98, 53, 30, 20, OLED_FILL);
     break;
   }
  /* 
  =======
     the rest states
  ========
  */
   }
  display.update();
}

If I use Serial - everything works as it should.
But with OLED the board is hanging completely. I even have to press reboot to have possibility to reload code.
I've tried to change Timer 0 to Timer3 with the same result.
In the WaterTimer class I use millis() to check if it is time to raise an alarm.

I understand that I, probably, have crossed the border of "you shouldn't do a lot of works in the interruption". But it is so cool to have empty loop, that I am still trying to figured out - is it possible to make my code working? Or just - forget it and use interruptions to set flags and that is it... ???

Welcome to the forum.

With the Arduino code it is often not needed to use interrupts. Some libraries use interrupts, but in the sketch it is rare.
If you need timing, you can use millis(). Start at the "Blink Without Delay" page: https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay
If you don't like that, then you can use a ESP32 with FreeRTOS or the Raspberry Pi Pico with Mbed multitasking operating system. There is also a simple Arduino task switcher (Schedular) for ATSAMD21G boards such as the MKR Zero.

Arduino uses Timer0 for millis(). I think the Arduino Leonardo also uses Timer0. If you use Timer0, then the base of the Arduino is swept away.

Is this the OLED library that you use ? https://github.com/GyverLibs/GyverOLED
Does it use a lot of memory ? It probably does. Can you check runtime how much memory you have with the freeMemory() function from Adafruit ?

What is your experience in programming ? The ATmega32U4 is a microcontroller. Keep everything simple. Don't call tasks from a interrupt.

Long story short: don't use Timer0, don't use a interrupt, use the loop(), rewrite the sketch, check runtime how much memory you have, show us the new sketch.

Hi, thank you for your reply.

I'm aware of the fact that Timer0 is already used for millis(). That's why I interrupt somewhere in the middle and call the "Compare A" function

  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);

In my WaterTimer library I use millis in the WaterTimer class - and it works perfectly.

WaterTimer.h

#pragma once
#include "Arduino.h"
class WaterTimer
{
  private:
    long timerLength; 
    unsigned long previousMillis; 
    String timerName;
    void (*ptrOnFuniction)(); 

  public:
    WaterTimer(String tName, long tLength, String tTipe, bool timerOn, void f()); 
    WaterTimer(String tName, long tLength, String tTipe, bool timerOn); 
    void timerOn(unsigned long currentMillis);
    void timerOff();
    void timerCheckUp(unsigned long currentMillis);
    String timerType = "loop"; // or "line"
    bool timerIsOn = false;  //  On / Off
};

WaterTimer.cpp

#include <WaterSystem.h>

WaterTimer::WaterTimer(String tName, long tLength, String tTipe, bool timerOn, void f()) {
  timerName = tName;
  timerType = tTipe;
  ptrOnFuniction = f;
  timerLength = tLength;
  timerIsOn = timerOn;
  previousMillis = millis();
}

WaterTimer::WaterTimer(String tName, long tLength, String tTipe, bool timerOn) {
  timerName = tName;
  timerType = tTipe;
  ptrOnFuniction = 0;
  timerLength = tLength;
  timerIsOn = timerOn;
  previousMillis = millis();
}

void WaterTimer::timerOn(unsigned long currentMillis) {
  previousMillis = currentMillis;
  timerIsOn = true;
}

void WaterTimer :: timerOff() {
  timerIsOn = false;
}

void WaterTimer::timerCheckUp(unsigned long currentMillis) {
  if ((timerIsOn) && (currentMillis - previousMillis >= timerLength))
  {
    if (timerType == "loop") {
      if (ptrOnFuniction != 0) {
        ptrOnFuniction();
      }
      previousMillis = currentMillis;
    }
    if (timerType == "line") {
      timerOff();
    }
  }
  else {
    // nothing
  }
}

The library GyverOLED.h uses almost no memory. Alex has cut almost everything what was not necessary for OLED.
Btw I've tried Adafruit_SSD1306.h as well - same result.

My experience not the big one (I'm a hobbyist), but I've completed CS50 and a bunch of FreeCodeCamp courses, partially completed SC50 AI, CS50 Games, CS50 WebApp. CS50 MobyleApp.

So the question still remains - is there anything I can do to make my approach works?

OK, guys.
I gave up.
This is the solution and I don't want to dig too deep.

Now my timer class has function
bool WaterTimer::timerCheckUp(unsigned long currentMillis)
it returns bool instead of running another function.
I add a flag
volatile bool fSensors = false;
And inside of interruption I just assign the flag

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();
  fSensors = timerReadSensors.timerCheckUp(currentMillis);
}

check it in the loop() and run proper function

void loop()
{
  if (fSensors){
    readSensors();
    fSensors = false;
  }
}

Done
But in case if someone has an idea how to fix previous variant - I will be very grateful.

Sorry, I don't want to fix something that is so far out of the ordinary and can not be fixed in the first place.

When you use the String class, then memory is allocated and released from the heap. You should not do that from a interrupt.

The OLED display uses the I2C bus. The Wire library is interrupt driven. It is not possible to use the I2C bus from a interrupt.

When a OLED is used in the loop(), then it is not possible that interrupts cause trouble for a OLED display. I read the other topic, but that is not a software issue with interrupts. That is about hardware noise.

You have a 5V board and the OLED runs internally at 3.3V and it uses 3.3V signals for SDA and SCL. You might need a I2C level shifter if you add more things to the I2C bus.

1 Like

Thank you, Koepel.
Especially for pointing out on the using String in the class. I've missed it somehow. I think, I'll just stick to this loop() solution and just will go on.
I want my WaterSystem start to work asap, and I don't want to create another OS :)))))

For the microcontrollers with little memory, using millis() for timing and to do multiple things at the same time is good way. If you have read the "Blink Without Delay" page, then I have some working examples for you: Fun With Millis (if you click on the small green buttons, then you will go to Wokwi and you can run it in a simulation).

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