Products > Embedded Computing

ILI9341 problem with incomplete number of pixels being displayed

(1/3) > >>

HwAoRrDk:
I'm experimenting with a TFT LCD driven by an ILI9341 controller that I have hooked up to an ESP32 via SPI. I've written some code to do something pretty basic, which is to draw a bunch of randomly sized and coloured rectangles on to the screen. But, I am encountering an odd problem, and I can't see where or what I've done wrong.

The problem is that when a filled rectangle is drawn, the last (i.e. bottom) row of pixels is always incomplete, and doesn't run the full width of the rectangle. The amount varies - sometimes a few are not drawn, sometimes almost all. Here's an artist's illustration of what I'm talking about: ^-^



I can't figure out why this is occurring. I've double-checked my code to make sure that I'm doing things correctly. I've even gone to the extent of breaking out the logic analyser to capture the SPI traffic so that I can confirm the ILI9341 is actually receiving the data I think I'm sending it. From this I have verified that the missing pixels aren't because not enough are being sent - I checked the number of bits being sent per rectangle (by counting the rising edges on SCK in the capture), and they are what they should be. For example, a rectangle with width and height of 90x10 has 900 pixels at 16bpp, so 14,400 bits should be transmitted, which the logic analyser capture does show. The pixel colour values are correct all the way through too, plus column and row (a.k.a. 'page') start and end addresses are being sent correctly.

Here's my code:


--- Code: ---void lcd_fill_colour(const uint16_t x1, const uint16_t y1, const uint16_t x2, const uint16_t y2, const uint16_t colour) {
uint16_t pixel_buf[256]; // Buffer size must be a multiple of 4 for DMA
uint16_t col_addr_params[2], page_addr_params[2];
uint16_t width, height;
size_t pixel_count, buffer_count;

if(x1 <= x2) {
col_addr_params[0] = __builtin_bswap16(x1);
col_addr_params[1] = __builtin_bswap16(x2);
width = x2 - x1;
} else {
col_addr_params[0] = __builtin_bswap16(x2);
col_addr_params[1] = __builtin_bswap16(x1);
width = x1 - x2;
}
if(y1 <= y2) {
page_addr_params[0] = __builtin_bswap16(y1);
page_addr_params[1] = __builtin_bswap16(y2);
height = y2 - y1;
} else {
page_addr_params[0] = __builtin_bswap16(y2);
page_addr_params[1] = __builtin_bswap16(y1);
height = y1 - y2;
}

if(width == 0 || height == 0) return;

for(size_t i = 0; i < (sizeof(pixel_buf) / sizeof(pixel_buf[0])); i++) {
pixel_buf[i] = __builtin_bswap16(colour);
}

pixel_count = width * height;

spi_device_acquire_bus(lcd_spi_dev, portMAX_DELAY);

ili9341_write_command(lcd_spi_dev, ILI9341_COL_ADDR_SET);
ili9341_write_data(lcd_spi_dev, col_addr_params, sizeof(col_addr_params));
ili9341_write_command(lcd_spi_dev, ILI9341_PAGE_ADDR_SET);
ili9341_write_data(lcd_spi_dev, page_addr_params, sizeof(page_addr_params));
ili9341_write_command(lcd_spi_dev, ILI9341_MEM_WRITE);

while(pixel_count > 0) {
buffer_count = (pixel_count >= (sizeof(pixel_buf) / sizeof(pixel_buf[0])) ? (sizeof(pixel_buf) / sizeof(pixel_buf[0])) : pixel_count);
ili9341_write_data(lcd_spi_dev, pixel_buf, buffer_count * sizeof(pixel_buf[0]));
pixel_count -= buffer_count;
}

spi_device_release_bus(lcd_spi_dev);
}

esp_err_t ili9341_write_command(const spi_device_handle_t dev, const uint8_t cmd) {
esp_err_t err = ESP_FAIL;
spi_transaction_t t;

memset(&t, 0, sizeof(t));
t.flags = SPI_TRANS_USE_TXDATA;
t.user = (void *)0; // Set D/CX low, indicating command
t.length = 8 * sizeof(cmd);
t.tx_data[0] = cmd;
err = spi_device_polling_transmit(dev, &t);

return err;
}

esp_err_t ili9341_write_data(const spi_device_handle_t dev, const void *data, const size_t data_len) {
esp_err_t err = ESP_FAIL;
spi_transaction_t t;

if(data_len > 0) {
memset(&t, 0, sizeof(t));
t.user = (void *)1; // Set D/CX high, indicating data
t.length = 8 * data_len;
t.tx_buffer = data;
err = spi_device_polling_transmit(dev, &t);
} else {
err = ESP_OK;
}

return err;
}

// Callback function registered to be called just before SPI transaction starts.
void lcd_spi_pre_transfer_callback(spi_transaction_t *t) {
const uint32_t dc = (uint32_t)t->user;
gpio_set_level((gpio_num_t)LCD_SPI_DCX_IO, dc);
}

--- End code ---

One thing I thought of trying to do is read back the frame memory from the ILI9341 and see what it's contents is (i.e. does it match what I wrote), but I can't do that because this damn LCD screen seems to be configured either with the SPI MISO/SDO line not connected, or possibly running in half-duplex mode (so it's trying to output data back on the MOSI/SDA line). |O I can't even read out the device ID, it's just all zeros.

SiliconWizard:
One thing I noted is that: for this controller, both the start column and end column parameters (same for row address) are inclusive, IIRC, but your code does as though the end parameter was not inclusive, which can be seen when you do this, for instance: "width = x2 - x1;".

That should be "width = x2 - x1 + 1;". Same for the other combinations and for the row address. Check this out and report back.

T3sl4co1l:
Yes, that sounds likely.

Diagnostics:
- See if the left and right edges line up.  Draw a rect at say (10, 10)-(20, 20) and another at (20, 13)-(30, 17).  Does it overlap?  Is there a gap?  Do the same for vertical alignments, and around screen edges.

- See if the number of pixels remaining (on the bottom row) is the one-off difference to the width.  That is, a 10x10 rect needs 100 pixels, but drawn at 11 width, 100 pixels only fills 9 rows, coming up exactly one pixel short on the 8th row.  Or at 9 width, one pixel more than 11 rows (just beginning a 12th).  Note that if overdraw goes into vertical overflow, it probably wraps to the start.

I haven't looked at ILI9341 exactly, but I've done a ILI9325 before, and actually have a ST7735 in front of me.  The latter are... moderately similar, IIRC?  Anyway, the ST does boundary-inclusive, so for example my drawFillRectangle() has a


--- Code: ---setScreenRegion(xStart, yStart, xStart + width - 1, yStart + height - 1);
--- End code ---

in it.  (Or use x1, y1, x2, y2 instead of width, height if you like.)

Tim

HwAoRrDk:

--- Quote from: SiliconWizard on December 05, 2021, 11:10:20 pm ---One thing I noted is that: for this controller, both the start column and end column parameters (same for row address) are inclusive, IIRC, but your code does as though the end parameter was not inclusive, which can be seen when you do this, for instance: "width = x2 - x1;".

That should be "width = x2 - x1 + 1;". Same for the other combinations and for the row address. Check this out and report back.

--- End quote ---

Aaaah, that was the problem. What an elementary mistake to make! :palm: :)

Yes, checking the datasheet again, the col/page end addresses do indeed appear to be inclusive.

You know how the saying goes: the two hardest problems in computing are concurrency, naming things, and off-by-one errors. ;D

HwAoRrDk:
Now, if I can just figure out how to be able to read data out of the ILI9341...

I thought maybe this LCD was configured for '4-line Serial Interface I', where there is a single SDA line that is half-duplex, because when issuing an 'Read ID' command (0x04) I was getting some data on the MOSI line. But it seems this is just junk from uninitialised memory that the ESP32 is clocking out, because the data bytes change on a power cycle.

Is there any way of checking what interface mode the ILI9341 is using? I understand from the datasheet that it is set by the IM[3:0] pins. But of course the chip is internal to the LCD, so I can't check their state. However, I notice there are some passive components on the flat flex. Might these be what sets IM? Edit: no, they're all just capacitors.

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version