olikraus / u8g2

U8glib library for monochrome displays, version 2
Other
4.91k stars 1.03k forks source link

Slow drawing speed for SSD1309 128x64 with HW SPI ATMega328p #2342

Closed mumrah closed 5 months ago

mumrah commented 6 months ago

Hello, thanks for this incredible library! It has saved me a ton of SRAM :)

I've run into some trouble with the drawing speed and I was wondering if there's anything obvious I'm doing wrong.

My microcontroller is an ATMega328p running at 8MHz.

I'm using this constructor U8G2_SSD1309_128X64_NONAME0_2_4W_HW_SPI u8g2(U8G2_R2, 10, 9);

I recently switched from using the blocking analogRead method to using interrupts for ADC reads. I am using Timer1 for this purpose. I noticed after this switch, things went wrong with my OLED display. Initially, I was updating the OLED in between analogRead calls as part of my main loop. The display was being refresh once every few hundred milliseconds and things looked smooth.

Now that I'm using interrupts, the display seems slow to draw and eventually it stops updating completely. I added some code to time the UI drawing (shown below) and its taking around ~190ms for each update (so, ~95ms per page)

I'm wondering if this could be related to the new interrupt code interfering with the SPI timing, or just generally slowing down the execution of my code. Or maybe my use of a Timer1 is throwing something off?

Any ideas?

Here is my setup code:

  // Enable ADC and interrupt vector
  sbi(ADCSRA, ADEN);
  sbi(ADCSRA, ADIE);
  sbi(ADCSRA, ADIF);
  sbi(ADCSRA, ADATE);

  // Set ADC pre-scaler to 128 for 126kHz ADC clock
  sbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);

  // Timer1 Compare Match B
  sbi(ADCSRB, ADTS0);
  sbi(ADCSRB, ADTS2);

  // External AREF
  cbi(ADMUX, REFS0);
  cbi(ADMUX, REFS1);

  sbi(PCICR, PCIE0);        // Enable pin change interrupts on port b
  sbi(PCMSK0, PCINT4);      // Set pin change mask for PB4 (D12, pin 18, MISO)

  sbi(PCICR, PCIE2);        // Enable pin change interrupts on port d
  sbi(PCMSK2, PCINT22);     // Set pin change mask for PD6

  // Timer1 for ADC sampling
  // http://www.8bit-era.cz/arduino-timer-interrupts-calculator.html
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;

  sbi(TCCR1B, WGM12);       // turn on CTC mode
  sbi(TIMSK1, OCIE1B);      // enable timer compare interrupt
  sbi(TCCR1B, CS11);        // prescaler 8   
  OCR1A = 19999;            // 50Hz
  OCR1B = 19999;
  sei();

and my main draw routine

void display_main(UIState * ui, unsigned long now, UserEvent event) {
  if (event == SCROLL_RIGHT) {
    ui->display_choice ++;
  } else if (event == SCROLL_LEFT) {
    ui->display_choice --;
  } else if (event == BUTTON_PRESS) {
    ui->config_mode_enabled = true;
  }

#ifdef ADC_DEBUG
  dtostrf(supplyVoltage->sensorValue, 0, 0, ui->message_line_1);
  dtostrf(supplyVoltage->filter, 0, 0, ui->message_line_2);
#endif
  uint8_t display_choice = ui->display_choice % 5;

  switch (display_choice) {
    case 0: // Battery
      dtostrf(Sensor_smoothedValueFast(&batteryVoltage), 4, 2, ui->buffer_1);
      break;
    case 1: // Supply
      dtostrf(Sensor_smoothedValueFast(&supplyVoltage), 4, 2, ui->buffer_1);
      break;
    case 2: // Current
      dtostrf(round_double(Sensor_smoothedValueFast(&currentSense), 1), 4, 1, ui->buffer_1);
      break;
      case 3: // Charge
      dtostrf(round_double(Sensor_smoothedValueFast(&chargeSense), 1), 4, 1, ui->buffer_1);
      break;
    case 4: // Temp
      dtostrf(temperature, 4, 2, ui->buffer_1);
      break;
    default:
      break;
  }

  // Figure out what to display in lines 1 and 2.
  if (ui->display_error) {
    if ((now - ui->last_error_display) > 3000) {
      strcpy_P(ui->message_line_1, error_0);
      //strcpy_P(ui->message_line_2, error_0);
      strcpy_P(ui->message_line_2, (char*)pgm_read_word(&(states[state])));
      ui->display_error = false;
    }
  } else {
    // If we're not displaying an error and one has been set, load it and display it
    if (ui->error_code != NoMessage) {
      ui->last_error_display = now;
      ui->display_error = true;
      ui->error_code = NoMessage;
      strcpy_P(ui->message_line_1, (char*)pgm_read_word(&(error_messages[ui->error_code])));
      //strcpy_P(ui->message_line_2, error_0);
      strcpy_P(ui->message_line_2, (char*)pgm_read_word(&(states[state])));
    }
  }

  t1 = micros();  
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_profont11_tr);   // choose a suitable font   
    switch (display_choice) {
      case 0: // Battery
        u8g2.drawStr(0, 7, "Battery Voltage");  
        break;
      case 1: // Supply
        u8g2.drawStr(0, 7, "Supply Voltage");   
        break;
      case 2: // Current
        u8g2.drawStr(0, 7, "Load Current"); 
        break;
       case 3: // Charge
        u8g2.drawStr(0, 7, "Charge Current");      
        break;
      case 4: // Temp
        u8g2.drawStr(0, 7, "Temperature");  
        break;
      default:
        break;
    }

    // Side info
    u8g2.drawStr(100, 7, "RPi"); // RPi power switch
    if (!rpi.switch_state) {
      u8g2.drawXBMP(122, 0, 6, 7, check_s);
    } else {
      u8g2.drawXBMP(122, 0, 6, 7, x);
    }

    u8g2.drawStr(100, 19, "OS"); // Linux running
    if (!rpi.rpi_poweroff_active) {
      u8g2.drawXBMP(122, 12, 6, 7, check_s);  
    } else {
      u8g2.drawXBMP(122, 12, 6, 7, x);
    }

    u8g2.drawStr(100, 31, "Py"); // Agent
    if (last_heartbeat == 0) {
      u8g2.drawXBMP(122, 24, 6, 7, interrogative); 
    } else if ((now - last_heartbeat) < 10000) {
      u8g2.drawXBMP(122, 24, 6, 7, check_s); 
    } else {
      u8g2.drawXBMP(122, 24, 6, 7, x); 
    }

    u8g2.drawStr(100, 43, "BPQ");
    if (last_heartbeat == 0 || (now - last_heartbeat) > 10000) {
      u8g2.drawXBMP(122, 36, 6, 8, interrogative);  // BPQ
    } else if (bpq_running) {
      u8g2.drawXBMP(122, 36, 6, 8, check_s);
    } else {
      u8g2.drawXBMP(122, 36, 6, 8, x);
    }

    //u8g2.drawStr(0, 56, "YOUR AD");
    //u8g2.drawStr(0, 64, "GOES HERE");

    u8g2.drawStr(0, 44, "PSU");
    u8g2.drawStr(70, 44, "BATT");

    display_switch_states(now, state);

    if (ui->headless_debug_mode_enabled) {
      u8g2.drawPixel(127, 63);
    }

    // Big text
    u8g2.setFont(u8g2_font_profont29_mn);
    u8g2.drawStr(0, 30, ui->buffer_1);

    //if (is_supply_connected(state) || is_battery_connected(state)) {
      u8g2.drawXBMP(38, 32, 14, 12, radio_glyph);
    //}

    // Battery icons, for now display the PWM value
    display_battery(now, state);

    u8g2.drawHLine(0, 46, 128);
    u8g2.drawVLine(96, 0, 46);

    // Display a message
    u8g2.setFont(u8g2_font_profont11_tr);

    //dtostrf(pid.pid_output, 0, 0, ui->message_line_2);
    /*
    memset(ui->message_line_2, 0, 21);
    memset(ui->buffer_1, 0, 21);

    itoa(rpi.ip_address[0], ui->buffer_1, 10);
    strcat(ui->message_line_2, ui->buffer_1);
    strcat(ui->message_line_2, DOT);
    itoa(rpi.ip_address[1], ui->buffer_1, 10);
    strcat(ui->message_line_2, ui->buffer_1);
    strcat(ui->message_line_2, DOT);
    itoa(rpi.ip_address[2], ui->buffer_1, 10);
    strcat(ui->message_line_2, ui->buffer_1);
    strcat(ui->message_line_2, DOT);
    itoa(rpi.ip_address[3], ui->buffer_1, 10);
    strcat(ui->message_line_2, ui->buffer_1);    
    */

    u8g2.drawStr(0, 56, ui->message_line_1);
    u8g2.drawStr(0, 64, ui->message_line_2);
  } while (u8g2.nextPage());

  t2 = micros();
  ui_us += (t2 - t1);
}
olikraus commented 6 months ago

U8g2 loop looks good. Interrupts might be an issue, but my ATMega knowledge is not that good. I see three interrupts enabled, maybe you should disable them step by step and see whether the performance of the display improves.

U8g2 performance improvement ideas:

mumrah commented 6 months ago

I've managed to make marginal improvements by removing some unnecessary draw calls (like when the data hasn't changed).

If possible, I want to avoid things like this:

image

where part of the old buffer is visible while the new buffer is being drawn. Maybe this is unavoidable?

Is it possible to only update portions of the display when using the paged memory mode?

olikraus commented 6 months ago

If possible, I want to avoid things like this:

It looks like, that your content changes during execution of the u8g2 while loop. I mean, "u8g2.drawStr(0, 30, ui->buffer_1);" seems to change although I don't see the reason for this in your code.

mumrah commented 5 months ago

After a lot of trial and error, I've gotten things working better.

One big help was to remove unnecessary calls to

  u8g2.drawStr(0, 56, ui->message_line_1);
  u8g2.drawStr(0, 64, ui->message_line_2);

(these are only populated in certain cases)

Another issue I kept having was that the display would blank out after a while. I believe this was due to the timing of the interrupt code. My interrupt was firing at 50Hz and I guess if it runs too long, it disrupts the SPI communication. I've optimized my interrupt code and now it's running at 500Hz and the UI is no slower (and doesn't crash).

I'm still seeing draw times around 600-700ms total using paged mode. I never really got it to be much faster without removing UI elements. I guess I'm asking a lot of this ATMega :)

In the future, I'll be trying a higher clock speed for the ATMega328p. Currently I'm running at 8Mhz. I'm hoping 12 or 16Mhz will make the UI a lot snappier.

Thanks for the help!