I2C OLED not updating when Arduino is reciving data over serial

Hey there! So, I'm working on this simple Arduino Project for some time. I have a simple software written in Python on the windows side that grabs the temps/fans rpm of sensors/fans in my PC using Open Hardware Monitor and sends that data over serial to an Arduino Pro Micro. The job of the pro micro is to display the data, in realtime, on the OLED display. I do also have a rotary encoder that acts as a volume knob/media control. My problem is that the OLED does not update. When i start the python script, the oled screen freezes on the current state , and after turning off the python script the oled displays once the last piece of data that the arduino has recived. Any advice? Sorry for the messy code, I'm a beginner and i do really try to improve the way i'm writing it.

Here's the Python script:

from __future__ import print_function
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
from time import sleep

import PySimpleGUI as gui
import serial
import wmi
import array

w = wmi.WMI(namespace="root\OpenHardwareMonitor")
cpu_temp = 0
gpu_temp = 0
gpu_rpm = 0
gpu_rpmPerc = ''
volume_value_string = ""

arduino_data = serial.Serial('COM19', 115200)


def gui_data():
    layout = [
        [gui.Text('CPU TEMP:'), gui.Text('N/A', size=(10, 1), key='-OUT_CPU-')],
        [gui.Text('GPU TEMP:'), gui.Text('N/A', size=(10, 1), key='-OUT_GPU-')],
        [gui.Text('GPU FAN RPM:'), gui.Text('N/A', size=(10, 1), key='-OUT_GPU_RPM-')],
        [gui.Text('GPU FAN RPM%:'), gui.Text('N/A', size=(10, 1), key='-OUT_GPU_RPMPERC-')],
        [gui.Button('Ok'), gui.Button('Exit')]]

    window = gui.Window('Test', layout, size=(500, 500))

    while True:
        event, values = window.read()
        window.refresh()
        if event is None or event == 'Exit':
            break
        if event == 'Ok':
            while True:
                get_sensors_temps()
                # print("CPU Temp: " + cpu_temp)
                # print("GPU Temp: " + gpu_temp)
                window['-OUT_CPU-'].update(str(cpu_temp) + "C")
                window['-OUT_GPU-'].update(str(gpu_temp) + "C")
                window['-OUT_GPU_RPM-'].update(str(gpu_rpm) + "rpm")
                window['-OUT_GPU_RPMPERC-'].update(str(gpu_rpmPerc) + "%")
                window.refresh()
                pass
        else:
            if event is None or event == 'Exit':
                break
    window.close()

def get_sensors_temps():
    global arduino_data
    global cpu_temp
    global gpu_temp
    global gpu_rpm
    global gpu_rpmPerc

    temperature_infos = w.Sensor()
    for sensor in temperature_infos:
        if sensor.SensorType == 'Temperature' and 'CPU Package' in sensor.Name:
            cpu_temp = format(sensor.Value, ".1f")
        if sensor.SensorType == 'Temperature' and 'GPU Core' in sensor.Name:
            gpu_temp = format(sensor.Value, ".1f")
        if sensor.SensorType == 'Fan' and 'GPU' in sensor.Name:
            gpu_rpm = int(sensor.Value)
        if sensor.SensorType == 'Control' and 'GPU Fan' in sensor.Name:
            gpu_rpmPerc = format(sensor.Value, ".1f")

    temps_array = str(cpu_temp) + ', ' + str(gpu_temp) + ', ' + str(gpu_rpm)
    arduino_data.write(str(temps_array).encode())


if __name__ == '__main__':
    gui_data()

Here's the Arduino Code:

#include <HID-Project.h>        // Used for the Media Controls and Keyboard
#include <Rotary.h>             // Used for handling the Rotary Encoder
#include <Adafruit_SSD1306.h>   // Used for handling the SSD1306 driver OLED display
#include <Wire.h>               // Used for I2C communication


// Rotary Encoder
const byte PinSW = 6;
int inSWState = HIGH;
Rotary rotary = Rotary(9, 8);

// OLED Screen
Adafruit_SSD1306 display(128, 64);

const byte OLED_IIC_ADDRESS = 0x3C;
const int NumberOfFunctions = 3;
int OLEDIndex = 0;
int itterationCount = 0;
int maxItterationCount = 10; //the number of the itterations the screen does before displaying the 
                                                   // next sensor

String CPU_s;
String CPUFanRPM_s;
String GPU_s;
String GPUFanRPM_s;
String volumeLevel_s;

// millis instead of delay
// int period = 1000;
// unsigned long  time_now = 0;

void setup() {
  Serial.begin(115200);   
  Consumer.begin();              
  rotary.begin();    
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, OLED_IIC_ADDRESS);
  display.clearDisplay();

  pinMode(PinSW, INPUT_PULLUP);
  PCICR |= (1 << PCIE0);
  PCMSK0 |= (1 << PCINT4) | (1 << PCINT5);
  sei();
}

void loop() {
  OLED();

  if (Serial.available()){
    ParseAndSendSerialData();
  }
}

void ParseAndSendSerialData() {
  CPU_s = Serial.readStringUntil(',');
  Serial.read();
  GPU_s = Serial.readStringUntil(',');
  Serial.read();
  GPUFanRPM_s = Serial.readStringUntil('\n');
  Serial.read();
  // Serial.println(CPU_s + " + " + GPU_s + " + " + GPUFanRPM_s);
  // delay(100);
}

void DisplayLogo() {
  display.clearDisplay();
  display.setTextSize(3);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("Prometheus"); 
  display.setTextSize(2);
  display.print(" V0.1"); 
  display.display();
}

void DisplayCPUTemps() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("CPU TEMP:"); 
  display.println(""); 
  display.setTextSize(4);
  display.print(CPU_s); 
  display.setTextSize(3);
  display.print("C");
  display.display();

  itterationCount++;
}

void DisplayGPUTemps() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("GPU TEMP:"); 
  display.println(""); 
  display.setTextSize(4);
  display.print(GPU_s); 
  display.setTextSize(3);
  display.print("C");
  display.display();

  itterationCount++;
}

void DisplayGPURPM() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("GPU RPM:"); 
  display.println(""); 
  display.setTextSize(4);
  display.print(GPUFanRPM_s); 
  display.display();


  itterationCount++;
}

void OLED() {
  switch(OLEDIndex) {
    case 0:
      DisplayCPUTemps();
      break;
    case 1:
      DisplayGPUTemps();
      break;
    case 2:
      DisplayGPURPM();
      break;
  }

  if (itterationCount > maxItterationCount) {
    OLEDIndex++;
    itterationCount = 0;
  }

  if (OLEDIndex > NumberOfFunctions - 1) {
    OLEDIndex = 0;
  }
}

ISR(PCINT0_vect) {
  unsigned char result = rotary.process();
  if (result == DIR_NONE) {
    // do nothing
  }
  else if (result == DIR_CW) {
    Consumer.write(MEDIA_VOLUME_UP);
  }
  else if (result == DIR_CCW) {
    Consumer.write(MEDIA_VOLUME_DOWN);
  }
}
}

Welcome to the forum.
A rotary encoder is often done with a library, so you don't have to put a interrupt routine in the sketch.

Why is there a sei(); in the setup function ?
Did you grab pieces of code that you don't understand ?

Some Arduino boards can run multitasking or a schedular. Then you can run multiple things at once. Each task needs its own stack, so it needs extra memory.

For the basic Arduino boards, there is not enough SRAM memory for that. It is possible to do multiple things at once with a millis-timer. If that is used, then the sketch may never wait for something.
Start by reading the Blink Without Delay page: https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay.

The function Serial.readStringUntil waits for serial data. During that waiting, there is nothing else you can do. The usual way is to do it like this:

void loop()
{
  if( Serial.available() > 0)
  {
    byte inChar = (byte) Serial.read();
    put byte in a buffer
    is the full line received ?
      then process the buffer
  }
}

I'm not sure if it is okay to send the keys "volume up" and "volume down" from an interrupt routine. Maybe that causes the freezing. I prefer to move those to the Arduino loop().

The Serial port on a Pro Micro board is not always available. I'm not sure at the moment how that should be handled.

Do you have a OLED display from Adafruit ? That is okay. Every other OLED display causes trouble on the I2C bus with a 5V Arduino board.

How long are your wires of the I2C bus ? Do you use flat ribbon cable with SDA next to SCL ? That will certain cause trouble.
A timeout function was added to avoid that a sketch halts on a I2C bus error. You could add that timeout.

Thank you for your reply! To be honest, i don't fully understand what that code block for the Rotary library does, I do still try wrapping my head around it. I know that it is a bad practice to copy/paste code that i do not understand, but somehow, without it, the rotary encoder doesn't work properly. I've tried writing my own functions for the Encoder, but the bounce was horrible and i could not make it work fine, so now, I rely on the Rotary library.

Leaving this aside, Thank you for your help! I will keep in mind your advices. When i'll get back home from work, i will try fixing the code. Also, the display is not from Adafruit, but it is a compatible one, a clone i could say (SSD1306 OLED Display). The connections are like 10cm long.

I did something that made the display work for some reason, last night. It seems like it is a bit of a random behavior. After sending back the data from the arduino to be printed in the python console, just to be sure that the arduino receives the data correctly, the screen suddenly started working as It should be. Strange.

Do you know the difference between beginners and experienced programmers ? A beginner rushes towards the end result and then tries to fix problems. An experienced programmer checks every part before combining them.

It is very normal to have a number of test-sketches to test the hardware en learn how it works.
That means you should make a test-sketch for just the OLED. Update text to the display en let it run for an hour. Then you know if that part is okay.
Then try to add the Serial data add the Python script. Let that run for an hour.

The NicoHood HID-project library is good :+1:

I see now that you use this example.
We use the "Encoder" library.
It should be in the Library Manager, but I can not find it there.
You can always download the ZIP file from Github from here:
https://github.com/PaulStoffregen/Encoder

Do you use pin 8 and 9 for the rotary encoder ?
The "Encoder" library wants normal interrupts, that means you can use pin 0, 1, 2, 3, 7.
See the reference of the attachInterrupt function :open_book:

Did you understand that you have to rewrite the code to read the serial data ? The three Serial.readStringUntil functions have a timeout of 1 second. They do not wait forever. That means you can easily get into a synchronization problem. That is just another problem with those functions. I can go on if you want :wink:

I am aware that the code has a lot of issues, and I'm willing to learn more and fix those mistakes for this project and take notes for future projects. Thank you verry much for your advices and guidance! I'll come back with updates ASAP.

I've tried writing my own functions for the Encoder, but the bounce was horrible and i could not make it work fine.

In case you are interested, I wrote code to read rotary encoders on a PIC. I found that you need to sample the inputs at least once per millisecond so I had a timer triggering an interrupt every millisecond to read the inputs, plus a state based approach to keep track of them turning.