espressif / esp-iot-solution

Espressif IoT Library. IoT Device Drivers, Documentations and Solutions.
Apache License 2.0
1.96k stars 780 forks source link

Unable to use I2S LCD mode with 8-bit parallel #19

Closed classic-gentleman closed 5 years ago

classic-gentleman commented 6 years ago

I have ported over an ILI9488 display with an init sequence that's known to work on other libraries. However I'm noticing the code expects 16 bit commands/registers, and there are no provisions on the code to check for 8 bit data paths for registers. In fact it seems that the 8 bit defines are just plain wrong for the combination of displays with 8 bit commands and 8 bit bus (https://github.com/espressif/esp-iot-solution/blob/db36832d90b0077bc7fa5a455b789497f8a12911/components/i2s_devices/lcd_common/i2s_lcd_com.c#L36 is pretty telling in this regard).

When connected via a 16 bit bus the top byte/8 lines are ignored, but as far as I can tell on 8 bits bus the ILI commands are seemingly being cancelled by NOPs when the bytes are swapped (high byte is 0x0). I unfortunately can't follow up on this suspicion right now as I don't have a logic analyser handy. I do know that the connections are right and I do know that bit banging the GPIOs as a bus works. I suspect there could be something weird with the WS clock but first I'd like to make sure that my reading of i2s_lcd_com makes any sense...

For kicks, I also attempted to port over ILI9341 as 8-bit parallel I2S; everything is royally garbled there but I can see some thin strips with parts of what the original image is supposed to be. So I'm still hoping it's just misconfigured somehow...

InfiniteYuan commented 5 years ago

Can you describe your development environment and hardware environment in detail?

InfiniteYuan commented 5 years ago

You can refer to issue

classic-gentleman commented 5 years ago

Environment

Other stuff: ILI9341 module: generic ILI9341 "mcufriend" Arduino shield which looks just like this:

pzapgiml

(Mods: 5V pin left floating, 3.3VCC pin jump-wired to regulator output for bypassing, CS held to ground, RD held to VCC)

ILI9488 module: ER-TFTM035-6 from EastRising (see picture below). It's jumpered for 8-bit 8080 parallel, and has a capacitive touch screen which I'm not using at the moment (rather getting the display right first).

JTAG interface: USB Blaster V4.1

I also used a test fixture with a bare ESP32-WROOM-32 module for testing. Results were identical.

Problem Description

I'm unable to drive a LCD using an 8-bit 8080 bus connection. The problem seems to be a combination of code expecting 16-bit words (https://github.com/espressif/esp-iot-solution/blob/db36832d90b0077bc7fa5a455b789497f8a12911/components/i2s_devices/lcd_common/i2s_lcd_com.c#L36 for example, which assumes 16-bit words and even byte-swaps them) and the I2S peripheral setup which is never configured for a given bus width (see https://github.com/pixelmatix/SmartMatrix/blob/teensylc/src/esp32_i2s_parallel.c#L191 and surrounding code conditional on I2S_PARALLEL_BITS_*), let alone a configurable clock to account for a particular displays' timing tolerances.

Expected Behavior

Except for register mapping and init sequence, driving the display with I2S should have worked out of the box.

Actual Behavior

Image remains blank (white). After fiddling a lot with the I2S peripheral parameters I coerced the display to show barely discernible parts of the original image. I didn't take a photo of it though, I might be able to on Monday. I never managed to get I2S LCD working, not even basing the peripheral setup code on SmartMatrix's. I've ordered a logical analyser to at least stand a chance in helping you guys debug this issue; not really sure when it will arrive.

Steps to reproduce

Wiring as follows:

TFT ESP32
LCD_RST EN
LCD_RD 3V3
LCD_CS GND
LCD_WR GPIO_22
LCD_RS GPIO_23
LCD_D0 GPIO_25
LCD_D1 GPIO_26
LCD_D2 GPIO_27
LCD_D3 GPIO_2
LCD_D4 GPIO_4
LCD_D5 GPIO_5
LCD_D6 GPIO_18
LCD_D7 GPIO_19

I wrote a GPIO bitbanging backend (which mirrors the structure of components/i2s_devices/lcd_common/i2s_lcd[.c|_com.c] but uses GPIO.out_w1t[s|c] and NOPs (asm volatile ("nop")) sprinkled here and there to force some delays) with which I get output from the example project just fine:

img_5889

So it's reasonable to assume the hardware is not at fault, nor is the wiring. Bitbanged mode works with both the ILI9488 module and the ILI9341 module.

Code to reproduce this issue

N/A. ILI9488 or ILI9341 as I2S drivers are reasonably large set of changes to paste inline, and the bitbanged GPIO versions are trivial.

int bb_lcd_write_data(bb_lcd_handle_t bb_lcd, const char *src, size_t size, TickType_t ticks_to_wait, bool swap);
void bb_lcd_write_smalldata(bb_lcd_handle_t bb_lcd_handle, uint8_t smalldata);
void bb_lcd_write_cmd(bb_lcd_handle_t bb_lcd_handle, uint8_t cmd);

But the meaty parts are direct duplicates of https://github.com/espressif/esp-iot-solution/tree/master/components/i2s_devices/ili9806 and changes are mostly related to the init sequence.

Debug Logs

N/A

Other items if possible

N/A

koobest commented 5 years ago

Hi, @classic-gentleman In order to adapt to the screen driverd by NT35510, we provide the API to send a command in this way, because the command of a this chip is two bytes. but ILI9488 is 1byte command, so, you can try the following API to check if you can make your screen works.

void iot_i2s_lcd_write_cmd(i2s_lcd_handle_t i2s_lcd_handle, uint16_t cmd)
{
    i2s_lcd_t *i2s_lcd = (i2s_lcd_t *)i2s_lcd_handle;
    i2s_port_t i2s_num = i2s_lcd->i2s_port;
    GPIO.out_w1tc = (1 << i2s_lcd->i2s_lcd_conf.rs_io_num);
    REG_WRITE(I2S_FIFO_ADD[i2s_num], cmd&0xff);
    I2S[i2s_num]->conf.tx_start = 1;
    while (!(I2S[i2s_num]->state.tx_idle)) {
        // vTaskDelay(20);
        ;
    }
    I2S[i2s_num]->conf.tx_start = 0;
    I2S[i2s_num]->conf.tx_reset = 1;
    I2S[i2s_num]->conf.tx_reset = 0;
    I2S[i2s_num]->conf.tx_fifo_reset = 1;
    I2S[i2s_num]->conf.tx_fifo_reset = 0;
    GPIO.out_w1ts = (1 << i2s_lcd->i2s_lcd_conf.rs_io_num);
}

we will update the driver to adapt to other command formats. thanks!!

anuprao commented 5 years ago

@koobest ,

Can you kindly describe the settings to achieve DMA of 8bit data using I2S ? I believe that is what @classic-gentleman is looking for. And yes, me too !

Thank you for spending your time to help us.

classic-gentleman commented 5 years ago

@anuprao, thats quite correct.

@InfiniteYuan1, @koobest: this is actually the first modification I tried.

It's interesting to note that using code almost identical to what you wrote above, and adapting for data as well, and looping that piece of code instead of using the DMA engine for large/framebuffer contents, makes the display work. Excruciatingly slow, but actually produces noncorrupt images.

So I've narrowed things down to the DMA engine. I'm trial-and-erroring the clock parameters to almost no avail and don't get whether the data format is the grander issue here. I'll send pictures later on to see if you recognise the glitch and an eventual sulution.

Also, would you be so kind to take a look at https://www.esp32.com/viewtopic.php?f=12&t=7955 ? I already had a slightly better understanding of the issue when I wrote that topic.

koobest commented 5 years ago

HI,@classic-gentleman Do you mean that void iot_i2s_lcd_write(i2s_lcd_handle_t i2s_lcd_handle, uint16_t *data, uint32_t len) works incorrectly? if yes, we will check it. this API is using DMA engine, it should work in 8-bit mode, but you should configure it in menuconfig. Considering that DMA is inefficient when sending several bytes, we add fifo operation(void iot_i2s_lcd_write_cmd).

Note that DMA cannot work in 8-byte mode when configured in LCD mode, so, we also use 16-byte mode when sending 8-byte mode of data, but before sending data, we convert data from 8 -byte mode to 16-byte mode.

classic-gentleman commented 5 years ago

Hi @koobest,

You got it right, the DMA part fails to work as expected when the connection is 8-bit 8080. Sure enough I configured it as 8-bit on make menuconfig and sure, the DMA setup (linked list, register config) is a bit expensive to send small amounts of data. Not to mention that it's virtually impossible to control the RS line with DMA (unless doing mad crazy stuff like the good people driving 4 bit displays with H-sync/V-sync encoded with the data bytes).

So I was right in my suspicion that the DMA engine isn't able to work with 8-bit packets. That's no big deal as long as the FIFO mode is able to work with arbitrary data lengths. For frame buffer transfers it's even adequate. I'm wondering however if other peripherals use or tolerate odd-sized transfers of large data streams (possibly zero-padded to an even length).

The thing is, the super useful thing about DMA is freeing the CPU for either being able to perform extra work or doing no work at all; in both cases we gain energy efficiency. Having to perform the byte-shuffling work described by Pixelhaze on the ESP32 forums (and likewise on https://github.com/pixelmatix/SmartMatrix/blob/teensylc/src/SmartMatrixMultiplexedCalcEsp32_Impl.h#L488) is a huge waste of CPU cycles and corresponding energy. For battery based applications this is so undesirable I'm really considering using an extra hardware buffer to send the data to the peripheral (I'm abstracting the fact it's a display here) in the desired order if I ever find out it's more energy efficient to do so.

classic-gentleman commented 5 years ago

As promised, here's what goes on when enabling the DMA engine; images from the HMI sample code. This is the ILI9488 module. As mentioned, it's possible to make out some lines that are not corrupted, which makes me suspect the timing configuration a lot. Or the order the bytes are sent. You may notice I have transferred the circuit from breadboard to PCB with consistent trace lengths to rule out extreme clocking artefacts (had to take that step anyway, so why not).

First tab (gauge, with leftover lines from second tab - graph): First tab of HMI sample code

Second tab (graph): Second tab of HMI sample code

Third tab (buttons, with leftover lines from first and second tabs): Third tab of HMI sample code

I did mess a little bit to see if changing the physical concept of width and height and corresponding rotations – as my application is landscape, so it made sense the logical rotation is 0 too – made any difference to the end result, and it's not really discernible. Pretty much the same lines are affected as far as I can say. Kind of expected given that rotations are handled by hardware.

koobest commented 5 years ago

Hi, @classic-gentleman Can you please refer to the code in the attached file to see if you can refresh your screen, but you have to replace the initialization part of the code with yours because I only have the screen drivered by ILI9806, this example can make my screen work in 8 bit mode, and all data transfers use the DMA engine.

lcd-8bit.tar.gz

koobest commented 5 years ago

Hi, @classic-gentleman Can the attached code make your screen work? I want to know if this is a DMA issue.

classic-gentleman commented 5 years ago

Hi,

I'm trying to get the logic analyser going, because so far your code made no difference. I didn't run your code directly, rather tracked the differences between it and the code on esp-iot-solution. I found that the differences are minimal, mainly comprising the change of DMA transfer size, undoing the byte swapping loop (would that work with a straight memcpy? Tried here and no dice) and one register clear of I2S[i2s_num]->fifo_conf.dscr_en missing at the end of i2s_lcd_write_data.

Regarding DMA: changing it's transfer size does produce an effect on the artefacts patterns. Still not sure what this means since I can't even fetch the init sequence correctly out of the dump.

The analyser I managed to get a hand on so far is a Saleae Logic 8ch 24MHz clone, and I found myself needing to sacrifice a channel for clock, else it all looks like a royal jumble as the I2S peripheral seemingly drives all data lines low as the hold time expires (or it begins sending the other byte which is never set?). Is it even possible to control the hold times? When I run the analyser on the GPIO-bitbanged version I can clearly see everything happening as it should, modulo missing D0 which I left unprobed in favour of /WR...

On the I2S version I see the clock period going way faster than my target clock, which should be closer to 10MHz. I'm trying to lower the clock to the vicinity of 4~8MHz but something seems off as I can't make out a single command out of it on the analyser. I see bytes for sure, but none remotely resembling the init sequence (unlike the bitbanged version).

me-no-dev commented 5 years ago

Hi guys :) I have this exact display running with parallel I2S, but I have done a lot of rework on how the driver handles data. We will have this "new" driver coming in IDF4.0. Issue with current driver is in how buffers are switched and what happens with old buffer data (gets replayed in this case).

classic-gentleman commented 5 years ago

Hi guys :) I have this exact display running with parallel I2S, but I have done a lot of rework on how the driver handles data. We will have this "new" driver coming in IDF4.0. Issue with current driver is in how buffers are switched and what happens with old buffer data (gets replayed in this case).

This is superb news! I'm afraid I can't wait for IDF 4.0 though; may I sneak a peek at your work?

classic-gentleman commented 5 years ago

@koobest,

Running your code as a stand-alone project works, at least I can cleanly see your init sequence on the logic analyser. I'm pasting over the init sequence of ILI9488 in a few moments and will get back to you asap, but it will take me a moment to undo the probes and whatnot.

me-no-dev commented 5 years ago

@classic-gentleman code is really rough, not thread safe, not many many things that it should/will be :) I have to test agains this version that is fifo based and see which one is faster. Reality is that with parallel mode, buffer ISR fires really often and all the content switching that is required for the event to get to you, might end up taking more time than fifo. That is definitely the case with SPI LCDs. I can do 60FPS SNES with optimised register calls for display fill. You can never do that with DMA... Also, DMA requires RAM that you might otherwise use for other things.

classic-gentleman commented 5 years ago

Holy Cow™.

Turns out that the main issue I was having was mixing up FIFO and DMA code. The other half of the story was the clock/WS frequency indeed. Scrap that. The logic analyser made a world of difference in understanding this issue, and 98% of it was related to clocking.

"I have no idea what I'm doing" dog

Finally understood you're filling the 32-bit register with 8-bit data, hence the apparently wasteful loop. From some trial and error and a ton of jotted down stuff I believe that fiddling with the registers and settings would yield a better buffer usage.

@me-no-dev: My use case is more energy-sensitive as I'm running from batteries, and considering what I'm seeing I'm positive you're right, the app is being stormed by interrupts. About RAM, it may or may not be a problem, I'm already willing to buy the SPIRAM module if it turns out necessary... Wasting power definitely is a problem, though.

Sidenote: You guys are doing a wonderful job with this SOC. I'm impressed how versatile the ESP32 is. For a future product (ESP64, perhaps?) I'd love to see something closer to NXP's FlexIO, however. Better yet, an easy way to flush a buffer by DMA and have that translated to GPIO pins in some arbitrary fashion. 💘

classic-gentleman commented 5 years ago

Hi all,

I'm thinking of doing a write-up of the success tomorrow so we close this ticket. However two questions spring to mind:

1) sprite_tm mentioned here this configuration:

1.you should use i2s1 module not i2s0
2.the output data bits are I2S1O_DATA_OUT[7:0], (only 8bits mode is bit7~0,other mode not change )
3. as I said, the order of 8-bit bytes per 32-bit word may be weird
4.set I2S_TX_FIFO_MOD_FORCE_EN =1
tx_chan_mod = 1;
tx_fifo_mod = 1;
I2S_LCD_TX_WRX2_EN_S =1;
I2S_LCD_TX_SDX2_EN_S =0;

I didn't have any success with those settings, but I did see familiar stuff on the logic analyser, usually with padded zeroes. So maybe I was getting a bit closer to my goal (see below)?

2) using a buffer comprised of 32-bit words and only filling 8 bits among those is really wasteful. Is there really no way to make a better use of those? Maybe 50% usage instead of 25%? Of course 100% is ideal even if it demands byte shuffling. Is it possible to change those settings on the fly? @me-no-dev I'm pretty interested in your FIFO code, even if you consider it "running with scissors" quality. 😋

classic-gentleman commented 5 years ago

3) Why isn't a linked list of DMA descriptors being built, like on the SPI Master driver? I'm attempting something here but the way the buffers are allocated is throwing me off... I suspect it has to do with the necessarily long word size of I2S, but I'm wondering whether the interrupts storm would be somewhat mitigated by a longer DMA chain. Right now we're EOFing on every transfer...

classic-gentleman commented 5 years ago

Answering myself:

1) sprite's settings are indeed correct for data index 0 and device 1. 2) It seems to be possible to switch settings between transfers. So it's indeed feasible to get better data density depending on the transfer length. 3) Jury's still out on this one...

classic-gentleman commented 5 years ago

Hello! Here's what I have found so far:

0) Constants:

classic-gentleman commented 5 years ago

Oh, forgot to mention that clkm_div_num set to 3 or 4 works, as long as the linked list isn't used. And that I'm yet to find a mix of parameters that allow me to use MODE_PACKED and FIFO on data_idx = I2S1O_DATA_OUT0_IDX...

classic-gentleman commented 5 years ago

Closing this as we got as far as the hardware allows here. Ideally there should be a way to control the order of the bytes output, perhaps with the aid of a hardware permutation instruction, but I understand the parallel I2S bus wasn't really made for this purpose.

classic-gentleman commented 5 years ago

Evening,

The remaining elephant in the room is how to drive the bus bidirectionally. Right now we can do either writes or reads; @koobest / @me-no-dev, is there a way to use the I2S peripheral for the general purpose of a bidirectional parallel bus? Not sure whether IDF or here is the right place to open an issue about this (and if we go by https://github.com/espressif/esp-idf/issues/1056 one would assume it's not really possible, but maybe somebody has some trick for that?)

talsaiag commented 4 years ago

@classic-gentleman have you managed to drive a ILI9488 display? mind sharing the diff? thanks

nopnop2002 commented 3 years ago

@talsaiag

Try this.

https://github.com/nopnop2002/esp-idf-parallel-tft