Урок 13. Аналоговые входы платы Ардуино. Чтение аналоговых сигналов. Измерение среднего значения сигнала.

Arduino UNO R3

В уроке научимся работать с аналоговыми входами Ардуино.

Предыдущий урок     Список уроков     Следующий урок

Аналоговые входы платы Ардуино.

Плата Arduino UNO содержит 6 аналоговых входов предназначенных для измерения напряжения сигналов. Правильнее сказать, что 6 выводов платы могут работать в режиме, как дискретных выводов, так и аналоговых входов.

 

Эти выводы имеют номера от 14 до 19. Изначально они настроены как аналоговые входы, и обращение к ним можно производить через имена A0-A5. В любой момент их можно настроить на режим дискретных выходов.

pinMode(A3, OUTPUT);  // установка режима дискретного вывода для A3
digitalWrite(A3, LOW);   // установка низкого состояния на выходе A3

Чтобы вернуть в режим аналогового входа:

pinMode(A3, INPUT);   // установка режима аналогового входа для A3

Аналоговые входы и подтягивающие резисторы.

К выводам аналоговых входов, так же как и к дискретным выводам, подключены подтягивающие резисторы. Включение этих резисторов производится командой

digitalWrite(A3, HIGH); // включить подтягивающий резистор к входу A3

Команду необходимо применять к выводу настроенному в режиме входа.

Надо помнить, что резистор может оказать влияние на уровень входного аналогового сигнала. Ток от источника питания 5 В, через подтягивающий резистор, вызовет падение напряжения на внутреннем сопротивлении источника сигнала. Так что лучше резистор отключать.

 

Аналого-цифровой преобразователь платы Ардуино.

Собственно измерение напряжение на входах производится аналого-цифровым преобразователем (АЦП) с коммутатором на 6 каналов. АЦП имеет разрешение 10 бит, что соответствует коду на выходе преобразователя 0…1023. Погрешность измерения не более 2 единиц младшего разряда.

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Это требование особенно важно при использовании резисторных делителей, подключенных к аналоговым входам платы. Сопротивление резисторов делителей не может быть слишком большим.

Программные функции аналогового ввода.

int analogRead(port)

Считывает значение напряжения на указанном аналоговом входе. Входное напряжение диапазона от 0 до уровня источника опорного напряжения (часто 5 В) преобразовывает в код от 0 до 1023.

При опорном напряжении равном 5 В разрешающая способность составляет 5 В / 1024 = 4,88 мВ.

Занимает на преобразование время примерно 100 мкс.

int inputCod;           // код входного напряжения
float inputVoltage; // входное напряжение в В

inputCod= analogRead(A3); // чтение напряжения на входе A3
inputVoltage= ( (float)inputCod * 5. / 1024. ); // пересчет кода в напряжение (В)

 

void analogReference(type)

Задает опорное напряжение для АЦП. Оно определяет максимальное значение напряжения на аналоговом входе, которое АЦП может корректно преобразовать. Величина опорного напряжения также определяет коэффициент пересчета кода в напряжение:

Напряжение на входе = код АЦП * опорное напряжение / 1024.

Аргумент type может принимать следующие значения:

  • DEFAULT – опорное напряжение равно напряжению питания контроллера ( 5 В или 3,3 В). Для Arduino UNO R3 – 5 В.
  • INTERNAL – внутреннее опорное напряжение 1,1 В для плат с контроллерами ATmega168 и ATmega328, для ATmega8 – 2,56 В.
  • INTERNAL1V1 – внутреннее опорное напряжение 1,1 В для контроллеров Arduino Mega.
  • INTERNAL2V56 – внутреннее опорное напряжение 2,56 В для контроллеров Arduino Mega.
  • EXTERNAL – внешний источник опорного напряжения, подключается к входу AREF.

analogReference(INTERNAL); // опорное напряжение равно 1,1 В

Рекомендуется внешний источник опорного напряжения подключать через токоограничительный резистор 5 кОм.

 

Двухканальный вольтметр на Ардуино.

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

Решим, что вольтметр должен измерять напряжение в пределах не меньше 0…20 В и разработаем схему подключения входов вольтметра к плате Arduino UNO.

Если мы зададим опорное напряжение равным 5 В, то аналоговые входы платы будут измерять напряжение в пределах 0…5 В. А нам надо как минимум 0…20 В. Значит надо использовать делитель напряжения.

Схема делителя напряжения

Напряжение на входе и выходе делителя связаны соотношением:

Uвыхода = ( Uвхода / (R1 + R2 )) * R2

Коэффициент передачи:

K = Uвыхода / Uвхода = R2 / ( R1 + R2 )

Нам необходим коэффициент передачи 1/4 ( 20 В * 1/4 = 5 В).

Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм. Поэтому выбираем резистор R2 равным 4,22 кОм. Рассчитываем сопротивление резистора R1.

0,25 = 4,22 / ( R1 + 4,22)
R1 = 4,22 / 0.25 – 4,22 = 12,66 кОм

У меня с ближайшим номиналом нашлись резисторы сопротивлением 15 кОм. С резисторами R1 = 15 кОм и R2 = 4,22 :

5 / (4,22 / (15 + 4,22)) = 22,77 В.

Схема вольтметра на базе Ардуино будет выглядит так.Схема вольтметра на Ардуино

Два делителя напряжения подключены к аналоговым входам A0 и A1. Конденсаторы C1 и C2 вместе с резисторами делителя образуют фильтры нижних частот, которые убирают из сигналов высокочастотные шумы.

Я собрал эту схему на макетной плате.

Вольтметр из Ардуино

Первый вход вольтметра я подключил к регулируемому источнику питания, а второй к питанию 3,3 В платы Ардуино. Для контроля напряжения к первому входу я подключил вольтметр. Осталось написать программу.

 

Программа для измерения напряжения с помощью платы Ардуино.

Алгоритм простой. Надо:

  • с частотой два раза в секунду считывать код АЦП;
  • пересчитывать его в напряжение;
  • посылать измеренные значения по последовательному порту на компьютер;
  • программой монитор порта Arduino IDE отображать полученные значения напряжений на экране компьютера.

Приведу скетч программы сразу полностью.

// программа измерения напряжения
// на аналоговых входах A0 и A1

#include <MsTimer2.h>

#define MEASURE_PERIOD 500  // время периода измерения
#define R1  15.  // сопротивление резистора R1
#define R2  4.22 // сопротивление резистора R2

int timeCount;  // счетчик времени
float u1, u2;   // измеренные напряжения

void setup() {
  Serial.begin(9600);  // инициализируем порт, скорость 9600
  MsTimer2::set(1, timerInterupt); // прерывания по таймеру, период 1 мс
  MsTimer2::start();              // разрешение прерывания
}

void loop() {

  // период 500 мс
  if ( timeCount >= MEASURE_PERIOD ) {
    timeCount= 0;

    // чтение кода канала 1 и пересчет в напряжение
    u1=  ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);
    // чтение кода канала 2 и пересчет в напряжение
    u2=  ((float)analogRead(A1)) * 5. / 1024. / R2 * (R1 + R2);

    // передача данных через последовательный порт
    Serial.print("U1 = ");     Serial.print(u1, 2);
    Serial.print("   U2 = ");  Serial.println(u2, 2);         
  } 
}

// обработка прерывания 1 мс
void  timerInterupt() {
  timeCount++;
}

Поясню строчку, в которой пересчитывается код АЦП в напряжение:

// чтение кода канала 1 и пересчет в напряжение   
u1= ((float)analogRead(A0)) * 5. / 1024. / R2 * (R1 + R2);

  • Считывается код АЦП : analogRead(A0).
  • Явно преобразуется в формат с плавающей запятой: (float).
  • Пересчитывается в напряжение на аналоговом входе: * 5. / 1024. Точка в конце чисел показывает, что это число с плавающей запятой.
  • Учитывается коэффициент передачи делителя: / R2 * (R1 + R2).

Загрузим программу в плату, запустим монитор последовательного порта.

Показания вольтметра на мониторе порта

Два бегущих столбика показывают значения измеренных напряжений. Все работает.

 

Измерение среднего значения сигнала.

Подключим первый канал нашего вольтметра к источнику напряжения с большим уровнем пульсаций. Увидим такую картину на мониторе.

Показания вольтметра на мониторе порта

Значения напряжения первого канала на экране монитора все время дергаются, скачут. А показания контрольного вольтметра вполне стабильны. Это объясняется тем, что контрольный вольтметр измеряет среднее значение сигнала, в то время как плата Ардуино считывает отдельные выборки каждые 500 мс. Естественно, момент чтения АЦП попадает в разные точки сигнала. А при высоком уровне пульсаций амплитуда в этих точках разная.

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

Решение – сделать несколько частых выборок и усреднить измеренное значение. Для этого:

  • в обработчике прерывания считываем код АЦП и суммируем его с предыдущими выборками;
  • отсчитываем время усреднения (число выборок усреднения);
  • при достижении заданного числа выборок – сохраняем суммарное значение кодов АЦП;
  • для получения среднего значения сумму кодов АЦП делим на число выборок усреднения.

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

// программа измерения среднего напряжения
// на аналоговых входах A0 и A1

#include <MsTimer2.h>

#define MEASURE_PERIOD 500  // время периода измерения
#define R1  15.  // сопротивление резистора R1
#define R2  4.22 // сопротивление резистора R2

int timeCount;  // счетчик времени
long  sumU1, sumU2; // переменные для суммирования кодов АЦП
long  avarageU1, avarageU2; // сумма кодов АЦП (среднее значение * 500)
boolean flagReady;  // признак готовности данных измерения

void setup() {
  Serial.begin(9600); // инициализируем порт, скорость 9600
  MsTimer2::set(1, timerInterupt); // прерывания по таймеру, период 1 мс
  MsTimer2::start();              // разрешение прерывания
}

void loop() {

  if ( flagReady == true ) {
    flagReady= false;
    // пересчет в напряжение и передача на компьютер
    Serial.print("U1 = ");
    Serial.print( (float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
    Serial.print("   U2 = ");
    Serial.println( (float)avarageU2 / 500. * 5. / 1024. / R2 * (R1 + R2), 2);
    }
}

// обработка прерывания 1 мс
void  timerInterupt() {

  timeCount++;  // +1 счетчик выборок усреднения
  sumU1+= analogRead(A0);  // суммирование кодов АЦП
  sumU2+= analogRead(A1);  // суммирование кодов АЦП

  // проверка числа выборок усреднения
  if ( timeCount >= MEASURE_PERIOD ) {
    timeCount= 0;
    avarageU1= sumU1; // перегрузка среднего значения
    avarageU2= sumU2; // перегрузка среднего значения
    sumU1= 0;
    sumU2= 0;
    flagReady= true;  // признак результат измерений готов
    }
}

В формулу пересчета кода АЦП в напряжение добавилось /500 – число выборок. Загружаем, запускаем монитор порта (Cntr+Shift+M).

Показания вольтметра на мониторе порта

Теперь, даже при значительном уровне пульсаций, показания меняются на сотые доли. Это только потому, что напряжение не стабилизировано.

Число выборок надо выбирать, учитывая:

  • число выборок определяет время измерения;
  • чем больше число выборок, тем меньше будет влияние помех.

Основным источником помех в аналоговых сигналах является сеть 50 Гц. Поэтому желательно выбирать время усреднения кратное 10 мс – времени полупериода сети частотой 50 Гц.

 

Оптимизация вычислений.

Вычисления с плавающей запятой просто пожирают ресурсы 8ми разрядного микроконтроллера. Любая операция с плавающей запятой требует денормализацию мантиссы, операцию с фиксированной запятой, нормализацию мантиссы, коррекцию порядка… И все операции с 32 разрядными числами. Поэтому необходимо свести к минимуму употребление вычислений с плавающей запятой. Как это сделать я расскажу в следующих уроках, но давайте хотя бы оптимизируем наши вычисления. Эффект будет значительный.

В нашей программе пересчет кода АЦП в напряжение записан так:

(float)avarageU1 / 500. * 5. / 1024. / R2 * (R1 + R2)

Сколько здесь вычислений, и все с плавающей запятой. А ведь большая часть вычислений – операции с константами. Часть строки:

/ 500. * 5. / 1024. / R2 * (R1 + R2)

мы можем расчитать на калькуляторе и заменить на одну константу. Тогда наши вычисления можно записать так:

(float)avarageU1 * 0.00004447756

Умные компиляторы сами распознают вычисления с константами и рассчитывать их на этапе компиляции. У меня возник вопрос, насколько умный компилятор Андруино. Решил проверить.

Я написал короткую программу. Она выполняет цикл из 10 000 проходов, а затем передает на компьютер время выполнения этих 10 000 циклов. Т.е. она позволяет увидеть время выполнения операций, размещенных в теле цикла.

// проверка оптимизации вычислений

int x= 876;
float y;
unsigned int count;
unsigned long timeCurrent, timePrev;

void setup() {
  Serial.begin(9600);
}

void loop() {
  count++;
  // y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);
  // y= (float)x * 0.00004447756;
   
  if (count >= 10000) {
    count= 0;
    timeCurrent= millis();
    Serial.println( timeCurrent - timePrev   );
    timePrev= timeCurrent;     
  }
}

В первом варианте, когда в цикле операции с плавающей запятой закомментированы и не выполняются, программа выдала результат 34 мс.

Показания вольтметра на мониторе порта

Т.е. 10 000 пустых циклов выполняются за 34 мс.

Затем я открыл строку:

y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);

повторяет наши вычисления. Результат 10 000 проходов за 922 мс или

( 922 – 34 ) / 10 000 = 88,8 мкс.

Т.е. эта строка вычислений с плавающей запятой требует на выполнение 89 мкс. Я думал будет больше.

Теперь я закрыл эту строку комментарием и открыл следующую, с умножением на заранее рассчитанную константу:

 y= (float)x * 0.00004447756;

Результат 10 000 проходов за 166 мс или

( 166 – 34 ) / 10 000 = 13,2 мкс.

Потрясающий результат. Мы сэкономили 75,6 мкс на одной строке. Выполнили ее почти в 7 раз быстрее. У нас таких строк 2. Но ведь их в программе может быть и гораздо больше.

Вывод – вычисления с константами надо производить самим на калькуляторе и применять в программах как готовые коэффициенты. Компилятор Ардуино их на этапе компиляции не рассчитает. В нашем случае следует сделать так:

#define ADC_U_COEFF 0.00004447756 // коэффициент перевода кода АЦП в напряжение

Serial.print( (float)avarageU1 * ADC_U_COEFF, 2);

Оптимальный по быстродействию вариант – это передать на компьютер код АЦП, а вместе с ним и все вычисления с плавающей запятой. При этом на компьютере принимать данные должна специализированная программа. Монитор порта из Arduino IDE не подойдет.

О других способах оптимизации программ Ардуино я буду рассказывать в будущих уроках по мере необходимости. Но без решения этого вопроса невозможно разрабатывать сложные программы на 8ми разрядном микроконтроллере.

 

В следующем уроке научимся работать с внутренним EEPROM, поговорим о контроле целостности данных.

Предыдущий урок     Список уроков     Следующий урок

25 комментариев на «Урок 13. Аналоговые входы платы Ардуино. Чтение аналоговых сигналов. Измерение среднего значения сигнала.»

  1. Имхо следующая формула чуть неверна:

    inputVoltage= ( (float)inputCod * 5. / 1024. ); // пересчет кода в напряжение (В)

    Посудите сами — вам пришел максимальный код 1023 (это 5 Вольт), а по этой формуле получим 1023*5/1024=4,9951В
    поэтому делить нужно на 1023
    1023*5/1023=5В

    • Не согласен. У 10 разрядного АЦП 1024 градаций. 0 это тоже ступенька, со своим диапазоном входного напряжения. Совсем точная формула должна учитывать округление и выглядит так:
      inputVoltage= ( (float)inputCod * 5. / 1024. + 0.0024414 ); // пересчет кода в напряжение (В),
      где 0.0024414 = 5 / 1024 /2 – половина дискретины АЦП. На эту тему много споров.

  2. Масштабируем задачу до однобитного АЦП.
    В однобитном АЦП максимальное значение выдаваемое АЦП будет 1 и разрешающая способность будет 5В/1=5В
    АЦП = 0 -> 0В
    АЦП = 1 -> 5В
    V = АЦП * 5 / 1 = 5В
    и я не должен предполагать сколько там на входе 2.8 или 5В — АЦП все равно покажет 1 (5В) и я должен ему верить при данной разрядности

    по вашему же получается разрешающая способность будет 5В / 2 = 2.5В
    V = АЦП * 5 / 2 = 2.5В
    т.е. по вашей формуле я никогда с такого АЦП не получу 5В при пяти вольтах на входе

    • Евгений! В Вашем примере разрешающая способность АЦП равна 2,5 В. Т.е. 2 ступени по 2,5 В. По последней формуле с округлением получается 1 * 2,5 + 1,25 = 3,75 В. Т.е. ровно в середине последней ступени. Это и есть самое правильное значение с минимальной погрешностью +- 1,25 В. Ведь 1 означает, что напряжение на входе АЦП находится в диапазоне 2,5 … 5 В.
      Тут тонкость в том, что есть разрешающая способность АЦП. И точно получить значение источника опорного напряжения при максимальном коде не возможно. Мы можем притянуть к 0 это 5 / 1024, можем притянуть к середине ступеней дискретности 5 / 1024 + 0,5 дискретины АЦП. А если 5 /1023 это уже изменение пропорции, мы притягиваем и к 0 и к ИОН. Согласны?

  3. Согласен с вами.
    Я наклонил свою линию (если отобразить графически V=f(ADC)), но нигде не превысил погрешность в диапазоне значений АЦП, а в итоге в приложении получаю и 0 и максимальное (опорное) напряжение.

  4. при расчете делителя напряжения как-то забывается о входном сопротивлении аналогового входа ардуинки, которое отнюдь не бесконечно.

    например, делитель из прецизионных резисторов 100к и 10к с допуском в 0.01% в рельной жизни даст коэффициент не ровно 10, а около 11

    • Я пишу:
      Для сохранения максимальной точности (10 разрядов) необходимо, чтобы внутреннее сопротивление источника сигнала не превышало 10 кОм.

      • наверное правильнее будет написать, что в определенных пределах с уменьшением сопротивлений резисторов делителя повышается общая точность измерения

        да и привязываться к тому, что напряжение питания у нас будет ровно 5В тоже не стоит, логичнее было бы analogReference выставить в internal

        в целом урок достаточно понятно написан, спасибо

        P.S. ну и, если можно, исправьте avarage на average, немного режет глаз

  5. скопировал программу измерения напряжения
    но компилятор ругается, выдает такую ошибку подскажите в чем дело:

    Arduino: 1.6.11 (Windows 7), Плата:»Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)»

    C:\Users\РїСЂ\Documents\Arduino\sketch_sep25a\sketch_sep25a.ino: In function ‘void setup()’:

    sketch_sep25a:15: error: ‘timerInterupt’ was not declared in this scope

    exit status 1
    ‘timerInterupt’ was not declared in this scope

    • Проверил. У меня все работает. Наверное, Вы не установили библиотеку MsTimer2. Загрузите ее из урока 10 и установите как там написано.

  6. Может я немного не по теме но очень волнует вопрос на который не могу найти ответа. Вопрос вот в чем на аналоговом входе подключен джойстик всегда находиться в среднем положении Задача нужно записать в переменную значение максимального отклонения стика как в большую сторону так и в меньшую.И при возврате в среднее положение сохранить это значение для дальнейшего использования. Подскажите пожалуйста как это реализовать?

    • Почему бы не сделать, как в программе измерения среднего значения из этого урока.
      Сделать прерывание по таймеру 2 мс.
      В нем суммировать значения АЦП в переменную усреднения.
      Каждые 20 мс (через 10 выборок по 2 мс) перегружать переменную усреднения в averageU.
      // обработка прерывания 2 мс
      void timerInterupt() {
      timeCount++; // +1 счетчик выборок усреднения
      sumU1+= analogRead(A0); // суммирование кодов АЦП

      // проверка числа выборок усреднения
      if ( timeCount >= 10 ) {
      timeCount= 0;
      averageU1= sumU1; // перегрузка среднего значения
      sumU1= 0;
      flagReady= true; // признак результат измерений готов
      }
      }
      В основном цикле сравнивать averageU со значением среднего положения джойстика (в каком-то диапазоне), а при отклонении записывать значение averageU. Многое зависит от остальной части программы. Лучше ее синхронизировать с измерением через флаг flagReady.

  7. Очень качественное изложение, спасибо.
    Проще, по моему, коэффициенты расчитывать не на калькуляторе, а в void setup() — наглядней, быстрее, меньше вероятность ошибки, выше точность, таже скорость, а код не намного больше

    • Можно и так. Но при явном задании коэффициентов они хранятся во FLASH. Это намного надежнее.

  8. (кратко повторно)
    Вычислять коэф. , по моему, проще не калькулятором, а в СЕТАПе скетча: наглядней, выше точность, меньше вероятность ошибки, а кода не намного больше
    (кстати, не собираетесь ли писать про оптимальную запись данных на СД-карту, особенно применительно к Ардуино ДУЕ? как-то у меня не получается чтобы и быстро и без разрывов выборки, попал на сайт ища инфу по Указателям)
    ПИД тоже буду реализовывать, жду продолжения

  9. Я коэффициенты вообще не вычислял. Все равно сопротивления делителя замерять для коррекции… Ну а я предпочел замерить фактическое напряжение на делителе (вольтметром с хорошей точностью и разрешением) и то, что показывается с АЦП разделить на фактически измеренное напряжение. Ну и потом полученное значение — в программу в качестве константы и делить на нее, чтобы получить напряжение. Все должно быть линейно. Такой вот незатейливый коэффициент )). Наверное, это не по-научному, но для моих целей хватало…
    Эдуард, приветствую! Не поясните поподробнее про точность и сопротивление? Вообще-то когда у вольтметра, любого, низкоомный вход — это плохо. А в нашем случае получается, что вход — это делитель 20к. Нельзя назвать такой вход высокоомным — эта штука будет шунтировать многие малопотребляющие нагрузки.

    • По поводу метода, который вы предложили. Это вполне научный метод. Называется калибровка измерителя. Я просто стремился показать как вычисляются коэффициенты.
      Что касается входного сопротивления 20 кОм, тоже все верно. А как вы хотите получить высокое входное сопротивление вольтметра при входном сопротивлении АЦП 10 кОм. Надо ставить усилитель.

        • В документации на ATmega168/328 указано, что для сохранения точности 10 разрядов выходное сопротивление источника сигнала должно быть не более 10 кОм.

          • Я подозревал, что у АЦП должно быть сопротивление порядка мегомов, ато и десятков. После вашего ответа пошел гуглить и получил информацию, что там даже 100М )) Однако, мешает коммутация каналов АЦП и паразитная емкость линии, которая вкупе с высоким сопротивлением, подключенным к линии, может портить измерение за счет разряда или заряда этой емкости.
            http://electronics.stackexchange.com/a/67173
            Интересный факт. Не знал и не заморачивался — лепил полумегаомные делители и особых потерь в точности не наблюдал. Если буду еще раз такое делать, видимо придется операционники на буферы изводить…

          • Входное сопротивление я сказал условно. Скорее это требования к выходному сопротивлению источника сигнала, обусловленные токами утечки, коммутацией устройства выборки-хранения АЦП и т.п.

          • Еще очень неприятно увеличивается влияние напряжения смещения компаратора АЦП (OFFSET) при уменьшении напряжения ИОН.

  10. Спасибо огромное за понятные уроки ардуино.
    Вашу программу залил, код с АЦП идёт как положено одинаковый с отклонением в единичку, умножает тоже правильно, только при измерении в 2-х точках, напряжений 1,25в и 4в на 4в отклонение +0,2В набегает. Так может быть и как с этим бороться? Вводить разные коэффициенты в зависимости от кода АЦП?
    Я недавно заинтересовался темой ардуино, привлекла простота реализации, стал разбираться. Для начала конечно попробовал сделать вольтметр, как основу всех измерений. Залил програмку по работе аналогичной Вашей, с делителем, внутренним опорным и с delay, такими забит интернет. Показания вольтметра при этом скакали на 0,6в при номинале 1,25в. Стал разбираться в чём дело, думал наводки, посмотрел код АЦП, оказалось там всё как положено, убегает на единицу в последнем знаке. Всё безобразие оказалось при умножении коэффициента те на этапе преобразования кода в число вольт. Пытался доводить программу, поставил одну цифру для умножения, после того как попался Ваш сайт, думал от того что много цифр ошибка набегает, или не успевает умножать. В сухом остатке сравнивая число АЦП и результат умножения оказалось что скачет именно коэффициент умножения даже при умножении на одну цифру. Я уж думал что плата попалась не качественная. Затем залил вашу программу с этой страницы и всё заработало, коэффициент умножения в каждом измерении одинаковый. Либо delay не давала выполнить операцию умножения, как Вы считаете?
    Теперь думаю что delay ни в каком цикле применять нельзя, лучше организовать по таймеру, как Вы прописали тут и для более сложного в уроке 38. Поэтому теперь только на Ваши статьи ориентируюсь, для меня это лучший сайт по данной тематике, и в интернете по крупицам, успехов Вам!

    Вот та косячная программа, которой нельзя пользоваться)

    #define analogInPin A2
    float outputValue = 0;
    const float Mult =0.018035;

    void setup()
    {
    analogReference(INTERNAL);
    Serial.begin(9600);
    }

    void loop()
    {
    outputValue =((float)analogRead(analogInPin))*Mult;
    Serial.print(analogRead(analogInPin));
    Serial.print(» «);
    Serial.println(outputValue,4);
    delay(500);
    }

    • Спасибо за добрые слова.
      Про погрешности измерения АЦП Ардуино можете посмотреть на форуме сайта http://mypractic-forum.ru/viewtopic.php?t=9.

      Что касается программы, которую вы привели. Мне не нравится, что в ней нет усреднения измеренного сигнала. Она показывает отдельные выборки каждые 0,5 секунды.

      • Спасибо Эдуард, изучил ссылку. Там перечисляете ошибки АЦП, по идее они суммируются, но в конце пишите, что «погрешность измерения АЦП Ардуино не превышает 0,2 %» Если просуммировать, получается до 4,75 единиц последнего разряда ошибка возможна те 0,47%. В моём случае с погрешностью, я по 2 точкам измерения рассчитал из системы 2 уравнений Uизм1=(АЦП1+Ошибка)*К и Uизм2=(АЦП2+Ошибка)*К, что ошибка 3,8 единицы младшего разряда и К тоже вычислил. На третьем напряжении, для проверки, всё также сходится

        Согласен, без усреднения плохо, но с усреднением тоже пробовал и результат был тот же, усреднённое число с ацп меньше плавало, но так же не верно умножалось, потом что умножение с delay не правильно выполняется, почему то. Когда искал в чём причина ошибки, упростил программу до максимума, чтобы её найти

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *