Trying to display an image on multiple ST7789 screens

Hi there.
I'm making a clock that displays time in single digits on several screens with nixie tubes images.
I have been trying for several days to get an image stored in SPIFFS with LittleFS to display on a 240x240 screen with several boards. Currently with a XIAO ESP32-S3.
I can get graphics to display with several different processors, no problem.
With this program I can see the 10 files I loaded with the IDE 2.3.7 into flash.
There are no errors regarding LittleFS in the serial monitor.
I have tried numerous types of files, made with several image converters. Raw RGB565 etc
The 16-bit binaries vary between 60k and 115200.
There is not the slightest pixel on the LCDto be seen. But I can display graphics just fine.
This is the offending function.

void showNumber() {
  char fileName[13];  // buffer
  snprintf(fileName, 12, "%i.bin", image_nb); // files: bin.0 to bin.9 for digit 0-9
  Serial.print("Opening: ");
  Serial.println(fileName);
  if (!fileName) Serial.println("File error");  // no error

  File file = LittleFS.open(fileName);
// this part does not work
  uint8_t buffer[64];  // Small buffer
  while (file.available()) {
    int bytesRead = file.read(buffer, sizeof(buffer));
    tft1.writePixels((uint16_t *)buffer, bytesRead / 2);
  }
  tft1.endWrite();  // End SPI transaction
  file.close();

  delay(1000);  // these test block works
  if (screen == 1) {
    tft1.fillScreen(ST77XX_BLUE);
    tft1.setCursor(75, 50);
    tft1.setTextSize(20);
    tft1.setTextColor(ST77XX_RED);
    tft1.print(image_nb);
  }
  if (screen == 2) {
    tft2.fillScreen(ST77XX_BLUE);
    tft2.setCursor(75, 50);
    tft2.setTextSize(20);
    tft2.setTextColor(ST77XX_RED);
    tft2.print(image_nb);
  }
}

I can post the whole sketch if needed, but it's a work in progress.
As said, graphics display fine, so I expect the problem to be in that function.
Leo..

Edit: Using the Adafruit tft library.

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <Adafruit_ImageReader.h>

// These functions require a chip-select and/or SPI transaction
// around them. Higher-level graphics primitives might start a
// single transaction and then make multiple calls to these functions
// (e.g. circle or text rendering might make repeated lines or rects)
// before ending the transaction. It's more efficient than starting a
// transaction every time.
void writePixel(int16_t x, int16_t y, uint16_t color);
void writePixels(uint16_t *colors, uint32_t len, bool block = true,
bool bigEndian = false);

Since you only posted a snippet, I have to ask:

Did you?

Do a chip select and/or SPI transaction around them? Do a startWrite? And a setAddrWindow?

There is only a chip select for the tft displays, which is handled in the declarations.
Adafruit_ST7789 tft1 = Adafruit_ST7789(D0, D6, D7); // CS, DC, RST
Adafruit_ST7789 tft2 = Adafruit_ST7789(D1, D6, -1);
There is no SD card to select.
Full code (ignore incomplete parts).
Leo..

#include <WiFi.h>
#include "esp_sntp.h"
#include "FS.h"
#include <SPI.h>
#include <LittleFS.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <Adafruit_ImageReader.h>
unsigned long prevTime;
unsigned int reSync, display;
time_t now;
tm tm;
int tensHr, unitsHr, tensMin, unitsMin, image_nb;
int prevTensHr = -1, prevUnitsHr = -1, prevTensMin = -1, prevWeekday = -1, prevMinute = -1;
int screen;

Adafruit_ST7789 tft1 = Adafruit_ST7789(D0, D6, D7);  // CS, DC, RST
Adafruit_ST7789 tft2 = Adafruit_ST7789(D1, D6, -1);
//Adafruit_ST7735 tft3 = Adafruit_ST7789(D0, D6, -1);
//Adafruit_ST7735 tft4 = Adafruit_ST7789(D0, D6, -1);

void cbSyncTime(struct timeval *tv) {
  reSync++;
  Serial.print("NTP update: ");
  Serial.println(reSync);
}

void showNumber() {
  char fileName[13];  // buffer
  snprintf(fileName, 12, "%i.bmp", image_nb);
  Serial.print("Opening: ");
  Serial.println(fileName);
  if (!fileName) Serial.println("File error");  // no error

  File file = LittleFS.open(fileName);
// this part does not work
  uint8_t buffer[64];  // Small buffer
  while (file.available()) {
    int bytesRead = file.read(buffer, sizeof(buffer));
    tft1.writePixels((uint16_t *)buffer, bytesRead / 2);
  }
  tft1.endWrite();  // End SPI transaction
  file.close();

  delay(1000);  // these test block works
  if (screen == 1) {
    tft1.fillScreen(ST77XX_BLUE);
    tft1.setCursor(75, 50);
    tft1.setTextSize(20);
    tft1.setTextColor(ST77XX_RED);
    tft1.print(image_nb);
  }
  if (screen == 2) {
    tft2.fillScreen(ST77XX_BLUE);
    tft2.setCursor(75, 50);
    tft2.setTextSize(20);
    tft2.setTextColor(ST77XX_RED);
    tft2.print(image_nb);
  }
}

void showFace() {
  for (int i = 0; i < 32; i++) {
    //if (birthdayEvent[i][1] == tm.tm_mday && birthdayEvent[i][2] == tm.tm_mon) {
  }
}

void setup() {
  Serial.begin(115200);
  delay(2000);
  if (!LittleFS.begin()) {
    Serial.println("LittleFS initialisation failed!");
  }

  sntp_set_time_sync_notification_cb(cbSyncTime);
  configTzTime("NZST-12NZDT,M9.5.0,M4.1.0/3", "nz.pool.ntp.org");

  tft1.init(240, 240);
  tft2.init(240, 240);

  //tft.setSPISpeed(40000000);
  tft1.setRotation(2);
  tft2.setRotation(2);

  tft1.fillScreen(ST77XX_RED);

  WiFi.begin("xxxxxxx", "xxxxxxx");

  Serial.println("Waiting for Time sync");
  while (!reSync) yield();
}


void loop() {
  time(&now);                // epoch
  if (now != prevTime) {     // time has changed
    prevTime = now;          // remember
    localtime_r(&now, &tm);  // local time elements
  }
  if (tm.tm_min != prevMinute) {  // minutes have changed ?
    prevMinute = tm.tm_min;       // remember
    // extract digits
    tensHr = tm.tm_hour / 10;
    unitsHr = tm.tm_hour % 10;
    tensMin = tm.tm_min / 10;
    unitsMin = tm.tm_min % 10;

    Serial.print("Units minutes:");
    Serial.println(unitsMin);
    screen = 1;
    image_nb = unitsMin;
    showNumber();


    if (tensMin != prevTensMin) {
      prevTensMin = tensMin;
      Serial.print("Tens minutes:");
      Serial.println(tensMin);
      screen = 2;
      image_nb = tensMin;
      showNumber();
    }

    if (unitsHr != prevUnitsHr) {
      prevUnitsHr = unitsHr;
      Serial.print("Units hour:");
      Serial.println(unitsHr);
      screen = 3;
      image_nb = unitsHr;
      showNumber();
    }

    if (tensHr != prevTensHr) {
      prevTensHr = tensHr;
      Serial.print("Tens hour:");
      Serial.println(tensHr);
      screen = 4;
      if (tensHr) {  // if not zero
        image_nb = tensHr;
        showNumber();
      } else if (unitsHr > 6) showFace();
      //else showTemp();
    }
  }
}

//if (tm.tm_wday != prevWeekday) {
//  flag = false;
//  prevWeekday = tm.tm_wday;
//}

I think @van_der_decken's point is correct.

You're executing endWrite(), but you're missing startWrite(), which starts the SPI transaction which should set CS low to start SPI communication.

setAddrWindow() sets the clipping region.

Use an array of instances.

And Nixie bitmaps...

1 Like

added it here

  tft1.startWrite();
  uint8_t buffer[64];

No difference.

I just added this line, to see the file size.

  File file = LittleFS.open(fileName);
  Serial.print(file.size()); Serial.println(" bytes"); // added this line

It prints 0 bytes.
Does that mean that LittleFS is not returning any data.

Then, how about this:

snprintf(fileName, 12, "/%i.bmp", image_nb);

Had that before.
No difference, because I have the files in a flat file system.
Leo..

Sorry but the only thing I can think of is to check it out at libraries/LittleFS/examples/LITTLEFS_test/LITTLEFS_test.ino.

That sketch lists all 10 files I have in LittleFS properly, including the right file sizes.

I see no setAddrWindow(...) call to tell the display where to put the bytes being written to it.

And if file.size() is returning 0, I don't think it opened properly. So no data.

It's been yonks since I played around with LittleFS, but I'd expect there to be another parameter to say whether the file is being opened for reading or writing.

Making progress.
Post#7 I had the forward slash in there when I was still working with SPIFFS.
Removed it at some stage.
With LittleFS it seems to be needed.
Now I have file content printed in the serial monitor.

Waiting for Time sync
NTP updat1
Units minutes:7
Opening: /7.bin
61920 bytes

:grinning_face:

Still no picture though. Wrong format?
I wish I had a example .bin file that worked.
Why setAddrWindow(...)
All files are trimmed to 240x240.
Leo..

I wrote:

But this was not correct and I think @van_der_decken's explanation is correct.

Adafruit GFX library says about setAddrWindow():

So it's worth a try.

1 Like
/**************************************************************************/
/*!
  @brief  SPI displays set an address window rectangle for blitting pixels
  @param  x  Top left corner x coordinate
  @param  y  Top left corner y coordinate
  @param  w  Width of window
  @param  h  Height of window
*/
/**************************************************************************/
void Adafruit_ST77xx::setAddrWindow(uint16_t x, uint16_t y, uint16_t w,
                                    uint16_t h) {
  x += _xstart;
  y += _ystart;
  uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
  uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);

  writeCommand(ST77XX_CASET); // Column addr set
  SPI_WRITE32(xa);

  writeCommand(ST77XX_RASET); // Row addr set
  SPI_WRITE32(ya);

  writeCommand(ST77XX_RAMWR); // write to RAM
}

Because without it, the commands to tell the controller where to write in RAM aren't sent.

Which means that the controller has no idea what do to with the flood of data that follows.

1 Like

With tft1.startWrite(); I see now something that could be the image (nixie tube).
It has the colours and detail. But only half the screen and skewed.
But more than I had in the last three days :grinning_face:


Not a good coder, so I have no clue what to do with the code from post#14.
Thanks for all the help so far.
Leo..

That's the source code for the setAddrWindow call. I showed it to you do demonstrate that's what sets the controller up to accept pixel data and that without calling it, the controller has no idea what to do.

Good luck with your project.

It looks like you are back at this "nixie" project.

Although the objective is to use multiple screens it seems that you are having problems with even a single screen.

Would it help if I adapted this project to work on an ST7789 screen? The dimensions will be different so there will be serious clipping but it may provide an example.
[Solved] Size issues when loading files into esp32 LittlefS - #35 by 6v6gt

1 Like

Try using the drawRGBBitmap() function from the Adafruit_GFX library, instead of trying to directly draw pixels. You will need to change 129 to 240, my test images are 129x240.

  size_t line = 0;
  File file = LittleFS.open(fileName);
  if (file) {
    uint8_t buffer[129 * 2];  // 2 bytes per pixel
    while (file.available()) {
      int bytesRead = file.read(buffer, sizeof(buffer));
      tft1.drawRGBBitmap(0, line, (uint16_t *)buffer, 129, 1);
      line++;
    }
    file.close();
  } else {
    Serial.println("File error");
  }

This is what I got.

  File file = LittleFS.open(fileName);
  Serial.print(file.size()); // prints size OK, 61920 bytes
  Serial.println(" bytes");
  tft1.fillScreen(ST77XX_BLACK); // clear screen
  tft1.startWrite();
  tft1.setAddrWindow(0, 0, 240, 240); // are these the right values for a 240x240 screen
  uint8_t buffer[64];  // Small buffer
  while (file.available()) {
    int bytesRead = file.read(buffer, sizeof(buffer));
    tft1.writePixels((uint16_t *)buffer, bytesRead / 2);
  }
  tft1.endWrite();  // End SPI transaction
  file.close();

No difference with/withoutset addrWindow().
Leo..

Can you post one of your image .bin files, so we can see if it is correct?