Нестабильность показаний с делителя - почему?

Тема в разделе "Arduino & Shields", создана пользователем Ru52, 16 янв 2018.

  1. Ru52

    Ru52 Нуб

    Добрый день!
    Мониторю состояние напруги в системе 12В.

    Ардуино питается с трансформатора, выходами подключенного к АКБ, через стабилизированный БП 3.3 V.
    С клемм АКБ взят входной сигнал на делитель. Напряжение на АКБ стабильно - 13,7 V без скачков.
    С делителя 1:5 подается сигнал на аналоговый вход ардуино, вход подтянут резистором к нулю.

    Питание стабильно, при этом на входе ардуины постоянное небольшое "дрожание" с регулярной просадкой до 0,5 вольт, что при моих задачах является неприятным отклонением.

    С чем может быть связано, посоветуйте как это побороть, если это возможно?
    2018-01-16_14-42-59.png График напряжения для иллюстрации приложил.
     
  2. issaom

    issaom Гуру

    http://forum.amperka.ru/threads/Кон...rduino-и-остальная-нагрузка.10210/#post-96212
    Посмотрите эту тему.....
     
    Ru52 нравится это.
  3. SergeiL

    SergeiL Оракул Модератор

    А если усреднять, замеров так за 30? + правильно округлять? :)
    Вы уверены, что Вам сотые на 13,7В нужны? :)
     
    Ru52 нравится это.
  4. Ru52

    Ru52 Нуб

    Там не сотые, а до полувольта скачки, к сожалению. На графике тоже видно - 13,72 и 13,27 соседствуют.
    Думал о таком варианте - округлять последние 5 замеров. Если не найдётся простого решения, скорее всего так и буду двигаться, вполне рабочий, хоть и не самый удобный вариант.
     
    Последнее редактирование: 16 янв 2018
  5. Ru52

    Ru52 Нуб

    Судя по этой теме - решением будет поставить внешний шилд для контроля напряжения. Другого решения я в теме не увидел.
    Кстати пробовал выбирать режим референса INTERNAL вместо DEFAULT (в котором VCC и есть референсное напряжение, как я понимаю) - в показаниях вообще полная белиберда была (то что опорное внутреннее должно быть порядка 1,1 я учитывал).
     
  6. SergeiL

    SergeiL Оракул Модератор

    Зачем там внешний шилд?

    На входе АЦП - обычный делитель, один вход АЦП заводим на 3.3В ардуины.
    Референс на VCC.

    Считываем АЦП подключенный к 3.3В.
    Вычисляем напряжение VCC по значению АЦП, подключенному к 3.3В (на 3.3 больше ничего не висит обычно)
    Считываем АЦП, подключенный к делителю, вычисляем напряжение с учетом рассчитанного VCC.

    Повторяем предыдущие шаги н-ное количество раз, вычисляем среднее с учетом округления.

    Ничего не прыгает вообще:

    Power_1.png Power.png

    На графике видно отключение питания 220В на даче, и цикл зарядки аккумулятора.

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

    Сделал два входа на будущее, чтобы при длительном отключении электричества, при сильной разрядке батареи, только сильно жрущее видеонаблюдение отключалось бы, а сигнализация осталась в работе.
     
    Последнее редактирование: 16 янв 2018
    Ru52 нравится это.
  7. Ru52

    Ru52 Нуб

    Сергей, не очень понятно для чего вы вычисляете значение Vcc? Если референсным напряжением на ардуино по умолчанию выставлен DEFAULT, то это означает, что в качестве опорного будет выступать напряжение на самом Vcc. А в этой ситуации попытка чтения своего же напряжения Vcc на любой другой ножке АЦП должно возвращать вам 1023.

    Можете показать кусок скетча в части измерения напряжений?
     
  8. SergeiL

    SergeiL Оракул Модератор

    Вы же рассчитываете напряжение на входе делителя из условия, что 1023 == 5.0 Так?
    А вы уверены, что напряжение VCC == 5В.
    А если у Вас там 4.76В, или 4.9В. ТО в формулу расчета реального напряжения на пине нужно уже подставлять не значение 5.0, а к примеру, 4.76, или 4.9, относительно которого измерялось напряжение.
    Поэтому, нужно понять, какое напряжение на референсе. и это напряжение использовать в мапинге.

    Как это сделать? Либо использовать внутренний опорный источник, либо - внешний.
    Я использую встроенный в Ардуину источник напряжения 3.3В. Аналоговый порт у меня свободный есть, на 3.3 у меня нет нагрузки, и по наблюдениям очень все стабильно. Проверенный мультиметр показывает 3.300.

    Замеряем напряжение на 3.3, получаем значение АЦП, принимаем его за 3.3, и по простейшей формуле рассчитываем чему равно значение 1023. Принимаем полученное расчетное значение напряжения за VCC.

    А далее рассчитываем напряжение на измеряемом входе, только не с константой VCC равной 5.0, а с полученным значением VCC.

    Я проверял, так видно и напряжение питания процессора, и, даже при питании от USB равным 4.7, напряжение на входе делителя измеряется корректно.

    Код посмотрю - попробую из него пример сделать. Там просто долго разбираться придется. много чего дописывалось, не оптимизировалось, как всегда, когда для себя, комментарии не пишем.:)
     
    Последнее редактирование: 16 янв 2018
    Ru52 нравится это.
  9. Ru52

    Ru52 Нуб

    Да, я собственно об этом и говорил. Но дело в моем случае в другом:
    https://www.arduino.cc/reference/en/language/functions/analog-io/analogreference/
    Указаны референсные напряжения на чипах:

    Arduino AVR Boards (Uno, Mega, etc.)
    • DEFAULT: the default analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
      INTERNAL: an built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328P and 2.56 volts on the ATmega8 (not available on the Arduino Mega)
    На атмеге 328 можно включить INTERNAL и в таком случае можно сравнивать с внутренним встроенным 1.1 вольта. А в случае с DEFAULT, как пишут в сети, просто замыкаются пины AREF и Vcc и референсом будет считаться поданное на Vcc напряжение питания.
    Так вот в случае с INTERNAL, с делителя подавать напряжение на пин ниже 1го вольта ничего не дало - всегда были максимальные значения, а в режиме DEFAULT любая нестабильность на входе будет влиять на качество измерений. И смысла получается нет измерять своё собственное напряжение, т.к. сравнивать мы будем с ним же самим, а это на уровне Мюнхгаузена, который сам себя за волосы вытащил из болота :)

    Если код выложите в любом виде, буду очень признателен.
     
  10. Ru52

    Ru52 Нуб

    Сергей, добавлю.
    Видимо, моя ошибка в том, что после выставления INTERNAL в качестве опорного, напряжение нужно снимать с Vcc на АЦП пин, питая саму ардуину через RAW. В таком случае как вы и говорите, я смогу найти реальную величину Vcc.
    И получается INTERNAL в качестве опорного не означает, что максимальное значение на входе АЦП будет 1,1V, как я думал ранее.
    Вот эта статейка натолкнула на мысль:
    https://jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/
     
  11. SergeiL

    SergeiL Оракул Модератор

    Подождите, не мучайте устройство :)
    Исходник частично перелопатил, вечером проверю, выложу.
    Но, мне кажется, он все равно избыточен.
     
    Ru52 нравится это.
  12. SergeiL

    SergeiL Оракул Модератор

    Вчера не смог завершить - пришлось по работе отвлечься. В командировке я :).
    Порт A0 соединяете с выходом 3.3V. он теперь служебный, ADC_Val[0] можно посмотреть напряжение питания процессора.
    В константе ADC_QUANTITY количество нужных Вам аналоговых портов. В примере все 6. С служебного A0 до A5. Для измерения одного напряжения - поставьте 2. (A0 и A1) Измеряемое напряжение подаете на А1.

    Код проверил, работает стабильно.
    В последовательный порт, данные выкидываются, только при наличии изменений, и по таймауту в 1 минуту.

    Как то так...

    Код (C++):

    //---------------- Serial  ------------------

    bool Serial_Flag = false;

    //-------------------- ADC -------------------------------

    #define FRC_Update_TO  60000       // период принудительного обновления 1 раз в минуту

    #define ADC_CHECK_TO  10       // период опроса АЦП для всех портов
    #define ADC_SUM_COUNT 20        // считаем среднее из количества ADC_SUM_COUNT если хотим не больше 16 - то можно поменять тип данных ADC_SUM_BUF  с long на unsigned

    #define ADC_QUANTITY  6         // количество аналоговых входов + первый - референс 3.3В


    uint32_t ADC_SUM_BUF[ADC_QUANTITY];  // буфер для накопления суммы значений АЦП для расчета среднего

    uint8_t  ADC_Count_BUF[6] = {8, 7, 6, 4, 2, 0};        // используется для сглаживания нагрузки на отправку изменений значений (например MQTT), чтобы новые значение не появлялись по всем портам сразу
                                                           // для 2-х портов  ADC_Count_BUF[2] = {5, 0};
    uint8_t  ADC_Firts_Flag;                              // флаг, для того чтобы скинуть первый неполный проход (из за того счетчики 8, 7, 6, 4, 2, 0,  см. выше)

    uint8_t  Update_flag = 0;                            // если измеряемое значение изменилось соответствующий бит будет взведен в 1

    uint8_t  ADC_Pins[6] = {A0, A1, A2, A3, A4, A5};     // пины аналоговых входов

    float    ADC_Val[ADC_QUANTITY];                  // здесь храним рассчитанные значения напряжений на входах ( не забываем, ADC_Val[0] это напряжение питания процессора)

    void ADC_data_init(void)
    {
      uint8_t i,n;
      for (i=0,n=1; i < ADC_QUANTITY; i++,n=n<<1)
      {
        ADC_Val[i] = 99.0;
        ADC_SUM_BUF[i] = 0;
        ADC_Firts_Flag |= n;
       }
    }

    void ADC_Val_Update(byte adc_numb, unsigned adc_val)   // функция пересчета значений АЦП в напряжение на входе
    {
      uint32_t      long_val;
      uint16_t      int_val;
      float         temp_f_val;

      if (adc_val > 1023) // такого быть не должно, но проверим
        adc_val = 1023;

      if (adc_numb == 0)  // вход подключенный к 3.3В
      {
          temp_f_val = 1023.0*3.32 / (float)adc_val;  // 3,32В измерено мультиметром на пине "Выход 3.3", подставить свое значение.
       }
      else
      {
          temp_f_val = (float)adc_val * ADC_Val[0] / 1023.0;  //  тут все понятно
             // temp_f_val = temp_f_val * 5.0;
      }

       int_val= (uint16_t)( temp_f_val*100.0);    // -----  округлим до десятых

       if ( int_val % 10 >=5 )                    // если сотые больше или равно 5 добавим одну десятую
          int_val = (int_val / 10) + 1;
        else
          int_val = (int_val / 10);
     
       temp_f_val = (float) int_val / 10.0;  // // -----  закончили округление, вернем во float
     
      if (ADC_Val[adc_numb] != temp_f_val)   // если значение изменилось, то взведем бит изменения в разряде соответсвующем номеру порта.
      {
        ADC_Val[adc_numb] = temp_f_val;      // запишем изменение
        Update_flag |= (1 << adc_numb);
      }

    }

    void ADC_Check_new(byte adc_numb)                 // функция проверки АЦП и накопление среднего
    {
      uint16_t  adc_val;

      adc_val = analogRead(ADC_Pins[adc_numb]);       // прочитаем значение из порта

      if (ADC_Count_BUF[adc_numb] < ADC_SUM_COUNT)    //проверим, если просуммировали меньше чем нужно - добавим
      {
        ADC_SUM_BUF[adc_numb] += adc_val;
        ADC_Count_BUF[adc_numb]++;
      }

      if (ADC_Count_BUF[adc_numb] == ADC_SUM_COUNT)  // если набрали нужное количество измерений для данного порта
      {
        uint8_t  bit_mask = 1 << adc_numb;

        if ( ADC_Firts_Flag & bit_mask)     // проверим - это первый, после старта проход, для данного порта, сбрасываем значение и сбросим бит и больше не будем сюда заходить.
          ADC_Firts_Flag &= ~bit_mask;
        else                               //  иначе найдем среднее и обработаем наколенные значения
        {
          adc_val = ADC_SUM_BUF[adc_numb] / ADC_SUM_COUNT;

          if (ADC_SUM_BUF[adc_numb] % ADC_SUM_COUNT >= ADC_SUM_COUNT / 2 ) // округлим с учетом остатка
            adc_val++;

          ADC_Val_Update(adc_numb, adc_val);  // обработаем наколенные значения
        }
        ADC_SUM_BUF[adc_numb] = 0;           // сбросим счетчики
        ADC_Count_BUF[adc_numb] = 0;
      }
    }


    /****************************************************************************
       Главная программа
    ****************************************************************************/


    void setup()
    {
      uint32_t start_millis = millis();

      ADC_data_init();                            // инициализируем переменные, связанные с АЦП

      Serial.begin(115200);

      while (millis() - start_millis < 8000 )     // ждем 8 секунд открытия последовательного порта если не открыли порт, не выводим в него ничего
      {
        if (Serial)
        {
          Serial_Flag = true;                     // будем выводить данные в порт
          Serial.println("Serial Connected");
          break;
        }
      }
    }

    void loop()
    {
      uint32_t         cur_ms = 0;
      static uint32_t  adc_last_ms = 0;
      static uint32_t  frc_last_ms = 0;
      static uint8_t   cur_ADC_port = 0;

      cur_ms = millis();

      if (  cur_ms - adc_last_ms > ADC_CHECK_TO )   // проверим ADC
      {
        if (cur_ADC_port == ADC_QUANTITY)            // если перебрали все порты - начнем сначала
          cur_ADC_port = 0;

        ADC_Check_new(cur_ADC_port);                // проверим порт
        cur_ADC_port++;

        adc_last_ms = cur_ms;                      // запомним время проверки
      }

      if(cur_ms - frc_last_ms > FRC_Update_TO)
      {
        uint8_t i,n;
        for (i=0,n=1; i < ADC_QUANTITY; i++,n=n<<1)
          Update_flag |= n;
        frc_last_ms=cur_ms;
        Serial.println("Update TimeOut");
      }


      if (Update_flag )                             // обработаем  изменения если есть
      {
        uint8_t  j, n;

        for (j=0,n=1; j< ADC_QUANTITY; j++,n=n<<1)   // пробежим по всем портам (по флагу)
        {
            if ( Update_flag & n )                    // если бит изменения взведен - есть изменения
            {
                if (Serial_Flag)                      // если сериал открыт выведем данные в порт
                {
                    Serial.print("ADC A");
                    Serial.print(j);
                    Serial.print(" val= ");
                    Serial.println(ADC_Val[j]);
                }

                // здесь можно отправить изменения куда-то, например MQTT
                       
                Update_flag &= ~n;   // сбросим флаг изменений
            }
       
        }
      }
    }
     
     
    Ru52 нравится это.
  13. SergeiL

    SergeiL Оракул Модератор

    Да, только сейчас заметил, как вы писали, у Вас ардуина питается от 3.3В - у меня от 5В.
    Поэтому использую встроенный ненагруженный стабилизатор 3.3В как референс.
    Так какая у Вас ардуина в результате?
     
  14. Ru52

    Ru52 Нуб

    Pro mini. Но там свой реф встроенный должен быть 1.1
    Проверю на выходных.
    Большое спасибо за код!