Esp32 second 8 bit interface display

If I understood right, color map array is divided by splitting x, y coordinates.

setting the displays up so one is on top in a vertical arrangement is going to be far easier to do because you can simply split the buffer instead of having to iterate over it and moving bytes into 2 new buffers…

The buffers are single dimension arrays so think of the data as being all on a single line.

as an example

say you have 3 rows of 10 pixels in each row. each pixel contains 2 bytes of color data.

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

The data is not arranged in the above kind of a fashion and instead is arranged like this

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

when you have the displays side by side as say you need to have 5 pixels written to the left display and 5 pixels written to the right display view it like this.

255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

the | is where the data needs to be split to send to the right and to the left.

Now when you look at it like this

255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

You cannot simply β€œcut” the buffer. You have to separate the data into 2 new buffers in order to do it.

where as if they are arranged vertically and say you have to write 15 pixels to the top display and 15 pixels to the bottom display.

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255| 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

you literally split the buffer, no need to iterate over it. It becomes a simple math problem. The area that is being updated gets passed to the flush callback by LVGL. This is an lv_area_t structure and that structure contains the upper left corner and the lower right corner of the area being changed.

if (area.y1 < top_display_height) {
    if (area.y2 < top_display_height) {
       // send entire buffer to top display
    } else {
        // buffer needs to be split
        uint16_t top_display_pixel_count = (area.x2 - area.x1 + 1) * (top_display_height - area.y1 + 1);
        uint16_t bottom_display_pixel_count = (area.x2 - area.x1 + 1) * (area.y2 - top_display_height + 1);

        uint16_t top_display_buf_size = top_display_pixel_count * sizeof(lv_color_t);
        uint16_t bottom_display_buf_size = bottom_display_pixel_count * sizeof(lv_color_t);

       // set the top display to be written to using
       // area.x1, area.y1
       // area.x2, top_display_height
       // and the bottom display using
       // area.x1, 0
       // area.x2, area.y2 - top_display_height

       // when writing the data there is normally a function you call to do that. 
       // pass the following to the function for the top display
       // top_display_buf_size, buf
       // and for the bottom display
       // bottom_display_buf_size, buf + top_display_buf_size 
    }
} else {
   // send entire buffer to bottom display
}

the above is pseudo code and is not working. It is for example only.

Another try to explain :wink:

  d1 is left display pixel data
  d2 is right display pixel data

  a00 to a27 is display/pixel data generated by lvgl (color_map).
  So a00 to a02 belongs to the left display
  and a03 to a06 belongs to the right display (just for one display line)...

  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  a00 a01 a02 a03 a04 a05 a06 d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  a07 a08 a09 a10 a11 a12 a13 d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  a14 a15 a16 a17 a18 a19 a20 d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  a21 a22 a23 a24 a25 a26 a27 d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d1  d1  d1  d1  d1  d1  d1  d1  d2  d2  d2  d2  d2  d2  d2  d2  d2

What Marian_M want’s to say, splitting would be easier in the following case.

  d1 is top display pixel data
  d2 is bottom display pixel data

  a00 to a27 is display/pixel data generated by lvgl (color_map array)
  a00 to a06 belongs to the top display
  and a07 to a27 belongs to the bottom display (only one split necessary).

  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1
  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1
  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1  d1
  d1  d1  d1  d1  d1  a00 a01 a02 a03 a04 a05 a06 d1  d1  d1  d1  d1
  d2  d2  d2  d2  d2  a07 a08 a09 a10 a11 a12 a13 d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  a14 a15 a16 a17 a18 a19 a20 d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  a21 a22 a23 a24 a25 a26 a27 d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2
  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2  d2
1 Like

Here is a flush function written in Python. This is for writing to a single display.

    def flush(self, disp_drv, area, color_p):
        # ============ tells display where to write the buffer to ================

        # Column addresses
        self.send_cmd(0x2A)

        self.word_trans_data[0] = (area.x1 >> 8) & 0xFF
        self.word_trans_data[1] = area.x1 & 0xFF
        self.word_trans_data[2] = (area.x2 >> 8) & 0xFF
        self.word_trans_data[3] = area.x2 & 0xFF
        self.send_trans_word()

        # Page addresses

        self.send_cmd(0x2B)

        self.word_trans_data[0] = (area.y1 >> 8) & 0xFF
        self.word_trans_data[1] = area.y1 & 0xFF
        self.word_trans_data[2] = (area.y2 >> 8) & 0xFF
        self.word_trans_data[3] = area.y2 & 0xFF
        self.send_trans_word()

        # ==========================================

        # ======== writing the data to the display ============

        self.send_cmd(0x2C)

        size = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1)

        # gets a pointer to the buffer 
        data_view = color_p.__dereference__(size * lv.color_t.__SIZE__)

        self.send_data_dma(data_view)
       # ===========================================

and adapted to write to 2 displays arranged vertically

def flush(self, disp_drv, area, color_p):
    # lets say the displays are 128 x 128 in size
    display_width = 128
    display_height = 128
    
    x1 = area.x1
    y1 = area.y1
    x2 = area.x2
    y2 = area.y2
    
    if y2 <= display_height:
        # all data is going to fit on the top display so write 
        # the entire buffer to the top display

        # ============ tells display where to write the buffer to ================

        # Column addresses
        self.send_cmd_top(0x2A)
    
        self.word_trans_data_top[0] = (x1 >> 8) & 0xFF
        self.word_trans_data_top[1] = x1 & 0xFF
        self.word_trans_data_top[2] = (x2 >> 8) & 0xFF
        self.word_trans_data_top[3] = x2 & 0xFF
        self.send_trans_word_top()
    
        # Page addresses
    
        self.send_cmd_top(0x2B)
    
        self.word_trans_data_top[0] = (y1 >> 8) & 0xFF
        self.word_trans_data_top[1] = y1 & 0xFF
        self.word_trans_data_top[2] = (y2 >> 8) & 0xFF
        self.word_trans_data_top[3] = y2 & 0xFF
        self.send_trans_word_top()
    
        # ==========================================
    
        # ======== writing the data to the display ============
    
        self.send_cmd_top(0x2C)
    
        size = (x2 - x1 + 1) * (y2 - y1 + 1)
    
        # gets a pointer to the buffer 
        data_view = color_p.__dereference__(size * lv.color_t.__SIZE__)
    
        self.send_data_dma_top(data_view)
        # ===========================================
    elif y2 > display_height:
        # all data is going to go to the bottom display
        
        # we have to modify the y1 and y2 to reposition where the data
        # is going to be written to on the display
        
        y1 -= display_height
        y2 -= display_height
        
        # ============ tells display where to write the buffer to ================

        # Column addresses
        self.send_cmd_bottom(0x2A)

        self.word_trans_data_bottom[0] = (x1 >> 8) & 0xFF
        self.word_trans_data_bottom[1] = x1 & 0xFF
        self.word_trans_data_bottom[2] = (x2 >> 8) & 0xFF
        self.word_trans_data_bottom[3] = x2 & 0xFF
        self.send_trans_word_bottom()

        # Page addresses

        self.send_cmd_bottom(0x2B)

        self.word_trans_data_bottom[0] = (y1 >> 8) & 0xFF
        self.word_trans_data_bottom[1] = y1 & 0xFF
        self.word_trans_data_bottom[2] = (y2 >> 8) & 0xFF
        self.word_trans_data_bottom[3] = y2 & 0xFF
        self.send_trans_word_bottom()

        # ==========================================

        # ======== writing the data to the display ============

        self.send_cmd_bottom(0x2C)

        size = (x2 - x1 + 1) * (y2 - y1 + 1)

        # gets a pointer to the buffer 
        data_view = color_p.__dereference__(size * lv.color_t.__SIZE__)

        self.send_data_dma_bottom(data_view)
        # ===========================================
    else:
        # we have to split the data. so lets start off with modifying the heights.
        
        y3 = 0
        y4 = y2 - display_height
        y2 = display_height
        
        # top display writing of the data
        # ============ tells display where to write the buffer to ================

        # Column addresses
        self.send_cmd_top(0x2A)

        self.word_trans_data_top[0] = (x1 >> 8) & 0xFF
        self.word_trans_data_top[1] = x1 & 0xFF
        self.word_trans_data_top[2] = (x2 >> 8) & 0xFF
        self.word_trans_data_top[3] = x2 & 0xFF
        self.send_trans_word_top()

        # Page addresses

        self.send_cmd_top(0x2B)

        self.word_trans_data_top[0] = (y1 >> 8) & 0xFF
        self.word_trans_data_top[1] = y1 & 0xFF
        self.word_trans_data_top[2] = (y2 >> 8) & 0xFF
        self.word_trans_data_top[3] = y2 & 0xFF
        self.send_trans_word_top()

        # ==========================================

        # ======== writing the data to the display ============

        self.send_cmd_top(0x2C)

        top_size = (x2 - x1 + 1) * (y2 - y1 + 1)

        # gets a pointer to the buffer 
        data_view = color_p.__dereference__(top_size * lv.color_t.__SIZE__)

        self.send_data_dma_top(data_view)
        # ===========================================
        
        # and now the botom display

        # ============ tells display where to write the buffer to ================

        # Column addresses
        self.send_cmd_bottom(0x2A)

        self.word_trans_data_bottom[0] = (x1 >> 8) & 0xFF
        self.word_trans_data_bottom[1] = x1 & 0xFF
        self.word_trans_data_bottom[2] = (x2 >> 8) & 0xFF
        self.word_trans_data_bottom[3] = x2 & 0xFF
        self.send_trans_word_bottom()

        # Page addresses

        self.send_cmd_bottom(0x2B)

        self.word_trans_data_bottom[0] = (y3 >> 8) & 0xFF
        self.word_trans_data_bottom[1] = y3 & 0xFF
        self.word_trans_data_bottom[2] = (y4 >> 8) & 0xFF
        self.word_trans_data_bottom[3] = y4 & 0xFF
        self.send_trans_word_bottom()

        # ==========================================

        # ======== writing the data to the display ============

        self.send_cmd_bottom(0x2C)

        # we make a pointer of the entire buffer and then split it 
        # where we need it to be split
        
        total_size = (x2 - x1 + 1) * (area.y2 - area.y1 + 1)
        
        # gets a pointer to the buffer 
        data_view = color_p.__dereference__(total_size * lv.color_t.__SIZE__)
        
        # we use the top size as the mechanism to cut the buffer 
        self.send_data_dma_bottom(data_view[top_size * lv.color_t.__SIZE__:])
        # ===========================================

1 Like

@robekras is example is a great visual. I am going to toss in some ascii art to beautify it.

       DISPLAY 1             DISPLAY 2
╔═════════════════════╦═════════════════════╗
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 1 1 1 β•‘ 1 1 1 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 1 1 1 β•‘ 1 1 1 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 1 1 1 β•‘ 1 1 1 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

The 1’s are the pixels being written to

The data is going to look like this

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

every 2 items in the array represent a single pixel of the display. How do you sort that data stream into 2 streams, one that gets written to DISPLAY 1 and the other to DISPLAY 2?

[]'s are DISPLAY 1 and {}'s are DISPLAY 2

[255, 255, 255, 255, 255, 255], {255, 255, 255, 255, 255, 255}, [255, 255, 255, 255, 255, 255], {255, 255, 255, 255, 255, 255}, [255, 255, 255, 255, 255, 255], {255, 255, 255, 255, 255, 255}

Now if they are arranged vertically

       DISPLAY 1
╔═════════════════════╗
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
╠═════════════════════╣
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
β•‘ 0 0 0 1 1 1 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•‘ 0 0 0 0 0 0 0 0 0 0 β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
       DISPLAY 2

The 1’s are the pixels being written to

same data format as the first example

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255

but when we separate the data watch what happens

[]'s are DISPLAY 1 and {}'s are DISPLAY 2

[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}

which one is easier to sort out?

Now to make things a bit more interesting the shape of the data being written is not a constant. All that matters is the data being written fits into the buffer so the visual representation of the data could change.

If the buffer is able to hold the data for say 12 pixels the buffer is going to have a length of 24 because there are 2 bytes for each pixel. the buffer could represent data being written that is 2 pixels wide by 6 pixels high, or it could be 6 pixels wide by 2 pixels high. so long as the data being written fits into the buffer it doesn’t matter the β€œshape” the data visually represents.

I think this is about the best that can be done to explain what needs to be done. it’s hard to visualize 2 dimensional display data being stored in a single dimension array. If the data was stored in a 2 dimension array it would be easier but there would need to be additional work done to send that data to the display which would slow things down.

1 Like