LCD corruption Xenon

Issue:
The LCD will show corruption only when the LCD is used while Serial1 is receiving/transmitting data and a timer is used to update the display. If the timer is disabled in setup() ( timerTick.start() ) and the seconds based check in main() is enabled - there is no corruption.

Serial data is from another module sending a char string of 40 chrs every 2 seconds.

Hardware:

  • Xenon
  • Ethernet Featherwing
  • LCD 16x2 I2C addr 0x20

Code:
Using WEB IDE
Library LiquidCrystal_I2C_Spark.h v1.1.0 (selected from library list)

#include "LiquidCrystal_I2C_Spark.h"


uint8_t bell[8]  = {0x4, 0xe, 0xe, 0xe, 0x1f, 0x0, 0x4};
uint8_t note[8]  = {0x2, 0x3, 0x2, 0xe, 0x1e, 0xc, 0x0};
uint8_t clockFace[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0};
uint8_t heart[8] = {0x0, 0xa, 0x1f, 0x1f, 0xe, 0x4, 0x0};
uint8_t duck[8]  = {0x0, 0xc, 0x1d, 0xf, 0xf, 0x6, 0x0};
uint8_t check[8] = {0x0, 0x1 ,0x3, 0x16, 0x1c, 0x8, 0x0};
uint8_t cross[8] = {0x0, 0x1b, 0xe, 0x4, 0xe, 0x1b, 0x0};
uint8_t retarrow[8] = {	0x1, 0x1, 0x5, 0x9, 0x1f, 0x8, 0x4};

// LiquidCrystal_I2C lcd;
LiquidCrystal_I2C lcd(0x20, 16, 2);

int lastSecond = 0;

const int timerTickPeriod = 1000;
Timer timerTick( timerTickPeriod , displayTime);

void displayChrCodes(void) {
	uint8_t i = 0;

	lcd.setCursor(0,1);
	lcd.write(i);

// 	for (int j = 0; j < 16; j++) {
// 		lcd.write(i + j);
// 	}
}

void displayTime(void)
{
    Serial.println(Time.timeStr());
    lcd.home();
    lcd.print("LCD test");
    
    lcd.setCursor(8,0);
    lcd.print(Time.hour() < 10 ? " " : "");
    lcd.print(Time.hour());
    lcd.print( Time.second() % 2 ? ":" : " ");
    lcd.print(Time.minute() < 10 ? "0": "");
    lcd.print(Time.minute());
    lcd.print( Time.second() % 2 ? " " : ":");
    lcd.print(Time.second() < 10 ? "0": "");
    lcd.print(Time.second());
}

void setup(void)
{
    Serial.begin(115200);
    Serial1.begin(9600);
  
    lcd.init();
    lcd.backlight();
    lcd.clear();
    
    lcd.createChar(0, bell);
    lcd.createChar(1, note);
    lcd.createChar(2, clockFace);
    lcd.createChar(3, heart);
    lcd.createChar(4, duck);
    lcd.createChar(5, check);
    lcd.createChar(6, cross);
    lcd.createChar(7, retarrow);
    
    timerTick.start();
}

void loop(void)
{

    // if (Time.second() != lastSecond)
    // {
    //     displayTime();
    // }

    if (Serial1.available()) {
        char inChar = Serial1.read();
        Serial.print(inChar);
        Serial1.print(inChar);
        displayChrCodes();
    }
}

If a millis() based timer library such as Polling Timer is used - there is no corruption.

Software timers are run on a separate thread which will preempt any ongoing other action (that's not protected against that) and since you are writing to the LCD from both threads at the same time (when you receive Serial1 data) it's not surprising that you run into inconsistencies.

Because the respective code blocks run sequentially on the same thread.

If you want to avoid that you need to guard your displayChrCodes() and displayTime() functions with a SINGLE_THREADED_BLOCK

BTW, there is an easier way to print your time

  if (Time.second() % 2)
    lcd.print(Time.format("%H %M:%S")); // maybe needs (const char*) cast
  else
    lcd.print(Time.format("%H:%M %S"));
  // or as single liner
  // lcd.print(Time.format(Time.second() % 2 ? "%H %M:%S" : "%H:%M %S"));

See here for more possible format place holders.

Thanks for the Time display tip - much appreciated.

Will feedback on the SINGLE_THREADED_BLOCK() approach.

SINGLE_THREADED_BLOCK() has solved the problem in the test code above - however when I move it to my production code it runs for about 30s and then freezes so badly that even the application watchdog does not reset it. I tried SYSTEM_THREAD(ENABLED) and no difference.

I will test more and see if I can pin it down.

If your single threaded blocks run for so long that they eventually deadlock the system then this is no option, but you need to make an informed decision of what block of code really needs to be uninterrupted and not just dump any code in there that just happens to be placed inside any given function.

Agreed. This is what I am doing in the block

//------------------------------------------------------------------
void LCDshowCommsRX()
//------------------------------------------------------------------
{   
     SINGLE_THREADED_BLOCK() {
        lcd.setCursor(2,0);
        lcd.write(5);
        rxFlash.start();
     }
}

The Timer is

Timer  rxFlash( txrxFlashPeriod, callback_rxFlash, true );

with

//------------------------------------------------------------------
void callback_rxFlash()
//------------------------------------------------------------------
{
    LCDclearCommsRX();
}

So it may be something else - I reverted the prod code to PollingTimer and with same SINGLE_THREADED_BLOCK() protections - it runs okay.

One thing I’d definetly move out of the block is rxFlash.start() as it doesn’t need guarding to avoid LCD corruption.
Since (re)starting the timer will involve some thread maintenance having that in a single threaded block may well be counterproductive.

No improvement.

The LCD lib uses delayMicroseconds() after the write commands to the I2C - would this be affected by the Timers function?

Is LCDshowCommsRX() called from the application thread or a timer?
If it’s the latter you could try using rxFlash.startFromISR() instead.

main() calls Ser1Receive() and then LCDshowCommsRX() is called when serial data is ready.

//------------------------------------------------------------------
void ser1Receive() // check if panel needs to send a message
//------------------------------------------------------------------
{
    static bool prevCTS;

    if ( checkCTS()  && !prevCTS ) {
        prevCTS = true;
        LCDshowCommsRX();
        raiseRTS(); // signal we are ready to recieve 
                    // serialEvent1 function should deal will all inbound data
    }
    else if (!checkCTS() && prevCTS ) {
        prevCTS = false;
        dropRTS(); // signal we are done 
    }
}

It displays a Rx symbol on display and then does rxFlash.start(). The callback from the rxFlash timer prints a space over the Rx symbol on the display after 400ms.

//------------------------------------------------------------------
void LCDclearCommsRX()
//------------------------------------------------------------------
{
    SINGLE_THREADED_BLOCK() {
        lcd.setCursor(2,0);
        lcd.print(" ");
    }
}

You may want to consider building a display buffer which you modify in loop() as necessary. Then, in your timer callback you only display the buffer. You can also set an “updated” flag in loop() so that the callback only displays if updated data is available (which you then clear in the callback). This makes the lcd calls exclusive to the callback. You could make you callback fire every second or half second since it would only update when told to.

1 Like

Thanks for the tip - I will approach this once I can get reliable writes in this single function working as it will certainly make the whole display management much neater.

@shanevanj Did you find a solution to this? I’m dealing with a similar issue of an I2C display being corrupted. I used the ATOMIC_BLOCK around display calls and it improved corruption, but now I’m seeing a bad freeze that prevents the application watchdog from working.

@homemaltster, the problem stems from accessing the I2C resource from two non mutually exclusive portions of code. I suggest NOT doing I2C updates in a Software Timer. ATOMIC_BLOCK is not a solution as you have found. Instead, build a display buffer in the timer callback and in loop() send the buffer to the display at fixed (millis() based) intervals. If you post your code, I may be able to make recommendations.

1 Like

@peekay123 Thanks for the quick reply.

I don’t have the exact same situation as above - I’m not using a timer to make I2C updates. I have a Publish class that handles all I2C updates with my display, and other portions of my code use this class. I also have an I2C port expander, which is seeing similar corruption, and is similarly controlled by one class that other portions of code use.

Would your advice still hold? My app has a large code base, but I’ll find something smaller to post. Thanks!

@homemaltster, it comes down to whether one set of I2C calls can occur at the same time as another set of calls. The Software Timer created such a condition. Are you completely closing every I2C transaction before another transaction could start somewhere else in your code?

@peekay123, I just went through my code base, and every I2C call has a Wire.beginTransmission followed by a Wire.endTransmission. I have system thread enabled - is there any use of I2C and the wire library inside the core Particle system firmware that could be influencing this?

I’ve just noticed that I’m also experiencing a high number of cloud disconnect events in the console. I hadn’t been experiencing this until adding the atomic blocks, but the atomic blocks have gotten rid of the corrupted LCD prints. It’s only after running for several hours that the port expander and display become corrupted, but it seems like the core app continues to run correctly even after this.

@homemaltster, ATOMIC BLOCK is not a good solution IMO. The DeviceOS does not use the I2C bus but there may be underlying issues in the OS I2C implementation that cause problems. A minimal test case needs to be created so an issue can be posted.

@peekay123, ok, I’ll work on creating a minimal test case. Thanks for your help!

1 Like