lexus2k / ssd1306

Driver for SSD1306, SSD1331, SSD1351, IL9163, ILI9341, ST7735, PCD8544, Nokia 5110 displays running on Arduino/ESP32/Linux (Rasperry) platforms
MIT License
664 stars 127 forks source link

low power mode #103

Closed philippedc closed 4 years ago

philippedc commented 4 years ago

Hi Aleksei, I'm wondering if there is a better way than _ssd1306_displayOff()_ to put the oled display in low power mode when the Attiny85 is in sleep mode ? Strangely I have 3 identical circuits based around an Attiny85, a BME280 and an oled SSD1306. In sleep mode I have one with 0.22mA, another with 0.25mA and one with 0.011mA only. I cannot understand why I get 20 time less power for this last one, except the oled itself.

lexus2k commented 4 years ago

Hi Philipp,

0xAE command is the only way to put ssd1306 display to sleep mode. According to datasheet in sleep mode ssd1306 controller power consumption should be less than 10uA for Vdd (logic power), and less than 10uA for Vcc (panel power) without panel attached. That means, that values from datasheet depend on display panel manufacturer. But anyway OLED display panel should not consume itself much power, if no pixels are ON. For display ON modes ssd1306 datasheet says that logic consumption is upto 150uA, and panel consumption is upto 780uA. Maybe, on 2 circuits the display actually doesn't go to sleep more for some reason?

Another thing: how did you implement pull up for i2c lines in your schematics?

philippedc commented 4 years ago

Many Thanks for your quick reply ! Here is the code:

`/*
Station météorologique pour ATtiny85 à économie d'énergie

pour la partie OLED écran I2C : https://github.com/lexus2k/ssd1306
pour la partie sonde BME280 :
https://github.com/conurb/low_energy_sensor/blob/master/low_energy_sensor.ino
et pour la partie SLEEP : 
https://github.com/blevien/attiny85-sleep/blob/master/attiny85-sleep.ino
https://gist.github.com/JChristensen/5616922
ATTENTION attachInterrupt(0, poussoir, LOW) gère par défaut les interruption en INT0, 
cad en PB2 déjà utilisé pour le SCL
D'où l'astuce pour changer la commande de l'interruption :
https://www.insidegadgets.com/2011/02/27/how-to-use-the-pin-change-interrupt-on-attiny85/

                        +-------+
                       1|*      |8  VCC
                  PB3  2|       |7  PB2 = SCL (BME280 + OLED)
                  PB4  3|       |6  PB1 => LED
                  GND  4|       |5  PB0 = SDA (BME280 + OLED)
                        +-------+
 connection I2C :
 SDA en PB0 = pin5
 SCL en PB2 = pin7
  A3 -> PB3 = pin2 => détecteur d'humidité
 LED en PB1 = pin6 => led d'avertissement de sécheresse
 SWI en PB4 = pin3 => push-button to wake-up
*/

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/power.h>    
#include <EEPROM.h>
#include "ssd1306.h"            // https://github.com/lexus2k/ssd1306
#include "bme280_tiny_i2c.h"    // https://github.com/conurb/low_energy_sensor

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

int v;
int memo[]={0,0,0};
char chaine[20];
bool r1=false;
volatile bool r2=false;
byte j;

BME280 bme;

void setup() {
  pinMode(1, OUTPUT);          // LED
  pinMode(3, INPUT_PULLUP);    // pin2 ground humidity level sensor
  pinMode(4, INPUT_PULLUP);    // pin3 to push-button

  j = EEPROM.read(3);

  //bme280_init(&bme);

  sbi(GIMSK,PCIE);    // Turn on Pin Change interrupt
  sbi(PCMSK,PCINT4);  // Which pins are affected by the interrupt

// the watch dog sets the interval delay :
// 0=16ms, 1=32ms,2=64ms, 3=128ms, 4=250ms, 5=500ms, 6=1s, 7=2s, 8=4s, 9=8s
  setup_watchdog(9);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode available

  // Replace the line below with ssd1306_128x32_i2c_init() if you need to use 128x32 display */
  //ssd1306_128x64_i2c_init();
  //ssd1306_fillScreen(0x00);  
  //ssd1306_setFixedFont(ssd1306xled_font6x8); 
  //ssd1306_printFixed(0,  8, "Line 1. Normal text", STYLE_NORMAL);
  //ssd1306_printFixed(0, 16, "Line 2. Bold text", STYLE_BOLD);
  //ssd1306_printFixed(0, 24, "Line 3. Italic text", STYLE_ITALIC);
  //ssd1306_printFixedN(0, 32, "Line 4. Double size", STYLE_BOLD, FONT_SIZE_2X);
}

void lecture() {
  // read values from sensor
  bme280_read_sensor(&bme);
  v = bme.temperature /10;
  afficher(0);
  v = bme.humidity /1000;
  afficher(1);
  v = bme.pressure /100;
  afficher(2);
  // battery level part
  //v = min(22528/analogRead(12),100);  // for 5V power supply
  v = min(33300/analogRead(12),99);
  afficher(3); 
}

void afficher( byte q ) {            // q: 0 => t, 1 => h, 2 => p
  char e;
  // value in string of characters
  if( q < 2) { 
    if( q == 0 ) {                   // case temperature
      e = tendance( v/5, memo[q] ); // only half degree precision
      memo[q] = v/5;
      chaine[0] = 'T'; chaine[1] = ':'; chaine[2] = ' ';
      if( v < 0 ) chaine[3] = '-';
      else if( v > 99 ) { chaine[3] = v/100 +48; v=v%100; }
      if( chaine[3] == '0' ) chaine[3] = ' ';
      chaine[4] = v/10 +48;          // +48 for ASCII reasons
      chaine[5] = '.';
      chaine[6] = v%10 +48;
      chaine[7] = '\''; chaine[8] = 'C'; chaine[9] = ' ';
      chaine[10] = e; chaine[11] = ' '; chaine[12] = ' ';
    }   // end of test q == 0
    else {                           // case hygrometry
      e = tendance( v, memo[q] );
      memo[q] = v;
      chaine[13] = 'H'; chaine[14] = ':'; chaine[15] = ' ';
      chaine[16] = v/10 +48; 
      chaine[17] = v%10 +48;
      chaine[18] = '%'; chaine[19] = ' ';
      chaine[20] = e;
      // display line 1 to OLED
      ssd1306_printFixed(0, 25, chaine, STYLE_NORMAL);
    }   // end of else q == 0
  }     // end of test q < 2
  else {                           
    if( q == 2 ) {                  // case pressure
      e = tendance( v, memo[q] );
      memo[q] = v;
      chaine[0] = 'P'; chaine[1] = ':'; chaine[2] = ' ';
      chaine[3] = v/1000 +48; v=v%1000;
      if( chaine[3] == '0' ) chaine[3] = ' ';
      chaine[4] = v/100 +48; v=v%100;
      chaine[5] = v/10 +48;
      chaine[6] = v%10 +48;
      chaine[7] = 'm'; chaine[8] = 'B'; chaine [9] = ' '; 
      chaine[10] = e; chaine[11] = ' '; chaine[12] = ' ';
    }   // end of test q == 2
    else {                         // case battery level 
      chaine[13] = 'B'; chaine[14] = 'a'; chaine[15] = 't'; 
      chaine[16] = ':'; chaine[17] = ' ';
      if( v > 99 ) { 
        chaine[18] = v/100 +48; v=v%100;; 
        chaine[19] = v/10 +48;
        chaine[20] = v%10 +48;
      }
      else {
        chaine[18] = v/10 +48;
        chaine[19] = v%10 +48;
        chaine[20] = ' ';
      }
     // display line 2 to OLED
     ssd1306_printFixed(0, 50, chaine, STYLE_NORMAL);
    }   // end of else q == 2
  }     // end of else q < 2
}       // end of afficher()

char tendance( int w, int n ) {
  if( w > n ) return('+');
  else if( w < n ) return('-');
  else return('=');
}

void ledflash() {
  digitalWrite( 1, HIGH );
  delay(1);
  digitalWrite( 1, LOW );
  //delay(500);
}

void loop() {
  if( r2 == true ) {          // when push-button is pressed to LOW
    r2 = false;
    r1 = false;    
    bme280_init(&bme);          
    memo[0] = EEPROM.read(0)-30;
    memo[1] = EEPROM.read(1);
    memo[2] = EEPROM.read(2)+900;
    ssd1306_128x64_i2c_init();
    ssd1306_fillScreen(0x00);  
    ssd1306_setFixedFont(ssd1306xled_font6x8); 
    ssd1306_printFixed(24, 2, "Station Meteo", STYLE_BOLD);
    lecture();
    EEPROM.write(0, memo[0]+30);  // +30 to prevent against any negatice value
    EEPROM.write(1, memo[1]);
    EEPROM.write(2, memo[2]-900); // -900 to stay in byte range
    EEPROM.write(3, j);           // save the counter 
    delay(3000);
    ssd1306_clearScreen();
    ssd1306_displayOff();
  }
  if( r1 == true ) {          // every 8s
    r1 = false;
// ground humidity part
    if( j++ > 14 ) {           // every 8x15 seconds 
      j = 0;
      sbi(ADCSRA,ADEN);       // Switch Analog to Digital converter ON
      if( analogRead(3) > 800 ) {
        ledflash();
        j = 15;
      }
    }
  }
  system_sleep();           // go to sleep after process
}

// set system into the sleep state - system wakes up when wtchdog is timed out
void system_sleep() {
  cbi(ADCSRA,ADEN);           // Switch ADC OFF, bacause ADC uses ~320uA
  EEPROM.write(3, j);         // save the counter 
  power_all_disable();        // power off ADC, Timer 0 and 1, serial interface
  sleep_enable();
  sleep_cpu();                // System actually sleeps here
  sleep_disable();            // System continues execution here when watchdog timed out 
  power_all_enable();         // power everything back on
}

void setup_watchdog( byte tempo ) {
  byte val;
  val = tempo & 7;
  if (tempo > 7) val |= (1<<5);
  val |= (1<<WDCE);
  MCUSR &= ~(1<<WDRF);                  // start timed sequence
  WDTCR |= (1<<WDCE) | (1<<WDE);        // set new watchdog timeout value
  WDTCR = val;
  WDTCR |= _BV(WDIE);
}

// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect) { r1 = true; r2 = false; }        // set global flag  

// External Interrupt Service / is executed when push-button is pressed
ISR(PCINT0_vect) { r2 = true;}      // set global flag  
lexus2k commented 4 years ago

Hello,

why don't you call sd1306_clearScreen( ); and ssd1306_displayOff(); before entering sleep mode in your example? The system_sleep() code looks OK: you set sleep mode SLEEP_MODE_PWR_DOWN, then disable ADC, sleep_enable(), and perform sleep_cpu().

And could you please, clarify? If you say that you have 3 devices, and 2 of them consume too much power, then maybe the root cause is hardware issue?

philippedc commented 4 years ago

I call ssd1306_clearScreen( ); and ssd1306_displayOff(); once I do not need to display anything, so in the same loop() sequence than initialization with ssd1306_128x64_i2c_init(); etc... Hoever you're right, I will try by repeating the displayOff instruction in the sleep function. Thanks

philippedc commented 4 years ago

So I made tests : when I insert ssd1306_clearScreen( ); and ssd1306_displayOff(); in the sleep function, I get a sleep current of 1mA. After I push the wakeup button, the next sleeping mode gives 0,24mA. So it can be said that these 2 instructions without a ssd1306 initialisation will put the ssd1306 in a mode that it will have a current about 4 times the "normal" sleeping current. So it is worst than expected.

lexus2k commented 4 years ago

Hello,

I've performed tests for ssd1306 display (i2c, 128x64) and ssd1306_displayOff()/ssd1306_displayOn() functions. And here are my results:

  1. When display is on and fully enabled, its power consumption is 10.79mA. So, that is expected.
  2. When I send ssd1306_displayOff() command it's power consumption is 5uA (this is also expected).

So, I would recommend you to check schematics, and pcb assembly.

philippedc commented 4 years ago

You are right, I have done the same tests, with similar results. I have ordered new BME280 to be able to test them separately. As the circuit is only a BME280, a SSD1306 and one LED, the circuit is quickly checked... Many thanks for your help.

lexus2k commented 4 years ago

Let me know, when you find the root cause. Thank you

lexus2k commented 4 years ago

Any updates on low power mode?

philippedc commented 4 years ago

Hi @lexus2k I confirm the over power consumption comes from the BME280. Thanks

lexus2k commented 4 years ago

Thank you very much