olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.04k stars 1.04k forks source link

Advice re: porting to ST7789 #763

Closed afarley-tangent closed 5 years ago

afarley-tangent commented 5 years ago

Hi,

First off, I would like to say: I'm reasonably familiar with u8g2 (including porting to other displays) so I'm not looking for detailed hand-holding, just some high-level advice.

I'm porting this library to work on ST7789 with a color 240x320 NHD display. I started by creating a new file called u8x8_d_st7789.c and I'm currently implementing the function u8x8_d_st7789_240x320. I'm ignoring everything except for the U8_MSG_DISPLAY_DRAW_TILE for now, since my code outside u8g2 handles everything else (screen initialiation etc).

It looks like U8_MSG_DISPLAY_DRAW_TILE basically does the following:

So my questions comes up when I'm converting the "set starting coordinates" lines:

if ( u8x8->x_offset == 0 )
  u8x8_cad_SendCmd(u8x8, FS );  /* select 00 commands */
else
  u8x8_cad_SendCmd(u8x8, FS ^ 0x018 );  /* select 00 commands */

u8x8_cad_SendCmd(u8x8, 0x040 | (((u8x8_tile_t *)arg_ptr)->y_pos));
u8x8_cad_SendCmd(u8x8, 0x0e0 | ((x&15)));
u8x8_cad_SendCmd(u8x8, 0x0f0 | (x>>4) );

For the ST7789 controller, I have a SetAddressWindow(int x_start, int y_start, int x_end, int y_end) which requires both the initial coordinate and the tile width/height, whereas the u8g2 library seems to want to set only the initial coordinate.

So my question is: Does u8g2 provide a mechanism to obtain the height and width of the tile being drawn in the case U8X8_MSG_DISPLAY_DRAW_TILE?

afarley-tangent commented 5 years ago

Ok, I'm a bit confused by the arguments to U8X8_MSG_DISPLAY_DRAW_TILE. My questions:

1) Can someone clarify the difference between ((u8x8_tile_t *)arg_ptr)->cnt and arg_int, as used in this case? My understanding is that cnt is how many unique tiles are being drawn, whereas arg_int is how many times the entire group of unique tiles should be repeated.

2) In cases where either cnt is greater than 1, or arg_int is greater than 1, how do we know the implied layout of the tiles? For example, if cnt is 3, we could render these three tiles horizontally or vertically. How do we know which?

olikraus commented 5 years ago

First and just to clarify this, u8g2 is a pure monochrome library. Driving a true color LCD controller is possible, but you will be able to draw only two of the 16 Mio colors. You probably need to decide whether this makes sense.

Does u8g2 provide a mechanism to obtain the height and width of the tile being drawn in the case U8X8_MSG_DISPLAY_DRAW_TILE?

A tile always has 8x8 bits (8 bytes). Because u8g2 is a monochrome library, one bit equals one pixel, so a tile is a 8x8 quadratic bitmap with 64 pixel.

These tiles are introduced because the memory of almost all monochrome LCDs/OLEDs is organized as a tile matrix.

For the ST7789 controller, I have a SetAddressWindow(int x_start, int y_start, int x_end, int y_end) which requires both the initial coordinate and the tile width/height, whereas the u8g2 library seems to want to set only the initial coordinate.

The address window is used to define a rectangular region on the target display. It can be filled without sending further commands to the display. The display controller will take care on line break (once x_end is reached). This also allows quick drawing a vertical line (I have used this in my true color liib ucglib)

Such a command is usually not there with monochrome displays. If it is, i set it to the maximum display size. Reason: Tiles are written from left to right and do not have line breaks.

My understanding is that cnt is how many unique tiles are being drawn, whereas arg_int is how many times the entire group of unique tiles should be repeated.

Yes, exactly.

For example, if cnt is 3, we could render these three tiles horizontally or vertically. How do we know which?

It is always horizontal. One tile has 8 bytes, so with cnt = 3, the total data length is 24 (three tiles).

afarley-tangent commented 5 years ago

Thank you for the quick reply.

Yes, I definitely understand the limitations of using a monochrome library on a color display; I'm just trying to get some simple text display working in black and white.

At a high level, my plan is to replace the byte-transfer by converting the monochrome pixel array into an RGB pixel array.

I've worked out everything re: order of tiling, and replaced the byte-transfer with a function that writes 0xFF to every pixel, so each tile that is being written ends up as pure white. I have verified this on my screen; I can see white blocks where text characters should appear, and the number/placement of white tiles varies as expected with the length of printed text.

I have encounted one problem: it seems that the local variable 'ptr' (in U8X8_MSG_DISPLAY_DRAW_TILE) is empty/pointing to an unused buffer, because it's always 0 based on my debug printouts. It seems that I'm doing something wrong in terms of allocating page/frame buffers and initialization. Edit for clarity: the values in the array pointed to by ptr are always zero, i.e. ptr[0] == 0, ptr[1] == 0, ptr[2] == 0, etc.

I've tried using both the u8g2 and u8x8 functions with the same effect. I think I've implemented the appropriate constructions/initializers, but I must be missing something or doing some operations in the wrong order. Here's the gist of what my code does:

u8x8_t u8x8;

u8x8_Setup(&u8x8, u8x8_d_st7789_240x320, u8x8_cad_001, u8x8_byte_8bit_8080mode, u8x8_gpio_and_delay_k22f); // init u8x8

u8x8_DrawString(&u8x8, 0, 0, "Hello World!");

Based on the above, can you see why ptr/tile_ptr would be empty at the time U8X8_MSG_DISPLAY_DRAW_TILE is called?

I noticed the following msg-case commented out in u8x8_d_st7789_240x320, which may be relevant:

/* handled by the calling function
case U8X8_MSG_DISPLAY_SETUP_MEMORY:
      u8x8_d_helper_display_setup_memory(u8x8, &u8x8_st7789_240x320_display_info);
      break;
*/

Should this be un-commented for u8x8 functionality?

olikraus commented 5 years ago

I have encounted one problem: it seems that the local variable 'ptr' (in U8X8_MSG_DISPLAY_DRAW_TILE)

Which code? Maybe you can link to an example or provide your code.

I noticed the following msg-case commented out in u8x8_d_st7789_240x320, which may be relevant

There are three steps:

  1. Constructor call, e.g. like this : u8x8_Setup(getU8x8(), u8x8_d_sh1108_160x160, u8x8_cad_001, u8x8_byte_arduino_4wire_sw_spi, u8x8_gpio_and_delay_arduino);. The content of setup is here: https://github.com/olikraus/u8g2/blob/master/csrc/u8x8_setup.c#L132
  2. Within the setup procedure the "SetupMemory" is called (https://github.com/olikraus/u8g2/blob/master/csrc/u8x8_display.c#L93) which is only a call to U8X8_MSG_DISPLAY_SETUP_MEMORY. Indeed it is vital to setup the display info struct. All the display specific information is located there.
  3. Finally we need to send the init code to the display, which would happen with U8X8_MSG_DISPLAY_INIT. But according to your previous posts, this will happen outside.
afarley-tangent commented 5 years ago

Thank you very much for the support on this. I've got my LCD working correctly under u8x8 mode.

Still having issues with u8g2 mode. I'm printing out the tiles to be transferred to the LCD in U8X8_MSG_DISPLAY_DRAW_TILE; the tile contents do not appear to contain the right data when u8g2 mode is called. Most tiles are blank as I mentioned before.

I'm wondering if this may be due to my compiler optimizing out the u8g2 fonts; actually I'm just looking in my IDE now, and it seems like the following macro is being "expanded" into nothing/a blank line: U8G2_FONT_SECTION("u8g2_font_artossans8_8r");

Does that ring a bell at all?

Here are a few code snippets in case you can see something obvious:

//In u8g2.h:
#define U8G2_16BIT

//In my top-level driver LCD.c:
u8g2_t u8g2;
//u8x8_t u8x8;

//In u8x8_d_st7789.c:
#define BYTES_PER_RGB_TILE 2*8*8
static const u8x8_display_info_t u8x8_st7789_240x320_display_info =
{
  /* chip_enable_level = */ 0,
  /* chip_disable_level = */ 1,

  /* post_chip_enable_wait_ns = */ 150,
  /* pre_chip_disable_wait_ns = */ 30,
  /* reset_pulse_width_ms = */ 5,
  /* post_reset_wait_ms = */ 5,         /**/
  /* sda_setup_time_ns = */ 60,     /* */
  /* sck_pulse_width_ns = */ 60,    /*  */
  /* sck_clock_hz = */ 4000000UL,   /* since Arduino 1.6.0, the SPI bus speed in Hz. Should be  1000000000/sck_pulse_width_ns */
  /* spi_mode = */ 0,       /* active high, rising edge */
  /* i2c_bus_clock_100kHz = */ 4,   /* 400KHz */
  /* data_setup_time_ns = */ 80,
  /* write_pulse_width_ns = */ 50,
  /* tile_width = */ 30,
  /* tile_hight = */ 40,
  /* default_x_offset = */ 0,   /* must be 0, because this is checked also for normal mode */
  /* flipmode_x_offset = */ 4,
  /* pixel_width = */ 240,
  /* pixel_height = */ 320
};

uint8_t u8x8_d_st7789_240x320(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
  uint8_t x, c;
  uint8_t *ptr;
  uint8_t rgb_pixels[BYTES_PER_RGB_TILE] = {0}; //Buffer holding modified pixel array for RGB display.
  char debug[50] = {0};
  //RGB display is configured to use 16-bit color, i.e. 2 bytes per pixel.

  switch(msg)
  {
    // handled by the calling function
    case U8X8_MSG_DISPLAY_SETUP_MEMORY:
      u8x8_d_helper_display_setup_memory(u8x8, &u8x8_st7789_240x320_display_info);
      break;
    case U8X8_MSG_DISPLAY_SET_POWER_SAVE:
      break;
#ifdef U8X8_WITH_SET_CONTRAST
    case U8X8_MSG_DISPLAY_SET_CONTRAST:
      break;
#endif
    case U8X8_MSG_DISPLAY_DRAW_TILE:
      u8x8_cad_StartTransfer(u8x8);
      x = ((u8x8_tile_t *)arg_ptr)->x_pos;    
      x *= 8;
      x += u8x8->x_offset;

      c = ((u8x8_tile_t *)arg_ptr)->cnt;
      ptr = ((u8x8_tile_t *)arg_ptr)->tile_ptr;

      int w = 8; //Tiles are always 8x8 in u8g2
      int h = 8;
      int y = (((u8x8_tile_t *)arg_ptr)->y_pos);

      int len = sprintf(debug, "c: %d ,arg_int: %d\r\n", c, arg_int);
      PrintREPL(debug, len, 0);
      len = sprintf(debug, "x: %d , y: %d\r\n", x, y);
      PrintREPL(debug, len, 0);
      WAIT1_Waitms(5);

      do
      {
          for(int j=0;j<c;j++) //For every tile
          {
              int x_start = (x + j*8);
              SetAddressWindow(x_start, y, x_start+w-1, y+h-1);

              len = sprintf(debug, "tile: %d\r\n", j);
              PrintREPL(debug, len, 0);
              len = sprintf(debug, "x_start: %d, x_end: %d\r\n", x_start, x_start+w-1);
              PrintREPL(debug, len, 0);
              len = sprintf(debug, "y_start: %d, y_end: %d\r\n", y, y+h-1);
              PrintREPL(debug, len, 0);
              WAIT1_Waitms(5);

              uint8_t tile[9];
              tile[0] = ptr[j*8 + 0];
              tile[1] = ptr[j*8 + 1];
              tile[2] = ptr[j*8 + 2];
              tile[3] = ptr[j*8 + 3];
              tile[4] = ptr[j*8 + 4];
              tile[5] = ptr[j*8 + 5];
              tile[6] = ptr[j*8 + 6];
              tile[7] = ptr[j*8 + 7];

              len = sprintf(debug, "%d %d %d %d %d %d %d %d\r\n", tile[0], tile[1], tile[2], tile[3], tile[4], tile[5], tile[6], tile[7]);
              PrintREPL(debug, len, 0);
              WAIT1_Waitms(5);

              for(int k=0;k<8;k++) //Convert monochrome data to RGB pixel-array
              {
                  uint8_t pixel_row = ptr[k+j*8];

                  bool pixel0 = (pixel_row & 0b00000001) >> 0;
                  bool pixel1 = (pixel_row & 0b00000010) >> 1;
                  bool pixel2 = (pixel_row & 0b00000100) >> 2;
                  bool pixel3 = (pixel_row & 0b00001000) >> 3;
                  bool pixel4 = (pixel_row & 0b00010000) >> 4;
                  bool pixel5 = (pixel_row & 0b00100000) >> 5;
                  bool pixel6 = (pixel_row & 0b01000000) >> 6;
                  bool pixel7 = (pixel_row & 0b10000000) >> 7;

                  rgb_pixels[0 + 2*k] = (pixel0) ? 0b11111111 : 0b00000000;
                  rgb_pixels[1 + 2*k] = (pixel0) ? 0b11111111 : 0b00000000;

                  rgb_pixels[16 + 2*k] = (pixel1) ? 0b11111111 : 0b00000000;
                  rgb_pixels[17 + 2*k] = (pixel1) ? 0b11111111 : 0b00000000;

                  rgb_pixels[32 + 2*k] = (pixel2) ? 0b11111111 : 0b00000000;
                  rgb_pixels[33 + 2*k] = (pixel2) ? 0b11111111 : 0b00000000;

                  rgb_pixels[48 + 2*k] = (pixel3) ? 0b11111111 : 0b00000000;
                  rgb_pixels[49 + 2*k] = (pixel3) ? 0b11111111 : 0b00000000;

                  rgb_pixels[64 + 2*k] = (pixel4) ? 0b11111111 : 0b00000000;
                  rgb_pixels[65 + 2*k] = (pixel4) ? 0b11111111 : 0b00000000;

                  rgb_pixels[80 + 2*k] = (pixel5) ? 0b11111111 : 0b00000000;
                  rgb_pixels[81 + 2*k] = (pixel5) ? 0b11111111 : 0b00000000;

                  rgb_pixels[96 + 2*k] = (pixel6) ? 0b11111111 : 0b00000000;
                  rgb_pixels[97 + 2*k] = (pixel6) ? 0b11111111 : 0b00000000;

                  rgb_pixels[112 + 2*k] = (pixel7) ? 0b11111111 : 0b00000000;
                  rgb_pixels[113 + 2*k] = (pixel7) ? 0b11111111 : 0b00000000;
              }

              for(int i=0;i<BYTES_PER_RGB_TILE;i++)
              {
                  WriteDataLCD(rgb_pixels[i]);
              }
          }
          arg_int--;
      } while( arg_int > 0 );
      u8x8_cad_EndTransfer(u8x8);
      break;
    default:
      return 0;
  }
  return 1;
}

//In u8g2_d_setup.c:
/* st7789 */
void u8g2_Setup_st7789_p_240x320_1(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_st7789_240x320, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_30_40_1(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_horizontal_right_lsb, rotation);
}
void u8g2_Setup_st7789_p_240x320_2(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_st7789_240x320, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_30_40_2(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_horizontal_right_lsb, rotation);
}
void u8g2_Setup_st7789_p_240x320_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
{
  uint8_t tile_buf_height;
  uint8_t *buf;
  u8g2_SetupDisplay(u8g2, u8x8_d_st7789_240x320, u8x8_cad_001, byte_cb, gpio_and_delay_cb);
  buf = u8g2_m_30_40_f(&tile_buf_height);
  u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_horizontal_right_lsb, rotation);
}

//In u8g2_d_memory.c:
uint8_t *u8g2_m_30_40_1(uint8_t *page_cnt)
{
  static uint8_t buf[320];
  *page_cnt = 1;
  return buf;
}
uint8_t *u8g2_m_30_40_2(uint8_t *page_cnt)
{
  static uint8_t buf[640];
  *page_cnt = 2;
  return buf;
}
uint8_t *u8g2_m_30_40_f(uint8_t *page_cnt)
{
  static uint8_t buf[9600];
  *page_cnt = 30;
  return buf;
}
afarley-tangent commented 5 years ago

Well, I think my observations above make sense in light of the following in u8x8.h. It looks like a generic GNUC environment causes U8X8_FONT_SECTION(name) to become unimplemented. Am I misinterpreting?

#ifdef __GNUC__
#  define U8X8_NOINLINE __attribute__((noinline))
#  define U8X8_SECTION(name) __attribute__ ((section (name)))
#  define U8X8_UNUSED __attribute__((unused))
#else
#  define U8X8_SECTION(name)
#  define U8X8_NOINLINE
#  define U8X8_UNUSED
#endif

#if defined(__GNUC__) && defined(__AVR__)
#  define U8X8_FONT_SECTION(name) U8X8_SECTION(".progmem." name)
#  define u8x8_pgm_read(adr) pgm_read_byte_near(adr)
#  define U8X8_PROGMEM PROGMEM
#endif

#if defined(ESP8266)
uint8_t u8x8_pgm_read_esp(const uint8_t * addr);   /* u8x8_8x8.c */
#  define U8X8_FONT_SECTION(name) __attribute__((section(".text." name)))
#  define u8x8_pgm_read(adr) u8x8_pgm_read_esp(adr)
#  define U8X8_PROGMEM
#endif

#ifndef U8X8_FONT_SECTION
#  define U8X8_FONT_SECTION(name) 
#endif
afarley-tangent commented 5 years ago

Stepping through, I see a few strange things happening in u8g2_font_decode_glyph.

decode->target_x is 0 (as expected for the first character) decode->target_y is 65529 (seems unexpected; shouldn't this be constrained to 0 <= target_y <= 360 ?

olikraus commented 5 years ago

U8G2_FONT_SECTION("u8g2_font_artossans8_8r"); Does that ring a bell at all?

U8g2 tries to put fonts into flash memory. For this reason sometimes a special section needs to be named. In principle it is not an error if this expands to nothing. What is your target uC?

Here are a few code snippets in case you can see something obvious:

I think you do not consider arg_int / c when calling SetAddressWindow. I mean, usually arg_int is 1, so it will not be a big problem, but for clearing the screen i use arg_int with a value greater than 1.

decode->target_y is 65529 (seems unexpected; shouldn't this be constrained to 0 <= target_y <= 360 ?

U8g2 should correctly handle unsigned wrap arounds, so it should be ok. It is just a negative number in your case.

afarley-tangent commented 5 years ago

The uC is a Kinetis K22F (Cortex-M4).

I see what you mean re: arg_int/c in SetAddressWindow, but I think an error in the arguments SetAddressWindow would not explain why the buffer is blank in the first place.

Right now I'm focusing mostly on u8g2_DrawStr to make sure it's doing what I think. I'm printing out the frame buffer (which I modified to be a global buffer rather than a static array allocated by a function) which is blank immediately after the call to u8g2_DrawStr.

Diving into u8g2_DrawGlyph, to u8g2_font_draw_glyph, to u8g2_font_decode_glyph, I'm printing out the values of variables a and b, and everything looks OK at this point; I can see individual characters of the font being decoded into lines as expected.

I'm trying to find the point where values from the decoded glyph are copied to the main frame buffer right now, so I can see exactly why the buffer isn't being written.

afarley-tangent commented 5 years ago

Ok, I just noticed something strange.

Diving into u8g2_DrawStr, I see it's ultimately using u8g2_DrawHVLine. So I tried calling u8g2_DrawHVLine directly instead of worring about strings.

When I attempt to draw a line (x=40,y=40,len=40), I actually see the buffer being populated with a line at x=4,y=4, of length 4.


Edit: Nevermind, the buffer is actually populated as-expected after calling u8g2_DrawHVLine. So it seems like the error I'm having is somewhere between u8g2_DrawStr and u8g2_DrawHVLine. I'll keep grinding away at it.

afarley-tangent commented 5 years ago

Oook, getting there... it appears that my major mistake was calling u8g2_DrawStr with a y-value of 0. I was thinking the coordinates 0,0 would cause the text to appear at the upper-left corner, but it fact I believe 0,0 represents an 'off-screen' location. I think I ran into this when I used u8g2 in the past.

Anyway, now I'm seeing something on-screen from u8g2_DrawStr but the text/font is garbled and looks like semi-random pixels in the tiles that actually get rendered. This may be a seperate issue from the "blank buffer" scenario I was originally hitting.

afarley-tangent commented 5 years ago

Here's what I'm seeing: Garbled text

I think my earlier statement of u8g2_DrawHVLine working correctly might be mistaken. I've made some changes to my U8X8_MSG_DISPLAY_DRAW_TILE routine so that the y-position is modified correctly.

U8X8_MSG_DISPLAY_DRAW_TILE is still working as-expected for u8x8-mode.

When I call u8g2_DrawHLine(&u8g2,0,0,40), the result on-screen looks like the 40-pixel line is wrapping within the first tile, so it ends up as a block of white pixels of shape 5x8. Here's an image: Block instead of line

The modified code is pasted below. Upon review, I think I am taking 'c' into account by adjusting 'x_start' with respect to 'j'. So every tile will end up starting 8 pixels to the right of the previous tile.

I understand I'm ignoring arg_int; I think that's OK for now, because I have a seperate clear-screen routine which is already working.

case U8X8_MSG_DISPLAY_DRAW_TILE:
      u8x8_cad_StartTransfer(u8x8);
      x = ((u8x8_tile_t *)arg_ptr)->x_pos;    
      x *= 8;
      x += u8x8->x_offset;

      c = ((u8x8_tile_t *)arg_ptr)->cnt;
      ptr = ((u8x8_tile_t *)arg_ptr)->tile_ptr;

      int w = 8; //Tiles are always 8x8 in u8g2
      int h = 8;
      int y = (((u8x8_tile_t *)arg_ptr)->y_pos);
      y *= 8;

      len = sprintf(debug, "c: %d ,arg_int: %d\r\n", c, arg_int);
      PrintREPL(debug, len, 0);
      len = sprintf(debug, "x: %d , y: %d\r\n", x, y);
      PrintREPL(debug, len, 0);
      WAIT1_Waitms(5);

      do
      {
          for(int j=0;j<c;j++) //For every tile
          {
              int x_start = (x + j*8);
              SetAddressWindow(x_start, y, x_start+w-1, y+h-1);

              //len = sprintf(debug, "tile: %d\r\n", j);
              //PrintREPL(debug, len, 0);
              //len = sprintf(debug, "x_start: %d, x_end: %d\r\n", x_start, x_start+w-1);
              //PrintREPL(debug, len, 0);
              //len = sprintf(debug, "y_start: %d, y_end: %d\r\n", y, y+h-1);
              //PrintREPL(debug, len, 0);
              //WAIT1_Waitms(5);

              uint8_t tile[9];
              tile[0] = ptr[j*8 + 0];
              tile[1] = ptr[j*8 + 1];
              tile[2] = ptr[j*8 + 2];
              tile[3] = ptr[j*8 + 3];
              tile[4] = ptr[j*8 + 4];
              tile[5] = ptr[j*8 + 5];
              tile[6] = ptr[j*8 + 6];
              tile[7] = ptr[j*8 + 7];

              //len = sprintf(debug, "%d %d %d %d %d %d %d %d\r\n", tile[0], tile[1], tile[2], tile[3], tile[4], tile[5], tile[6], tile[7]);
              //PrintREPL(debug, len, 0);
              //WAIT1_Waitms(5);

              //len = sprintf(debug, "Monochrome-to-RGB\r\n");
              //PrintREPL(debug, len, 0);
              //WAIT1_Waitms(5);

              for(int k=0;k<8;k++) //Convert monochrome data to RGB pixel-array
              {
                  uint8_t pixel_row = ptr[k+j*8];

                  bool pixel0 = (pixel_row & 0b00000001) >> 0;
                  bool pixel1 = (pixel_row & 0b00000010) >> 1;
                  bool pixel2 = (pixel_row & 0b00000100) >> 2;
                  bool pixel3 = (pixel_row & 0b00001000) >> 3;
                  bool pixel4 = (pixel_row & 0b00010000) >> 4;
                  bool pixel5 = (pixel_row & 0b00100000) >> 5;
                  bool pixel6 = (pixel_row & 0b01000000) >> 6;
                  bool pixel7 = (pixel_row & 0b10000000) >> 7;

                  //len = sprintf(debug, "Row(%d): %d %d %d %d %d %d %d %d\r\n", k, pixel0, pixel1, pixel2, pixel3, pixel4, pixel5, pixel6, pixel7);
                  //PrintREPL(debug, len, 0);
                  //WAIT1_Waitms(5);

                  rgb_pixels[0 + 2*k] = (pixel0) ? 0b11111111 : 0b00000000;
                  rgb_pixels[1 + 2*k] = (pixel0) ? 0b11111111 : 0b00000000;

                  rgb_pixels[16 + 2*k] = (pixel1) ? 0b11111111 : 0b00000000;
                  rgb_pixels[17 + 2*k] = (pixel1) ? 0b11111111 : 0b00000000;

                  rgb_pixels[32 + 2*k] = (pixel2) ? 0b11111111 : 0b00000000;
                  rgb_pixels[33 + 2*k] = (pixel2) ? 0b11111111 : 0b00000000;

                  rgb_pixels[48 + 2*k] = (pixel3) ? 0b11111111 : 0b00000000;
                  rgb_pixels[49 + 2*k] = (pixel3) ? 0b11111111 : 0b00000000;

                  rgb_pixels[64 + 2*k] = (pixel4) ? 0b11111111 : 0b00000000;
                  rgb_pixels[65 + 2*k] = (pixel4) ? 0b11111111 : 0b00000000;

                  rgb_pixels[80 + 2*k] = (pixel5) ? 0b11111111 : 0b00000000;
                  rgb_pixels[81 + 2*k] = (pixel5) ? 0b11111111 : 0b00000000;

                  rgb_pixels[96 + 2*k] = (pixel6) ? 0b11111111 : 0b00000000;
                  rgb_pixels[97 + 2*k] = (pixel6) ? 0b11111111 : 0b00000000;

                  rgb_pixels[112 + 2*k] = (pixel7) ? 0b11111111 : 0b00000000;
                  rgb_pixels[113 + 2*k] = (pixel7) ? 0b11111111 : 0b00000000;
              }

              for(int i=0;i<BYTES_PER_RGB_TILE;i++)
              {
                  WriteDataLCD(rgb_pixels[i]);
              }
          }
          arg_int--;
      } while( arg_int > 0 );
      u8x8_cad_EndTransfer(u8x8);
afarley-tangent commented 5 years ago

Ok, a few more findings:

I tested drawing vertical lines and ellipses. It seems that the arrangement of pixels in the frame buffer is not what I'm expecting. The strange thing is, everything works in u8g8; I would have thought that the tiling/de-tiling functionality would be entirely hidden in the background, but it seems like I've somehow caused the pixels to be mapped into (or out of) the frame buffer incorrectly.

Here's what I get when I draw a vertical line: Vertical line, x=0, y=0, len=40

And when I draw an ellipse: Ellipse, x=40, y=40, rx=40, ry=40, U8G2_DRAW_ALL

olikraus commented 5 years ago

wow, a lot of comments.

The uC is a Kinetis K22F (Cortex-M4).

Then it is fine, that the section macro expands to nothing if you use the arm-gcc compiler.

Here's what I get when I draw a vertical line:

Did you ever test u8x8_DrawGlyp()? I think there is a problem in your tile draw procedure.

olikraus commented 5 years ago

I reviewed your could. To me it looks ok. So if it still doesn't work, then maybe the SetAddressWindow procedure is broken.

afarley-tangent commented 5 years ago

Just tested u8x8_DrawGlyph(), it's working as expected.

I'm fairly certain from inspection of the tiles that the tile-printing is working the way I expect; however, the tiles themselves don't look the way I expect.

I'm also fairly certain that SetAddressWindow is working as expected; I've tested this seperately, wrote a screen-test-pattern function which is working exactly as I expect.

If SetAddressWindow wasn't working, I don't think u8x8 mode would work correctly, would it?

I'm going to print out the frame-buffer contents after calling some rendering functions and see if I can figure out anything from there.

olikraus commented 5 years ago

hmmm... usually the displays have a data and command mode. How do you escape from data mode? I mean, the difference between u8g2 and u8x8 is the argument c, which is 1 in most cases for u8x8 and > 1 for u8g2. Assume you are still in data mode after printing the first tile, then the SetAddressWindow commands might be ignored or misinterpreted. Maybe you should put the StartTransfer and EndTransfer into the innermost loop, encapsulating the SetAddressWindow and data output... just for testing.

afarley-tangent commented 5 years ago

To answer your questions: SetAddressWindow and WriteDataLCD handle the data/command details. I've already tested those functions enough to know that they're doing what I think.

Actually I didn't implement the hooks for StartTransfer and EndTransfer (since they're taken care of by SetAddressWindow and WriteDataLCD); I just left the calls in the code anyway.

Here's the first 480 bytes (2x pages) from the frame buffer. This corresponds to the tiles I'm seeing in U8X8_MSG_DISPLAY_DRAW_TILE.

This particular frame-buffer is resulting from a single u8g2 function: u8g2_DrawHLine(&u8g2,0,0,16);

My expectation is that the horizontal line should occupy one row of the first two tiles. However, as you can see in the buffer, it's ending up as two 8-pixel lines in the first tile. Is there something here I'm misunderstanding?

Page: T[255,255,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0]
Page: T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0] T[0,0,0,0,0,0,0,0]
afarley-tangent commented 5 years ago

Here's an image showing that u8x8_DrawGlyph seems to be working correctly with my implementation of U8X8_MSG_DISPLAY_DRAW_TILE:

DrawGlyph test

olikraus commented 5 years ago

You did not provide the code, how you actually setup the u8g2 object. The problem is, there are two different kinds of layout structures for the u8g2 memory buffer.

There is a so called vertical layout. This is what we have discussed so far here and this is also what is most common: https://github.com/olikraus/u8g2/blob/master/csrc/u8g2_d_setup.c#L1712

It is actually a callback procedure called u8g2_ll_hvline_vertical_top_lsb. According to your bitmap it looks like you have used u8g2_ll_hvline_horizontal_right_lsb for u8g2. https://github.com/olikraus/u8g2/blob/master/csrc/u8g2_d_setup.c#L1722

Is it possible that you have used u8g2_ll_hvline_horizontal_right_lsb for u8g2?

Technical background: 90% of all monochrome LCD/OLED controller use the u8g2_ll_hvline_vertical_top_lsb layout (SSD1306 oleds). 9% use the u8g2_ll_hvline_horizontal_right_lsb layout (most important are the ST7920 displays here). Same rare LCDs use something which is again different, but for these LCDs there is no special layout, instead I convert the page on the fly.

afarley-tangent commented 5 years ago

That was it! Thank you very much. Sorry it was so much trouble to identify the memory-layout issue; I obviously didn't pay attention to the significance of the layout callback procedure.

Do you accept tips/donations?

olikraus commented 5 years ago

Do you accept tips/donations?

Still I think U8g2 will not fit to your display and you had to spend so much time on porting the display... But if you want to "pay me a beer"... my e-mail account (see source code/ChangeLog) is connected to paypal.

afarley-tangent commented 5 years ago

Hah yeah that might have been a bad choice on my part. I didn't get any hits on ST7789 when I googled around for libraries so I just went with the first embedded graphics library that I was familiar with.

I see you have an Arduino TFT library available; I initially assumed it didn't have a C API, but now that I look at the files, it seems that it does have a C interface. Maybe if we have extra time on this project I'll add support for ST7789 to ucglib. I could probably even submit the code to you for merging into master if you're interested.

olikraus commented 5 years ago

Thanks a lot for the "beer". If you could just paste the code of your callback functions here, it might be useful for others also.

I have stopped working on ucglib at the moment, but yes, extensions are still thinkable. Ucglib is a little bit more complicated. It tries to optimize a lot and therefore requires more effort in porting to a new device.

afarley-tangent commented 5 years ago

I requested permission to post my adaptations here, but apparently our agreement with our client may prohibit it. I'm still waiting for the official answer, but assuming they say no, here's my advice for anyone else who wants to use ST7789:

If anything else comes up, just post here and I will try to answer.

olikraus commented 5 years ago

No problem, your advice is good enough. I am happy to see, that this lib is also helpful in commercial projects.