Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReadRect returns incorrect pixels for ILI9488 SPI driver (cannot drawPngFile with transparent background) #667

Open
AlmirKadric opened this issue Jan 10, 2025 · 1 comment

Comments

@AlmirKadric
Copy link

AlmirKadric commented Jan 10, 2025

Environment ( 実行環境 )

Problem Description ( 問題の内容 )

When rendering PNGs with alpha values the image is corrupted and most of it turns black. It appears to me that the ReadRect call is reading incorrect pixel data from the screen which causes incorrect pixel blending. If I disable the readable flag in the configuration, the image is rendered correctly, albeit without proper transparency settings making the image a bit rigid around the edges (blurring negated).

Expected Behavior ( 期待される動作 )

Properly renders PNGs with alphas values and shows smooth images when readable is true.

Actual Behavior ( 実際の動作 )

Image is corrupted with black pixels when readable is true

Steps to reproduce ( 再現のための前提条件 )

MCU Schematics
Screenshot 2025-01-11 at 1 44 44
LCD & Touch Schematics
Screenshot 2025-01-11 at 1 44 31

Issue when readable is true
IMG_3090
Working when readable is false (albeit not as smooth without alpha blending)
IMG_3092

Code to reproduce this issue ( 再現させるためのコード )

board.h

// I2C Frequencies
#define I2C_SLAVE_TOUCH_FREQ	(10)	// 10Hz
// SPI Frequencies
// NOTE: ESP32 Series MCUs have max 80MHz SPI Clock
// NOTE: SPI can only divide master max frequency by an intenger 1~255 to
// achieve slave frequency. So actual frequency may be slightly different.
#define SPI_MASTER_FREQ			(80 * 1000 * 1000)	// 80MHz
#define SPI_SLAVE_SDCARD_FREQ	(80 * 1000 * 1000)	// 80MHz
#define SPI_SLAVE_LCD_FREQ		(10 * 1000 * 1000)	// 10MHz

/**
 * PIN Map
 *  IO0 System RST
 *  IO1 LCD PWR		IO13 FSPI MISO	IO25 N/A		IO37 N/A
 *  IO2 Touch IRQ		IO14 FSPI SS LCD	IO26 N/A		IO38
 *  IO3				IO15				IO27 N/A		IO39
 *  IO4				IO16				IO28 N/A		IO40
 *  IO5				IO17 I2C SDA		IO29 N/A		IO41 
 *  IO6				IO18 I2C SCL		IO30 N/A		IO42
 *  IO7				IO19				IO31 N/A		IO43 UART TX
 *  IO8 LCD BLight PWR	IO20			IO32 N/A		IO44 UART RX
 *  IO9 FSPI SS		IO21 LCD RS		IO33 N/A		IO45
 * IO10 FSPI SS SDCard	IO22 N/A			IO34 N/A		IO46
 * IO11 FSPI MOSI		IO23 N/A			IO35 N/A		IO47
 * IO12 FSPI SCLK		IO24 N/A			IO36 N/A		IO48
 */
// Shared I2C Pins
#define I2C_PIN_SCL 18
#define I2C_PIN_SDA 17
// Shared SPI Pins
#define SPI_PIN_SCLK 12
#define SPI_PIN_MISO 13
#define SPI_PIN_MOSI 11

// LCD Pins
#define LCD_PIN_PWR 1
#define LCD_PIN_BLIGHT_PWR 8
// LCD SPI
#define LCD_PIN_SS 14
#define LCD_PIN_SCLK SPI_PIN_SCLK
#define LCD_PIN_MISO SPI_PIN_MISO
#define LCD_PIN_MOSI SPI_PIN_MOSI
#define LCD_PIN_DAT_CMD 21
// Touch Pins
#define TOUCH_PIN_IRQ 2
// Touch I2C
#define TOUCH_PIN_SCL I2C_PIN_SCL
#define TOUCH_PIN_SDA I2C_PIN_SDA
#define TOUCH_ADDRESS 0x38

lcd.h

#ifndef _LCD_H_
#define _LCD_H_

#include <LovyanGFX.hpp>

#include <esp32_spi.h>
#include <esp32_i2c.h>
#include "console.h"

class _CKLCD {
	// LCD Driver
	static lgfx::Bus_SPI lcdBus;
	static lgfx::Panel_ILI9488 lcdDriver;
	static lgfx::Touch_FT5x06 touchDriver;
	static lgfx::LGFX_Device lcdDevice;

	// Power Pin
	static int8_t pinPWR;
	// Backlight Power Pin
	static int8_t pinBLightPWR;

	// SPI Data Pin
	static int8_t pinSS;

	// Touch IRQ Pin
	static int8_t pinTouchIRQ;

	// Whether or not LCD is currently enabled for I/O
	static bool enabled;

	private:
		static TaskHandle_t TouchMoveTask;
		static void touchIRQ();
		static void touchTask(void* pvParameters);
		static void touchMoveTask(void* pvParameters);

	public:
		static bool setup(
			const uint8_t pinPWR, const uint8_t pinBLightPWR,
			const uint8_t pinSS, const uint8_t lcdDatCmd, S_SPI_CFG &spi, uint32_t freq,
			const uint8_t touchAddr, S_I2C_CFG &i2c, uint32_t i2cFreq, const uint8_t pinTouchIRQ
		);

		static void powerOn();
		static void powerOff();

		static void backlightOn();
		static void backlightOff();

		static bool enable();
		static void disable();

		static void onTouchDown(void (*callback)());
		static void onTouchMove(void (*callback)());
		static void onTouchUp(void (*callback)());

		static lgfx::LGFX_Device interface();
};

const _CKLCD KLCD;

#endif /* _LCD_H_ */

lcd.cpp

#include "lcd.h"

// LCD Driver
lgfx::Bus_SPI _CKLCD::lcdBus;
lgfx::Panel_ILI9488 _CKLCD::lcdDriver;
lgfx::Touch_FT5x06 _CKLCD::touchDriver;
lgfx::LGFX_Device _CKLCD::lcdDevice;

// Power Pin
int8_t _CKLCD::pinPWR = -1;
int8_t _CKLCD::pinBLightPWR = -1;

// SPI Data Pin
int8_t _CKLCD::pinSS = -1;

// Touch IRQ Pin
int8_t _CKLCD::pinTouchIRQ = -1;

// Whether or not LCD is currently enabled for writing
bool _CKLCD::enabled = false;

// Touch Callback
#define TOUCH_IRQ_CPU 1
#define TOUCH_MAX_IRQS 10
#define TASK_STACK_SIZE 16 * 1024
TaskHandle_t _CKLCD::TouchMoveTask = NULL;
void (*touchDownCallbacks[TOUCH_MAX_IRQS])() = {};
void (*touchMoveCallbacks[TOUCH_MAX_IRQS])() = {};
void (*touchUpCallbacks[TOUCH_MAX_IRQS])() = {};


/**
 *
 */
bool _CKLCD::setup(
	const uint8_t pinPWR, const uint8_t pinBLightPWR,
	const uint8_t lcdSS, const uint8_t lcdDatCmd, S_SPI_CFG &spi, uint32_t spiFreq,
	const uint8_t touchAddr, S_I2C_CFG &i2c, uint32_t i2cFreq, const uint8_t pinTouchIRQ

) {
	// Configure SPI Data Bus
	{
		lgfx::Bus_SPI::config_t cfgSPI;

		spi_host_device_t spi_host;
		uint8_t	dma_channel;
		if (spi.num == FSPI) {
			spi_host = SPI2_HOST;
			dma_channel = 1;
		} else if (spi.num == HSPI) {
			spi_host = SPI3_HOST;
			dma_channel = 3;
		} else {
			spi_host = SPI_HOST_MAX;
			dma_channel = 0;
		}

		cfgSPI.spi_host = spi_host;			// SPI Host
		cfgSPI.pin_sclk = spi.sclk;			// SPI SCLK pin number
		cfgSPI.pin_miso = spi.miso;			// SPI MISO pin number
		cfgSPI.pin_mosi = spi.mosi;			// SPI MOSI pin number
		cfgSPI.pin_dc = lcdDatCmd;			// SPI Data or Command pin number

		cfgSPI.spi_mode = SPI_MODE0;		// TODO: Investigate differences between SPI modes 0-3
		cfgSPI.freq_write = 20000000;		// SPI transmitting frequency
		cfgSPI.freq_read = 16000000;		// SPI receiving frequency

		cfgSPI.spi_3wire = false;			// True if receiving is performed on MOSI pin
		cfgSPI.use_lock = true;				// True if transaction locks are used
		cfgSPI.dma_channel = dma_channel;	// DMA channel (0-2, 0=disable)

		lcdBus.config(cfgSPI);
	}

	// Configure Panel Driver
	{
		lgfx::Panel_ILI9488::config_t cfgPanel;

		cfgPanel.pin_cs = lcdSS;		// SS (Data) pin number (-1 = disable)
		cfgPanel.pin_rst = -1;			// RST pin number (-1 = disable)
		cfgPanel.pin_busy = -1;			// BUSY pin number (-1 = disable)

		cfgPanel.dlen_16bit = false;	// True if the panel transmits data in 16-bit units.
		cfgPanel.bus_shared = true;		// True if the bus is shared with the SD card (bus control is performed using drawJpgFile, etc.)

		// TODO: fix reading from SPI, for some reason the read pixels are
		// corrupted and as a result the lcd library cannot properly handle
		// alphas values as it needs to blend current pixels with new incoming
		// alphas. This is why transparent PNGs dont work when this is enabled.
		cfgPanel.readable = false;		// True if data can be read
		cfgPanel.dummy_read_pixel = 8;	// Number of dummy read bits before pixel read
		cfgPanel.dummy_read_bits = 1;	// Number of dummy read bits before non-pixel data read

		cfgPanel.memory_width = 320;	// Max width supported by driver IC
		cfgPanel.memory_height = 480;	// Max height supported by driver IC
		cfgPanel.panel_width = 320;		// Actual displayable width
		cfgPanel.panel_height = 480;	// Actual displayable height

		cfgPanel.offset_x = 0;			// Panel X-direction offset
		cfgPanel.offset_y = 0;			// Panel Y-direction offset
		cfgPanel.offset_rotation = 1;	// Rotation value offset 0~7 (4~7 are upside down)

		cfgPanel.invert = false;		// True if the panel's light and dark are inverted.
		cfgPanel.rgb_order = false;		// True if the panel's red and blue are swapped.

		lcdDriver.config(cfgPanel);
	}

	// Configure Touch Driver
	{
		lgfx::ITouch::config_t cfgTouch;

		cfgTouch.i2c_port = i2c.num;	// I2C Bus
		cfgTouch.pin_sda = i2c.sda;		// I2C SDA Pin
		cfgTouch.pin_scl = i2c.scl;		// I2C SCL Pin
		cfgTouch.i2c_addr = touchAddr;	// I2C Address

		cfgTouch.freq = i2cFreq;		// TODO: needed but why? dont see it used in touch controller code

		cfgTouch.pin_int = pinTouchIRQ;	// IRQ pin number (-1 = disable)
		cfgTouch.pin_rst = -1;			// RST pin number (-1 = disable)

		cfgTouch.x_min = 0;				// Start X position
		cfgTouch.x_max = 320;			// End X position
		cfgTouch.y_min = 0;				// Start Y position
		cfgTouch.y_max = 480;			// End Y position

		cfgTouch.offset_rotation = 0;	// Rotation value offset 0~7 (4~7 are upside down)
		cfgTouch.bus_shared = false;	// True if panel and touch share the same bus

		touchDriver.config(cfgTouch);
	}

	// TODO Configure Backlight Driver
	{}

	// Configure Device
	lcdDriver.setBus(&lcdBus);
	lcdDriver.touch(&touchDriver);
	lcdDevice.setPanel(&lcdDriver);

	// Keep track of required pins
	_CKLCD::pinPWR = pinPWR;
	_CKLCD::pinBLightPWR = pinBLightPWR;
	_CKLCD::pinSS = pinSS;
	_CKLCD::pinTouchIRQ = pinTouchIRQ;

	// Check if the LCD successfully initializes
	bool initialized = lcdDevice.init();

	// Configure Touch controller IRQ
	uint8_t TOUCH_INT_REG[1] = { 0xA4 };
	uint8_t TOUCH_INT_VAL[1] = { 0x00 };
	KI2C.write(touchAddr, I2C_0, TOUCH_INT_REG, 1, TOUCH_INT_VAL, 1);

	pinMode(pinTouchIRQ, INPUT);
	attachInterrupt(digitalPinToInterrupt(pinTouchIRQ), touchIRQ, CHANGE);

	// Disable LCD at start
	disable();
	backlightOff();
	powerOff();

	return initialized;
}

/**
 *
 */
void _CKLCD::powerOn() {
	if (pinPWR >= 0) {
		pinMode(pinPWR, OUTPUT);
		digitalWrite(pinPWR, HIGH);
	}

	// Initialize LCD when powered on
	enable();
	lcdDevice.init();
	disable();

	// Force IRQ pin mode after every LCD initialization
	pinMode(pinTouchIRQ, INPUT);
}

/**
 *
 */
void _CKLCD::powerOff() {
	if (pinPWR >= 0) {
		pinMode(pinPWR, OUTPUT);
		digitalWrite(pinPWR, LOW);
	}
}

/**
 *
 */
void _CKLCD::backlightOn() {
	if (pinBLightPWR >= 0) {
		pinMode(pinBLightPWR, OUTPUT);
		digitalWrite(pinBLightPWR, HIGH);
	}
}

/**
 *
 */
void _CKLCD::backlightOff() {
	if (pinBLightPWR >= 0) {
		pinMode(pinBLightPWR, OUTPUT);
		digitalWrite(pinBLightPWR, LOW);
	}
}

/**
 *
 */
bool _CKLCD::enable() {
	// Check if already enabled
	if (enabled) {
		return true;
	}

	// Enable SPI Data Pin
	pinMode(pinSS, OUTPUT);
	digitalWrite(pinSS, LOW);

	// Mark as enabled
	enabled = true;
	return true;
}

/**
 *
 */
void _CKLCD::disable() {
	// Disable SPI Data Pin
	pinMode(pinSS, OUTPUT);
	digitalWrite(pinSS, HIGH);

	// Mark as disabled
	enabled = false;
}

/**
 *
 */
void _CKLCD::touchTask(void* pvParameters) {
	int pinValue = *(int*)pvParameters;

	// Check if touch action was DOWN or UP
	if (pinValue == LOW && TouchMoveTask == NULL) {
		// Call Touch Down Callbacks
		for (uint8_t i = 0; i < TOUCH_MAX_IRQS; i += 1) {
			if (touchDownCallbacks[i] != NULL) {
				(*touchDownCallbacks[i])();
			}
		}

		// Create touch move task
		xTaskCreatePinnedToCore(touchMoveTask, "Touch_IRQ", TASK_STACK_SIZE, NULL, 1, &TouchMoveTask, TOUCH_IRQ_CPU);
	} else if (pinValue == HIGH && TouchMoveTask != NULL) {
		// Notify touch move task to delete itself and remove handle
		xTaskNotifyGive(TouchMoveTask);
		TouchMoveTask = NULL;

		// Call Touch Up Callbacks
		for (uint8_t i = 0; i < TOUCH_MAX_IRQS; i += 1) {
			if (touchUpCallbacks[i] != NULL) {
				(*touchUpCallbacks[i])();
			}
		}
	}

	// Delete this task
	vTaskDelete(NULL);
}

void _CKLCD::touchMoveTask(void* pvParameters) {
	while (true) {
		// Check if we've been notified to delete
		if (ulTaskNotifyTake(false, 0) > 0) {
			break;
		}

		// Check if the touch instance has already been destroyed
		if (TouchMoveTask == NULL) {
			break;
		}

		// Call Touch Move Callbacks
		for (uint8_t i = 0; i < TOUCH_MAX_IRQS; i += 1) {
			if (touchMoveCallbacks[i] != NULL) {
				(*touchMoveCallbacks[i])();
			}
		}

		// Delay between movement events
		delay(100);
	}

	vTaskDelete(NULL);
}

void _CKLCD::touchIRQ() {
	// Get pin state at time of IRQ
	int pinValue = digitalRead(pinTouchIRQ);

	// Create new task to actiom the IRQ
	xTaskCreatePinnedToCore(touchTask, "Touch_IRQ", TASK_STACK_SIZE, &pinValue, 1, NULL, TOUCH_IRQ_CPU);
}

void _CKLCD::onTouchDown(void (*callback)()) {
	for (uint8_t length = 0; length < TOUCH_MAX_IRQS; length += 1) {
		if (touchDownCallbacks[length] == NULL) {
			touchDownCallbacks[length] = callback;
			break;
		}
	}
}

void _CKLCD::onTouchMove(void (*callback)()) {
	for (uint8_t length = 0; length < TOUCH_MAX_IRQS; length += 1) {
		if (touchMoveCallbacks[length] == NULL) {
			touchMoveCallbacks[length] = callback;
			break;
		}
	}
}

void _CKLCD::onTouchUp(void (*callback)()) {
	for (uint8_t length = 0; length < TOUCH_MAX_IRQS; length += 1) {
		if (touchUpCallbacks[length] == NULL) {
			touchUpCallbacks[length] = callback;
			break;
		}
	}
}

/**
 *
 */
lgfx::LGFX_Device _CKLCD::interface() {
	return lcdDevice;
}

main.ino

#include "board.h"
#include "esp32_spi.h"
#include "esp32_i2c.h"
#include "flash.h"
#include "sdcard.h"
#include "lcd.h"

/**
 * Initial setup funtion called before main Arduino sketch loop function
 */
void setup() {
	// Setup I2C Interface
	if (!KI2C.setup(0, I2C_PIN_SDA, I2C_PIN_SCL)) {
		while (true);
	}
	KConsole.println("I2C Bus (0) Setup Complete");

	// Setup SPI Interfaces
	if (!KSPI.setup(FSPI, SPI_PIN_SCLK, SPI_PIN_MISO, SPI_PIN_MOSI)) {
		while (true);
	}
	KConsole.println("SPI Bus (FSPI Shared) Setup Complete");


	/**
	 * MODULES
	 */
	// Setup Flash Storage
	if (!KFLASH.setup("spiffs")) {
		while (true);
	}
	KConsole.println("Flash Storage Setup Complete");
	KFLASH.printInfo();
	KFLASH.listDir("/", 0);

	// Setup SDCard Storage
	if (!KSDCARD.setup(
		SDCARD_PIN_PWR,
		SDCARD_PIN_SS, SPI_FSPI, SPI_SLAVE_SDCARD_FREQ
	)) {
		while (true);
	}
	KConsole.println("SDCard Storage Setup Complete");
	KSDCARD.printInfo();
	KSDCARD.listDir("/", 0);

	// Setup LCD
	if (!KLCD.setup(
		LCD_PIN_PWR, LCD_PIN_BLIGHT_PWR,
		LCD_PIN_SS, LCD_PIN_DAT_CMD, SPI_FSPI_CFG, SPI_SLAVE_LCD_FREQ,
		TOUCH_ADDRESS, I2C_0_CFG, 100000, TOUCH_PIN_IRQ
	)) {
		while (true);
	}
	KConsole.println("LCD Setup Complete");
	
	
	//
	KLCD.backlightOn();
	KLCD.powerOn();
	KLCD.enable();

	KLCD.interface().setColorDepth(24);
	KLCD.interface().clear(TFT_RED);

	KFLASH.enable();
	KLCD.interface().drawPngFile(KFLASH.fs, "/logo_full_420_bg_transparent.png", 30, 96);
	KFLASH.disable();

	KLCD.disable();


	/**
	 * SETUP COMPLETE
	 */
	KConsole.println("Setup Complete");
}


/**
 * Main Arduino sketch loop function
 */
void loop() {
	delay(100);
}
@tobozo
Copy link
Collaborator

tobozo commented Jan 19, 2025

hi,

When seeing a custom board it's tempting to say any problem is caused by bad wiring or bad routing, so the obvious question is: does this issue happen on a breadboard too, and does it persist when non-panel components (touch, sdcard) are disabled?

I strongly recommend you create a sketch for that, to test readRect() and readRectRGB() with a 10x10 square, and only doing this, no sdcard, no touch, no TFT_ESPI macros for pins assignment, no leftovers of your app, nothing else.

If the problem still exists in those conditions, then I can help with troubleshooting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants