Вольтметр на ATtiny85 и TM1637

Добрый день.
Делаю вольтметр на ATtiny85 и TM1637. За основу взят скетч Самодельный вольтметр для батареек от Sega-san. Алгоритм измерения протестировал контролем по serial. Точность понравилась, практически одна сотая вольта. У автора применен OLED дисплей. Мне надо выводить на TM1637.
Проблема в том, что при использовании этого индикатора, начинает сбиваться процесс измерения. Если без вывода на индикатор, точность измерения одна сотая. При включении функции вывода точность падает до 3 десятых и показания прыгают в этих пределах, что не устраивает. Вот код:

[code]
/*  ATtiny85 Battary_tester TM1637 & Serial
  *********************************************   
  * ATtiny85 @ 8 MHz (internal); BOD(2,7V); Optiboot
  ******************************************** 
  *      TM1637  ATtiny85   
  *         <<------>>
  *       PB5-|    |-VCC
  *  CLK--PB3-|    |-PB2--DIO
  *  AIN--PB4-|    |-PB1--Serial Rx
  *       GND-|    |-PB0--Serial Tx
  *       
  ********************************************           
  * V2 - библиотекa TM1637Display https://github.com/jasonacox/TM1637TinyDisplay
*/

// НАСТРОЙКА
/* 
 * Ставим #define NASTROYKA 1
 * Заливаем код, запускаем, запоминаем значение на дисплее, например 5291
 * Измеряем мультиметром реальное напряжение на выходе преобразователя, например 4999 (это в мВ)
 * Считаем (4999/5291)*1.1=1.0392931
 * Считаем 1.0392931*1023*1000 = 1063196
 * Записываем результат в строку 100 в виде result = 1063196L
 * Ставим #define NASTROYKA 0
 * Заливаем код, запускаем, готово.
 */

#include <TM1637TinyDisplay.h>

#define NASTROYKA 0

long Vcc;
float Vbat;

#define CLK           PB3
#define DIO           PB2
#define t1_Interval  1000 // время (1000) милисекунд

// тонкая настройка алгоритма сглаживания  shumodav()
#define ts   5  // *table size* количество строк массива для хранения данных, для девиации +/- 2 отсчёта оптимально 4 строки и одна в запас.
#define ns  25  // *number samples*, от 10..до 50 максимальное количество выборок для анализа 1й части алгоритма
#define ain A2  // какой аналоговый вход читать  (А2 это PB4)
#define mw  50  // *max wait* от 15..до 200 ms ожидать повтора отсчёта для 2 части алгоритма

unsigned int myArray[ts][2], aread, firstsample, oldfirstsample, numbersamples, rezult;
unsigned long prevmillis = 0;
boolean waitbegin = false; //флаг включённого счётчика ожидания повтора отсчёта

unsigned long timer1;

TM1637TinyDisplay display(CLK, DIO);
//---------------------------------------------
void setup(){
// инициируем Serial
    Serial.begin(9600);

// инициируем дисплей 
   display.begin();
   display.setBrightness(BRIGHT_7); 
}
//---------------------------------------------
void loop(){
 if(millis() - timer1 >= t1_Interval){                  // ищем разницу
    timer1 = millis();
      
    for(byte i = 0; i < 5; i++){Vcc += readVcc();}
    Vcc /= 5;
    shumodav();
    Vbat = ((rezult / 1023.0) * Vcc) / 1000;

  if(Vbat >= 0.95){
    
#if NASTROYKA
    Serial.println(rezult);
#else
    Serial.println(Vbat, 2);
// выводим на индикатор
//    display.showNumber(Vbat, 2); 
#endif
  }
    Vcc = 0;

 }
}
//---------------------------------------------
long readVcc(){    // чтение реального напряжения питания
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(75); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

// индикатор показывал 4990, вольтметр 4576мВ (4576/4990)*1.1=1.008737
  result = 1063196L / result; // Calculate Vcc (in mV); 1031938 = 1.008737*1023*1000

  return result; // Vcc in millivolts
}
//---------------------------------------------
void shumodav(){ // главная функция
// заполнить таблицу нолями в начале цикла
  for(int s=0; s<ts; s++){
     for(int e=0; e<2; e++){myArray[s][e] = 0;}
     }

// основной цикл накопления данных
  for(numbersamples=0; numbersamples<ns; numbersamples++){
    
#if NASTROYKA
    aread = readVcc();
#else
    aread = analogRead(ain);
#endif

// уходим работать с таблицей
    tablework();
    }

// заполнен массив, вычисляем максимально повторяющееся значение
  int max1 = 0;                   // временная переменная для хранения максимумов
  for(byte n=0; n<ts ; n++){
     if(myArray[n][1] > max1){    // перебор 2-х элементов строк
     max1 = myArray[n][1];        // запомним куда больше всего попало
     firstsample = myArray[n][0]; // его 1 элемент = промежуточный результат.
     }
  }

//*****вторая фаза алгоритма *********//
  // если старый отсчёт не равен новому,
  //и флага включения счёта времени небыло, то
  if(oldfirstsample != firstsample && waitbegin == false){
     prevmillis = millis();  // скидываем счётчик времени на начало
     waitbegin = true;       // активируем флаг ожидания
     } 

// если до истечения лимита времени отсчёт сравнялся
// со старым, то снимаем флаг
  if(waitbegin == true && oldfirstsample == firstsample){
     waitbegin = false;
     rezult = firstsample;
     }

// если всё таки отсчёт не сравнялся, а время ожидания вышло
  if(waitbegin == true && millis() - prevmillis >= mw){
     oldfirstsample = firstsample;
     waitbegin = false;
     rezult = firstsample; // то признаём новый отсчёт конечным результатом функции
     } 
} 
//---------------------------------------------
void tablework() { // функция внесения данных в таблицу
// если в таблице совпадает отсчёт, то инкрименировать
// его счётчик во втором элементе
  for(byte n=0; n<ts; n++){
     if(myArray[n][0] == aread){
        myArray[n][1] ++;
        return;
        }
     }

// перебираем ячейки что б записать значение aread в таблицу
  for(byte n=0; n<ts; n++){
    if(myArray[n][0] == 0){ // если есть пустая строка
      myArray[n][0] = aread;         
      return;
      }
    }

// если вдруг вся таблица заполнена раньше чем кончился цикл,
// то счётчик циклов на максимум
    numbersamples = ns;
} 
//END------------------------------------------
[/code]

Если 79 строку закомментировать результат измерения будет выводится только в serial, и точность составит одну сотую вольта. Если её раскомментировать и выводить на TM1637, то результат измерения будет прыгать в диапазоне 3 десятых вольта.
Автор, к сожалению помочь не смог, сказал, что алгоритм брал здесь на форуме у товарища dimax. Поэтому и обращаюсь к форуму и уважаемому dimax, как сделать, что бы при выводе на TM1637 процесс измерения не сбивался и точность осталась прежней. Пробовал с тремя разными библиотеками TM1637, на всех сбивается.

Возможно дело в наводках по питанию при работе дисплея.
Для ПРОВЕРКИ можно перед циклом измерения делать display.clear(); delay(500);

 if(millis() - timer1 >= t1_Interval){                  // ищем разницу
    display.clear(); delay(500);
    timer1 = millis();

Если дело в этом, то потом подобрать delay(); на минимально допустимое значение, когда наводки будут приемлемы и моргание дисплея тоже. Или (и) сделать цикл измерения не раз в секунду а реже.

1 лайк

вывсёврёти!!!

1 лайк

При таком измерении напряжения, стабильного результата Вы не получите. Не дисплей, так ещё что… У Вас четыре принципиальные ошибки в работе с ADC.

Ошибки такие:

  1. Вы измеряете Vcc один раз при включённом макросе NASTROYKA. Правильно измерять напряжение питания всякий раз непосредственно перед измерением нужного напряжения и использовать измеренное напряжение питания, а не что-то раз и навсегда запомненное;
  2. Вы не привели схемы, и даже не сказали, что у Вас за плата, но если у Вас обычная ардуино UNO/NANO (не самоделка на голом чипе), то у Вас нет шумоподавителя на питании ADC, а для измерительного прибора он абсолютно необходим (конкретная, сегодняшняя, Ваша проблема, скорее всего, здесь зарыта);
  3. Опять же Вы не привели схемы, но я почти уверен, что у Вас не конденсатора на пине ARef (его никто из новичков не ставит потому, что “и так работает”);
  4. Наконец, Вы измеряете напряжение во время работы контроллера. Для, например, контроля заряда собственной батареи пойдёт, но Вы же измерительный прибор делаете! Надо использовать специальный режим “Понижения шума АЦП” - т.е. погружать контроллер в сон на время измерения, чтобы ничто не влияло.

Пока Вы это не исправите, нормальной стабильной работы прибора не будет. Вам, как тому танцору, всё будет мешать – не дисплей, так ещё чего (плоть, до работающего рядом утюга).

2 лайка

Что не так?

Схема подключения не так. У тебя неправильно разведена плата. Не хватает фильтров по питанию и фильтров питания цепей АЦП.

1 лайк

главное это п 1.
Вы откалибровали точность и думаете это навсегда? Нет, эту калибровку надо повторять перед каждым использованием.
Для начала повторите “Настройку” с подключенным дисплеем - может станет чуть лучше.

*********************************************   
  * ATtiny85 @ 8 MHz (internal); BOD(2,7V); Optiboot
  ******************************************** 
  *      TM1637  ATtiny85   
  *         <<------>>
  *       PB5-|    |-VCC
  *  CLK--PB3-|    |-PB2--DIO
  *  AIN--PB4-|    |-PB1--Serial Rx
  *       GND-|    |-PB0--Serial Tx
  *       
  ********************************************           
  * V2 - библиотекa TM1637Display https://github.com/jasonacox/TM1637TinyDisplay

Схема была приведена в заголовке скетча. Пин ARef у ATtiny85 это РВ0, на нем висит Serial Tx, поэтому конденсатор не могу повесить.

Плата не разведена, подключено пока на макетке. ATtiny85 подключена к UART, а тот к USB, от него и питается. По питанию стоит конденсатор 100 мк, керамика 0,15мк. На измерительном входе РВ4 тоже конденсатор 100 мк, но керамики нет.

Да скорее всего так и есть. Увеличил емкость по питанию до 2000мк, точность повысилась с 3 до 1 десятых.

Я делаю именно для контроля заряда собственной батареи.

Тут что то не понял, если контроллер спит, кто измерения делает?

А вот замена 100мк на 2200мк по измерительному входу не добавило точности измерения, так и осталось в пределах 1 десятой.
Но всё равно прогресс есть. Возможно вся проблема именно в этом. Спасибо всем кто откликнулся.

Явно не указали, у Вас голый чип или плата на базе тиньки.

Кстати, я не обратил внимание на то, что это тинка, если не поздно, замените на Atmega328P, тинька плохо подходит для прибора, т.к. у неё нет отдельного питания АЦП. Хотя, если всё остальное соблюсти, то в принципе результат можно приемлемый получить.

Не была. Вы не знаете как схемы выглядят?

Ну, это опять про то, что я не обратил внимания, что у Вас тинька. Там это не надо. Всё же лучше Вам от тиньки уйти, если прибор делаете.

Кстати, схемы я не видел, но я надеюсь, Вы знаете, как правильно делается

в том смысле, что Вы понимаете, что для измерения батареек недостаточно просто измерить напряжение, а надо делать это под нагрузкой (чем и отличается специальный режим проверки батареек в мультиметре)? Вот посмотрите в этом отзыве фото измерения одной и той же батарейки в режиме вольтмерта и в режиме проверки батареек.

1 лайк

Виноват Евгений Петрович, на будущее учту. Заменять не хотелось бы.

Опять вы правы, знаю конечно и повиняюсь.

Обязательно посмотрю. Благодарю за столь развернутые ответы.

Первое - Вы не указали, чем Вас не устраивает OLED. Зачем надо было что-то менять, если первоначальная схема устраивает?

1 лайк

Мне надо 2 вольтметра для балансира свинцовых АКБ. ТМ1637 есть в наличии, устраивает конструктив, яркость, и вообще нравятся семисегментные индикаторы. OLEDов нет, надо покупать, дорого и самое неприятное выгорают они в режиме работы 24/7.

Вы всерьез думаете, что переделка проекта будет дешевле?

1 лайк

Надеюсь.