Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No battery detection in heltec devices #769

Closed
superjasiek opened this issue Mar 26, 2021 · 7 comments · Fixed by #1183
Closed

No battery detection in heltec devices #769

superjasiek opened this issue Mar 26, 2021 · 7 comments · Fixed by #1183
Assignees
Labels
question Further information is requested

Comments

@superjasiek
Copy link

superjasiek commented Mar 26, 2021

Describe the bug
Due to lack of battery recognition and voltage reading heltec devices does not use light sleep or any other power saving modes

To Reproduce
Steps to reproduce the behavior:
After uploading sketch battery level and power saving modes not available

Expected behavior
Read battery voltage and use power saving modes

Device info:

  • Device model: Heltec ESP32 LoRa
  • Software Version [1.2.13]

Additional context
Manufacturer did not documented battery recognition mechanism. After search and test here what we have:
battery_pin = GPIO37
ratio of voltage divider = 3.20 (R1=100k, R2=220k)

working sketch for battery level measurement:

// Heltec WiFi LoRa V2 battery read example 
// by Jeff McClain  jeff@themcclains.net
//
#include <Arduino.h>
#include <esp_adc_cal.h>
#include <driver/adc.h>
#include "heltec.h"

#define MAXBATT                 4200    // The default Lipo is 4200mv when the battery is fully charged.
#define LIGHT_SLEEP_VOLTAGE     3750    // Point where start light sleep
#define MINBATT                 3200    // The default Lipo is 3200mv when the battery is empty...this WILL be low on the 3.3v rail specs!!!

#define VOLTAGE_DIVIDER         3.20    // Lora has 220k/100k voltage divider so need to reverse that reduction via (220k+100k)/100k on vbat GPIO37 or ADC1_1 (early revs were GPIO13 or ADC2_4 but do NOT use with WiFi.begin())
#define DEFAULT_VREF            1100    // Default VREF use if no e-fuse calibration
#define VBATT_SAMPLE            500     // Battery sample rate in ms
#define VBATT_SMOOTH            50      // Number of averages in sample
#define ADC_READ_STABILIZE      5       // in ms (delay from GPIO control and ADC connections times)
#define LO_BATT_SLEEP_TIME      10*60*1000*1000     // How long when low batt to stay in sleep (us)
#define HELTEC_V2_1             1       // Set this to switch between GPIO13(V2.0) and GPIO37(V2.1) for VBatt ADC.
#define VBATT_GPIO              21      // Heltec GPIO to toggle VBatt read connection ... WARNING!!! This also connects VEXT to VCC=3.3v so be careful what is on header.  Also, take care NOT to have ADC read connection in OPEN DRAIN when GPIO goes HIGH
#define __DEBUG                 0       // DEBUG Serial output

uint16_t Sample();
void drawBattery(uint16_t, bool = false);

esp_adc_cal_characteristics_t *adc_chars;



void setup() {
  while (! Serial);
  delay(20);

  // Characterize ADC at particular atten
  #if (defined(HELTEC_V2_1))
  adc_chars = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
  esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_1,ADC_ATTEN_DB_6);
  #else
  // Use this for older V2.0 with VBatt reading wired to GPIO13
  adc_chars = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
  esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_2, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
  adc2_config_channel_atten(ADC2_CHANNEL_4,ADC_ATTEN_DB_6);
  #endif

  #if defined(__DEBUG) && __DEBUG > 0
  Serial.printf("ADC Calibration: ");
  if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
      Serial.printf("eFuse Vref\n");
  } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
      Serial.printf("Two Point\n");
  } else {
      Serial.printf("Default[%dmV]\n",DEFAULT_VREF);
  }
  #else
  if (val_type);    // Suppress warning
  #endif

  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_10);
  Heltec.display->clear();

  #if defined(__DEBUG) && __DEBUG >= 1
  Serial.printf("ADC Calibration: ");
  if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
      Serial.printf("eFuse Vref\n");
  } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
      Serial.printf("Two Point\n");
  } else {
      Serial.printf("Default[%dmV]\n",DEFAULT_VREF);
  }
  #else
  if (val_type);    // Suppress warning
  #endif

  // Prime the Sample register
  for (uint8_t i = 0;i < VBATT_SMOOTH;i++) {
    Sample();
  }

  pinMode(VBATT_GPIO,OUTPUT);
  digitalWrite(VBATT_GPIO, LOW);              // ESP32 Lora v2.1 reads on GPIO37 when GPIO21 is low
  delay(ADC_READ_STABILIZE);                  // let GPIO stabilize

}


void loop() {
  Heltec.display->clear();
  uint16_t voltage = Sample();
  drawBattery(voltage, voltage < LIGHT_SLEEP_VOLTAGE);
  Heltec.display->display();

  if (voltage < MINBATT) {                  // Low Voltage cut off shut down to protect battery as long as possible
    Heltec.display->setColor(WHITE);
    Heltec.display->setFont(ArialMT_Plain_10);
    Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
    Heltec.display->drawString(64,24,"Shutdown!!");
    Heltec.display->display();
    delay(2000);
    #if defined(__DEBUG) && __DEBUG > 0
    Serial.printf(" !! Shutting down...low battery volotage: %dmV.\n",voltage);
    delay(10);
    #endif
    esp_sleep_enable_timer_wakeup(LO_BATT_SLEEP_TIME);
    esp_deep_sleep_start();
  } else if (voltage < LIGHT_SLEEP_VOLTAGE) {     // Use light sleep once on battery
    uint64_t s = VBATT_SAMPLE;
    #if defined(__DEBUG) && __DEBUG > 0
    Serial.printf(" - Light Sleep (%dms)...battery volotage: %dmV.\n",(int)s,voltage);
    delay(20);
    #endif
    esp_sleep_enable_timer_wakeup(s*1000);     // Light Sleep does not flush buffer
    esp_light_sleep_start();
  }
  delay(ADC_READ_STABILIZE);
}


// Poll the proper ADC for VBatt on Heltec Lora 32 with GPIO21 toggled
uint16_t ReadVBatt() {
  uint16_t reading = 666;

  digitalWrite(VBATT_GPIO, LOW);              // ESP32 Lora v2.1 reads on GPIO37 when GPIO21 is low
  delay(ADC_READ_STABILIZE);                  // let GPIO stabilize
  #if (defined(HELTEC_V2_1))
  pinMode(ADC1_CHANNEL_1, OPEN_DRAIN);        // ADC GPIO37
  reading = adc1_get_raw(ADC1_CHANNEL_1);
  pinMode(ADC1_CHANNEL_1, INPUT);             // Disconnect ADC before GPIO goes back high so we protect ADC from direct connect to VBATT (i.e. no divider)
  #else
  pinMode(ADC2_CHANNEL_4, OPEN_DRAIN);        // ADC GPIO13
  adc2_get_raw(ADC2_CHANNEL_4,ADC_WIDTH_BIT_12,&reading);
  pinMode(ADC2_CHANNEL_4, INPUT);             // Disconnect ADC before GPIO goes back high so we protect ADC from direct connect to VBATT (i.e. no divider
  #endif

  uint16_t voltage = esp_adc_cal_raw_to_voltage(reading, adc_chars);  
  voltage*=VOLTAGE_DIVIDER;

  return voltage;
}

//  Use a buffer to average/sample ADC
uint16_t Sample() {
  static uint8_t i = 0;
  static uint16_t samp[VBATT_SMOOTH];
  static int32_t t = 0;
  static bool f = true;
  if(f){ for(uint8_t c=0;c<VBATT_SMOOTH;c++){ samp[c]=0; } f=false; }   // Initialize the sample array first time
  t -= samp[i];   // doing a rolling recording, so remove the old rolled around value out of total and get ready to put new one in.
  if (t<0) {t = 0;}

  // ADC read
  uint16_t voltage = ReadVBatt();

  samp[i]=voltage;
  #if defined(__DEBUG) && __DEBUG > 0
  Serial.printf("ADC Raw Reading[%d]: %d", i, voltage);
  #endif
  t += samp[i];

  if(++i >= VBATT_SMOOTH) {i=0;}
  uint16_t s = round(((float)t / (float)VBATT_SMOOTH));
  #if defined(__DEBUG) && __DEBUG > 0
  Serial.printf("   Smoothed of %d/%d = %d\n",t,VBATT_SMOOTH,s); 
  #endif

  return s;
}


void drawBattery(uint16_t voltage, bool sleep) {
  Heltec.display->setColor(BLACK);
  Heltec.display->fillRect(99,0,29,24);

  Heltec.display->setColor(WHITE);
  Heltec.display->drawRect(104,0,12,6);
  Heltec.display->fillRect(116,2,1,2);

  uint16_t v = voltage;
  if (v < MINBATT) {v = MINBATT;}
  if (v > MAXBATT) {v = MAXBATT;}
  double pct = map(v,MINBATT,MAXBATT,0,100);
  uint8_t bars = round(pct / 10.0);
  Heltec.display->fillRect(105,1,bars,4);
  Heltec.display->setFont(ArialMT_Plain_10);
  Heltec.display->setTextAlignment(TEXT_ALIGN_RIGHT);
  // Draw small "z" when using sleep
  if (sleep > 0) {
    Heltec.display->drawHorizontalLine(121,0,4);
    Heltec.display->drawHorizontalLine(121,5,4);
    Heltec.display->setPixel(124,1);
    Heltec.display->setPixel(123,2);
    Heltec.display->setPixel(122,3);
    Heltec.display->setPixel(121,4);
  }
  Heltec.display->drawString(127,5,String((int)round(pct))+"%");
  Heltec.display->drawString(127,14,String(round(voltage/10.0)/100.0)+"V");
  #if defined(__DEBUG) && __DEBUG > 0
  static uint8_t c = 0;
  if ((c++ % 10) == 0) {
    c = 1;
    Serial.printf("VBAT: %dmV [%4.1f%%] %d bars\n", voltage, pct, bars);
  }
  #endif
}

[Link to thread on discourse] (https://meshtastic.discourse.group/t/heltec-board-is-deep-discharging-batteries/1349/13 )

@geeksville
Copy link
Member

This issue has been mentioned on Meshtastic. There might be relevant details there:

https://meshtastic.discourse.group/t/heltec-board-is-deep-discharging-batteries/1349/15

@mc-hamster
Copy link
Member

Closed as duplicate of #442

@geeksville geeksville self-assigned this May 9, 2021
@geeksville geeksville reopened this May 9, 2021
@geeksville
Copy link
Member

This issue has been mentioned on Meshtastic. There might be relevant details there:

https://meshtastic.discourse.group/t/heltec-lora-32-usb-power-sensing-fixed/3237/11

@geeksville
Copy link
Member

This issue has been mentioned on Meshtastic. There might be relevant details there:

https://meshtastic.discourse.group/t/meshtastic-device-1-2-49-5354c49-alpha/4502/5

@mkinney
Copy link
Contributor

mkinney commented Jan 16, 2022

Here is some output from --info:

My info: { "myNodeNum": 682584012, "numBands": 13, "firmwareVersion": "1.2.51.f9ff06b", "rebootCount": 5, "bitrate": 17.08847, "messageTimeoutMsec": 300000, "minAppVersion": 20200, "maxChannels": 8, "hasWifi": true, "channelUtilization": 4.6 }
Nodes in mesh:  {'num': 682584012, 'user': {'id': '!28af67cc', 'longName': 'Unknown 67cc', 'shortName': '?CC', 'macaddr': '24:6f:28:af:67:cc', 'hwModel': 'HELTEC_V2_1'}, 'position': {'batteryLevel': 47}, 'lastHeard': 1642359394}

Note that there is no battery connected to the Heltec, but it shows a batteryLevel of 47.

@garthvh
Copy link
Member

garthvh commented Jan 24, 2022

Additional Details here: #1025

@garthvh garthvh added the question Further information is requested label Jan 24, 2022
@garthvh
Copy link
Member

garthvh commented Jan 24, 2022

This is a collection of all the assorted reports of wildly inconsistent behavior with the Heltec power detection. Now that Heltec devices have a variant file if someone wants to try and improve this it may be a good first step to replicate the battery detection logic from our RAK firmwares for Heltec and TLora boards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants