РЕШЕНО Хаос в значениях АЦП. ESP32

Тема в разделе "ESP8266, ESP32", создана пользователем MajorXaker, 25 дек 2019.

Метки:
  1. MajorXaker

    MajorXaker Нуб

    Понадобилось в моём проекте на ESP32 Devkit v1 измерение напряжения батареи, которая сей есп и питает. Через делитель напряжения я проложил в GPIO36 линию. (Распиновка моего ЕСП)

    Написал простой код с analogRead, написал сложный код с adc1_get_raw(ADC1_CHANNEL_0);
    Во всех случаях получил погоду на марсе, график получаемых значений после десяти минут измерений выглядит вот так:
    [​IMG]
    Рядом у меня подключён вольтметр, который и глазом не ведёт и держит значение на одном уровне. Я вычислил его - оно должно быть где-то около 2234. Но вместо этого вот.

    Я пробовал менять плату (у меня есть 2 одинаковых) и конкретный канал. Значения меняются, но всё равно остаются полной чепухой. Проверил опорное напряжение чипа - идеальные 1.11
    При этом если брать ардуинку и её аналогРид - результат больше похож на правду (ссылка на 6 графиков).

    Запускаю следующий код.
    Код (C++):
    #include <driver/adc.h>

    int ledPin = 2;
    volatile bool ledState = true;

    void setup()
    {
      pinMode(ledPin, OUTPUT);
     
      Serial.begin(9600);
     
      adc1_config_width(ADC_WIDTH_12Bit);//настройка ширины канала ацп 0-4095
      adc1_config_channel_atten(ADC1_CHANNEL_6,ADC_ATTEN_DB_11); //настройка уровня приёма, тут от 150 до 1750 мВ
     
    }
    void loop()
    {
      /*
      digitalWrite(ledPin, ledState);
      ledState = !ledState;*/

      delay(500);
      int val = adc1_get_raw(ADC1_CHANNEL_6); //считывание из ГПИО36
      //int val = analogRead(36);
      Serial.println(val); //заряжаем в сериал

    }
    Может кто сталкивался с такими приколами платы? Как её перебороть?
     
  2. parovoZZ

    parovoZZ Гуру

    1. Либо измеряется не тот канал
    2. Либо ногу надо перевести в альт. функцию
    3. Возможно, что это всё бред, ибо догадки...
     
  3. В примере у espressif-а используется тот же GPIO36
    При инициализации
    используют adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
    вместо adc1_config_channel_atten(ADC1_CHANNEL_6,ADC_ATTEN_DB_11);
    на ADC_ATTEN_DB_0 пока не смотрим он нужен для измерений 1/1, наш случай ADC_ATTEN_DB_11 1/3.6
    но смущает ваш ADC1_CHANNEL_6 который объявлен в перечислении (в adc.h) и должен соответствовать GPIO34
    Код (Text):
    typedef enum {
      ADC1_CHANNEL_0 = 0, /*!< ADC1 channel 0 is GPIO36 */
      ADC1_CHANNEL_1,  /*!< ADC1 channel 1 is GPIO37 */
      ADC1_CHANNEL_2,  /*!< ADC1 channel 2 is GPIO38 */
      ADC1_CHANNEL_3,  /*!< ADC1 channel 3 is GPIO39 */
      ADC1_CHANNEL_4,  /*!< ADC1 channel 4 is GPIO32 */
      ADC1_CHANNEL_5,  /*!< ADC1 channel 5 is GPIO33 */
      ADC1_CHANNEL_6,  /*!< ADC1 channel 6 is GPIO34 */
      ADC1_CHANNEL_7,  /*!< ADC1 channel 7 is GPIO35 */
      ADC1_CHANNEL_MAX,
    } adc1_channel_t;
    Для исключения путаницы ещё есть определения в adc_channel.h
    Код (Text):
    #define ADC1_GPIO36_CHANNEL  ADC1_CHANNEL_0
    #define ADC1_GPIO34_CHANNEL  ADC1_CHANNEL_6
    С измерениями тоже не те adc1_channel_t
    используют adc1_get_raw(ADC1_CHANNEL_0);
    вместо adc1_get_raw(ADC1_CHANNEL_6);
     
  4. MajorXaker

    MajorXaker Нуб

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

    Ещё интересное наблюдение. При использовании в одном скетче блока работы с вайфай и функции analogRead МК вешается при прошивке. Потом только с танцем с бубном получается его вернуть в адекватное состояние. А вот если использовать ESPшные функции - всё отлично.
     
  5. Anrikigai

    Anrikigai Нерд

    Хоть и решено, все-таки замечу, что в ESP32 есть встроенная функциоя калибровки. У меня значительно повысилась точность при ее использовании.
    Ну т.е. без нее если я подгонял по вольтметру показания в районе 3.7V (18650), то при разряде до 3V показания вольтметра и ESP32 сильно уже различались. И наоборот. А с ней все четко.

    Код (C++):
    //#define ADC_coeff_a    54142
    //#define ADC_coeff_b    142
    // ADC VRef:       1128
    // выше просто для памяти, что мне функция калибровки выдала, в реальности я их при старте измеряю и использую вместо дефайнов

    #define ADC_CHANNEL ADC1_CHANNEL_0
    #define ADC_NO_OF_SAMPLES 64
    #define ADC_V_MULT 2.01 // зависит от делителя на резисторах
    #define VPIN_Voltage 10


    #pragma region ADC calibration // Полезные функции, связанные с АЦП
    #define DEFAULT_VREF 1100
    /*
    ADC_0db: sets no attenuation (1V input = ADC reading of 1088).
    ADC_2_5db: sets an attenuation of 1.34 (1V input = ADC reading of 2086).
    ADC_6db: sets an attenuation of 1.5 (1V input = ADC reading of 2975).
    ADC_11db: sets an attenuation of 3.6 (1V input = ADC reading of 3959).
    */

    static const adc_atten_t atten = ADC_ATTEN_DB_11;
    static const adc_unit_t unit = ADC_UNIT_1; // -!- 0?
    void check_efuse() {
      //Check TP is burned into eFuse
      if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        Serial.println("eFuse Two Point: Supported");
      } else {
        Serial.println("eFuse Two Point: NOT supported");
      }
      //Check Vref is burned into eFuse
      if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        Serial.println("eFuse Vref: Supported");
      } else {
        Serial.println("eFuse Vref: NOT supported");
      }
    }

    void print_char_val_type(esp_adc_cal_value_t val_type) {
      if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        Serial.println("Characterized using Two Point Value\n");
      } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        Serial.println("Characterized using eFuse Vref");
      } else {
        Serial.println("Characterized using Default Vref");
      }
    }

    String NumToAtten(int atten) {
      switch (atten)
      {
        case 0:
          return "ADC_ATTEN_DB_0. No chages for the input voltage";
        case 1:
          return "ADC_ATTEN_DB_2_5. The input voltage will be reduce to about 1/1.34.";
        case 2:
          return "ADC_ATTEN_DB_6. The input voltage will be reduced to about 1/2";
        case 3:
          return "ADC_ATTEN_DB_11. The input voltage will be reduced to about 1/3.6";
      }
      return "Unknown attenuation.";
    }

    String NumToWidth(int width) {
      switch (width)
      {
        case 0:
          return "ADC_WIDTH_BIT_9. ADC capture width is 9Bit";
        case 1:
          return "ADC_WIDTH_BIT_10. ADC capture width is 10Bit";
        case 2:
          return "ADC_WIDTH_BIT_11. ADC capture width is 11Bit";
        case 3:
          return "ADC_WIDTH_BIT_12. ADC capture width is 12Bit";
      }
      return "Unknown width.";
    }

    void adc_calibrate() {
      adc1_config_width(ADC_WIDTH_BIT_12);
      adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_DB_11);
      check_efuse();
      //Characterize ADC at particular atten
      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(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
      Serial.println("ADC number:\t" + String(adc_chars->adc_num));
      Serial.println("ADC attenuation:\t" + NumToAtten(adc_chars->atten));
      Serial.println("ADC bit width:\t" + NumToWidth(adc_chars->bit_width));
      Serial.println("ADC coeff_a:\t" + String(adc_chars->coeff_a));
      Serial.println("ADC coeff_b:\t" + String(adc_chars->coeff_b));
      Serial.println("ADC VRef:\t" + String(adc_chars->vref));
      //Check type of calibration value used to characterize ADC
      print_char_val_type(val_type);
    } // adc_calibrate
    #pragma endregion  // ADC calibration

    // собственно измерение:
      uint32_t reading = 0;
      //Multisampling
      for (int i = 0; i < ADC_NO_OF_SAMPLES; i++) {
        reading += adc1_get_raw((adc1_channel_t)ADC_CHANNEL);
      }
      reading /= ADC_NO_OF_SAMPLES;
      uint32_t pinVolt = esp_adc_cal_raw_to_voltage(reading, adc_chars);  // основная функция, преобразующая из измерянных сырых самплов (0..4095) в милливольты исходя из коэффициентов, измеренных при автокалибровке и сохраненных в струтуре  adc_chars
      uint32_t mvolt = pinVolt * ADC_V_MULT; // это уже преобразую измеренные милливольты в оригинальные, поскольку использую делитель на резисторах
      Blynk.virtualWrite(VPIN_Voltage, mvolt);
      BLYNK_LOG6("mV: ", mvolt, " PinV: ", pinVolt, " Raw: ", reading);
      snprintf(str, 50, "mV: %5i, Volt: %5i, Raw: %5i", mvolt, pinVolt, reading);
      // Serial.println(str);
      terminal.println(str);
      terminal.flush();
      snprintf(str, 20, "%5.2f", (float) mvolt / 1000);