olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
5.14k stars 1.05k forks source link

slow update rate on st7920 128x64 (4 wires spi) #1289

Closed Pacimani closed 4 years ago

Pacimani commented 4 years ago

Hi Oli, I am using u8g2 on a small project(with atnmega328p) in Atmel studio, and the LCD is updating very slow. Is there a way that I can increase the update speed? Thanks

olikraus commented 4 years ago

You probably need to implement hardware SPI: https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform#atmel-avr

My own example is a pure software solution which drives the data / clock lines manually via port commands: https://github.com/olikraus/u8g2/blob/master/sys/avr/as7/main.c#L112

Instead you would need an implementation which will use the SPI subsystem of your controller. Something like this: https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform#hardware-spi-communication but specific to your target controller.

Pacimani commented 4 years ago

Thanks for your prompt response Oli. I see the example, this is pretty involving. I am new to Atmel studio, and honestly, I don't know how and where to start. I implement an IMU in my project such that the LCD rotates from 0 to 360 degree based on the acceleration due to gravity on the device; to achieve this, I am using the u8g2_SetDisplayRotation and u8g3_DrawFame(to go into different mode on LCD with different contents on screen). I believe this is the reason why the update mode is pretty slow. Any help would be appreciated.

olikraus commented 4 years ago

Not sure how I can help here

olikraus commented 4 years ago

See the third link in my first reply as starting point to improve speed.

Pacimani commented 4 years ago

Thank you for your help Oli. Between these two function, which one can I implement (or change) uint8_t u8x8_avr_gpio_and_delay(u8x8_t u8x8, uint8_t msg, uint8_t arg_int, void arg_ptr) uint8_t u8x8_avr_delay(u8x8_t u8x8, uint8_t msg, uint8_t arg_int, void arg_ptr)

This is the README, This directory contains code that can be shared across all AVR uC, when developing with https://www.nongnu.org/avr-libc/.

Please refer to https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform to understand what is here.

At lib/u8x8_avr.h, you can find:

u8x8_byte_avr_hw_spi
    Implements HW SPI communication. To use it, you're required to define SCK and MOSI ports externally (see example Makefiles).
u8x8_avr_delay
    Implements the delay functions that are required by the GPIO and Delay callback function.

This means, you have to write little code to support your own project.

Please refer to the existing main.c and Makefile examples for details on how to use it.

I visited the link in the README but there is no place where I can see this _lib/u8x8_avr.h_

Pacimani commented 4 years ago

Hi Oli, I am sorry to bother you again.I found the library and edited it to suit my board How can I use now? Thanks.

olikraus commented 4 years ago

Looks like, i missed your comment 5 days back.

I found the library and edited it to suit my board How can I use now? Thanks.

Not sure what you mean by "library", but if you have the neccessary byte and deley functions, then you can just use this sequence:

u8g2_t u8g2; // a structure which will contain all the data for one display
...
u8g2_Setup_ssd1306_i2c_128x64_noname_2(&u8g2, U8G2_R0, my_byte_fn, my_delay_fn);  // init u8g2 structure
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
u8g2_SetPowerSave(&u8g2, 0); // wake up display

Of course you have to choose your own setup procedure for your display. They are all listed here: https://github.com/olikraus/u8g2/wiki/u8g2setupc

Also note that there are details on seting up the display here: https://github.com/olikraus/u8g2/wiki/u8g2setupc#introduction

Pacimani commented 4 years ago

Thank you very much. Made it!

olikraus commented 4 years ago

:-)

fedcard87 commented 4 years ago

@Pacimani hey how did you go about doing this, im currently struggling with the same concept, did you just alter the u8x8_byte_avr_hw_spi and u8x8_avr_delay functions?

fedcard87 commented 4 years ago

@olikraus Hi, im trying to do the same thing as @Pacimani did, but im trying to translate the arduino code for the hardwire spi communication from https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform#atmel-avr and i have no idea how to translate this to avr. Any help would be great. Ive currently set up the GPIO and Delay callback for my specific LCD st7920

Pacimani commented 4 years ago

Hi, It pretty easy to do it. If you are using atmega328p which was in my case, the pins are already defined. Just comment out the pin definition in .c file and you can get rid of I2C codes as well since you don't need it. Then you can follow the example in main

fedcard87 commented 4 years ago

@Pacimani sorry which .c file and which main file? we are using atmega328P as well If you could could you show us in person? we are also apart of engg

fedcard87 commented 4 years ago

@Pacimani, im more so confused on how to tranlsate this to avr for the atmega

xtern "C" uint8_t u8x8_byte_arduino_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
  uint8_t *data;
  uint8_t internal_spi_mode; 
  switch(msg) {
    case U8X8_MSG_BYTE_SEND:
      data = (uint8_t *)arg_ptr;
      while( arg_int > 0 ) {
        SPI.transfer((uint8_t)*data);
        data++;
        arg_int--;
      }  
      break;
    case U8X8_MSG_BYTE_INIT:
      u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
      SPI.begin();
      break;
    case U8X8_MSG_BYTE_SET_DC:
      u8x8_gpio_SetDC(u8x8, arg_int);
      break;
    case U8X8_MSG_BYTE_START_TRANSFER:
      /* SPI mode has to be mapped to the mode of the current controller, at least Uno, Due, 101 have different SPI_MODEx values */
      internal_spi_mode =  0;
      switch(u8x8->display_info->spi_mode) {
        case 0: internal_spi_mode = SPI_MODE0; break;
        case 1: internal_spi_mode = SPI_MODE1; break;
        case 2: internal_spi_mode = SPI_MODE2; break;
        case 3: internal_spi_mode = SPI_MODE3; break;
      }
      SPI.beginTransaction(SPISettings(u8x8->display_info->sck_clock_hz, MSBFIRST, internal_spi_mode));
      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);
      SPI.endTransaction();
      break;
    default:
      return 0;
  }  
  return 1;
}
fedcard87 commented 4 years ago

@Pacimani , ive got this for my avr_delay and gpio_delay

uint8_t u8x8_avr_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    uint8_t cycles;

    switch(msg)
    {
        case U8X8_MSG_DELAY_NANO:     // delay arg_int * 1 nano second
            // At 20Mhz, each cycle is 50ns, the call itself is slower.
            break;
        case U8X8_MSG_DELAY_100NANO:    // delay arg_int * 100 nano seconds
            // Approximate best case values...
#define CALL_CYCLES 26UL
#define CALC_CYCLES 4UL
#define RETURN_CYCLES 4UL
#define CYCLES_PER_LOOP 4UL

            cycles = (100UL * arg_int) / (P_CPU_NS * CYCLES_PER_LOOP);

            if(cycles > CALL_CYCLES + RETURN_CYCLES + CALC_CYCLES) 
                break;

            __asm__ __volatile__ (
            "1: sbiw %0,1" "\n\t" // 2 cycles
            "brne 1b" : "=w" (cycles) : "0" (cycles) // 2 cycles
            );
            break;
        case U8X8_MSG_DELAY_10MICRO:    // delay arg_int * 10 micro seconds
            for(int i=0 ; i < arg_int ; i++)
                _delay_us(10);
            break;
        case U8X8_MSG_DELAY_MILLI:      // delay arg_int * 1 milli second
            for(int i=0 ; i < arg_int ; i++)
                _delay_ms(1);
            break;
        default:
            return 0;
    }
    return 1;
}

uint8_t u8x8_avr_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    // Re-use library for delays
    switch(msg)
    {
        case U8X8_MSG_GPIO_AND_DELAY_INIT:  // called once during init phase of u8g2/u8x8
            DISPLAY_CLK_DIR |= 1<<DISPLAY_CLK_PIN;
            DISPLAY_DATA_DIR |= 1<<DISPLAY_DATA_PIN;
            DISPLAY_CS_DIR |= 1<<DISPLAY_CS_PIN;
            DISPLAY_RESET_DIR |= 1<<DISPLAY_RESET_PIN;
            break;              // can be used to setup pins
        case U8X8_MSG_GPIO_SPI_CLOCK:        // Clock pin: Output level in arg_int
            if(arg_int)
                DISPLAY_CLK_PORT |= (1<<DISPLAY_CLK_PIN);
            else
                DISPLAY_CLK_PORT &= ~(1<<DISPLAY_CLK_PIN);
            break;
        case U8X8_MSG_GPIO_SPI_DATA:        // MOSI pin: Output level in arg_int
            if(arg_int)
                DISPLAY_DATA_PORT |= (1<<DISPLAY_DATA_PIN);
            else
                DISPLAY_DATA_PORT &= ~(1<<DISPLAY_DATA_PIN);
            break;
        case U8X8_MSG_GPIO_CS:        // CS (chip select) pin: Output level in arg_int
            if(arg_int)
                DISPLAY_CS_PORT |= (1<<DISPLAY_CS_PIN);
            else
                DISPLAY_CS_PORT &= ~(1<<DISPLAY_CS_PIN);
            break;
        case U8X8_MSG_GPIO_RESET:     // Reset pin: Output level in arg_int
            if(arg_int)
                DISPLAY_RESET_PORT |= (1<<DISPLAY_RESET_PIN);
            else
                DISPLAY_RESET_PORT &= ~(1<<DISPLAY_RESET_PIN);
            break;
        default:
            if (u8x8_avr_delay(u8x8, msg, arg_int, arg_ptr))    // check for any delay msgs
                return 1;
            u8x8_SetGPIOResult(u8x8, 1);      // default return value
            break;
    }
    return 1;
}
Pacimani commented 4 years ago

This is the place where you can find the codes for hardware spi, you don't need to convert Arduino to avr. https://github.com/olikraus/u8g2/tree/master/sys/avr/avr-libc/lib

Here is the example in the main. You can get rid of the codes you currently have and use these three functions.(the one in the main as your delay and the two function is the library above). also get rid of I2C codes. https://github.com/olikraus/u8g2/blob/master/sys/avr/avr-libc/atmega328p/main.c