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

 

Lesson_65_6

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

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

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

Плата 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ми разрядном микроконтроллере.

На сайте появился еще один урок (урок 65) посвященный измерению аналоговых сигналов. В нем рассматривается работа АЦП в фоновом режиме.

 

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

 

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

2

Автор публикации

не в сети 2 недели

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

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

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

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

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

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

      0
      • 10 бит — это 1024 числа от 0 до 1023.
        Сколько отрезков числовой оси между этими 1023 числами, между 0 и 1023? Отрезков всего 1023. Чему равна цена дискрета, или длина одного отрезка между двумя соседними числами? 1/1023.
        Это банальная арифметика.

        0
        • Здравствуйте!
          Отрезков 1024. Первый отрезок это 0, второй — 1 и т.д.
          Если входное напряжение в пределах от 0 до значения ИОН / 1024 / 2, то АЦП должен показывать число 0. Это и есть первый отрезок.

          0
  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В при пяти вольтах на входе

    0
    • Евгений! В Вашем примере разрешающая способность АЦП равна 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 и к ИОН. Согласны?

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

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

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

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

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

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

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

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

        0
  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

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

      0
      • А у меня не работает! И причина в pin mode(A0,input)
        А если это не писать работает правильно

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

    0
    • Почему бы не сделать, как в программе измерения среднего значения из этого урока.
      Сделать прерывание по таймеру 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.

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

    0
      • А если в начале задать константу
        const float coeff = 1./ 500. * 5. / 1024. / R2 * (R1 + R2);
        то и храниться она будет в том же флеше, и кода больше не станет (вычисление произойдет на этапе компиляции), и калькулятор открывать не надо 🙂

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

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

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

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

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

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

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

            0
  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);
    }

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

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

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

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

        0
  11. Присоединяюсь ко всем добрым словам про автора и его уроки. читать приятно и понимать легко. сам только начинаю разбираться с ардуино и языком программирования, плата заказана и едет ко мне. вопрос, который возник уже много уроков назад, но вот только сейчас озвучиваю:
    мы обьявляем и изменяем некоторые переменные, допустим в этом примере это timeCount — мы ее увеличиваем в обработчике прерывания, и обнуляем при превышении заданной величины в основном теле кода. Почему мы явно не указываем на то, что изначально переменная должна быть ==0 ?? это синтаксис языка такой? по умолчанию переменная=0 ? заранее благодарен за ответ. успехов вам.

    0
    • Спасибо. При объявлении переменных они инициализируются 0, хотя лучше указывать явно, там где это необходимо.

      0
  12. Добрый день! При смене полярности источника питания arduino показывает 0в. Возможно ли программно отследить смену полярности, что бы вольтметр показывал, ну например, отрицательные значения?

    0
    • Здравствуйте! Нет. Аналоговые входы Ардуино работают в диапазоне 0…5 В. Более того, если подать отрицательное напряжение или напряжение более 5 В без ограничительного резистора, то микроконтроллер выйдет из строя.
      Отрицательное напряжение можно измерить для гальванически развязанной цепи если добавить к нему положительное смещение. В уроке 27 для измерения отрицательного напряжения термопары я добавляю смещение.

      0
  13. Здравствуйте! Кто-то реализовывал, чтобы программа выводила результаты не в монитор, а в виде двух значений в части экрана? По моему где-то видел, не могу найти, помогите. С уважением Юроий

    0
  14. Здравствуйте! При исполнении программы по оптимизации вычислений получил следующие значения:
    1. Простой прогон 10000 циклов 19-20ms;
    2. Полный набор вычислений 23-25ms;
    3. вычисления с коэффициентом тоже 23-25ms;
    Может ли отсутствие разницы в скорости вычислений быть связано с версией компилятора у меня 1.6.13?
    Пробовал варианты, добавлял вычислений во второй вариант, все равно время 2 и 3 равны

    0
    • Здравствуйте!
      Чудес не бывает. А как вы вычисляли время? Повторили мои скетчи?
      Попробуйте поставьте квалификатор volatile перед переменной y.
      volatile float y;

      0
    • Более точное измерение времени выполнения команд и функций есть на форуме сайта. Попробуйте ту методику.

      0
  15. Пробовал разные варианты, в том числе и скопировал Ваш скетч. Добавил квалификатор volatile, пока время 2 и 3 остались прежними.
    1. Пустое время прогона 10000 циклов 19-20ms. против 34ms;
    2. Прочие значения остались прежними.
    То есть вариант, что в новой версии, что-то могли оптимизировать исключен?
    Завтра посмотрю форум попробую другой вариант, потом напишу

    0
    • Нет, почему. Умные компиляторы рассчитывают константы при трансляции. Может и компилятор Ардуинео поумнел.

      0
  16. Здравствуйте! Прогнал четыре варианта через программу с форума.
    1. //y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    y= (float)x * 0.00004447756; — 0,75мкс;

    2. y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    //y= (float)x * 0.00004447756; -0,75 мкс;

    3. y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    y= (float)x * 0.00004447756; -1,5мкс;

    4. y=x*1024*5*500;- 0.75мкс;

    То есть получается, что при операциях с целыми числами и числами с плавающей точкой время операции получилось одинаковым? 0,75мкс, или я в чем-то ошибся? Вот скетч который я использовал
    // определение времени выполнения программного блока Ардуино

    unsigned int timerValue; // значение таймера
    int x= 876;
    volatile float y;

    void setup()
    {
    Serial.begin(9600); // инициализируем последовательный порт, скорость 9600
    // установки таймера 1
    TCCR1A = 0;
    TCCR1B = 0;

    }

    void loop() {
    noInterrupts(); // запрет прерываний
    TCNT1H = 0; // сброс таймера
    TCNT1L = 0;
    TCCR1B = 1; // разрешение работы таймера

    //y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    //y= (float)x * 0.00004447756;
    y=x*1024*5*500;
    // ———- исследуемый программный блок ———

    // ————————————————-

    TCCR1B = 0; // остановка таймера
    timerValue = (unsigned int)TCNT1L | ((unsigned int)TCNT1H << 8); // чтение таймера
    interrupts(); // разрешение прерываний

    // вывод на компьютер
    Serial.print( (float)(timerValue — 2) * 0.0625);
    Serial.println(" mks");
    delay(500);
    }

    0
      • Да, теперь разница есть.
        1. С одной переменной float 13,44мкс;
        2. С набором операций с float 98,44мкс;
        4. С целыми числами 3.81 мкс;
        Остается немного непонятно, как определить момент когда нужно поставить квалификатор volatile? Каждый раз, на всякий случай, когда программа выдает что то нелогичное или есть какой нибудь алгоритм его применения?

        0
  17. Да все получилось, спасибо!

    Здравствуйте! Прогнал четыре варианта через программу с форума.
    1. //y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    y= (float)x * 0.00004447756; — 13,44мкс;

    2. y=(float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22)*2.335;
    //y= (float)x * 0.00004447756; -98,44 мкс;

    4. y=x*1024*5*500;- 3,81мкс;

    Остается немного непонятно, как определить момент, что необходимо поставить кваливикатор volatile? Ставить каждый раз на всякий случай, когда программа выдает что нибудь нелогичное? Или существует примерный алгоритм на этот счет?

    0
  18. Подскажите как должен выглядеть код для преобразования сигнала с аналогового датчика Холла работающего только в одну сторону ( т.е. увеличения напряжения до максимума или наоборот от половины ( состояния покоя датчика)) в восьмибитное значение ? Задача такая — делаю миди контроллер , нужно сделать к нему педаль экспрессии . Дабы не городить конструкцию из переменника и механизма его крутящего , а так же оптопар решил воспользоватся датчиком Холла и магнитом закрепленным на педали. Приблизил магнит- напряжение выросло до максимума ( минимума), отдалил — в состоянии покоя датчик выдает ровно половину напряжения питания . Пробовал заливать скетч в ардуино и подключать ДХ вместо обычного потенциометра , но скетчи миди контроллеров считывают весь диапазон гапряжения на аналоговом взоде и переводят его в восьмибитное значение от 0 до 127 , а мне получается нужно за » ноль» принять полпитания на аналоговом входе , а 127 соотв. 5 В. А все , что ниже » ноля» игнорировать . Это нужен какойто трешхолд ставить или как?

    0
    • Здравствуйте!
      Возможно, я не понял задачу, тогда опишите более формально. Смещение кода делается очень просто. Вычитается число из кода.
      int x;
      x= analogRead(A0);
      x -= 512; // 512 — середина диапазона АЦП
      if(x < 0) x=0; // ограничение на всякий случай // при изменении напряжения на входе АЦП от половины до максимума x будет меняться от 0 до 511 // если нужен диапазон 0... 255 надо сделать x /= 2; // если нужен диапазон 0... 127 надо сделать x /= 4;

      0
      • Когда подключаю датчик вместо обычного потенциометра , то в состоянии покоя он выдает полпитания , что в десятибитном представлении получается 512 . МИДИ протокол работает в восьмибитном представлении , там получается диапазон полученного напряжения от нуля до пяти вольт переводится в диапазон 0-127 . Так вот в состоянии покоя датчик шлет в программу на ПК миди сообщение что виртуальная ручка потенциометра ( например ручка громкости гитарного усилителя) находится в среднем положении , что соответствует величине MIDI CC = 64 . Для того , чтобы переместить ее в ноль или на максимум нужно поднести к датчику магнит разными полюсами или перемещать сам датчик между двумя магнитами .Но в этом случае в позиции датчика близком к середине т.е. к состоянию покоя значение напряжения на мгновение как бы » залипает» и в итоге виртуальная громкость работает не совсем линейно . Я так понимаю , что нужно всего навсего программно указать ардуине , что замерять напряжение на аналоговом входе нужно начиная с половины питания и до максимума ( думаю это проще будет) и преобразовывать его в диапазон 0-127 Т.е. физический диапазон замеров должен быть не 0- +5В а + 2.5 — +5 В . Как перевести полученный десятибитный диапазон 0-1023 в 0-127 я уже понял , не разобрался только с тем как задать ардуине нижний порог измеряемого напряжения в 2.5В или еще лучше с половины фактического напряжения т.к. там может быть не ровно 5 В а +/- .

        0
        • Пример, который я вам написал измеряет от 2,5 В до 5 В. При 2,5 В код 512, при 5 В код = 1023. Если вычесть число 512, то получается код от 0 до 511.

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

            0
          • Кажется нашел . В Ардуино ИДЕ в примерах , папка Analog есть скетч с названием Calibrate который как раз показывает как задать верхнюю и нижнюю границу из доступного диапазона значений.

            const int sensorPin = A0; // pin that the sensor is attached to
            const int ledPin = 9; // pin that the LED is attached to

            // variables:
            int sensorValue = 0; // the sensor value
            int sensorMin = 1023; // minimum sensor value
            int sensorMax = 0; // maximum sensor value

            void setup() {
            // turn on LED to signal the start of the calibration period:
            pinMode(13, OUTPUT);
            digitalWrite(13, HIGH);

            // calibrate during the first five seconds
            while (millis() sensorMax) {
            sensorMax = sensorValue;
            }

            // record the minimum sensor value
            if (sensorValue < sensorMin) {
            sensorMin = sensorValue;
            }
            }

            // signal the end of the calibration period
            digitalWrite(13, LOW);
            }

            void loop() {
            // read the sensor:
            sensorValue = analogRead(sensorPin);

            // apply the calibration to the sensor reading
            sensorValue = map(sensorValue, sensorMin, sensorMax, 0, 255);

            // in case the sensor value is outside the range seen during calibration
            sensorValue = constrain(sensorValue, 0, 255);

            // fade the LED using the calibrated value:
            analogWrite(ledPin, sensorValue);
            }

            Только здесь калибровка минимума и максимума происходит в Setup , а мне получается нужно сразу объявить готовый результат для аналогового входа без предварительного замера входящего сигнала .

            0
          • Да точно , проверил и работает как надо. В функции setup в вышеприведенном коде удалил код калибровки минимума и максимума в течении 5 сек. и вместо этого сразу задал значение sensormin = 512 , sensor max = 1023 , в loop оставил как есть . Яркость светодиода регулируется с половины положения потенциометра до максимального.

            0
          • Вот собственно делюсь результатом .Весь день провозился чтобы разобраться окончательно со скетчем. Таки использовал функцию map . С ней контроллер просто игнорирует все , что ниже половины питания а берет диапазон от 512 до 1023 , переводит его в градацию 0 — 127 и посылает как сообщение MIDI CC . Осталось сделать корпус педали ) https://youtu.be/vVMVy9xoRoM

            0
  19. Эдуард, спасибо за познавательный материал, проглотил залпом.
    — есть вопрос:
    Объясните как вы рассчитали 500. * 5. / 1024. / 4,22 * (15 + 4,22)
    и на выходе получилось 0.00004447756 . Сам вольтметр я уже сделал, меряет от 0 до 100DC. Ваш урок закидываю себе в закрома. Но с математикой у меня туго что-то. Ну не выходит на выходе такое значение хоть убейте((((

    Спасибо за внимание!

    0
    • Здравствуйте!
      Исходное выражение было:
      y= (float)x / 500. * 5. / 1024. / 4.22 * (15. + 4.22);
      Преобразуем для того чтобы в начале было умножение:
      y= (float)x * 5. / 500. / 1024. / 4.22 * (15. + 4.22);
      Теперь считайте.

      0
    • Не обязательно. Два сопротивления 4,22 и (15 + 4,22) делятся друг на друга. Главное, чтобы в одинаковых единицах были заданы.

      0
  20. спасибо за разъяснение!
    — а как быть когда не нужно
    период 500 мс?
    у меня сейчас считает
    float Vin = ((float)analogRead(A3)) * 5./1023./ 4.2 * (99.6 + 4.2);
    заменил на float Vin = ((float)analogRead(A3)) * 0.12009495880;
    и тоже работает.
    периоды замеров отдельно задаются- один раз в 900мс.
    п.с надо би порыться у вас в уроках, синтаксис не ражовывали? ибо интересная тема для перехода С для stm32)))

    0
  21. Вставлю свои 5 копеек в спор Эдуарда и Евгения, а так же по поводу приводимых здесь примеров.

    Во первых значение 0 на АЦП может означать короткое замыкание датчика (или переполнение по минимуму), а значение 1023 может означать отрыв датчика (или переполнение по максимуму). И то и другое тревога, которую надо уметь отслеживать.

    Мне представляется что калибровку датчиков надо производить так, что бы минимум измеряемого значения выдавал напряжение соответствующее значению АЦП = 12, а максимум значению АЦП 1011. Таким образом

    12 значений АЦП 0..11 — должны интерпретироваться как КЗ датчика или переполнение в минимум (данным доверять нельзя)

    12 значений АЦП 1012..1023 — должны интерпретироваться как отрыв датчика или переполнение в максимум (данным доверять нельзя)

    1000 значений АЦП 12… 1011 мы считаем достоверными.

    Убиваем двух зайцев. Во-первых отслеживаем недостоверность данных, во-вторых сокращаем дробность до 1000 (что дает нам человеческие единицы. милливольты миллиамперы и т. д.)

    Далее. Есть одна особенность Ардуинок, о которых мало говорят. Они БЕЗУМНО медленно выполняют умножение и деление даже в ЦЕЛОЧИСЛЕННОЙ арифметике. Про вещественную я вообще молчу.

    Поэтому изобилие данных float в примерах меня слегка смущает. Использовать их на ардуинках нецелесообразно, а использовать их, например, в обработчиках прерываний просто безумие.

    Поэтому, все значения надо хранить в целых числах, при необходимости искусственно из увеличивать умножением на целочисленных коэффициент.

    Например, если у вас диапазон измерений от 00 до 100 градусов Цельсия то для увеличения точности хранить их надо в сотых градуса или в 0.0025 градуса.

    Тогда у вас оконечный диапазон будет либо от 0 до 10000 или от 0 до 40000 (не превышает 2 байта).

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

    если вы измеряете напряжение от -5 до 5 вольт. то хранить их надо в микровольтах или еще мельче.

    Далее.
    Перевод единиц АЦП в реальные единицы. Формула проста как три рубля. Называется уравнение прямой.

    Пусть например, есть весы измеряющие от 0 до 65 кг. Храним значения в граммах. То есть в качестве результата мы должны выдать двубайтное число из диапазона 0 … 65000

    Шаманим схему весов так, что бы
    при MinГ= 0 на весах значение MinАЦП = 12
    при MaxГ = 65000г на весах значение MaxАЦП = 1011

    далее имеем прямую проходящую через две точки

    (MinАЦП, MinКг) — (MaxАЦП, MaxКг)

    При наших данных
    (12,0) — (1011, 65000)

    Вспоминая геометрию из школы или из гугла
    (y-y1)/(y2-y1) = (x-x1)/(x2-x1)

    Для нашего случая
    (Г-MinГ)/(MaxГ-MinГ) = (АЦП-MinАЦП)/(MaxАЦП-MinАЦП)

    Г = MinГ + (АЦП-MinАЦП)*(MaxГ-MinГ)/(MaxАЦП-MinАЦП)

    MaxГ-MinГ это число сохраняем его как множитель m
    MaxАЦП-MinАЦП это число сохраняем его как делитель d

    тогда формула перевода
    Г = MinГ + (АЦП-MinАЦП)*m/d

    И все в целых числах.
    При изменении АЦП от 12 до 1011 Г будет изменяться в диапазоне от 0 до 65000

    И в заключении скажу, что усреднение метод не очень хороший по точности. Лучше использовать интегрирование. Один из наиболее подходящих для Ардуино это метод Симпсона (метод парабол) он дает очень хорошую точность по сравнению другими при минимальных затратах, там только сложение вычитание сдвиг и под конец одно деление..

    Вкратце суть метода.

    1. Отдельно суммируются четные отсчеты
    2. Отдельно нечетные.
    3. Четные отсчеты удваиваются (можно сделать сложением или сдвигом т. е. без умножения)
    4. Нечетные учетверяются (можно сделать сложением и сдвигом т. е. без умножения)
    5. Потом это все складывается между собой и добавляются 2 крайних крайние отсчета.
    6. Выполняется одно деление деление.

    Опять же все можно сделать в целых числах

    (Подробнее можно посмотреть здесь

    https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D1%83%D0%BB%D0%B0_%D0%A1%D0%B8%D0%BC%D0%BF%D1%81%D0%BE%D0%BD%D0%B0)

    0
  22. Детальное описание метода Симпсона

    /// Интегрирование выполняет методом Симпсона (парабол) на интервале [a;b] число разбиений 2n (точек разбиения 2n-1 или 2n+1 вместе с концами отрезка)
    /// f(x0)=f(a)
    /// f(2n)=f(b)
    /// В отличии от метода прямоугольников (точность порядка 0) и метода трапеций (точность порядка 1) обладает точностью порядка 3)
    /// При этом нужно хранить всего три значения значение: нулевое предыдущее и текущее. И две суммы: по четным отсчетам и по нечетным
    /// Последнее значение
    /// Формула следующая I = ( f(x0) + f(2n) + 4(f(x1) + f(x3) + f(x5) + … + f(x_2n-1) ) + 2(f(x2) + f(x4) + f(x6) + … + f(x_2n-2) ) )*(b-a)/6n =
    ///
    /// Или по-другому
    /// I = (b-a)(4A + 2B + ( f(x0) + f(x_2n) ) )/(6*n)
    /// b-a смело можно считать 1. Положим b = 1, a = 0 (Начальный момент интегрирования считаем нулевым временм, а длинна интегрирования одной условной единицой)
    ///
    /// где A = f(x1) + f(x3) + f(x5) + … + f(x_2n-1)
    /// где B = f(x2) + f(x4) + f(x6) + … + f(x_2n-2)
    /// При подсчете общей суммы не забывать из A вычитать последнее значение, если последний отсчет нечетный (отбрасывание последнего нечетного значения)
    ///
    /// ОКОНЧАТЕЛЬНАЯ ФОРМУЛА БУДЕТ СЛЕДУЮЩАЯ
    /// I = (4A + 2B + ( f(x0) + f(x_2n) ) )/(3*(2n))
    /// или
    /// I = ( 2(2A + B) + ( f(x0) + f(x_2n) ) )/(3*n))
    ///
    /// Пример для разбиения на n=2 отрезка (1 точека разбиения + 2 точки концы отрезка)
    /// I = (f(x0)+f(x2) + 4f(x1) )/6

    /// Пример для разбиения на n=4 отрезка (3 точек разбиения + 2 точки концы отрезка)
    /// I = (f(x0)+f(x4) + 2( 2(f(x1) + f(x3)) + f(x2)) )/12

    0
  23. Для Ардуинки оптимально 19 отсчетов (входят в два байта без переполнения)

    0
  24. Эдуард, а, что если случайных помех в расчетном периоде будет много? Усредненное значение усреднит все это дело и результат будет далеким от истины. А можно ли вычислив среднее значение за заданный период сравнить его с каждым измерением в этом интервале и если какое-то значение существенно отличается от среднего, то присвоить ему значения предыдущего измерения (или последующего) и заново вычислить среднее значение и его уже вывести на дисплей. Таким образом мы выбрасываем из расчета измерения, которые явно являются помехами и они уже не повлияют на результат измерения. Жаль только сделать скетч с таким фильтром у меня не хватает знаний и опыта.

    0
    • Здравствуйте!
      Помеха передается через емкостную, индуктивную связь, по принципу радиоволн. Во всех этих случаях уровень помех имеет среднее значение равное 0. На самом деле не совсем так. Но для уровней сигналов АЦП Ардуино (0…5 В) в большинстве случаев это верно.
      Существует много алгоритмов обработки сигналов, но для Ардуино с его ограниченной производительностью самым рациональным является алгоритм усреднения сигналов.

      0
  25. Приветствую. Какая будет оптимальная схема измерения переменного напряжения ? Однополупериодная или мостовая схема выпрямления перед делителем напряжения ? Емкость я понимаю нужна после выпрямления ?

    0
    • Здравствуйте!
      У переменного напряжения можно измерить амплитудное значение, среднее по модулю, среднеквадратичное (действующее).
      Схемотехника зависит от предела измеряемого напряжения. Если оно достаточно высоко, то можно выпрямить его на диодах и измерять. Если необходимо измерять небольшое напряжение, то падение на диодах будет сильно искажать результат. Необходимо делать выпрямитель на операционном усилителе.
      Можно делать регулярные выборки напряжения, с периодом кратным периоду измеряемого напряжения и вычислять результат.

      0
  26. не пойму почему вы умножаете везде R2 на сумму R1 и R2???
    R2 * (R1 + R2),правильней будет делить R2 / (R1 + R2) или я не прав где то,поправьте меня.

    0
  27. Статья огонь. Очень доходчиво и конструктивно. Спасибо автору за статью, всем, кто задавал вопросы и делился мыслями — вопросы и ответы интересные.
    Единственное, что я тут не осилил — это как выбирать сопротивление для делители.

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

    Почему 4.22кОм?

    Например китайский делитель 30ком и 7.5ком.
    Другой 820кОм на 180кОм.

    Мне нужно выбрать сопротивления для напряжения 33В и 1.1в опорного напряжения. Делитель 1/30.
    Чтобы терять меньше энергии, так как батарейка будет подключена постоянно, выбрал 1МОм на 33кОм. Это не допускается? Что при этом страдает?

    Как правильно выбирать сопротивления, чтобы не терять точности? Китайские модули получается не точны? Или вписываются? Ищу ищу — нигде не могу найти понятной информации, как правильно выбирать сопротивления.

    Есть ли разница, если используешь 1.1 или 5в. При 1.1 точность ведь повышается?

    0
    • Здравствуйте! Спасибо за отзыв.
      Ограничение по выходному сопротивлению обуславливается токами утечки входов ( до 1 мкА ) и временем заряда емкости АЦП. Если вам не нужна высокая точность, то сопротивление источника сигнала можно повысить.
      При ИОН 1,1 В повышается коэффициент деления, за счет чего повышается входное сопротивление резисторного делителя.
      Точность при низком напряжении ИОН уменьшается. Просмотрите статью на форуме сайта http://mypractic-forum.ru/viewtopic.php?t=9 . Там же вы можете задавать вопросы.
      Существует ошибка смещения нуля АЦП (OFFSET). Эта ошибка показывает значение напряжения на входе АЦП при нулевом выходном коде. Для напряжения источника опорного напряжения 4 В она нормируется на уровне 2%. При снижении опорного напряжения ошибка смещения нуля увеличивается, т.к. она определяется параметрами аналоговых элементов АЦП. При снижении диапазона входного сигнала ее вес становится значительнее.

      0
  28. Поделились вот такими данными:

    https://drive.google.com/file/d/1awcncshUUcN5SZljCCQFgZidFZbZ-smX/view

    Судя по ним, использование делителя на 1.1 уменьшает погрешность при измерении каждого аккумулятора в сборке например.

    Также видел множество роликов, где реккомендуют использовать именно это опорное напряжение.

    Вывод:
    1. применить делитель на 16
    2. использовать прецизионные резистры
    3. использовать источник опорного напряжения 1,1В

    Также

    Даташит «ATmega48A/PA/88A/PA/168A/PA/328/P»

    1. (про оптимально до 10К) страница 244, третья строка сверху: «The ADC is optimized for analog signals with an output impedance of approximately 10К or less»

    2. (про 100М) страница 310, последняя строка таблицы: «Analog Input Resistance 100 M»

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

    820ком и 180ком:
    Высокая Точность DC Напряжение Сенсор модуль Напряжение детектор Напряжение делитель модуль обнаружения для Arduino
    http://s.aliexpress.com/aq6RnQvq?fromSns=Copy to Clipboard

    Кому верить?

    0
  29. Здравствуйте Эдуард. Не могли бы Вы объяснить, как вы рассчитали емкость конденсаторов RC-фильтра. Предполагаю что при таком включении конденсатора учитывается сопротивление последовательно подключенного ко входу резистора 15 кОм и частота среза высокочастотных помех составляет 1 кГц.

    0
    • Здравствуйте!
      Информации по RC фильтрам много в любом учебнике по электронике. Посмотрите урок 37. Там эта тема затронута.

      0
  30. Эдуард, уроки у Вас очень грамотные. Естественно, скетчи я не повторяю, пишу свои основываясь на Вами описанных идеях и алгоритмах. Считаю, что так полезней для самообразования. Естественно, возникают сопутствующие вопросы. Иду по урокам подряд. До этого урока, вопросы решались без Вашего участия. Сейчас нужна помощь.

    Почему у Вас R2=4.33 kOm, а R1=15 kOm?

    Поясню. У меня в рассчётах в тетрадке (я все естественно проверяю карандашом по закону Ома), чтобы получить на аналоговом входе Ардуины 40 мА (может я неправильно понял, что такой ток нужен на аналоговый вход Ардуины… читал arduino.ru тех характеристики платы…) надо сделать R2 = 125 Om. Т.е. никаких «кило» Ом. Откуда «кило», «мега» (люди в комментах указывают) Омы?

    Получается, согласно Вамшему уроку, ток, который мы даём во вход А1 Ардуины на самой первой принципиальной схеме по закону Ома следующий:

    5 (В) / 4220 (Ом) = 1.2 мА — это нормально?

    Прошу, разъясните этот момент.

    ПС …

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

    Второе предложение — это повторение части первого предложения?

    ПС2 …

    Зачем во второй принципиальной схеме дублируется первая принципиальная схема с общей землёй?

    О себе. Начинающий электронщик, профи программер и дизайнер.

    0
    • Здравствуйте!
      Не обижайтесь, но вы очень много чего напутали.
      Ток 40 мА это максимально допустимый ток в в режиме выхода. Если вывод перевести в режим выхода и подключить к нему нагрузку, то разработчики микроконтроллера разрешают потреблять ток до 40 мА. И то с определенными оговорками.
      Когда вы используете вывод в качестве входа, то для нас важно входное сопротивление входа. Т.е. насколько вход будет нагружать наш входной сигнал.
      Сопротивление входа несколько сот кОм. Нарисуйте цепь из последовательно включенных: источник сигнала (идеальный), внутреннее сопротивление источника сигнала, сопротивление резисторного делителя и входное сопротивление аналогового входа. Вы увидите, что входной ток (в нашем случае паразитный), который появляется в цепи за счет входного сопротивления входа, вызывает падение напряжения на сопротивлении источника сигнала, в том числе и на резисторном делителе. Это напряжение искажает сигнал. Чем меньше оно, тем меньше искажения.
      Не буду вдаваться в подробности, но разработчики микроконтроллера заявляют, что сопротивление источника сигнала для работы АЦП с 10ти разрядной точностью не должно превышать 10 кОм. Исходя из этого я и выбрал параметры делителя.
      Если вы будете использовать низкоомный делитель, то ваш Ардуино-вольтметр будет «садить» измеряемый сигнал и практического значения такой вольтметр иметь не будет.

      0
      • Благодарю. Разобрался. Пофиг на ток в микроАмперы, главное не терять те самые 5 В на втором резисторе делителя и не уводить ток от источника в землю сильно маленькими резисторами делителя. Параллельное соединение вольтметра (его сопротивление д.б. бесконечно большим, чтобы ток не тёк и не искажал измеряемый сигнал).

        0
      • Здравствуйте Эдуард, присоединюсь к вопросу Алексея, так как я тоже не опытен здесь и мучаюсь тем же — почему выбор именно r1=15КОм, r2=4.22КОм. В сумме это 19.22КОм, округленно 20КОм. При этом указано, что для точности нужно выбрать до 10КОм. Почему тогда выбрано для K=0.25 не r1=6KОм, r2=2KОм? Еще вопрос, верны ли мои рассуждения: если ток утечки 1 mkA, и измерение величин тока 1mkA не возможно, тогда 10mkA (10х) уже должно годится для измерения, что будет соответствовать r=5V/10mkA=500KOm. Еще, полное сопротивление входа 100МОм, полагаю данные аналогового входа снимаются параллельно, тогда полное сопротивление участка составит 497,5КОм, что приводит к искажению в 0.5%(от 500КОм). Вывод тут ясен, что чем меньше сопротивление входного сигнала тем луше. Озвучена рекомендация что сопротивление должно быть не больше 10КОм для сохранения точности, тогда я так к сожалению и не понял в отличие от Алексея, почему сопротивление не сделать равным исходя из тока хотя бы 20mA? Для искомых максимальных 20V сопротивление тогда можно понизить до 1КОм. Согласно расчетам точность при этом должна стать близкой к максимальной.

        0
        • Здравствуйте!
          В чем проблема с током утечки. Он будет протекать через входной делитель и создаст паразитное падение напряжения на нем. Что исказит результат измерения. Основное падение будет происходить на резисторе 4,22 кОм. И будет равно 4,22 кОм * 1 мкА = 4,22 мВ. Если вы увеличите сопротивления делителя, то ошибка будет больше.
          Если уменьшите, то у вашего вольтметра будет низкое входное сопротивление, что приведет к погрешности уже в падении напряжения в измеряемой цепи.
          Представьте, что входной ток вольтметра 20 мА, как вы предлагаете сделать. А вы измеряете сопротивление в середине цепи, состоящей из последовательно соединенных резисторов по 1 кОм каждый. Ток 20 мА, потребляемый входом вольтметра, вызовет падение напряжения на резисторах цепи 1 кОм * 20 мА = 20 В. И что вы там измерите?

          0
          • Спасибо, думаю что понял. В вашем примере в цепи из 2-х резисторов по 1КОм при 20В ток составит 10мА, значит падение на каждом должно быть 10В. Если подключим к одному из них наш вольтметр с входным сопротивлением 1КОм, то сопротивление этого участка понизится до 0.5КОм, а значит всей цепи до 1.5КОм и изменит ток в сторону увеличения до 13.3мА, т.е. наш вольтметр вместо 10В отобразит 13.3В, и получим погрешность в 3.3В, вызванную самим измерением. Если же входное сопротивление нашего вольтметра сделать 10К, то уже без подробностей расчета погрешность составит 0,47В, т.е. уменьшиться. Увеличивая далее входное сопротивления и снижая погрешность в падении напряжении мы через некоторые значения ощутим уже снижение точности измерения аналоговым портом которое обсудили выше. Спасибо еще раз. В Ваших проектах нравится то, что есть теория. Зачастую хочется не только собрать интересную вещь, но и добиться понимания процесса.

            0
  31. Эдуард, добрый день!
    Подскажите, не приходилось ли вам подключать датчики с унифицированным сигналом 4-20mA к аналоговому входу Ардуино? Достаточно ли преобразовать токовый сигнал в сигнал напряжения установкой шунтирующего резистора? Где то попадалась информация, что шунта в 200 Ом достаточно, чтобы получить на входе АЦП 1-5 V. Тогда каким напряжением лучше питать токовую петлю? Хотелось бы использовать имеющийся у меня датчик тока с выходом 4-20mA с Ардуино. С уважением, Герман

    0
    • Здравствуйте!
      Если использовать опорное напряжение 1,1 В, то можно использовать шунт и меньшего сопротивления.
      Умножаете максимальный ток на сопротивление шунта — получаете максимальное напряжение на входе АЦП. Напряжение питания петли должно быть больше этого напряжения на значение, необходимое для питания датчика.

      0
  32. Здравствуйте! Скажите, а можно все аналогичные операции по обработке сигнала с аналогового входа производить с его кодом, а уже потом результат пересчитывать в напряжение? (чтобы не перегружать программу работой с большими числами, которые все-равно результата точнее чем разрешающая способность АЦП не дадут)

    0
    • Здравствуйте!
      Да, конечно. Это значительно увеличивает скорость обработки. Но если вам необходимо перейти на дробные форматы, например умножить значение АЦП на дробный коэффициент, то это не так просто. На форуме я пытался описать способ вычисления только с фиксированными числами http://mypractic-forum.ru/viewtopic.php?t=24.

      0
      • Ой, спасибо вам большое, что потрудились ответить даже на такой элементарный вопрос )

        0
  33. Ничего не нашёл про время преобразования команды analogRead(), странно. Датчик дс18б20 высчитывает температуру 750 мс, в ардуино аналоговая величина считывается мгновенно? Или всего за один машинный цикл?

    0
      • Не мгновенно… Но ведь тогда возникает другой вопрос — контроллер зависает на это время при выполнении команды analogRead(), или АЦП работает непрерывно с таким шагом дискретности времени, и командой analogRead() мы считываем последнее измеренное значение без зависания программы?

        0
  34. Добрый день Эдуард!

    Объясните пожалуйста строку sumU1+= analogRead(A0); // суммирование кодов АЦП.
    Мы перегружаем аналоговое значение из analogRead(A0) в sumU1!?
    Знак «+» какую функцию выполняет?

    Спасибо.

    0
    • Изначально равен нулю. Затем в него суммируются значения 500 выборок АЦП. После значение суммы считывается, а sumU1 обнуляется. Снова накапливаются 500 выборок и так в бесконечном цикле.

      0
  35. Эдуард, здравствуйте
    Помогите плиз, всю голову сломал. Вот код (он один в один Ваш, убрал из него только всю математику, чтобы не было сомнений на ее счет):
    void loop() {
    if ( flagReady == true ) {
    flagReady= false;
    Serial.print(«avarageU1 = «); Serial.print(avarageU1);
    Serial.print(«avarageU2 = «); Serial.println(avarageU2);
    }}
    void timerInterupt() {
    timeCount++; // +1 счетчик выборок усреднения
    sumU1+= analogRead(A1); // суммирование кодов АЦП
    sumU2+= analogRead(A2); // суммирование кодов АЦП
    if ( timeCount >= MEASURE_PERIOD ) {
    timeCount= 0;
    avarageU1= sumU1; // перегрузка среднего значения
    avarageU2= sumU2; // перегрузка среднего значения
    sumU1= 0;
    sumU2= 0;
    flagReady= true; // признак результат измерений готов
    }}
    В мониторе порта благополучно выводятся avarageU1 и avarageU2. НО! Если закомментировать строку
    // sumU1+= analogRead(A1); или
    // sumU2+= analogRead(A2);
    то значение оставшегося незакомментированным avarageU1 (или avarageU2) меняется кардинально! Складывается ощущение, что если к А1 и А2 подключены какие-то аналоговые датчики и считывание с них происходит одновременно, то они как будто «мешают» друг другу. Как такое может быть?
    Конкретно в моем примере к А1 и А2 подключены датчики LM35, но складывается ощущение что они какие-то кривые. Если по одиночке (один из analogRead закомментирован), то после пересчета усредненного значения в градусы получается температура градусов на 5 ниже правильной. А если вместе — то они выдают вообще какую-то чушь. Но дело даже не в температуре, Бог с ней. Дело в том, что полученное значение кардинально меняется, в зависимости от того читаю я данные со второго аналогового входа или нет.
    Что я делаю не так?
    Кстати, к А0 подключен терморезистор и он работает абсолютно стабильно и правильно и не обращает никакого внимания что там происходит на А1 и А2.

    0
    • Здравствуйте!
      А вы измеряли реальное напряжение вольтметром на входах A1 и A2? Если входы брошены в воздухе, то напряжение на них непредсказуемо и зависит от многих факторов.
      Еще вопрос. С каким периодом вызывается прерывание. Чтение каждого аналогового входа занимает более 100 мкс. Если прерывание вызывается слишком часто, то тоже может быть ерунда.

      0
      • Входы не брошены в воздухе, на них висят датчики LM35. Прерывание вызывается с частотой 2 мс. Потом увеличил до 5 мс — не помогло. Вотльтметром нет, не измерял, надо попробовать…

        0
      • Тут вопрос даже не в том, что напряжение на А1 и А2 неправильное. Вопрос в том что оно зависит от того — читаю я со второго аналогового входа или нет. Если читаю — то оно одно, если не читаю — другое и очень сильно отличается. Мистика какая-то…

        0
  36. Есть такая проблема, на линии появляется напряжение 12-15 вольт(автомобиль) и при появлении этого напряжения нужно подать сигнал на вход ардуино, я применял делитель на основе стабилитрона 3,3 вольта с добавлением токоограничивающего резистора и по факту у меня на аналоговый вход приходило 1,2 вольта, как такое может получится?

    0
    • Здравствуйте!
      У стабилитронов есть такой параметр — минимальный ток стабилизации. Т.е. стабилитрон ведет себя как стабилитрон только в случае, если через него протекает ток не меньше этого параметра. У низковольтных стабилитронов этот ток особенно велик и может достигать 3 мА.
      Уберите стабилитрон, рассчитайте делитель, чтобы при пороге на высокой стороне напряжение на выходе делителя было примерно 2,5 В. Напряжение на входе микроконтроллера будет в любом случае ограничено на уровне 5 В, если ток не превысит 20 мА. Т.е. в принципе можно через один резистор на вход 220 В подать.

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

    0
    • Здравствуйте!
      В мониторе последовательного порта?
      В функции print второй необязательный параметр это число знаков после запятой.
      Serial.print(u1, 3);

      0
  38. Эдуард, добрый день!
    В примере кода у Вас последовательно вызывается analogRead(A0) и сразу analogRead(A1). Но ведь при изменении входного пина АЦП записью в регистр ADMUX нового значения первое значение будет некоррктным? Или в функции analogRead() отбрасывается первый (неверный) результат преобразования?

    0
    • Здравствуйте!
      Эта функция коммутирует вход АЦП на нужный канал, запускает преобразование, ждет его окончания и возвращает результат. Т.е. она выполняет полную последовательность для получения результата. Время выполнения 105-110 мкс. И все это время программа «висит» в функции.
      В уроке 65 я описываю другой способ измерения аналогового сигнала.

      0

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

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

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".