Добрый день! Мониторю состояние напруги в системе 12В. Ардуино питается с трансформатора, выходами подключенного к АКБ, через стабилизированный БП 3.3 V. С клемм АКБ взят входной сигнал на делитель. Напряжение на АКБ стабильно - 13,7 V без скачков. С делителя 1:5 подается сигнал на аналоговый вход ардуино, вход подтянут резистором к нулю. Питание стабильно, при этом на входе ардуины постоянное небольшое "дрожание" с регулярной просадкой до 0,5 вольт, что при моих задачах является неприятным отклонением. С чем может быть связано, посоветуйте как это побороть, если это возможно? График напряжения для иллюстрации приложил.
http://forum.amperka.ru/threads/Кон...rduino-и-остальная-нагрузка.10210/#post-96212 Посмотрите эту тему.....
А если усреднять, замеров так за 30? + правильно округлять? Вы уверены, что Вам сотые на 13,7В нужны?
Там не сотые, а до полувольта скачки, к сожалению. На графике тоже видно - 13,72 и 13,27 соседствуют. Думал о таком варианте - округлять последние 5 замеров. Если не найдётся простого решения, скорее всего так и буду двигаться, вполне рабочий, хоть и не самый удобный вариант.
Судя по этой теме - решением будет поставить внешний шилд для контроля напряжения. Другого решения я в теме не увидел. Кстати пробовал выбирать режим референса INTERNAL вместо DEFAULT (в котором VCC и есть референсное напряжение, как я понимаю) - в показаниях вообще полная белиберда была (то что опорное внутреннее должно быть порядка 1,1 я учитывал).
Зачем там внешний шилд? На входе АЦП - обычный делитель, один вход АЦП заводим на 3.3В ардуины. Референс на VCC. Считываем АЦП подключенный к 3.3В. Вычисляем напряжение VCC по значению АЦП, подключенному к 3.3В (на 3.3 больше ничего не висит обычно) Считываем АЦП, подключенный к делителю, вычисляем напряжение с учетом рассчитанного VCC. Повторяем предыдущие шаги н-ное количество раз, вычисляем среднее с учетом округления. Ничего не прыгает вообще: На графике видно отключение питания 220В на даче, и цикл зарядки аккумулятора. Сейчас используется одна батарея, оба входа устройства подключены к ней. Сделал два входа на будущее, чтобы при длительном отключении электричества, при сильной разрядке батареи, только сильно жрущее видеонаблюдение отключалось бы, а сигнализация осталась в работе.
Сергей, не очень понятно для чего вы вычисляете значение Vcc? Если референсным напряжением на ардуино по умолчанию выставлен DEFAULT, то это означает, что в качестве опорного будет выступать напряжение на самом Vcc. А в этой ситуации попытка чтения своего же напряжения Vcc на любой другой ножке АЦП должно возвращать вам 1023. Можете показать кусок скетча в части измерения напряжений?
Вы же рассчитываете напряжение на входе делителя из условия, что 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, напряжение на входе делителя измеряется корректно. Код посмотрю - попробую из него пример сделать. Там просто долго разбираться придется. много чего дописывалось, не оптимизировалось, как всегда, когда для себя, комментарии не пишем.
Да, я собственно об этом и говорил. Но дело в моем случае в другом: 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 любая нестабильность на входе будет влиять на качество измерений. И смысла получается нет измерять своё собственное напряжение, т.к. сравнивать мы будем с ним же самим, а это на уровне Мюнхгаузена, который сам себя за волосы вытащил из болота Если код выложите в любом виде, буду очень признателен.
Сергей, добавлю. Видимо, моя ошибка в том, что после выставления INTERNAL в качестве опорного, напряжение нужно снимать с Vcc на АЦП пин, питая саму ардуину через RAW. В таком случае как вы и говорите, я смогу найти реальную величину Vcc. И получается INTERNAL в качестве опорного не означает, что максимальное значение на входе АЦП будет 1,1V, как я думал ранее. Вот эта статейка натолкнула на мысль: https://jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/
Подождите, не мучайте устройство Исходник частично перелопатил, вечером проверю, выложу. Но, мне кажется, он все равно избыточен.
Вчера не смог завершить - пришлось по работе отвлечься. В командировке я . Порт 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; // сбросим флаг изменений } } } }
Да, только сейчас заметил, как вы писали, у Вас ардуина питается от 3.3В - у меня от 5В. Поэтому использую встроенный ненагруженный стабилизатор 3.3В как референс. Так какая у Вас ардуина в результате?