Reverse RGB565 byte order for ILI9341 TFT Display

My application needs a larger display with higher resolution than the OpenMV 1.8" 128x160 LCD Shield (ST7735R driver). I’ve connected a 2.8" 320x240 TFT display (ILI9341 driver) to SPI2 using the simple SPI write of the image frame buffer by OutOfTheBots GitHub - OutOfTheBots/OpenMV_TFT.

LCD Display Test V1.py (917 Bytes)
OpenMV_TFT.py (3.3 KB)

Writing to the 320x240 TFT display is fast at 28 fps, but the image color is distored. It looked like the byte order was incorrect. When I swapped the byte order for each pixel in the frame buffer with the following code snippet the image on the TFT display is perfect, but the frame rate falls to 2 fps.

img_byte = img.bytearray()
for i in range (0, 153599, 2):
    tmp = img_byte[i]
    img_byte[i] = img_byte[i+1]
    img_byte[i+1] = tmp

I cannot find a setting on the ILI9431 for changing the byte order.

Is there a way to change the RGB565 byte order in the image frame buffer, or quickly swap the byte order in the frame buffer, or swap the byte order during the SPI.write.

Would be great to be able to use the OpenMV with these larger higher resolution displays.

Thanks in advance for help on this.

You need to modify their code:

Read the datasheet for the display. There’s a register called memory data access control where you can invert the byte order the display draws the image with.

I tried both the Memory Access Control and the Interface Control Registers, to set the RGB <-> BGR order, but neither worked. These set the order of the 5bit 6bit 5bit encoding of RGB565 (16 bits total). The issue is not the order of RGB vs BGR, it is the order of the 2 bytes encoding RGB565, which split in the middle of the 6 bits encoding green in both RGB and BGR. These bytes need to be reversed for the ILI9341. Manually swapping the 2 bytes fixes the display, but doing this in python is very slow. This is the byte order required for the ILI9341 for 4-wire SPI:

I also tried the Endian bit in the Interface Control Register, but this is only valid for 8-bit and 9-bit parallel connections, not SPI.

There should be a register which allows you to reverse this.

Well, there’s also a BGR field which allows you to reverse the red/blue pixels for the LCD which you can pass as an argument.

Reserving the bytes is not hard in the c code if you can edit it.

The registers that swap BGR <-> RGB did not solve the issue. In RGB565 the three colors are encoded into two bytes. Swapping blue and red does not change the byte order. I cannot find a register in the ILI9341 that swaps the byte order. Where in the OpenMV C code can I swap the byte order? This would be a useful setting for OpenMV to have to be able to work with different displays.

Can you post the datasheet for the display?

Sure, here it is
ILI9341-Datasheet.pdf (3.4 MB)

So, this is weird. I have the same display and just switching BGR is fine for it. I actually updated the driver to work with the higher resolution display.

Can you post an image of the distorted picture? If the byte order is reversed it should look completely crazy.

The display needs this:

And the H7 is configured to output the image in 16-bit data chunks: openmv/py_lcd.c at master · openmv/openmv · GitHub

RGB565 pixels are stored in RAM with the such that when you read them they get loaded into a word as RGB565 with the MSB as red.

And the finally the SPI controller is setup to do MSB first. So, you should be getting what you want.

How do I test the driver you have updated to work with the higher resolution display?

It’s been updated since 2 years ago.

My application needs UART, I2C, and a TFT display. I cannot therefore use the openmv lcd driver because it uses P7 and P8 (I2C4), leaving only I2C2 which is on the same pins as UART3. The OpenMV LCD Driver ties up P6 and P7 for backlight and reset, which are not required for the TFT display to function. This is why I turned to the OutOfTheBots TFT driver.

Can the pins for CS and DC be user defined with the OpenMV LCD Driver? Can the OpenMV LCD driver be configured to have backlight and reset be optional, so P6 and P7 can be used for other purposes. Also, can P1 (SPI2 MISO) be used for another purpose?

If you don’t call any of the backlight functions I don’t use the pin in our LCD driver. We do use the reset pin though.

Anyway, so, you can fix your issue then easily by just modifying the out of bots code to setup the SPI bus in 16-bits more when you send to the screen. This will fix the problem.

Can you edit our firmware? It’s trivial then to move the pins that we use and then you can flash a custom firmware.

Hi, I’m tring to use the same library and TFT with my H7 , but what I notice is that before appear the screen it take a while (more than 10 second before it start to show Fps on serial monitor) that seems quite strange ,and then in any case I got this proble with the image on the screen .The problem seems similar to the one showed in this video https://www.youtube.com/watch?v=A1ZeEZCyMKg, but in my case the firmware should be at the last level (4.3.3) . I’ve also modified the code as suggested at the beginning of this post. @ajacobs Did you never experienced problem like this using the library end same TFT ?

I have come across a couple of potential causes for these distortions on the display.

  1. Wiring - jumper wires pick up interference from the high speed USB that can disrupt the SPI signals to the display. Try lowering the SPI baud rate.
  2. Window - once I forgot the set_window and this caused smaller, multiple images on the display.

This is my code to setup the display.
spi_display = SPI(2, baudrate=20000000)
TftDisplay = TFT(spi_display, cs = ‘P3’, dc = ‘P9’)
TftDisplay.set_window(0,0,320,240)

  1. Byteswap. The bytearray object of the image in OpenMV is reversed from the order required by the ILI9341. I could not find a setting to change this on the ILI9341, so I swap the byte order of the image object before sending to the display. Doing this is slow in python so I swap the bytes in assembly with this function:

@micropython.asm_thumb
def byteswap(r0, r1): # bytearray, len(bytearray)
mov(r3, 1)
lsr(r1, r3) # divide len by 2
mov(r4, r0)
add(r4, 1) # dest address
label(LOOP)
ldrb(r5, [r0, 0])
ldrb(r6, [r4, 0])
strb(r6, [r0, 0])
strb(r5, [r4, 0])
add(r0, 2)
add(r4, 2)
sub(r1, 1)
bpl(LOOP)

This is the function I use to send an image to the display. My display is mounted upside down relative to the IDE, so I flip the image first, and then flip back before sending to IDE:

def displayTFT(self):
    # Rotate 180 degrees
    self.camera.img.replace(vflip=True, hmirror=True, transpose=False)
    # object that points to image byte array
    img_byte_array = self.camera.img.bytearray()
    # swap byte order for ILI9341 TFT Driver
    TFT.byteswap(img_byte_array,len(img_byte_array))
    self.TftDisplay.send_spi_image(img_byte_array)
    # swap back for IDE
    TFT.byteswap(img_byte_array,len(img_byte_array))
    # rotate back for IDE
    self.camera.img.replace(vflip=True, hmirror=True, transpose=False)
    # send image to IDE
    self.camera.img.copy(copy_to_fb=True)

Hope this helps

Hi, can you make a GitHub ticket for this and I’ll try to fix this issue in the firmware.

Sure, and thanks.

Thanks ! I’ll try your suggestions.

Nothing to do , I’m not able to see the image on TFT.
The displayTFT function, why seems expect one argument but in the funtion definition I don’t see any
parameter to pass to this function?.

This is the code

#TFT screen demo to steam frame buffer to a external SPI screen

import sensor, image, time
from machine import SPI,Pin
from OpenMV_TFT import TFT

cs = Pin("P3", Pin.OUT)
dc = Pin("P9", Pin.OUT)


sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(time = 2000)

clock = time.clock()

spi_display = SPI(2, baudrate=20000000)

TftDisplay = TFT(spi_display, cs , dc )
TftDisplay.set_window(0,0,320,240)

@micropython.asm_thumb
def byteswap(r0, r1): # bytearray, len(bytearray)
    mov(r3, 1)
    lsr(r1, r3) # divide len by 2
    mov(r4, r0)
    add(r4, 1) # dest address
    label(LOOP)
    ldrb(r5, [r0, 0])
    ldrb(r6, [r4, 0])
    strb(r6, [r0, 0])
    strb(r5, [r4, 0])
    add(r0, 2)
    add(r4, 2)
    sub(r1, 1)
    bpl(LOOP)

def displayTFT(self):
    # Rotate 180 degrees
    self.camera.img.replace(vflip=True, hmirror=True, transpose=False)
    # object that points to image byte array
    img_byte_array = self.camera.img.bytearray()
    # swap byte order for ILI9341 TFT Driver
    TFT.byteswap(img_byte_array,len(img_byte_array))
    self.TftDisplay.send_spi_image(img_byte_array)
    # swap back for IDE
    TFT.byteswap(img_byte_array,len(img_byte_array))
    # rotate back for IDE
    self.camera.img.replace(vflip=True, hmirror=True, transpose=False)
    # send image to IDE
    self.camera.img.copy(copy_to_fb=True)

while(True):
    clock.tick()
    #
    #img = sensor.snapshot()
    displayTFT()
    # some image processing code goes here...

    
    print(clock.fps())

firmware.zip (1.0 MB)

Try this firmware. When initializing the LCD pass the byte_reverse=True argument.

See: ports/stm32: Add byte reversal support to lcd screen. by kwagyeman · Pull Request #1772 · openmv/openmv · GitHub

The reversal is accomplished in hardware by not telling the SPI pheriperial to process the data as 16-bits but instead as 8-bits. This will result in it reversing the byte order sent to the display.

There should be no performance impact.

You have snapshot commented out. Camera needs to take image first and then send to displayTFT. The code I posted is part of my larger project and will not work directly. You will have to modify. Here is my TFT class:

TftILI9341.py (3.3 KB)