rm-hull / luma.lcd

Python module to drive PCD8544, HT1621, ST7735, ST7567 and UC1701X-based LCDs
https://luma-lcd.readthedocs.io
MIT License
156 stars 56 forks source link

Support for ili9486 #135

Closed mattblovell closed 3 years ago

mattblovell commented 3 years ago

Type of Raspberry Pi

RPi 4 Model B

Linux Kernel version

Linux raspberrypi 5.10.0-rc4-v7l+ #1372 SMP Tue Nov 17 14:26:25 GMT 2020 armv7l GNU/Linux

Description

This is not an issue with luma.lcd. It is instead a request for help in adding ili9486 support.

The display is alive, and luma.lcd can turn it on and off. Those commands and the column, row commands have not changed from the ili9341.

The display is definitely not correct though. The attached images show what's going on for the demo and bounce examples from luma.examples. I was just curious whether you've seen anything resembling that before and had any advice. It's almost like some sort of odd interlacing mode.

You're under absolutely no obligation to help, of course. :) If you don't have any suggestions, I can just close this issue out.

Modification to device.py at this point amount to the following, adapted from the MIT Licensed code at

https://github.com/bkosciow/gfxlcd/blob/master/gfxlcd/driver/ili9486/ili9486.py

        self.command(0x0b, 0x00, 0x00)                      # Read Display MADCTL
        self.command(0x11)                                  # sleep out
        self.command(0x3a, 0x66)                            # Interface Pixel Format
        self.command(0x36, 0x88)                            # Memory Access control (rotations)
        self.command(0xc2, 0x44)                            # Power Control 3 (for normal mode)
        self.command(0xc5, 0x00, 0x00, 0x00, 0x00)          # VCOM control

        self.command(0x0e,                                  # Positive Gamma control
                     0x0f, 0x1f, 0x1c, 0x0c, 0x0f, 0x08, 0x48, 0x98,
                     0x37, 0x0a, 0x13, 0x04, 0x11, 0x0d, 0x00)
        self.command(0xe1,                                  # Negative Gamma control
                     0x0f, 0x32, 0x2e, 0x0b, 0x0d, 0x05, 0x47, 0x75,
                     0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00)
        self.command(0xe2,                                  # Digital Gamma control 1
                     0x0f, 0x32, 0x2e, 0x0b, 0x0d, 0x05, 0x47, 0x75,
                     0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00)
        self.command(0x21)                                  # Display inversion ON for LCD(B)
        self.command(0x11)                                  # sleep out
        self.clear()
        self.show()

I'm just hard-coding rotation at this point. I need another 90-degress of course to get into a landscape orientation, but I thought the existing view captured the problem nicely.

Thanks, Matt

demo

IMG-0702_sm

bounce

IMG-0704_sm

rm-hull commented 3 years ago

Yes this looks familiar and is quite common when tweaking the correct init sequences. Your best bet is to get hold of the datasheet for the device. You're on the right track with the rotation, but there will be another init setting that dictates the way the display memory is laid out (I vaguely recall it is often called something like Remap, MUX or COM mode). There will almost certainly be a section in the datasheet going into quite a bit of detail about how it can be configured. However, usually, for me at least, it is sometimes a game of whack-a-mole until it works 😬

Does the other library code work unaltered with your device?

mattblovell commented 3 years ago

Does the other library code work unaltered with your device?

So far. I've only been using the full_frame update technique. The display on and display off commands do work. The rest of the infrastructure, with suitable updates in luma/lcd/driver.py is perfectly happy to give a display type of ili9486 a try.

Hmm... this comment, in the "blazing fast" C-based driver juj/fbcp-ili9341 is potentially interesting:

https://github.com/juj/fbcp-ili9341/blob/5ff367c0966e933659599ed7e3abd46dead82948/ili9486.h#L19

// On ILI9486 the display bus commands and data are 16 bits rather than the usual 8 bits that most other controllers have.
#define DISPLAY_SPI_BUS_IS_16BITS_WIDE

Because of that define, the initialization sequence in that driver's ili9486.cpp ends up having pre-processor branches that conditionally "expand" the initialization commands. For instance:

https://github.com/juj/fbcp-ili9341/blob/662e8db76ba16d86cf6fd09d85240adc19e62735/ili9486.cpp#L57-L66

Standalone commands, such as controlling display inversion, are not similarly modified (see Lines 51-55 in that file).

If the ili9486 (or Waveshare's implementation of it) indeed uses a 16-bit shift register to receive SPI data and then transfers that on a parallel interface, what accommodation would have to be made within luma.lcd?

I've modified my initialization code to match the 16-bit version shown, but I'm still essentially at the same point I was. The display shows all of the elements of demo.py, but only at half-width, splitting interlaced lines across that half. (If one stares at the "hello", for instance, you can see that different pixels are present in each half's letters. Taken together, they would form the intended character.)

The display I picked up is the 3.5-inch IPS LCD(B) from Waveshare: https://www.waveshare.com/wiki/3.5inch_RPi_LCD_(B)

I'll have to play more tomorrow.

Cheers! Matt

mattblovell commented 3 years ago

// On ILI9486 the display bus commands and data are 16 bits rather than the usual 8 bits that most other controllers have.

Looking at the datasheet for ILI9486 (a version marked preliminary) and comparing it with ILI9341, I'm not sure where the above statement originates. I haven't found much of a difference, thus far, in the discussing of serial transfers for the two controllers.

UPDATE:

Just after I go and make that statement, I run across this (apparently viable) source version of the DT overlay for the Waveshare (B) display:

https://github.com/TobiasVanDyk/RaspberryPi-GPIO-Displays/blob/master/ili9486/Waveshare/waveshare35b-v2.dts (GPL-3.0)

The excerpt below shows 8 for buswidth and 16 for regwidth.

            waveshare35b: waveshare35b@0 {
                compatible = "ilitek,ili9486";
                reg = <0>;
                pinctrl-names = "default";
                pinctrl-0 = <&waveshare35b_pins>;

                spi-max-frequency = <15000000>;
                txbuflen = <32768>;
                rotate = <90>;
                bgr = <0>;
                fps = <30>;
                buswidth = <8>;
                regwidth = <16>;
                reset-gpios = <&gpio 25 1>;
                dc-gpios = <&gpio 24 0>;
                debug = <0>;

                init = <0x10000b0 0x0
                        0x1000011
                        0x20000ff
                        0x1000021
                        0x100003a 0x55
                        0x10000c2 0x33
                        0x10000c5 0x0 0x1e 0x80
                        0x1000036 0x28
                        0x10000b1 0xb0
                        0x10000e0 0x0 0x13 0x18 0x4 0xf 0x6 0x3a 0x56 0x4d 0x3 0xa 0x6 0x30 0x3e 0xf
                        0x10000e1 0x0 0x13 0x18 0x1 0x11 0x6 0x38 0x34 0x4d 0x6 0xd 0xb 0x31 0x37 0xf 
                        0x1000011
                                        0x20000ff
                        0x1000029>;
            };
mattblovell commented 3 years ago

Instead of doggedly treating the display as 480x320, which is what I was doing yesterday, I decided to try letting it be a 320x480 display. Look what resulted!

UPDATE: This is solely with full buffer updates. There's still something broken about trying the diff version.

IMG-0707_sm

mattblovell commented 3 years ago

Rotation (on the command line of the luma.examples) works nicely, too.

IMG-0710_sm

mattblovell commented 3 years ago

The following changes seem to work for display(). There are two aspects of the changes:

Anywhere, here is the working display(), complete with print statement for debug:

        for image, bounding_box in self.framebuffer.redraw(image):
            top, left, bottom, right = self.apply_offsets(bounding_box)
            print("left",left," top",top," right",right," bottom",bottom)

            self.command(0x2a, 0, top >> 8, 0, top & 0xff, 0, (bottom - 1) >> 8, 0, (bottom - 1) & 0xff)     # Set row addr
            self.command(0x2b, 0, left >> 8, 0, left & 0xff, 0, (right - 1) >> 8, 0, (right - 1) & 0xff)     # Set column addr
            self.command(0x2c)                                                                   # Memory write

            self.data(image.tobytes())

The bounce demo is achieving 17.6 fps on this 320x480 display at 48 MHz with the print statement in place (and 18.1 without it). Pushing the SPI frequency much higher results in some artifacts.

That seems pretty good for a display of this size.

mattblovell commented 3 years ago

Here's my application on the (rotated) display. The update speed is perfectly adequate for this usage.

I'll get the code cleaned up for submission.

IMG-0713_sm

mattblovell commented 3 years ago

driver.py modifications submitted as #136

Matthew1471 commented 1 year ago

The following changes seem to work for display(). There are two aspects of the changes:

* There's definitely something to the "Waveshare is using a 16-bit shift register" statement.  Note the zeros between the arguments; these appear to be necessary for this display.

* Switching width and height requires a similar transpose to other measurements.

Anywhere, here is the working display(), complete with print statement for debug:

        for image, bounding_box in self.framebuffer.redraw(image):
            top, left, bottom, right = self.apply_offsets(bounding_box)
            print("left",left," top",top," right",right," bottom",bottom)

            self.command(0x2a, 0, top >> 8, 0, top & 0xff, 0, (bottom - 1) >> 8, 0, (bottom - 1) & 0xff)     # Set row addr
            self.command(0x2b, 0, left >> 8, 0, left & 0xff, 0, (right - 1) >> 8, 0, (right - 1) & 0xff)     # Set column addr
            self.command(0x2c)                                                                   # Memory write

            self.data(image.tobytes())

The bounce demo is achieving 17.6 fps on this 320x480 display at 48 MHz with the print statement in place (and 18.1 without it). Pushing the SPI frequency much higher results in some artifacts.

That seems pretty good for a display of this size.

RE: 16-bit registers, I just found this : https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/tiny/ili9486.c#L38