TFT SPI display corruption when interrupts

Wondering if anyone can confirm the cause of a problem I am seeing. Ever so occasionally I am seeing corrupted drawing on a TFT display (ST7735) that is connect to a photon using SPI. I am beginning to think that the cause is due to an interrupt arriving at the same time the application is writing to the TFT. The corruption is somewhat random and manifests as image drawn in the wrong place, half images drawn, lines or shapes drawn out of place or wrong size. Hence, not easy to post an image to help. Would it help to put no interrupts() and interrupts()? And where should these be placed? I am using calls to the Adafruit_mfGFX library for ST7735. Does this not protect itself from interrupt disruption? Lots of questions!

@armor, are you using software or hardware SPI? Are the interrupts used in your code? Interrupts should not impact the display UNLESS they are writing or reading from the same GPIO pins used by the display and/or are using hardware SPI. Under those conditions, the interrupt code will interfere with the display code.

@peekay123 Hi, not sure I fully follow the and’s and or’s in your reply.
I am using hardware SPI

Adafruit_ST7735 tft = Adafruit_ST7735(tftcs, tftdc, tftrst);

I am setting the SD with

  if (!sd.begin(sdcs, SPI_HALF_SPEED))

I am using the SDFAT library.

I have separate CS/SS pins for the SD card reader and the TFT and don’t use the pins for anything else.

I use 2 GPIO pins attached to interrupt handlers. I have implemented a bmpDraw() function to read image files from SD card and write these to the TFT. I was wondering if this is being affected by an interrupt from one of the 2 pins or from a timer whilst it is reading from the SD card and writing to the TFT.

@armor, can you post your interrupt handler functions and any software timer handlers as well?

PS2 keyboard interrupt handler

void ps2interrupt(void)
{
	static uint8_t bitcount=0;
	static uint8_t incoming=0;
	static uint32_t prev_ms=0;
	uint32_t now_ms;
	uint8_t n, val;

	val = digitalRead(DataPin);
	now_ms = millis();
	if (now_ms - prev_ms > 250) {
		bitcount = 0;
		incoming = 0;
	}
	prev_ms = now_ms;
	n = bitcount - 1;
	if (n <= 7) {
		incoming |= (val << n);
	}
	bitcount++;
	if (bitcount == 11) {
		uint8_t i = head + 1;
		if (i >= BUFFER_SIZE) i = 0;
		if (i != tail) {
			buffer[i] = incoming;
			head = i;
		}
		bitcount = 0;
		incoming = 0;
	}
}

button interrupt handler

void wakeupButtonPressed()
{
    if (powerState == battery)
    {
        if (runState == standby) runState = waking;
        if (runState == timing && msLastPress == 0) msLastPress = millis();
        if (runState == sleeping) runState = startup;
        if (runState == screenonstandby) runState = wakefromscreenonstandby;
        if (runState == scheduled) screenOnAutosched = TRUE;
        if (runState == autosched) screenOnAutosched = TRUE;
    }
}

Timer handlers

// handler for timer1 ended
void timing_solenoid()
{
    int redrw = mins1;
    mins1++;
    if (mins1 >= param.timerPeriod)
    {                   //solenoid has been on for timerperiod minutes - now turn off
        drawTimeRemaining(0,0); 
        screenStaysOn = FALSE;
        stopTimer1();
    }
    else
    {                   // not ended - update display with latest minutes remaining check if need to change display background to red
        if ((param.timerPeriod-redrw > param.redMin) && (param.timerPeriod-mins1 == param.redMin)) redrw = -1;  // last time was > redMin now = redMin change background
        else redrw = 0; // do nothing
        drawTimeRemaining(param.timerPeriod-mins1, redrw); 
    }
}
// handler for timer2 ended
void standby_screen()
{
    mins2++;
    if (mins2 >= param.stayOn)
    {                   //screen has stayed on for stayOn minutes - now dim
        screenStaysOn = FALSE;
        stopTimer2();
    }
}
// handler for timer3 ended
void admin_inactive()
{
    timer3.stop();      // if no keyboard entry during period ADMININACTIVE milliseconds then exit admin
    runState = adminexitinactive;
}
// handler for timer4 ended
void screen_on_auto()
{
    timer4.stop();      // when in auto  and PTS pushed screen goes on and stays on for period until timer ends
    screenOff();        // screen off
}

Looking at the timer 1 handler I am thinking that there is too much screen drawing in this. Perhaps I should be doing the TFT (slow) image writes outside of the timer 1 handler in the main loop. I am just worried about the reliability of that and the need to flag set. :confused:

@armor, picture this. The user thread running your code, including the TFT library, is writing to the display via SPI and software timer1 calls timing_solenoid(). The catch is that all timers are running in a separate thread so the callbacks should be treated like ISRs. Now timing_solenoid() calls drawTimeRemaining() which writes to the display (using SPI of course), unaware of the original SPI transactions occurring in the user thread. This is a formula for conflict and display mayhem! This is also a common RTOS shared resource challenge.

You have two choices. Block other threads from running every time you want to display stuff in your code (user thread) OR rethink how design your code, particularly around software timers. A well written non-blocking timer in loop() will achieve the same thing as what your software timer does. You can even explore the elapsedMills library to make things even easier.

OR, you can set flags in the software timer which you can read in loop() to do what needs to be done. Either way, the work is done in the user thread, without hardware resource (SPI) conflicts. Hope that helps! :wink:

1 Like

@peekay123 Yes it does help. Thanks for explaining that clearly. I have got two other timed processes going on in the main loop and I have used millis() checking difference from a lastms value (I assume that what you mean by non-blocking timer) - both these write updates to the screen and work fine. I will have a look into the library. Cheers.

1 Like