olikraus / u8g2

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

Tips on making software SPI on STM32 faster with pcd8544 (Nokia 5110 LCD) #1986

Closed mm40 closed 1 year ago

mm40 commented 1 year ago

I'm using a stm32l053r8 nucleo board, and experimenting with low frequencies. System clock is 1048 KHz.

I'm comparing the u8g2 library to this bare bones library, consisting of files nokia5110_LCD.c and nokia5110_LCD.h, and the u8g2 appears to be slower out of the box. I'm looking for tips to make it faster.

I don't notice any difference between full buffer and page buffer modes. Used full buffer in this example.

u8g2_gpio_and_delay_stm32 ```c uint8_t u8g2_gpio_and_delay_stm32(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr) { switch (msg) { // Initialize SPI peripheral case U8X8_MSG_GPIO_AND_DELAY_INIT: /* HAL initialization contains all what we need so we can skip this part. */ break; // Function which implements a delay, arg_int contains the amount of ms case U8X8_MSG_DELAY_MILLI: HAL_Delay(arg_int); break; // Function which delays 10us case U8X8_MSG_DELAY_10MICRO: for (uint16_t n = 0; n < 320; n++) { __NOP(); } break; // Function which delays 100ns case U8X8_MSG_DELAY_100NANO: __NOP(); break; // Function to define the logic level of the clockline case U8X8_MSG_GPIO_SPI_CLOCK: HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, arg_int ? SET : RESET); break; // Function to define the logic level of the data line to the display case U8X8_MSG_GPIO_SPI_DATA: HAL_GPIO_WritePin(DIN_GPIO_Port, DIN_Pin, arg_int ? SET : RESET); break; // Function to define the logic level of the CS line case U8X8_MSG_GPIO_CS: HAL_GPIO_WritePin(CE_GPIO_Port, CE_Pin, arg_int ? SET : RESET); break; // Function to define the logic level of the Data/ Command line case U8X8_MSG_GPIO_DC: HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, arg_int ? SET : RESET); break; // Function to define the logic level of the RESET line case U8X8_MSG_GPIO_RESET: HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, arg_int ? SET : RESET); break; default: return 0; // A message was received which is not implemented, return 0 to // indicate an error } return 1; // command processed successfully. } ```
Constructor and init ```c u8g2_Setup_pcd8544_84x48_f(&u8g2, U8G2_R0, u8x8_byte_4wire_sw_spi, u8g2_gpio_and_delay_stm32); u8g2_InitDisplay(&u8g2); u8g2_SetPowerSave(&u8g2, 0); ```

Other library init ```c LCD_setRST(RST_GPIO_Port, RST_Pin); LCD_setCE(CE_GPIO_Port, CE_Pin); LCD_setDC(DC_GPIO_Port, DC_Pin); LCD_setDIN(DIN_GPIO_Port, DIN_Pin); LCD_setCLK(CLK_GPIO_Port, CLK_Pin); LCD_init(); ```

The following 2 blocks of code I'm executing one after another in the video. It starts after the first block was already executed once.

The code executed to paint the black rectangle in u8g2:

u8g2_ClearBuffer(&u8g2);
u8g2_DrawBox(&u8g2, 0, 0, 84, 48);
u8g2_SendBuffer(&u8g2);

The code executed to paint text by other library:

LCD_print("Hello my World", 0, 0);
LCD_print("Hello my World", 3, 1);
LCD_print("Hello my World", 5, 2);
LCD_print("Hello my World", 7, 3);
LCD_print("Hello my World", 9, 4);
LCD_print("Hello my World", 11, 5);

https://user-images.githubusercontent.com/75584780/196167013-2e5fb494-0c1f-4b2c-971a-33ba0193631e.mp4

olikraus commented 1 year ago

The problem is "u8x8_byte_4wire_sw_spi". The code is here: https://github.com/olikraus/u8g2/blob/d987608d40284f8efc75da202308442f953ad431/csrc/u8x8_byte.c#L107-L137

Idea 1: Use Hardware SPI

As you can see, the bytes are sent bit by bit which is very slow. This reason is simple: U8g2 does not know how to use your SPI subsystem. This means: You need to create a new byte sending function. Instructions how to create such a custom callback are here: https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform#hardware-spi-communication

Idea 2: Accelerate "u8x8_byte_4wire_sw_spi"

Lets have a look to the referenced code https://github.com/Zeldax64/Nokia-LCD5110-HAL/blob/39a73f22f445d0bc1a4e77f100d0449820e2485a/src/nokia5110_LCD.c#L43-L52

You could create a copy of "u8x8_byte_4wire_sw_spi" and insert the code from your referenced code.

Currently it looks like this

        b = *data; 
    data++; 
    arg_int--; 
    for( i = 0; i < 8; i++ ) 
    { 
      if ( b & 128 ) 
        u8x8_gpio_SetSPIData(u8x8, 1); 
      else 
        u8x8_gpio_SetSPIData(u8x8, 0); 
      b <<= 1; 

      u8x8_gpio_SetSPIClock(u8x8, not_takeover_edge); 
      u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->sda_setup_time_ns); 
      u8x8_gpio_SetSPIClock(u8x8, takeover_edge); 
      u8x8_gpio_Delay(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->sck_pulse_width_ns); 
    }     

This could be modified like this...

        b = *data; 
    data++; 
    arg_int--; 
        for(i = 0; i < 8; i++){
          HAL_GPIO_WritePin(lcd_gpio.DINPORT, lcd_gpio.DINPIN, !!(b & (1 << (7 - i))));
          HAL_GPIO_WritePin(lcd_gpio.CLKPORT, lcd_gpio.CLKPIN, GPIO_PIN_SET);
          HAL_GPIO_WritePin(lcd_gpio.CLKPORT, lcd_gpio.CLKPIN, GPIO_PIN_RESET);
        }
mm40 commented 1 year ago

That's amazing. You hit the nail on the head and saved me a lot of time with idea 2.

Finished u8x8_byte_4wire_sw_spi Note : `XXX_GPIO_Port` and `XXX_Pin` were renamed in Stm32cubeIDE. ```c uint8_t u8x8_byte_4wire_sw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { uint8_t i, b; uint8_t *data; uint8_t takeover_edge = u8x8_GetSPIClockPhase(u8x8); uint8_t not_takeover_edge = 1 - takeover_edge; switch(msg) { case U8X8_MSG_BYTE_SEND: data = (uint8_t *)arg_ptr; while( arg_int > 0 ) { b = *data; data++; arg_int--; for(i = 0; i < 8; i++){ HAL_GPIO_WritePin(DIN_GPIO_Port, DIN_Pin, !!(b & (1 << (7 - i)))); HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET); } } break; case U8X8_MSG_BYTE_INIT: /* disable chipselect */ u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level); /* no wait required here */ /* for SPI: setup correct level of the clock signal */ u8x8_gpio_SetSPIClock(u8x8, u8x8_GetSPIClockPhase(u8x8)); break; case U8X8_MSG_BYTE_SET_DC: u8x8_gpio_SetDC(u8x8, arg_int); break; case U8X8_MSG_BYTE_START_TRANSFER: u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_enable_level); u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->post_chip_enable_wait_ns, NULL); break; case U8X8_MSG_BYTE_END_TRANSFER: u8x8->gpio_and_delay_cb(u8x8, U8X8_MSG_DELAY_NANO, u8x8->display_info->pre_chip_disable_wait_ns, NULL); u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level); break; default: return 0; } return 1; } ```

I'll try the hardware solution, and I'll keep this issue opened for a few days if you don't mind, to update what I did and ask any follow-up questions.

https://user-images.githubusercontent.com/75584780/196424573-d53c061e-e1fb-4e31-a39d-0dbd0996ea7f.mp4

mm40 commented 1 year ago

Finally, hardware SPI nailed it.

Constructor :

u8g2_Setup_pcd8544_84x48_f(&u8g2, U8G2_R0, u8x8_byte_4wire_hw_spi, u8g2_gpio_and_delay_stm32);

Toggling between

u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_7x14_mr);
u8g2_DrawStr(&u8g2, 0, 15, "Hello World!");
u8g2_DrawCircle(&u8g2, 50, 30, 8, U8G2_DRAW_ALL);
u8g2_SendBuffer(&u8g2);

And :

u8g2_ClearBuffer(&u8g2);
u8g2_DrawBox(&u8g2, 0, 0, 84, 48);
u8g2_SendBuffer(&u8g2);

https://user-images.githubusercontent.com/75584780/196465442-e021048f-fe75-4fd6-b755-3af052287004.mp4

olikraus commented 1 year ago

Nice πŸ‘

olikraus commented 1 year ago

Maybe you can share your byte procedure so that others can learn from it.

olikraus commented 1 year ago

Oh, it's there already. Thanks a lot for sharing 😊