This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Controlling ST7789 LCD with NRF52 DK

I am working on a project that requires an LCD and the one I have found is a 240x240px 1.54" LCD from Adafruit (Product ID 3787 https://www.adafruit.com/product/3787 ). I'm communicating with it over SPI and I am powering it externally from my NRF52 DK (NRF52832). I have the NRF5 SDK v.14.2.0 and have been trying to use the external driver .c file included in "\components\drivers_ext\st7735" for the ST7735 LCD driver with some luck. It took a while testing to find defining the tab color in the "sdk_config.h" as green, calling "nrf_gfx_rotation_set(p_lcd, NRF_LCD_ROTATE_180);" after initializing the GFX library, and calling "nrf_gfx_invert(p_lcd, true);" allows me to draw graphics and text on the screen appropriately (correct location, entire screen not partial, correct colors, etc.). I test this with drawing pixels and lines to confirm I could move the pointer up, down, left, and right as expected and can draw lines from one corner all the way to the other exactly. I am not completely sure of all the differences between the ST7789 that my LCD is using and the ST7735 that the NRF library is written for, and believe that I'm running into a slowdown issue related to this. Writing a line of five bold characters to the screen takes almost an entire second as it draws each character and drawing over the entire screen with a single color takes over a full second.

I have searched and not found an ST7789 library like the ST7735 one included in the NRF5 SDK made by Nordic. The ST7789's datasheet looks like it has multiple different commands from the ST7735's datasheet so I am unsure how I would go about rewriting the library to command the ST7789 more effectively. I looked through application responses from Adafruit for other LCDs of people complaining about slow refresh rates and the usual response is that the SPI bus speed is a bottleneck, although I have changed the define "SPI_DEFAULT_FREQUENCY" in the "sdk_config.h" between 8Mhz and 1Mhz as well as gone into "st7735.c" to change the SPI frequency when it calls "err_code = nrf_drv_spi_init(&spi, &spi_config, NULL, NULL);" to initialize the SPI and I did not see any notable difference in the refresh rate. I'm trying to use write and erase techniques like Matthew McMillan used in his blog ( http://matthewcmcmillan.blogspot.com/2014/08/arduino-tft-lcd-display-refresh-rate-part2.html ) to write over a character with itself the same color as the background then place the new character in its spot as the text color to try to reduce the amount of pixels being drawn, which has helped a lot but has't fully fixed my issue. I'm sure there are even more efficient ways of drawing new characters but this method is simple to implement and immediately cut my draw time considerably as it use to be even longer.

Ideally I want to be able to update up to four bold characters at a time without any noticeable draw time as I intend to include a clock counter on the screen and will periodically be updating smaller text entire strings of up to 32 characters. We do NOT want to replace the LCD we have as the color, brightness, dimensions, and resolution are perfect for our application so if there is any better way to be controlling an ST7789 with the NRF52 DK that would be excellent.

Thanks for any help!

Edit: I have also tested the NRF5 SDK 14.2.0's included external driver for the ILI9341 and it also seems at least partly compatible with the ST7789. I can draw the same characters, pixels, graphics, etc. using the same color inversion and rotation, the only notable difference being the colors aren't organized RGB they're BGR so my reds and blues are reversed. I have a simple timer also included and using it I estimated that with "ILI9341.c" defining the SPI clock speed as 4MHz it takes ~7.67 seconds from initialization through drawing multiple characters and graphics (just some testing I had, the graphics aren't final or important for now) and when I altered the file to define it as 8MHz the same process took ~6.96 seconds. There is some improvement but I don't believe that the SPI bus is what is holding up the drawing speed. I also noticed on the Nordic Infocenter there is a section in the GFX Library about the LCD's frame buffer being faster ( http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v14.2.0/lib_gfx.html?cp=4_0_2_3_18_3#gfx_lib_frame_buffer ). It says to use functions for pixel drawing, rectangle drawing, and LCD display updating, and from what I found the included libraries both already use pixel and rectangle drawing if you follow the function calls back to the "st7735.c" and "ili9341.c" files and in both the display function is called "dummy" and is left empty with the comment "/* No implementation needed. */". I am not sure if or how I would be able to use either of these to fully fix my refresh rate speed but it seems like a step in the right direction.

Parents
  • Hi

    Unfortunately, we're limited to what we can help you with since we don't have any official drivers for that LCD. Maybe the Adafruit help forum is a better place for getting help regarding that LCD screen: https://forums.adafruit.com/

    Good Luck

    Jared

  • Thanks Jared,

    I'll work towards implementing the Adafruit library for the ST7789 onto the NRF52. Originally I started prototyping this with a different LCD, their smaller TFT (Product ID 3533 https://www.adafruit.com/product/3533 ) and an Arduino UNO but since we needed both a higher resolution and wireless communication I moved to the NRF52 DK. It's odd that I managed to get the display at least partially functioning with the given code considering it's intended for a different driver. I've read that many LCD drivers like the ST7735, ILI9341, ST7781, etc. use some sort of standard but have variation in how they're initialized, certain commands, etc. (bottom of the page suggests close equivalents for the driver https://www.ramtex.dk/display-controller-driver/rgb/st7789.htm ). I've also read the Adafruit library is not particularly optimized ( https://github.com/XarkLabs/PDQ_GFX_Libs ) and that's mostly why I was staying away from it and switching over to Nordic libraries but if I absolutely need a library designed for my specific driver type I'll work with what I can get. I'll post updates here on my status and any issues I run into trying to get the Adafruit code working.

    Thanks

  • An update on this,

    I'm able to drive the ST7789 much better than previously, although there is still a noticeable delay when I am erasing and drawing 72pt Arial font characters it's taking no more than 200ms now regardless of the character as opposed to the 300+ms previously. I am making fonts to work with the GFX library's typedef "nrf_gfx_font_desc_t" with the Dot Factory generator by Eran Duchan ( http://www.eran.io/the-dot-factory-an-lcd-font-and-image-generator/ ) and they only require small changes from the default settings to make work. The .h and .c files it generates need "uint_8" changed to "uint8_t", an include for "nrf_font.h", character height in the font info struct at the bottom of the .c file changed to be in bits instead of bytes, and the comments for the "\" character need apostrophes around them and an additional space after to prevent a glitch where ASCII characters that come after "\" are shifted over by one. Here's the contents of one of the .h files I'm using right now.

    // Font data for Arial 72pt
    #ifndef _ARIAL_72pt__
    #define _ARIAL_72pt__
    
    #include "nrf_font.h"
    
    extern const uint8_t arial_72ptBitmaps[];
    extern const FONT_INFO arial_72ptFontInfo;
    extern const FONT_CHAR_INFO arial_72ptDescriptors[];
    //End
    #endif

    To further reduce the character refresh time I changed how I was erasing characters and added a new SPI library that operates notably faster than the normal NRF SPI. I assumed that the most efficient way of erasing a character was by drawing an identical character the color of the background in the same place to minimize the number of pixels that need to be redrawn, but I've found using "lcd_rect_draw" for a rectangle the background's color takes far less time, ~26ms as opposed to the ~140ms needed for a character in that space. I'm guessing the"nrf_gfx.c" functions like "line_draw" and "write_character" aren't super efficient (they both call the "nrf_lcd.c" function "lcd_pixel_draw", not sure if that's the issue).

    The different SPI library I'm using was linked here by Ovrebekk: https://devzone.nordicsemi.com/f/nordic-q-a/10330/spi-optimization , the "spi_master_fast.zip". I made an option in my "st7789.c" (a heavily modified copy of the external driver "ili9341.c" from the 14.2.0 SDK) to choose between enabling the normal SPI or the fast SPI library by changing some comments in the LCD's init function. Previously I had a visible delay on my oscilloscope when probing the MOSI & SCK lines between bursts of 8Mbps activity of 16µs (looked just like the delays people were seeing in other threads: https://devzone.nordicsemi.com/f/nordic-q-a/9636/spi-clock-bug ) that with the fast SPI library has been reduced to 8.56µs. I don't have a gapless clock like is described here: https://devzone.nordicsemi.com/f/nordic-q-a/6139/spi-master-high-speed-behavior-gapless-transmission but since I will need a bundle of interrupts later on in this project and users RK and Sam noted that interrupts would interfere with how his SPI was operating so I'm not sure that's the best idea for me to try out. I am not sure why these delays still exist for the SPI and considering how much reducing them has sped up my LCD's refresh rate I bet those are the source of most of my problems.

    I've managed to trim the time down a bit by changing how I was erasing characters and changing my fonts from bold to not but my ultimate goal is still to get a seamless change of characters on the screen. Next I'm going to be testing out drawing bitmap images onto the LCD because that's another essential task I'll need to do later on as well as try out 12-bit color mode as it should notably reduce the number of bytes I'll need to send when using "lcd_rect_draw" and functions that use it, but since text is still drawn ultimately with "lcd_pixel_draw" this likely won't have much of an impact on the way everything looks. Also to get the ST7789 working more correctly I used parts of an initialization found from New Haven Display intended for an ST7789: http://www.newhavendisplay.com/app_notes/2-4TFT_ST7789.txt . Here is the initialization function for the LCD: 

    static void command_list(void)
    {
    //NewHaven's http://www.newhavendisplay.com/app_notes/2-4TFT_ST7789.txt
        //THIS ONE WORKED THE BEST
        
        write_command(ST7789_SWRESET);  //Software reset
        nrf_delay_ms(125);
    
        write_command(ST7789_SLPOUT);  //Exit sleep
        nrf_delay_ms(150);
    
        write_command(ST7789_MADCTL);  //Memory Data Access control
        write_data(0x08); //Bottom to top page address order
    
        write_command(ST7789_COLMOD); //Color mode
        write_data(0x55); //16-bit, 565 RGB
        //write_data(0x53); //12-bit, 444N RGB (N=col for next byte, uses 1.5B per pixel when mass sending data)
    
        write_command(ST7789_FRMCTR2);  //Frame rate control in normal mode
        write_data(0x01); //111Hz
        
        //nrf_delay_ms(2000);
    
        write_command(ST7789_PVGAMCTRL); //+ voltage gamma control
        write_data(0xD0);
        write_data(0x00);
        write_data(0x05);
        write_data(0x0E);
        write_data(0x15);
        write_data(0x0D);
        write_data(0x37);
        write_data(0x43);
        write_data(0x47);
        write_data(0x09);
        write_data(0x15);
        write_data(0x12);
        write_data(0x16);
        write_data(0x09);
    
        write_command(ST7789_NVGAMCTRL); //- voltage gamma control
        write_data(0xD0);
        write_data(0x00);
        write_data(0x05);
        write_data(0x0D);
        write_data(0x0C);
        write_data(0x06);
        write_data(0x2D);
        write_data(0x44);
        write_data(0x40);
        write_data(0x0E);
        write_data(0x1C);
        write_data(0x18);
        write_data(0x16);
        write_data(0x19);
    
        write_command(ST7789_INVON);  //Screen inversion on, added in because colors were reversed without
    
        write_command(ST7789_DISPON);
    }

    I still had to change some color and inversion parts for it to work entirely correctly, but I never managed to get the 0º rotation to work correctly, it always appears offset vertically by ~80 pixels, but 3/4 is flexible enough to get most any orientation for the screen to work. Here's the modified rotation code I used (typically I'm using 270º): 

    static void st7789_rotation_set(nrf_lcd_rotation_t rotation)
    {
        write_command(ST7789_MADCTL);
        switch (rotation % 4) {
            case NRF_LCD_ROTATE_0:
                write_data(ST7789_MADCTL_MX | ST7789_MADCTL_MY | ST7789_MADCTL_RGB);  //Not working correctly
                //Column address (MX): Right to left
                //Page address (MY): Bottom to top
                //Page/ Column order (MV): normal
                //RGB/BGR order: RGB
                break;
            case NRF_LCD_ROTATE_90:
                write_data(ST7789_MADCTL_MV | ST7789_MADCTL_RGB);
                //Column address (MX): Left to right
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): reverse
                //RGB/BGR order: RGB
                break;
            case NRF_LCD_ROTATE_180:
                write_data(ST7789_MADCTL_RGB);
                //Column address (MX): Left to right
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): normal
                //RGB/BGR order: RGB
                break;
            case NRF_LCD_ROTATE_270:
                write_data(ST7789_MADCTL_MX | ST7789_MADCTL_MV | ST7789_MADCTL_RGB);
                //Column address (MX): Right to left
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): reverse
                //RGB/BGR order: RGB
                break;
            default:
                break;
        }
    }

    Here is an image of the oscilloscope signals I am seeing now (Blue is SCK, Yellow is MOSI): 

  • I've managed to get the LCD screen working very quickly now with a few tweaks I made to the code in the NRF GFX and NRF LCD libraries. I got a different LCD to test with as well, a 1.44" 132x132 LCD using an ST7735 (Product ID 2088 https://www.adafruit.com/product/2088 it says it has 128x128 resolution but it's not) that showed me where the problems I was mostly having were. 

    After reading through the datasheet for the ST7789, ILI9341, and ST7735 over and over as well as coming across an LCD test done by "Kbiva" (Article: https://kbiva.wordpress.com/2016/12/10/nokia-6020-lcd-using-framebuffer/ Video: https://youtu.be/718ShQZI2rg ) I found that the NRF GFX library was not utilizing the frame buffer on the LCD drivers efficiently. The function for printing a string "nrf_gfx_print" repeatedly calls "write_character" which in turn repeatedly calls "pixel_draw". "pixel_draw" will scan through the font file's bitmap information and check if a bit is a 0 or a 1 for every single pixel and draw that pixel to the screen if it's a 1 and ignore it if it's a 0. This is why drawing multiple characters in the same region on the LCD results in a garbled mess, the characters beneath are never erased and essentially both combine, but since that is real easy to fix in multiple ways that's not an issue. What was an issue is how "pixel_draw" will send 13B of data over SPI for ever pixel it draws. It first sets the address window by sending 1B command (CASET) followed by 4B of data for X start and end positions, 1B command (RASET) followed by 4B of data for Y start and end positions, 1B command (RAMWR) to write data to the frame buffer's memory, then 2B of data (16-bit 565 RGB color). RAMWR will end when a new command is sent, so sending the next pixel will start with a CASET command and end sending data to the frame buffer abruptly, which results in every pixel being sent to the screen one at a time. These are the major changes I made to the code for an alternate method of writing to the frame buffer:

    //IN NRF_GFX.h
    /**
     * @brief Function for displaying data from an internal frame buffer.
     *  modified to write data directly to frame buffer over SPI
     *
     * @param[in] p_instance            Pointer to the LCD instance.
     * @param[in] * dat                 Pointer to data array
     * @param[in] len                   Length of data in array
     * @param[in] x0,y0,x1,y1           Coordinates for setting window address if selected
     */
    void nrf_gfx_display(nrf_lcd_t const * p_instance, uint8_t * dat, uint16_t len, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);
    
    //IN NRF_GFX.c
    void nrf_gfx_display(nrf_lcd_t const * p_instance, uint8_t * dat, uint16_t len, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
    {
        ASSERT(p_instance != NULL);
    
        p_instance->lcd_display(dat, len, x0, y0, x1, y1);
    }
    
    //IN NRF_LCD.h
    void nrf_gfx_display(nrf_lcd_t const * p_instance, uint8_t * dat, uint16_t len, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
    {
        ASSERT(p_instance != NULL);
    
        p_instance->lcd_display(dat, len, x0, y0, x1, y1);
    }
    
    //IN ST7789.c, ST7735.c, etc.
    static void st7789_dummy_display(uint8_t * data, uint16_t len, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
    {
        if(len == 0xFFFF){
            set_addr_window(x0, y0, x1, y1);
        }
        else{
          //spi_write(&data, len);
          write_data_buffered(data, len);
        }
    }
    
    static inline void write_data_buffered(uint8_t * c, uint16_t len)
    {
        nrf_gpio_pin_set(ST7789_DC_PIN);
    
        spi_write(c, len);
    }

    This makes use of the normally empty functions for display and allows me to either send data over SPI or to set the address window to some specific values. This avoids needing to reset the address window over and over, sending new commands that break up sending data to the frame buffer. The downside with this method is that now I need to handle the pixels that are 0s in the font bitmaps otherwise I'll end up with garbage on the display. I added two new functions to "nrf_gfx.c" to take advantage of this: 

    //added in
    
    //#define enable_prints
    
    static void write_character_fast(nrf_lcd_t const * p_instance,  //nrf_lcd_st7789
                                nrf_gfx_font_desc_t const * p_font, //arial_72ptFontInfo
                                uint8_t character,                  //character
                                uint16_t * p_x,                     //x starting point
                                uint16_t y,                         //y starting point
                                uint16_t background_color,          //16-bit color
                                uint16_t font_color)                //16-bit color
    {
        uint8_t char_idx = character - p_font->startChar; //character - 33 ("!" ASCII)
        uint16_t bytes_in_line = CEIL_DIV(p_font->charInfo[char_idx].widthBits, 8); //# bytes per line
    
        if (character == ' ')           //in case of a space
        {
            *p_x += p_font->height / 2; //increase x position by half font's height
            return;
        }
        
        uint8_t font_col[2] = {font_color >> 8, font_color};
        uint8_t background_col[2] = {background_color >> 8, background_color};
    
        //p_instance->lcd_pixel_draw(*p_x, y, background_color);
        nrf_gfx_display(p_instance, 0, 0xFFFF, *p_x, y, *p_x + bytes_in_line * 8 - 1, y + p_font->height);
    
        for (uint16_t i = 0; i < p_font->height; i++) //Increment through the rows
        {
            for (uint16_t j = 0; j < bytes_in_line; j++)  //Increment through the columns
            {
                for (uint8_t k = 0; k < 8; k++) //Check 0 to 7 per Byte
                {
                    if ((1 << (7 - k)) & p_font->data[p_font->charInfo[char_idx].offset + i * bytes_in_line + j])
                    {
                        //pixel_draw(p_instance, *p_x + j * 8 + k, y + i, font_color);
                        //p_instance = nrf_lcd_st7789
                        //p_x = X starting position   -> X starting position + Column * 8 + Bit # 
                        //y = y starting position     -> Y starting position + Row
                        //font_color = 16-bit color
                        
                        nrf_gfx_display(p_instance, font_col, sizeof(font_col),0,0,0,0);
                        #ifdef enable_prints
                        printf("1");
                        #endif
                    }
                    else{
    
                        nrf_gfx_display(p_instance, background_col, sizeof(background_col),0,0,0,0);
                        #ifdef enable_prints
                        printf("0");
                        #endif
                    }
                }
            }
            #ifdef enable_prints
            printf("\n");
            #endif
        }
        //After through entire character incremented through
    
        *p_x += p_font->charInfo[char_idx].widthBits + p_font->spacePixels;
        //X starting position + character's width in bits + size of space in pixels
    }
    
    ret_code_t nrf_gfx_print_fast(nrf_lcd_t const * p_instance,
                             nrf_gfx_point_t const * p_point,
                             uint16_t background_color,
                             uint16_t font_color,
                             const char * string,
                             const nrf_gfx_font_desc_t * p_font,
                             bool wrap)
    {
        ASSERT(p_instance != NULL);
        ASSERT(p_instance->p_lcd_cb->state != NRF_DRV_STATE_UNINITIALIZED);
        ASSERT(p_point != NULL);
        ASSERT(string != NULL);
        ASSERT(p_font != NULL);
    
        uint16_t x = p_point->x;
        uint16_t y = p_point->y;
    
        if (y > (nrf_gfx_height_get(p_instance) - p_font->height))
        {
            // Not enough space to write even single char.
            return NRF_ERROR_INVALID_PARAM;
        }
    
        for (size_t i = 0; string[i] != '\0' ; i++)
        {
            if (string[i] == '\n')
            {
                x = p_point->x;
                y += p_font->height + p_font->height / 10;
            }
            else
            {
                write_character_fast(p_instance, p_font, (uint8_t)string[i], &x, y, background_color, font_color);
            }
    
            uint8_t char_idx = string[i] - p_font->startChar;
            uint16_t char_width = string[i] == ' ' ? (p_font->height / 2) :
                                                    p_font->charInfo[char_idx].widthBits;
    
            if (x > (nrf_gfx_width_get(p_instance) - char_width))
            {
                if (wrap)
                {
                    x = p_point->x;
                    y += p_font->height + p_font->height / 10;
                }
                else
                {
                    break;
                }
    
                if (y > (nrf_gfx_height_get(p_instance) - p_font->height))
                {
                    break;
                }
            }
        }
    
        return NRF_SUCCESS;
    }

    They function essentially the same as the normal "nrf_gfx_print" and "write_character" functions except they utilize the frame buffer to (almost always) print characters to the screen much faster than the normal functions do. I also need to pass in a second color so I know what to draw the 0s in the font's bitmap as, and since I am using a consistent and known background color this was never a problem for me. Not intentionally, this also now erases the previous pixels drawn behind any new text which allows me to repeatedly write characters in the same positions without needing to erase the area before (some characters are wide enough that they aren't completely erased when drawing a new one, but by drawing a small rectangle just offset from the character's position I was easily able to clean these up). Now I am able to set the address window then sent a continuous stream of data to the LCD's frame buffer which offsets needing to send data for every single bit. This method will set the address window once (11B) and then send color data for each pixel (2B/pixel) as opposed to sending doing both on every pixel that's a 1 in the font's bitmap (13B/pixel). I did a test calculation on paper first then set up a spreadsheet to try to calculate the number of bytes each method would need to send over SPI to write the character.

    For light characters with few pixels like "1" or "I" they are roughly even. For yuGothic_42pt font's "1" the default method should take ~2.6kB (202 pixels * 13B/pixel = 2,626B). Using the method I implemented the same character would take ~2.3kB (1,152 pixels * 2B/pixel + 11B = 2,315B). This also gave some of the best benefit of the doubt to the default method by using a very thin font and character for the test. When I tested with heavier fonts with a lot of pixels the difference became very significant. For arial_bold_72pt font I imported the hexadecimal bitmap data for the character "8", found out which pixels were 1s and which were 0s (tables on the upper right) to make sure that it was the same data I was getting from the printf statements in my "fast" functions, and summed them for the total number of pixels being drawn. The number of pixels my implementation draws is just the area of the bitmap, width times height, in pixels.

     

    Mine sends ~18k fewer bytes over SPI to draw the same character, resulting in barely if any flashing of basically any character I draw.

    I also couldn't quite figure out exactly how the function "nrf_gfx_bmp565" was expecting me to pass data to it and I was unable to find an example of the function in action so I made an alternate version to work with data arrays created out of bitmap images from a generator I downloaded online. The converter is "ImageConverter 565 v2.2" from https://www.14core.com/c-array-generator/ and the bitmaps were PNGs made in paint saved as .bmp files then opened in the converter. I made separate headers for different sizes that included the unsigned char arrays that I passed to my own function "nrf_gfx_bmp565_draw_big_endian" which I didn't really bother optimizing by also utilizing the frame buffer since these will be drawn to the screen once but the idea is similar to the "write_character_fast" function. 

    ret_code_t nrf_gfx_bmp565_draw_big_endian(nrf_lcd_t const * p_instance,   //LCD screen type instance
                                              nrf_gfx_rect_t const * p_rect,  //Rectangle object
                                              uint16_t const * img_buf)       //Array of color data
    {
        ASSERT(p_instance != NULL);
        ASSERT(p_instance->p_lcd_cb->state != NRF_DRV_STATE_UNINITIALIZED);
        ASSERT(p_rect != NULL);
        ASSERT(img_buf != NULL);
    
        if ((p_rect->x > nrf_gfx_width_get(p_instance)) || (p_rect->y > nrf_gfx_height_get(p_instance)))
        {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        //size_t idx;                           //
        uint16_t idx = 0;                        //Offset into data array
        uint16_t pixel;                         //Color to write to pixel
        //uint8_t padding = p_rect->width % 2;  //No padding
    
        for (int32_t i = 0; i < p_rect->height; i++)
        {
            for (uint32_t j = 0; j < p_rect->width; j++)
            {
                //idx = (uint32_t)((p_rect->height - i - 1) * (p_rect->width + padding) + j);
    
                //pixel = img_buf[idx]; //Color stored in Big Endian
    
                pixel_draw(p_instance, p_rect->x + j, p_rect->y + i, img_buf[idx]);
    
                idx++;
            }
        }
    
        return NRF_SUCCESS;
    }

    I doubt these are 100% correctly implemented with the biggest issue being if I try to print a string fast to the screen and its address window goes out of the screen's boundaries my NRF52 hard faults, which I'm sure could be prevented by adding in a check like a bunch of other NRF GFX functions seem to use. For now though, I have the 240x240 screen updating significantly faster than before, especially with the fast SPI library, such that the characters appear to change instantly from one to the next almost every time and updating the entire XX:XX clock happens so fast you can't see any characters wiping just like they all change back to back very fast. Since this is working well enough for my needs in this project I'm going to consider this part essentially done and move onto other parts of the same project and mark this ticket as answered. I ended up NOT implementing the Adafruit library because when I looked around it looked like A) it would require including a LOT of additional stuff from Arduino libraries that I didn't want to use or have taking up space and B) it seemed to also suffer from some speed issues that I was already experiencing with Nordic's libraries and seemed like it would probably have ended up being a large hassle to wind up in the exact same position.

  • Also I added in that the frame buffer method only almost always writes faster because in specific situations the default NRF GFX method will be faster, specifically for drawing character with small numbers of pixels like very thin and small fonts or, most regularly, punctuation. Since something like a colon has so few pixels drawn relative to its total area, even when sending 6.5 times as much data per pixel if the number of pixels being drawn is less than the character's (width*height*8pixels/B / 6.5) it will end up sending fewer total bytes over SPI. I still use the default "nrf_gfx_print" function to draw certain small texts and characters like the clock's colon because of this. It's just that the default Nordic libraries were not efficient at drawing large characters to the screen like I needed to do.

Reply
  • Also I added in that the frame buffer method only almost always writes faster because in specific situations the default NRF GFX method will be faster, specifically for drawing character with small numbers of pixels like very thin and small fonts or, most regularly, punctuation. Since something like a colon has so few pixels drawn relative to its total area, even when sending 6.5 times as much data per pixel if the number of pixels being drawn is less than the character's (width*height*8pixels/B / 6.5) it will end up sending fewer total bytes over SPI. I still use the default "nrf_gfx_print" function to draw certain small texts and characters like the clock's colon because of this. It's just that the default Nordic libraries were not efficient at drawing large characters to the screen like I needed to do.

Children
  • Hi Andy, would you be so kind and share your final version. We try to use a display with ST7789 aswell. Thanks

  • Hi Constantin,

    Here's some code from when I was working with drawing two different examples onto the screen: ST7789 test code.zip .

    Since I modified my gfx library to make the screen work how I wanted it to it would be a good idea to copy the original, unmodified library from your SDK and keep it somewhere in case you need to modify the library even further and then something breaks. There were still a few bugs in my code, such as the program crashing if text would be drawn out of bounds vertically instead of just returning in that function. Sorry it's a few days late but I hope this helps you out.

  • Hi Andy,

    thank you for sharing this! I am also using the ST7789 display driver and your test code helped me a lot. I resolved a bug, where a display rotation of 0° was not working with a 240x240 display. It is quite simple, you have to edit the file st7789.c at three points.

    After

    //Position setting for 240x240 LCD
    #define ST7789_240x240_XSTART 0
    #define ST7789_240x240_YSTART 80

    insert the variables storing the offset:

    uint16_t x_offset = 0;
    uint16_t y_offset = 0;

    in function set_addr_window, 4 lines have to be added where the offset is added to the address window:

    static void set_addr_window(uint16_t x_0, uint16_t y_0, uint16_t x_1, uint16_t y_1)
    {
        ASSERT(x_0 <= x_1);
        ASSERT(y_0 <= y_1);
    
        // Adaption for rotation
        y_0 += y_offset;
        y_1 += y_offset;
        x_0 += x_offset;
        x_1 += x_offset;    
    
        write_command(ST7789_CASET);
        write_data(x_0 >> 8);
        write_data(x_0);
        write_data(x_1 >> 8);
        write_data(x_1);
        write_command(ST7789_RASET);
        write_data(y_0 >> 8);
        write_data(y_0);
        write_data(y_1 >> 8);
        write_data(y_1);
        write_command(ST7789_RAMWR);
    }

    and in function st7789_rotation_set the offset has to be set:

    static void st7789_rotation_set(nrf_lcd_rotation_t rotation)
    {
        write_command(ST7789_MADCTL);
        switch (rotation % 4) {
            case NRF_LCD_ROTATE_0:
                write_data(ST7789_MADCTL_MX | ST7789_MADCTL_MY | ST7789_MADCTL_RGB);  //Not working correctly
                //Column address (MX): Right to left
                //Page address (MY): Bottom to top
                //Page/ Column order (MV): normal
                //RGB/BGR order: RGB
                x_offset = 0;
                y_offset = ST7789_240x240_YSTART;
                break;
            case NRF_LCD_ROTATE_90:
                write_data(ST7789_MADCTL_MV | ST7789_MADCTL_RGB);
                //Column address (MX): Left to right
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): reverse
                //RGB/BGR order: RGB
                x_offset = 0;
                y_offset = 0;
                break;
            case NRF_LCD_ROTATE_180:
                write_data(ST7789_MADCTL_RGB);
                //Column address (MX): Left to right
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): normal
                //RGB/BGR order: RGB
                x_offset = 0;
                y_offset = 0;
                break;
            case NRF_LCD_ROTATE_270:
                write_data(ST7789_MADCTL_MX | ST7789_MADCTL_MV | ST7789_MADCTL_RGB);
                //Column address (MX): Right to left
                //Page address (MY): Top to bottom
                //Page/ Column order (MV): reverse
                //RGB/BGR order: RGB
                x_offset = 0;
                y_offset = 0;
                break;
            default:
                break;
        }
    }

    That's it!

    Maybe you already solved it yourself, but for future users this could be helpful.

    Greetings

    Walther

Related