LCD ILI 9341 graphical memory reading

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello guys,

 

I am using ILI9341, which has well developped libraries, I've combined functions from several of them and added some additional, which are specific for my application.

Since I need both horizontal and vertical scrolling, I use the integrated vertical scroll functionallity (and it works well), but I need to make my own horizontal scroll, since there is no integrated functionallity for it.

 

Here also the things seem to work well, but I encounter a problem with reading the visual memory. I'd appreciate some help if you happen to have an idea what could be wrong. Here is some code:

 

Notes:

ILI9341_CMD_COLUMN_ADDRESS_SET and ILI9341_CMD_PAGE_ADDRESS_SET blocks definitely work, I use the same structure when I SetWindow.

Also LCD_WriteCommand and LCD_Write_DATA work.

LCD_MovePixelsLeft and Right seem to do their job fine. I've marked green the sections, which I have almost no doubt on.

I use window from x = 0 to x = 239 and y from 40 to 279, I copy blocks 8 pixels wide and 240 pixels high.

Also, I intentionally ignore the color LSB - I can have only 3 colors and they have the same LSB - I do this for speed optimization.

 

/********************************************************/
static void LCD_GetWindow(unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1)
{
#ifdef ILI_PARAM_CHECK
    if (x1 >= LCD_MAX_X) x1 = LCD_MAX_X - 1;
    if (x0 >= LCD_MAX_X) x0 = LCD_MAX_X - 1;
    if (y1 >= LCD_MAX_Y) y1 = LCD_MAX_Y - 1;
    if (y0 >= LCD_MAX_Y) y0 = LCD_MAX_Y - 1;
#endif
    LCD_WriteCommand(ILI9341_CMD_COLUMN_ADDRESS_SET);
    LCD_Write_DATA((unsigned char)((x0>>8)&0xff));
    LCD_Write_DATA((unsigned char)(x0&0xff));
    LCD_Write_DATA((unsigned char)((x1>>8)&0xff));
    LCD_Write_DATA((unsigned char)(x1&0xff));
    LCD_WriteCommand(ILI9341_CMD_PAGE_ADDRESS_SET);
    LCD_Write_DATA((unsigned char)((y0>>8)&0xff));
    LCD_Write_DATA((unsigned char)(y0&0xff));
    LCD_Write_DATA((unsigned char)((y1>>8)&0xff));
    LCD_Write_DATA((unsigned char)(y1&0xff));
    LCD_WriteCommand(ILI9341_CMD_MEMORY_READ);
}

/********************************************************/
__inline void LCD_CopyPixelBlock(unsigned int source, unsigned int destination)
{
    unsigned char colorMatrix[8*SCROLL_WINDOW_SIZE]; //8*240 = 1920 bytes, only one byte
    unsigned int matrixPointer;

    /**  read the data  **/
    LCD_GetWindow(source, SCAN_WINDOW_OFFSET_Y, source+7, SCAN_LAST_Y_LINE);
    //prepare for reading
    ILI_CS_CLR();
    ILI_RS_SET(); //data mode
    ILI_MSB_PORT_DIR = 0x00; //make inputs
    ILI_LSB_PORT_DIR = 0x00;
    for (matrixPointer = 0; matrixPointer < (8*SCROLL_WINDOW_SIZE); matrixPointer++)
    {
        ILI_RD_CLR(); //read one byte
        ILI_RD_SET();
        colorMatrix[matrixPointer] = ILI_MSB_PORT_IN; // I aso tried this to be between clr and set
    }

    //make outputs
    ILI_MSB_PORT_DIR = 0xFF;
    ILI_LSB_PORT_DIR = 0xFF;
    ILI_CS_SET(); //needed because of the quickwrite

    /**  write the data  **/
    LCD_SetWindow(destination, SCAN_WINDOW_OFFSET_Y, destination+7, SCAN_LAST_Y_LINE);
    LCD_SetColor(COLOR_YELLOW); //just any with the same LSB
    for (matrixPointer = 0; matrixPointer < (8*SCROLL_WINDOW_SIZE); matrixPointer++)
    {
        ILI_MSB_PORT_OUT = colorMatrix[matrixPointer];
        LCD_QuickWriteSame_DATA();
    }
    ILI_CS_SET(); //needed because of the quickwrite
}

/********************************************************/
void LCD_MovePixelsRight(unsigned char source) //source, destination is always 29
{
    unsigned int realSource = source*8;
    unsigned int destination = 29*8;
    while (realSource < 232) //cheat by overflow; decrement to bottom; the idea is "less than zero"
    {
        LCD_CopyPixelBlock(realSource,destination);
        realSource -= 8; //let it overflow below zero
        destination -= 8;
    }
}
/********************************************************/
void LCD_MovePixelsLeft(unsigned char source) //source, destination is always 0
{
    unsigned int realSource = source*8;
    unsigned int destination = 0;
    while (realSource <= 232) //increment to top
    {
        LCD_CopyPixelBlock(realSource,destination);
        realSource += 8;
        destination += 8;
    }
}

 

This topic has a solution.
Last Edited: Wed. Feb 3, 2016 - 01:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Please use CODE tags or print in black and white.    Your current arrangement is difficult to read.

 

It is simple enough to read the GRAM of an ILI9341.   Just set the "window" and read R-G-B of each pixel into your AVR memory.

Perform your magic.    Then set the "window" and write it back.

 

I experimented once with 8-colour mode.   It seemed a bit pointless when I could use 65k colours.

 

David.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, I editted the initial post.

Do you happen to have the code of your experiment, it is simple, but I'm probably missing something.

I need only the reading part, the rest is fine.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

The code from the MCUFRIEND_kbv library is complicated by the multiple controller support.

 

This is the function from the singl-purpose ILI9341_kbv.cpp

int16_t ILI9341_kbv::readGRAM(int16_t x, int16_t y, uint16_t * block, int16_t w, int16_t h)
{
    uint8_t r, g, b;
	  int16_t n = w * h;    // we are NEVER going to read > 32k pixels at once
    setAddrWindow(x, y, x + w - 1, y + h - 1);
    CS_ACTIVE;
    WriteCmd(ILI9341_CMD_MEMORY_READ);
    CD_DATA;

    // needs 1 dummy read
    r = xchg8(0xFF);
    while (n-- > 0) {
        r = xchg8(0xFF);
        g = xchg8(0xFF);
        b = xchg8(0xFF);
		*block++ = color565(r, g, b);
    }
    CS_IDLE;
    setAddrWindow(0, 0, width() - 1, height() - 1);
    return 0;
}

You can down load MCUFRIEND_kbv library from http://forum.arduino.cc/index.php?topic=366304.msg2524865#msg2524865

Run the examples.  You can see what is possible with hardware and software scrolling.

 

I will be posting a new version shortly.   The code is a lot tidier (and smaller) now.

 

Note that you read the GRAM memory differently when in 8-colour mode.

 

David.

Last Edited: Mon. Feb 1, 2016 - 02:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks, I will test tomorrow and post the scrolling block when ready.

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is the solution:

 

First of all note, that I am not using SPI, so SPI solutions might differ.

The format for writing colors for pixels in the memory (16 bit with one write operation - MSB and LSB port) has nothing to do with the reading format.

The reading format converts each color to 6-bits, aligned to the left (msb).

This happens even though I am using MCU 16 bit formating.

 

Example:

Writing 0xF81F //Magenta done in one operations

will be

Read 0xFC00 0xFCxx //the xx LSB of the second byte is the RED of the next pixel

Two subsequent Magenta bits will be read as

0xFC00 0xFCFC 0x00FC

 

So, 5-6-5 bit colors are converted to 6 bit colors and allocate 8-8-8 bits, left shitfed.

WRITE, 16 bits

RRRR RGGG GGGB BBBB   <- pixel 1

(RRRR RGGG GGGB BBBB) <- pixel 2

=>

READ, 16 bits

RRRR RR00  GGGG GG00  

BBBB BB00  (RRRR RR00  <- pixel 2

GGGG GG00 BBBB BB00)  <- pixel 2

 

Here is the tested but unoptimized block for such reading, I'd recommend using pointers. This is use specific:

 - it reads 8X240 blocks

 - it takes only the first 8 bytes of 16 bit color - RRRR RGGG

    LCD_GetWindow(source, SCAN_WINDOW_OFFSET_Y, source+7, SCAN_LAST_Y_LINE);

    //prepare for reading
    ILI_CS_CLR();
	ILI_RS_SET(); //data mode

    ILI_MSB_PORT_DIR = 0x00; //make inputs
    ILI_LSB_PORT_DIR = 0x00;

    ILI_RD_CLR(); //read dummy
    ILI_RD_SET();

    for (matrixPointer = 0; matrixPointer < (8*SCROLL_WINDOW_SIZE); matrixPointer++) //perform pixel reading - 2 each time
    {
        ILI_RD_CLR(); //read RED and GREEN of first pixel
        ILI_RD_SET();

        colorMatrix[matrixPointer++] = ((ILI_MSB_PORT_IN &0xF8) | (ILI_LSB_PORT_IN >> 5));

        ILI_RD_CLR(); //read BLUE of first pixels and RED of second pixel
        ILI_RD_SET();

        colorMatrix[matrixPointer] = (ILI_LSB_PORT_IN &0xF8);

        ILI_RD_CLR(); //read GREEN and BLUE of second pixel
        ILI_RD_SET();

        colorMatrix[matrixPointer] |= (ILI_MSB_PORT_IN >> 5);
    }

    ILI_CS_SET(); //needed because of the quickread

    ILI_MSB_PORT_DIR = 0xFF; //make outputs
    ILI_LSB_PORT_DIR = 0xFF;

 

I tried different configurations to avoid this 8-bits per color reading by configuring ILI9341_CMD_INTERFACE_CONTROL (0xF6) RIM variable, but it made no difference.

When reading ILI9341_CMD_READ_DISP_PIXEL_FORMAT (0x0C) I get 0x05 which means MCU transfer 16 bit format per pixel, but it is still encoded in 24 bits, so I have 3 read operations for 2 pixels and some convertions of the colors, so that I can copy them.

 

The solution works, though, slow as it is. If anyone knows how to get 16 bit per pixel read, that would be great ;)

 

Last Edited: Wed. Feb 3, 2016 - 01:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Yes,   it is a feature of the ILI9341 that you can writeGRAM in different formats e.g. 16-bits or 18-bits

You always readGRAM as 6-6-6 i.e. three 8-bit reads.     The same applies for ST7789V, ILI9488

 

If you want to do 16-bit reads,   you need to choose a different controller.   e.g. the mystery ILI9329

 

Yes,   I pasted the readGRAM() method from a SPI ILI9341.   However,  it is the same code for a parallel bus ILI9341.   I only have 8-bit write-read controllers.    8-bit reads are no penalty.   i.e. 3 read-strobes.

I am fairly certain that reading GRAM via the 16-bit bus is the same.  i.e. 3 read-strobes.

 

A controller like ILI9327, ILI9481, ILI9486, ... can read 5-6-5 in 16-bits.    I think that you can read all 16-bits with a single read-strobe.

Untested.    I have no readable 16-bit bus.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks again, I will research those when there is time for optimization.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've recently attempted to read the screen memory for the purposes of dumping the data to a file.  However, I discovered that instead of 1 dummy byte, I must actually send 3 dummy bytes (24 clocks) following the sending of the command (0x2E).  This contradicts what everyone else is doing as well as the datasheet.  Does anyone have any idea why I am having this issue?

 

BTW - My setup is the typical 4 wire SPI/16 bit pixel mode.

 

Any help would be appreciated. 

Jim Flanagan

Largo, Florida

Last Edited: Thu. Dec 22, 2016 - 12:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, you always need a dummy read. Followed by 3 8-bit reads per pixel. Even if you are using a 2 writes per pixel mode e.g 565.
.
It is far more efficent to read a block of pixels. e.g. set a window to the size of the block, read all the pixels.
.
David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi David -

Thanks for responding.

 

Where my confusion lies, however, is the length of the dummy read.  Is it a 1 byte dummy read or a 3 byte dummy read? 

When I use a 1 byte dummy (per the datasheet), the pixel data is corrupt.  If I use a 3 byte dummy read, the subsequent pixel data is fine.

 

Yes, I perform my screen dump as you suggest:

  • Set Address to 0,0
  • Send the Read command (0x2e)
  • Perform a 3 byte dummy Read  (datasheet says only 1 byte required)
  • Read and Loop to read back the entire screen's pixel data.

 

Anyone understand my discrepancy?

 

Jim

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

Shortly after writing my post, the answer dawned on me.

 

I don't use one of the traditional SPI ports but use one of the USART ports setup in the Master SPI mode. 

The reason is to take advantage of the 2 byte buffered TX/RX registers in order to efficiently write screen information. 

What I am experiencing is the fact that I need to clear the buffers before actually reading valid Pixel data, thus the need for extra dummy bytes.

 

Jim

Last Edited: Thu. Dec 22, 2016 - 03:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is the method that I have used:

int16_t ILI9341_kbv::readGRAM(int16_t x, int16_t y, uint16_t * block, int16_t w, int16_t h)
{
    uint8_t r, g, b;
	  int16_t n = w * h;    // we are NEVER going to read > 32k pixels at once
    setAddrWindow(x, y, x + w - 1, y + h - 1);
    CS_ACTIVE;
    WriteCmd(ILI9341_CMD_MEMORY_READ);
    CD_DATA;

    // needs 1 dummy read
    r = xchg8(0xFF);
    while (n-- > 0) {
        r = xchg8(0xFF);
        g = xchg8(0xFF);
        b = xchg8(0xFF);
		*block++ = color565(r, g, b);
    }
    CS_IDLE;
    setAddrWindow(0, 0, width() - 1, height() - 1);
    return 0;
}

If you are using a write8() without keeping up with the corresponding read8(),   you will get out of step.

Simply flush the read buffer and you will get into sync again.

 

Yes,  you get a massive increase in performance from USART_MSPI.   Even better with an Xmega DMA.

 

You can see that you can read whatever size block that you want.   i.e. a W x H rectangle.

In practice,   you will use smallish chunks to save to an SD card.   Unless you have a massive external RAM e.g. with an Xmega.

 

David.

Last Edited: Fri. Dec 23, 2016 - 04:55 PM