Skip to content

Wrong colors on ILI9488 with 8-bit parallel (I80) interface since uDisplay refactor #24582

@augea

Description

@augea

Wrong colors on ILI9488 with 8-bit parallel (I80) interface since uDisplay refactor

After upgrading from Tasmota 15.0.1.1 to 15.3.0.3, the ILI9488 display connected via 8-bit parallel (I80) interface on an ESP32-S3 shows wrong colors: blue appears as green, and the overall color palette is severely reduced (appears to be ~16 colors instead of 65K). The same display.ini worked perfectly on Tasmota 15.0.1.1.

SPI-connected ILI9488 displays are not affected — only the parallel (I80) interface path is broken.

Symptoms:

Symptom Detail
Blue → Green Pure blue backgrounds appear green
Reduced colors Display looks like ~16 colors instead of 65K (RGB565)
LVGL Mirror Shows correct colors (confirms LVGL renders correctly)
SPI display Same ILI9488 via SPI on ESP32-D0WD works correctly
Tasmota 15.0.1.1 Worked perfectly with identical display.ini
Tasmota 15.3.0.3 Broken (both master and development branch)

Root Cause:

The uDisplay driver was refactored in PR #24007 (Oct 2025), splitting the monolithic uDisplay.cpp into panel-specific files. During this refactoring, the byte order logic for 8-bit parallel (I80) pixel pushing was inverted.

The bug is in I80Panel::pushColors() in lib/lib_display/UDisplay/src/uDisplay_I80_panel.cpp, line ~264:

bool I80Panel::pushColors(uint16_t *data, uint32_t len, bool swapped) {
    pb_pushPixels(data, len, !swapped, false);  // <-- BUG: the '!' inverts byte order
    return true;
}

The call chain:

  1. LVGL flush calls uDisplay::pushColors(data, len, not_swapped=true) — comment in uDisplay_graphics.cpp: "not_swapped is always true in call from LVGL driver!!!!"
  2. uDisplay::pushColors calls universal_panel->pushColors(data, len, not_swapped=true)
  3. I80Panel::pushColors(data, len, swapped=true) calls pb_pushPixels(data, len, !true=false, false)
  4. pb_pushPixels with swap_bytes=false sends: low byte first, then high byte — but ILI9488 expects high byte first

In the old monolithic uDisplay.cpp (15.0.1.1), the PAR8 path always used pb_pushPixels(data, len, true, false) with swap_bytes=true, producing the correct byte order.

Byte order on the 8-bit bus for RGB565 — Pure Blue (0x001F):

Code path 1st byte on bus 2nd byte on bus Display sees
Old code (correct) 0x00 (RRRRRGGG) 0x1F (GGGBBBBB) R=0, G=0, B=31 → Blue
New code (broken) 0x1F (GGGBBBBB) 0x00 (RRRRRGGG) R=0, G=56, B=0 → Green

Note: The comment on line ~264 says "swap_bytes=true to match old driver", which confirms the intent was to have swap_bytes=true — but the ! negation produces the opposite result.

Proposed Fix:

In lib/lib_display/UDisplay/src/uDisplay_I80_panel.cpp, line ~264, remove the ! negation:

// Before (broken):
pb_pushPixels(data, len, !swapped, false);

// After (fixed):
pb_pushPixels(data, len, swapped, false);

Workaround (display.ini):

Until the fix is merged, setting swap_color bit in the :B parameter compensates for the inverted logic:

:B,20,2    (was: :B,20,0)

This sets bit 1 (swap_color=1) in LVGL_PARAMS_t.data, which adds an additional not_swapped = !not_swapped inversion in uDisplay::pushColors() before calling the I80 panel, canceling out the erroneous ! in the I80 code. This workaround must be reverted back to :B,20,0 once the source code fix is applied.

Affected Versions:

  • Tasmota master (v15.3.0): Bug present
  • Tasmota development: Bug present (identical code)
  • Tasmota 15.0.1.1: Not affected (old monolithic uDisplay.cpp)

REQUESTED INFORMATION

  • Read the Contributing Guide and Policy and the Code of Conduct
  • Searched the problem in issues
  • Searched the problem in discussions
  • Searched the problem in the docs
  • Searched the problem in the chat
  • Problem is not scripter related, in this case open a discussion and tag gemu2015
  • Device used (e.g., Sonoff Basic): Custom ESP32-S3 board with ILI9488 (8-bit parallel) + FT6236 touch
  • Tasmota binary firmware version number used: 15.3.0.3
    • Pre-compiled
    • Self-compiled
  • Flashing tools used: PlatformIO (esptool via USB)
  • Provide the output of command: Backlog Template; Module; GPIO 255:
{"NAME":"ESP32S3","ARCH":"ESP32S3","GPIO":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1],"FLAG":0,"BASE":1}
{"Module":{"1":"ESP32S3"}}
GPIO8: I2C SDA1 (640)
GPIO9: I2C SCL1 (608)
GPIO10: Switch1 (160)
GPIO11: Relay1 (224)
GPIO45: Option A3 (6210)
(all other GPIOs: Nonedisplay pins configured via display.ini, not GPIO template)
  • If using rules, provide the output of this command: Backlog Rule1; Rule2; Rule3:
No rulesBerry/LVGL application, rules disabled in build (#undef USE_RULES)
  • Provide the output of this command: Status 0:
{"Status":{"Module":1,"DeviceName":"Tasmota-DispP","FriendlyName":["Tasmota-DispP",""],"Topic":"tasmota_217DF8","ButtonTopic":"0","Power":"00","PowerLock":"00","PowerOnState":3,"LedState":1,"LedMask":"FFFF","SaveData":1,"SaveState":1,"SwitchTopic":"0","SwitchMode":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"ButtonRetain":0,"SwitchRetain":0,"SensorRetain":0,"PowerRetain":0,"InfoRetain":0,"StateRetain":0,"StatusRetain":0},"StatusPRM":{"Baudrate":115200,"SerialConfig":"8N1","GroupTopic":"tasmotas","OtaUrl":"http://ota.tasmota.com/tasmota32/release/tasmota32s3.bin","RestartReason":"Software reset CPU","Uptime":"0T00:05:50","StartupUTC":"2026-03-23T10:22:51","Sleep":50,"CfgHolder":4617,"BootCount":797,"BCResetTime":"2025-06-16T19:23:11","SaveCount":4351},"StatusFWR":{"Version":"15.3.0.3(tasmota)","BuildDateTime":"2026.03.23 09:59:06","Core":"3.3.7","SDK":"5.3.4.260127","CpuFrequency":240,"Hardware":"ESP32-S3 v0.2","CR":"423/699"},"StatusLOG":{"SerialLog":3,"WebLog":2,"MqttLog":0,"FileLog":0,"SysLog":0,"LogHost":"","LogPort":514,"SSId":["RAPTOR1",""],"TelePeriod":300,"Resolution":"558180C0","SetOption":["0000C009","0A05C80001000600003C5A0A1928FA000000","40008080","04006000","00004001","00000000"]},"StatusMEM":{"ProgramSize":2208,"Free":4959,"Heap":108,"StackLowMark":17,"PsrMax":6016,"PsrFree":5706,"ProgramFlashSize":16384,"FlashSize":16384,"FlashChipId":"184046","FlashFrequency":80,"FlashMode":"QIO","Features":["0407","87BAC7CF","001482A1","000214CF","014017F1","C0000981","000040A0","00200000","5400002C","00020080","00000004"],"Drivers":"1,2,!3,!4,!5,7,!8,9,12,13,!16,!20,!21,!24,26,27,29,!35,38,50,52,54,55,56,62,!67,!68,!121","Sensors":"1,2,3,5,6,9,10,14,29,42,127","I2CDriver":"3,5,7,10,11,15,22,26,29,47,77"},"StatusNET":{"Hostname":"tasmota-217DF8-7672","IPAddress":"192.168.168.105","Gateway":"192.168.168.1","Subnetmask":"255.255.255.0","DNSServer1":"192.168.168.131","Mac":"30:ED:A0:21:7D:F8","Webserver":2,"HTTP_API":1,"WifiConfig":4,"WifiPower":19.0},"StatusMQT":{"MqttHost":"raspberrypi5","MqttPort":1883,"MqttClientMask":"DVES_%06X","MqttClient":"DVES_217DF8","MqttUser":"master","MqttCount":1,"MqttTLS":0,"MAX_PACKET_SIZE":1200,"KEEPALIVE":30,"SOCKET_TIMEOUT":4},"StatusTIM":{"UTC":"2026-03-23T10:28:42Z","Local":"2026-03-23T11:28:42","StartDST":"2026-03-29T02:00:00","EndDST":"2026-10-25T03:00:00","Timezone":"+01:00","Sunrise":"06:46","Sunset":"19:06"},"StatusSNS":{"Time":"2026-03-23T11:28:42","Switch1":"OFF","BME280":{"Temperature":23.7,"Humidity":34.5,"DewPoint":7.1,"Pressure":989.1},"BH1750":{"Illuminance":56},"SHT3X":{"Temperature":23.2,"Humidity":40.2,"DewPoint":8.9},"SCD30":{"CarbonDioxide":975,"eCO2":998,"Temperature":25.8,"Humidity":35.1,"DewPoint":9.2},"Shutter1":{"Position":0,"Direction":0,"Target":0,"Tilt":0},"PressureUnit":"hPa","TempUnit":"C"},"StatusSTS":{"Time":"2026-03-23T11:28:42","Uptime":"0T00:05:51","UptimeSec":351,"Heap":108,"SleepMode":"Dynamic","Sleep":10,"LoadAvg":99,"MqttCount":1,"Berry":{"HeapUsed":230,"Objects":3056},"POWER1":"OFF","POWER2":"OFF","Wifi":{"AP":1,"SSId":"RAPTOR1","BSSId":"34:81:C4:F2:1C:A1","Channel":1,"Mode":"HT20","RSSI":82,"Signal":-59,"LinkCount":1,"Downtime":"0T00:00:03"},"Hostname":"tasmota-217DF8-7672","IPAddress":"192.168.168.105"}}
  • Set weblog to 4 and then, when you experience your issue, provide the output of the Console log:
Not applicablethis is a pixel byte-order issue in the display driver, not a runtime error.
The display initializes and renders without errors. Colors are simply wrong due to
swapped byte order on the 8-bit parallel bus.

TO REPRODUCE

  1. Use an ILI9488 display with 8-bit parallel (I80) interface on ESP32-S3
  2. Configure display.ini with :H,ILI9488,480,320,16,PAR,8,... and 3A,1,55 (RGB565)
  3. Enable LVGL (Berry or HASPmota)
  4. Observe that blue backgrounds appear green and the color palette is severely reduced

The issue does not occur with:

  • SPI interface (3A,1,66 + :P,18) — SPI path has its own color conversion, unaffected
  • Tasmota 15.0.1.1 or earlier (before the uDisplay refactor)

EXPECTED BEHAVIOUR

Colors should be displayed correctly on 8-bit parallel (I80) ILI9488 displays, as they were in Tasmota 15.0.1.1. Blue should appear blue, not green. The full 65K color palette (RGB565) should be visible.

The one-character fix (removing ! from line ~264 in uDisplay_I80_panel.cpp) restores correct behavior:

pb_pushPixels(data, len, swapped, false);  // remove the '!' before 'swapped'

SCREENSHOTS

If applicable, add screenshots to help explain your problem.

ADDITIONAL CONTEXT

Add any other context about the problem here.

(Please, remember to close the issue when the problem has been addressed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions