Рассчитать уровень заряда Li-Po батареи в ESP-Wroom-02

esp8266 lipo

Я новичок в микроконтроллерах и схемах. Я вообще не в этой сфере. Но пытаюсь создать домашний проект, к которому ниже прикреплено изображение устройства.

Это устройство с питанием от аккумулятора 18650 Lipo. Здесь Я хочу рассчитать уровень заряда батареи. Проведя небольшое исследование в Google, я обнаружил, что мне нужен делитель напряжения, который, кажется, я уже включил сюда с сопротивлением 220 кОм и 100 кОм

Люди используют разные способы его расчета. Что я нашел в нескольких примерах. Я вообще не могу понять, какую формулу или значения они имеют в виду для расчета.

Если кто-то сможет помочь понять, это будет полезно. Вот как я это кодирую, что я видел где-то в Интернете.

      #include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
//#include "DHTesp.h"

#define DHT_PIN       16


//SSID и пароль вашего Wi-Fi-роутера
const char* ssid = "Asus";
const char* password = "Xmv02488!!**";

ESP8266WebServer server(80); //Сервер на порту 80


/***************************************************************
 * SETUP
 **************************************************************/
void setup(void){
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);     //Подключаемся к Wi-Fi роутеру
  Serial.println("");

  // Ожидаем соединения
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  //Если соединение успешно, отобразить IP-адрес на последовательном мониторе
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //IP-адрес, назначенный вашему ESP

  server.on("/", handleRoot);      //Какую подпрограмму обрабатывать в корневом месте

  server.begin();                  //Запускаем сервер
  Serial.println("HTTP server started");

  pinMode(LED_BUILTIN, OUTPUT); 
  pinMode(A0, INPUT); 

}
/***************************************************************
 * LOOP
 **************************************************************/
void loop(void){
  server.handleClient();          //Обработка клиентских запросов


}


/***************************************************************
 * This function converts IPAddress struct to a String
 **************************************************************/
String IpAddress2String(const IPAddress& ipAddress)
{
  return String(ipAddress[0]) + String(".") +\
  String(ipAddress[1]) + String(".") +\
  String(ipAddress[2]) + String(".") +\
  String(ipAddress[3])  ;
}


/***************************************************************
 * This rutine is exicuted when you open its IP in browser
 **************************************************************/
 void handleRoot() {
  IPAddress ip_address = WiFi.localIP();
  String ip_str = IpAddress2String(ip_address);

  int nVoltageRaw = analogRead(A0);
  float fVoltage = (float)nVoltageRaw * 0.00486;

  float fVoltageMatrix[22][2] = {
    {4.2,  100},
    {4.15, 95},
    {4.11, 90},
    {4.08, 85},
    {4.02, 80},
    {3.98, 75},
    {3.95, 70},
    {3.91, 65},
    {3.87, 60},
    {3.85, 55},
    {3.84, 50},
    {3.82, 45},
    {3.80, 40},
    {3.79, 35},
    {3.77, 30},
    {3.75, 25},
    {3.73, 20},
    {3.71, 15},
    {3.69, 10},
    {3.61, 5},
    {3.27, 0},
    {0, 0}
  };

  int i, perc;

  perc = 100;

  for(i=20; i>=0; i--) {
    if(fVoltageMatrix[i][0] >= fVoltage) {
      perc = fVoltageMatrix[i + 1][1];
      break;
    }
  }




  server.send(200, "text/plain", "Hello from esp8266!\n\rIP: " + ip_str + 
    ".\n\rTemp: " + "NO" + 
    ", Hum: " + "NO" + 
    "\n\r" + "NO" + 
    "\n\r" + "Voltage: " + fVoltage + 
    "\n\r" + "Charge: " + perc + '%');
}

Это скопированный код. Я не уверен, откуда они взяли эту формулу и как они получили значение 0,00486

Я скопировал этот код из разных источников. Но я надеюсь, что это поможет вам разобраться в вопросе.

Любые предложения будут полезны!

Спасибо! (заранее)

, 👍0

Обсуждение

Самая большая проблема в Интернете — это количество людей, которые думают, что знают, что делают, но на самом деле понятия не имеют, но все же чувствуют необходимость публиковать обучающие материалы о том, о чем они ничего не знают… вероятность «0,00486» была либо результат "проб и ошибок", либо случай "китайского шепота".. код копировал и копировал и еще раз копировал, и где-то по ходу опечатка, или две, или три... и вам конец вверх с *посудомойкой Purple Monkey* (отсылка к Симпсонам), @Jaromanda X


4 ответа


Лучший ответ:

0

Проблема LiPo-аккумуляторов в том, что кривая разряда очень пологая. Вот пример:

Как видите, в течение примерно 95 % срока службы аккумулятора напряжение меняется очень незначительно. Вы не можете просто взять напряжение между «полным» и «пустым» и получить от этого процент. Вместо этого вам придется сопоставить точки на этой кривой с разными процентами.

Самый простой способ — просто иметь набор интересующих вас процентов — каждые 5%, как в приведенном выше коде. Затем вы говорите: «Если напряжение выше этого значения, то оно составляет 100%. Если оно выше следующего значения вниз, это 95%. Если оно выше следующего значения вниз, то это 90%». и т. д.

Массив fVoltageMatrix содержит это сопоставление. Для 100% напряжения должно быть 4,2 В или выше. Для 95% оно должно быть 4,15 В или выше. И так далее.

Если мы возьмем эти значения напряжения из массива и нанесем их на график, они будут выглядеть так:

Как видите, это похоже. Достаточно ровный, с резким обвалом.

Код, получающий процентный заряд, просто считывает значение из АЦП и преобразует его в напряжение, умножая его на 0,004861, а затем последовательно проходит по элементу массива. В первый раз, когда он находит тот, который не проходит тест «Порог этого процента меньше напряжения», он принимает предыдущий в списке в качестве процента. Лично я считаю, что это обратный и плохой способ сделать это. Вместо этого он должен быть первым, прошедшим тест «Напряжение больше или равно этому процентному порогу», который следует пройти. Я бы переписал цикл так:

perc = 0;

for(i=0; fVoltageMatrix[i][0] > 0; i++) {
  if(fVoltage >= fVoltageMatrix[i][0]) {
    perc = fVoltageMatrix[i][1];
    break;
  }
}

1 Число 0,00486 — это количество вольт, подаваемых на вход, чтобы выдать 1 от АЦП. Кстати, если я использую правильные цифры, то по моим расчетам оно должно быть 0,003125. 1 * (1/1024) дает 0,000976563 (это максимальный диапазон АЦП 1 В, умноженный на один бит - 1024-й, поскольку на ESP8266 разрешение составляет 10 бит). Умножьте это на соотношение резисторов ((R1 + R2)/R2), которое равно 3,2, и это даст 0,003125. Чтобы сжать его, вы получите: x = (R1 + R2) / R2 * 1/1024 = 320000 / 100000 * 0.000976563 = 0.003125< /п>

Однако мои цифры могут быть неверными...

,

Спасибо большое за такое подробное объяснение. Это помогло многое понять. Вот если я использую это float fVoltage = (float)nVoltageRaw * 0.0041015625; и получаю значение напряжения 4,2, что потрясающе. Я добавил резистор сопротивлением 100 кОм к A0 к плюсу моей батареи. Теперь это имеет большой смысл! :), @user3201500


0

Одна из проблем, с которой я столкнулся при работе с ESP8266, — это плохие результаты работы аналого-цифрового преобразователя. Есть ряд препятствий, которые вам нужно пройти, чтобы планеты выровнялись и получили последовательные показания. См. раздел «Флуктуирующий АЦП со стабилизированным источником» https://github.com/esp8266/Arduino/issues/2070

Другая проблема, с которой я столкнулся, связана с предположением, что диапазон работы АЦП составляет 0–1,0 В, а это, конечно, не так.

По моим расчетам, оно должно быть 0,003125. 1 * (1/1024) дает 0,000976563. (это максимальный диапазон АЦП 1 В

Вышеупомянутое показывает, насколько точными могут быть наши расчеты, но, к сожалению, шестизначная точность теряется, если предполагаемый верхний предел в 1 В имеет отклонения в 5 %. К сожалению, в миниатюрной спецификации не указаны технические характеристики аналого-цифрового преобразователя.

Да, аналого-цифровой преобразователь ESP8266 можно калибровать, а измерения проводить в контролируемых условиях. Но прежде чем доверять своим результатам, убедитесь, что вы выполнили необходимую работу и проверили результаты при всех условиях эксплуатации.

,

3

Еще одна проблема, с которой вы можете столкнуться, заключается в том, что многие аналого-цифровые преобразователи используют VCC в качестве источника опорного напряжения. Это означает, что по мере того, как ваше входное напряжение падает (то, что вы пытаетесь измерить), опорное напряжение также падает, а это означает, что записываемые вами значения не будут меняться или, по крайней мере, не будут давать точных эталонных значений.

,

0

Ниже вы можете найти цифровой плотномер, который я сделал для пива, и созданное мной программное обеспечение для отслеживания температуры и заряда батареи.

1 . Эта часть процесса — это процесс, который я проделал, чтобы уравнять напряжение, поступающее на делитель напряжения, с напряжением батареи.
float fVoltage = (float)nVoltageRaw * 1,23;)
Вы можете рассчитать и найти это значение (1,23) в соответствии с вашим делителем напряжения. Но по какой-то причине состояние батареи немного колеблется 2 . Я использовал 2 транзистора BC238 для датчиков и делителя напряжения, чтобы ESP не потреблял энергию, когда не считывает. Напряжение проходит только при срабатывании транзисторов. 3 . float pot = map(a.acceleration.x, yogunlukreset0, yogunlukreset1, 997, 1100 );

Эта деталь использовалась для калибровки устройства.


    #include <ESP32Firebase.h>
    
    #include "I2Cdev.h"
    //#include "MPU6050.h"
    #include <Adafruit_MPU6050.h>
    #include <Adafruit_Sensor.h>
    
    
    #include <Wire.h>
    #include "Adafruit_MCP9808.h"
    
    #define _SSID ""          
    #define _PASSWORD ""      
    #define REFERENCE_URL ""  
    
     Firebase firebase(REFERENCE_URL);
     
     Adafruit_MCP9808 tempsensor = Adafruit_MCP9808();
     Adafruit_MPU6050 mpu;
     sensors_event_t a, g, temp;
     
    
    #define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
    #define TIME_TO_SLEEP  3600       /* Time ESP32 will go to sleep (in seconds) */
    
    #define bc1 33
    #define bc2 16
    
     RTC_DATA_ATTR int bootCount = 0;
     const int potPin = 34;
    
     #include <Preferences.h>
     Preferences pref;
    
    namespace{
     float yogunlukreset0;
     float yogunlukreset1;
    
    }
    void wakeup_reason(){
      esp_sleep_wakeup_cause_t wakeup_reason;
      wakeup_reason = esp_sleep_get_wakeup_cause();
    
    }
    
    
    
    void setup() {
      
      Serial.begin(115200);
      delay(100);
      pinMode(bc1, OUTPUT);
      pinMode(bc2, OUTPUT);
      delay(100);  
      digitalWrite(bc1, HIGH);
      digitalWrite(bc2, HIGH);
      delay(100);
      
      Wire.begin();
      WiFi.mode(WIFI_STA);
      WiFi.disconnect();
      delay(1000);
      Serial.println();
      Serial.println();
      Serial.print("Connecting to: ");
      Serial.println(_SSID);
      WiFi.begin(_SSID, _PASSWORD);
    
      while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print("-");
      }
    
      Serial.println("");
      Serial.println("WiFi Connected");
    
      Serial.print("IP Address: ");
      Serial.print("http://");
      Serial.print(WiFi.localIP());
      Serial.println("/");
    
    
    
      if (!tempsensor.begin(0x18)) {
      Serial.println("Couldn't find MCP9808! Check your connections and verify the address is correct.");
      while (1);
      }
      if (!mpu.begin()) {
        Serial.println("Failed to find MPU6050 chip");
        while (1) {
          delay(10);
        }
     }
    
      
    ++bootCount; 
    
      
      
      Serial.flush();
      pref.begin("Relay-State", false);  
      yogunlukreset0 = pref.getFloat("namespace", false); 
      yogunlukreset1 = pref.getFloat("namespa", false); 
        
    }
    
    
    void loop() {
    
      
      
      mpu.setCycleRate(MPU6050_CYCLE_20_HZ);
    
      mpu.getEvent(&a, &g, &temp);
       
      tempsensor.wake();
       
      
      pref.putFloat("namespace", yogunlukreset0);
      pref.putFloat("namespa", yogunlukreset1);
      
      float c = tempsensor.readTempC();
    
      String reset2 = firebase.getString("Data/RT2");
    
    
      if (reset2 == "1"){
      String reset0 = firebase.getString("Data/RT0");
      String reset1 = firebase.getString("Data/RT1");
      String reset3 = firebase.getString("Data/RT3");
    
     
      if (reset0=="1") {
        yogunlukreset0 = (a.acceleration.x);
        }
      if (reset1=="1") {
        yogunlukreset1 = (a.acceleration.x);
        }
    
      if (reset3 == "1"){ 
        pref.clear(); 
        bootCount = 0;
        }
      
      
      }
      
         
      if (reset2 == "0"){
    
       
      float pot = map(a.acceleration.x, yogunlukreset0, yogunlukreset1, 997, 1100 );
    
      int nVoltageRaw = analogRead(potPin);
      
      float fVoltage = (float)nVoltageRaw * 1.23;
      
      float fVoltageMatrix[22][2] = {
        {4150, 100},
        {4130, 95},
        {4110, 90},
        {4080, 85},
        {4040, 80},
        {4010, 75},
        {3970, 70},
        {3930, 65},
        {3890, 60},
        {3850, 55},
        {3810, 50},
        {3760, 45},
        {3710, 40},
        {3660, 35},
        {3610, 30},
        {3550, 25},
        {3490, 20},
        {3430, 15},
        {3370, 10},
        {3320, 5},
        {3270, 0},
        {0, 0}
      };
    
      int i, perc;
    
      perc = 100;
    
      for(i=20; i>=0; i--) {
        if(fVoltageMatrix[i][0] >= fVoltage) {
          perc = fVoltageMatrix[i + 1][1];
          break;
        }
      }
      
      firebase.setFloat("Data/YG", pot);
      firebase.setFloat("Data/TM", c);
      firebase.setFloat("Data/SYC", bootCount);
      firebase.setFloat("Data/VOLT", fVoltage);
      firebase.setFloat("Data/ORAN", perc);
      
      
      digitalWrite(bc1, LOW);
      digitalWrite(bc2, LOW);
      
      wakeup_reason();
      esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); 
      esp_deep_sleep_start(); 
      delay(50);
      }
       
      
    }
,