Округление данных ацп в ардуино. Аналоговые измерения с Arduino

15.03.2019

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

Выбор этого источника происходит при вызове функции analogReference(type) , где type может принимать одно из трех значений:

  • DEFAULT : напряжение питания, около 5 Вольт (по умолчанию, после старта скетча);
  • INTERNAL : встроенный ИОН - 1.1 Вольт для ATmega168 и 2.56 Вольт для ATmega8;
  • EXTERNAL : напряжение на пине AREF.
Я не случайно написал "около 5 Вольт", потому что в действительности питание шины USB составляет 4,40 .. 5,25 В, а стабилизатора L7805 - 4,65 .. 5,35 В. Простой математический подсчет показывает, что диапазон 0,7 В "выливается" в 14% от ожидаемых 5.00 Вольт. А теперь обратите внимание, что один шаг нашего 10-битного АЦП составляет 5.00/1024 = 0,0048828, и 0,7 Вольт в пересчете analogRead составит 143 единицы.

Это подводит нас к грустному, но неотвратимому выводу - точность лучше одного вольта при использовании DEFAULT нам заказана . Может быть, нам поможет INTERNAL?

Тут нас будет ждать второе разочарование - дело в том, что внутренний источник опорного напряжения ATmega требует предварительной калибровки . Надо взять эталонный источник напряжения, подать его на аналоговый пин и сравнить с внутренним, получив таким образом поправку, компенсирующую т.н. систематическую погрешность. Полученное значение надо сохранить в EEPROM микроконтроллера, поскольку оно индивидуально для каждого конкретного чипа, а в скетче считывать после старта. Немного занудно, но ничего сложного. Но, быть может, есть более простой путь?

Надо каким-то образом подать внешнее опорное напряжение на наш АЦП, да поточнее (это вариант EXTERNAL). В этом нам могут помочь:

Этот прием я использовал в схеме SMPReaderUSB , для измерения напряжения внутренней литиевой батареи модуля МПО-10. Резистор R9 22К выбран с таким расчетом, чтобы через LM385 протекал небольшой ток, как и положено по его документации. Измеряемый вход BATT притянут через R10 22K к земле на тот случай, если модуль не подключен и вход ADC0 повис в воздухе.

Далее, происходит считывание с двух аналоговых пинов - к одному подключен измеряемый источник напряжения BATT (ADCm), к другому - LM385 (ADC385). Оба значения передаются в хост-программу на PC, которая вычисляет пропорцию:

Um = ADCm * 1,235 / ADC385

Значение опорного напряжения в этой формуле не участвует, и хотя зависимость по-прежнему есть, выведя это значение из формулы мы понизили его влияние на пару порядков (речь про ошибку квантования). Такой способ позволяет улучшить точность до 0,03 В, что - согласитесь - для Arduino весьма неплохо!

Введение в библиотеку SPI для Arduino с примером скетча для 12-битного аналого-цифрового преобразователя LTC1286 и 16-битного цифро-аналогового преобразователя DAC714.

Об SPI

Последовательный периферийный интерфейс, более известный как SPI, был создан компанией Motorola для передачи данных между микроконтроллерами и периферийными устройствами, используя меньшее количество выводов по сравнению с параллельной шиной. SPI может использоваться для сопряжения любых возможных периферийных устройств, таких как датчики, сенсорные экраны и IMU датчики. SPI может даже использоваться для связи одного микроконтроллера с другим или для связи с физическими интерфейсами Ethernet, USB, USART, CAN и WiFi модулей. SPI чаще всего реализуется, как четырехпроводная шина с линиями для тактового сигнала, входа данных, выхода данных и выбора периферийного устройства. Линии тактового сигнала и данных используются всеми периферийными (или ведомыми) устройствами на шине, а вывод выбора ведомого служит для идентификации каждого подключенного периферийного устройства.

Все шины SPI должны содержать одно ведущее устройство (master) и одно или несколько ведомых устройств (slave). Некоторые устройства, такие как DAC714, используют дополнительные линии управления. В случае с DAC714 эта линия используется для очистки двойного буфера, позволяя использовать до трех микросхем DAC714 на одной пятипроводной шине, устраняя необходимость в дополнительной линии управления выборов ведомого устройства. Основными ограничивающими характеристиками шины SPI являются полоса пропускания и количество доступных выводов выбора ведомого устройства. Для микроконтроллеров в больших корпусах с тактовой частотой 20 МГц и выше и с сотнями выводов GPIO это вряд ли является ограничением.

Также об основных особенностях SPI можно прочитать в статье «Назад к основам: SPI (последовательный периферийный интерфейс) ».

Реализация SPI

Существует два основных способа реализации связи через SPI на Arduino или любом микроконтроллере. Первый и более распространенный метод - это аппаратный контроллер SPI. Arduino поставляется с библиотекой SPI для взаимодействия с аппаратным контроллером SPI, поэтому мы будем использовать эту библиотеку в наших примерах. Другой метод - программный SPI или "побитовое управление". Побитовое управление включает в себя ручное определение всех аспектов связи через SPI программно и может быть реализовано на любом выводе, в то время как аппаратный SPI должен быть подключен к конкретным SPI выводам микроконтроллера. Программный SPI намного медленнее, чем аппаратный SPI, и может использовать на себя ценную память программ и ресурсы процессора. Однако, в некоторых случаях, когда требуется несколько шин SPI на одном микроконтроллере, или при отладке нового SPI интерфейса побитовое управление может быть очень полезным.

Когда у ведомого устройства вывод выбора ведомого устройства принимает состояние низкого логического уровня, значит, это ведомое устройство пытается отправить данные по шине SPI ведущему устройству или принять данные. Когда у ведомого устройства вывод выбора ведомого устройства находится в состоянии логической единицы, это ведомое устройство игнорирует ведущее устройство, что позволяет нескольким ведомым устройствам совместно использовать одни и те же линии данных и тактового сигнала. Для передачи данных от ведомого устройства к ведущему служит линия MISO (Master In Slave Out, вход ведущего - выход ведомого), иногда называемая SDI (Serial Data In, вход последовательных данных). Для передачи данных от ведущего устройства к периферии используется линия MOSI (Master Out Slave In, выход ведущего - вход ведомого), также известная как SDO (Serial Data Out, выход последовательных данных). И наконец, тактовые импульсы от ведущего устройства SPI идут по линии, обычно называемой SCK (Serial Clock, последовательный тактовый сигнал) или SDC (Serial Data Clock, тактовый сигнал последовательных данных). В документации на Arduino предпочтение отдается названиям MISO, MOSI и SCK, поэтому мы будем использовать их.

Начало работы

Прежде чем приступить к написанию нового кода для периферийных устройств SPI, крайне важно обратить внимание на пару элементов в техническом описании новых компонентов. Во-первых, мы должны учитывать полярность и фазу тактового сигнала по отношению к данным. Они различаются у разных устройств и разных производителей. Полярность тактового сигнала может быть высокой или низкой и, как правило, называется CPOL (C lock POL arity). Когда CPOL = 0, логическая единица указывает на тактовый цикл, и когда CPOL = 1, логический ноль указывает на тактовый цикл. Фаза тактового сигнала, обычно называемая CPHA, указывает, когда данные захватываются и продвигаются в сдвиговом регистре относительно тактового сигнала. Для CPHA = 0 данные захватываются при нарастающем фронте тактового импульса, а продвигаются при спадающем фронте; а для CPHA = 1 - наоборот. Комбинация полярности и фазы тактового сигнала дает четыре отдельных режима данных SPI. SPI режим 0: CPOL и CPHA равны 0. SPI режим 1: CPOL = 0, а CPHA = 1. SPI режим 2: CPOL = 1, а CPHA = 0. И наконец, SPI режим 3: я уверен, вы сможете догадаться, в каких состояниях будут CPOL и CPHA.


Некоторые технические описания не испльзуют названия CPOL и CPHA, разработанные Freescale. Чтобы помочь понять режимы SPI, LTC1286 использует SPI режим 2. Взгляд на временную диаграмму в техническом описании поможет вам ознакомиться с SPI режимами данных. Для справки, DAC714 использует SPI режим 0 (спецификации DAC714 и LTC1286 приведены ниже). Далее нам нужно определить, как периферийное устройство сдвигает биты. Существует два возможных варианта: MSB или LSB - идет первым старший или младший значащий бит, и установить порядок функцией setBitOrder() . Наконец, нам нужно определить, какую тактовую частоту наше устройство может принять, и на какой тактовой частоте работает аппаратный SPI на нашей плате Arduino. В случае с Arduino Mega и платами с тактовой частотой 16 МГц по умолчанию значение тактового сигнала шины SPI составляет 4 МГц. Библиотека SPI для Arduino позволяет разделить тактовую частоту на 2, 4, 8, 16, 32, 64 и 128.

Библиотека SPI для Arduino

Библиотека SPI для Arduino за раз передает и принимает один байт (8 бит). Как мы увидим в примерах два и три, для этого требуется выполнить некоторые манипуляции с переданными и полученными байтами. Выводы аппаратного SPI на платах Arduino используются для разъема ICSP, на всех платах Arduino MOSI находится на выводе 4 ICSP разъема, MISO - на выводе 1, а SCK - на выводе 3. Если Arduino является ведущим устройством на шине SPI, любой ее вывод может использоваться в качестве вывода выбора ведомого устройства. Если Arduino является ведомым устройством на шине SPI, то для выбора ведомого должен использоваться вывод 10 на платах Uno и Duemilanove и вывод 53 на платах Mega 1280 и 2560.

Мы сфокусируемся на следующих функциях библиотеки SPI для Arduino:

  • SPISettings()
  • begin()
  • end()
  • beginTransaction()
  • endTransaction()
  • setBitOrder()
  • setClockDivider()
  • setDataMode()
  • transfer()

Пример 1

В примере датчика давления BarometricPressureSensor SCP1000 требует записи конкретных значений в определенные регистры для настройки SCP1000 для работы с низким уровнем шума. Этот скетч также содержит определенную команду для чтения и записи в SCP1000. Это самый важный шак во взаимодействии с периферийными SPI устройствами и требует внимательно изучения технического описания и временной диаграммы.

Const byte READ = 0b11111100; // команда чтения SCP1000 const byte WRITE = 0b00000010; // команда записи SCP1000 // Настройка SCP1000 для работы с низким уровнем шума: writeRegister(0x02, 0x2D); writeRegister(0x01, 0x03); writeRegister(0x03, 0x02); // дать датчику время для настройки: delay(100);

Пример 2

В примере 2 демонстрируется прием данных от 12-разрядного АЦП с помощью библиотеки SPI для Arduino. В качестве подопытного АЦП используется LTC1286. 1286 - это хорошо известный АЦП, который существует на рынке очень долгое время и имеет несколько аналогов. 1286 - это дифференциальный АЦП последовательного приближения, доступный в 8-выводном DIP корпусе, что делает его удобным для макетирования и прототипирования. Способ, которым мы получаем данные от LTC1286, также приведет к редкому сценарию, в котором побитовое управление менее сложно, чем использование библиотеки SPI для Arduino. Прикрепленное описание LTC1286 содержит временную диаграмму передачи данных, которая очень полезна для понимания кода. 1286 не требует настройки, а только передает данные. Это делает реализацию связи с 1286 на Arduino очень простой.

Однако, сложная часть заключается в том, как библиотека SPI будет интерпретировать то, что получила. Вызов SPI.transfer() обычно передает команду по каналу SPI и прослушивает его на предмет получения данных. В этом случае мы ничего не передаем: SPI.transfer(0) . Функция transfer принимает первый байт данных и присвает его byte_0 . Первый байт данных включает все принятые данные в то время, когда на CS (выбор ведомого) был низкий логический уровень. Он включает в себя два бита данных HI-Z , когда АЦП производит выборку аналогового напряжения для преобразования, и нулевой бит, указывающий начало пакета. Это означает, что наш первый байт будет содержать только пять полезных битов. Сразу после нашего первого вызова SPI.transfer(0) , мы вызываем эту функцию снова и на этот раз присваиваем ее результат переменной byte_1 . byte_1 будет содержать 8 бит данных, но нам интересны только семь из них. седьмой бид будет обычно совпадать с шестому, и его можно не учитывать, так как эффективное количество бит составляет только одиннадцать из двенадцати. По этой причине справедливо рассматривать LTC1286 как 11-разрядный АЦП. После отброса ненужных битов восстанавливается аналоговое значение.


Временная диаграмма получения данных от АЦП LTC1286 через шину SPI
// SPI выводы // SS вывод 48 // MISO вывод 50 // SCK вывод 52 #include const int spi_ss = 48; // вывод выбора ведомого SPI uint8_t byte_0, byte_1; // Первый и второй байты для чтения uint16_t spi_bytes; // Окончательное 12-разрядное сдвинутое значение float v_out; // Напряжение с десятичной запятой float vref = 5.0; // Опорное напряжение на выводе Vref void setup() { Serial.begin(9600); // Инициализировать последовательный порт и установить скорость pinMode(spi_ss, OUTPUT); // Установить SPI вывод выбора ведомого на выход digitalWrite(spi_ss, HIGH); // Убедиться, что на spi_ss установлена логическая единица SPI.begin(); // begin SPI } void loop() { // установить скорость, формат и полярность тактового сигнала/данных во время инициации SPI SPI.beginTransaction(SPISettings(1000, MSBFIRST, SPI_MODE2)); // установить CS вывод LTC в низкий уровень для инициации выборки АЦП и передачи данных digitalWrite(spi_ss, LOW); byte_0 = SPI.transfer(0); // read firt 8 bits byte_1 = SPI.transfer(0); // read second 8 bits // установить CS вывод LTC в высокий уровень, чтобы остановить LTC от передачи нулей digitalWrite(spi_ss, HIGH); // закрыть SPI соединение SPI.endTransaction(); // & B000 сбрасывает первые 3 бита (два HI-Z бита и один нулевой бит) и сдвинуть в spi_bytes // затем мы добавляем оставшийся байт, сдвинутый вправо для удаления бита 12 spi_bytes = (((byte_0 & B00011111) <<7) + (byte_1 >>1)); // и наконец, мы преобразуем значение в вольты. 1LSB = vref/2048 v_out = vref * (float(spi_bytes) / 2048.0); Serial.println(v_out, 3); delay(250); }

Пример 3

Мы увидели, как получать данные по SPI, теперь пришло время рассмотреть, как отправлять данные. В примере 3 рассматривается, как общаться с микросхемой с наследием, аналогичным LTC1286, но с совершенно противоположной функциональностью. DAC714 - это 16-разрядный цифро-аналоговый преобразователь. У DAC714 имеется дополнительный вывод связи, который включает дополнительную защелку данных. Это позволяет включать DAC714 последовательно с другими DAC714 (до двух штук) без дополнительной линии выбора ведомого. Двойной буфер DAC714 позволяет загружать два значения в DAC714 за каждый цикл. Временная диаграмма DAC714 приведена в техническом описании.


// Выводы SPI // SS вывод 48 // MOSI вывод 51 // MISO вывод 50 // SCK вывод 52 // latch вывод 46 #include const int spi_ss = 48; // регистр сдвига A0 DAC714P const int dac_lch = 46; // защелка ЦАП A1 DAC714 uint16_t input_0, input_1; // входные 16-битные значения uint8_t byte_0, byte_1, byte_2, byte_3; // байты для передачи SPI void setup() { Serial.begin(9600); pinMode(spi_ss, OUTPUT); pinMode(dac_lch, OUTPUT); digitalWrite(spi_ss, HIGH); digitalWrite(dac_lch, HIGH); SPI.setDataMode(SPI_MODE0); SPI.setBitOrder(MSBFIRST); SPI.setClockDivider(SPI_CLOCK_DIV16); SPI.begin(); } void loop() { static uint16_t count = 0; input_0 = count; input_1 = -count; count += 1; Serial.println(input_0); Serial.println(input_1); // digitalWrite(spi_ss, LOW); // A0 byte_0 = (input_1 >> 8); byte_1 = (input_1 & 0xFF); byte_2 = (input_0 >> 8); byte_3 = (input_0 & 0xFF); SPI.transfer(byte_0); SPI.transfer(byte_1); SPI.transfer(byte_2); SPI.transfer(byte_3); digitalWrite(spi_ss, HIGH); digitalWrite(dac_lch, LOW); digitalWrite(dac_lch, HIGH); delay(3); }

Мы указали параметры SPI с помщью setDataMode() , setBitOrder() и setClockDivider() в void setup() , вместо использования SPI.beginTransaction() , просто, чтобы продемонстрировать еще один способо настройки. Снова испльзуется функция SPI.transfer() , но на этот раз нам неинтересен прием данных. Два 16-разрядных целых числа преобразуются в четыре байта для передачи через функцию SPI.transfer() . Сначала мы закружаем второе входное целое число, input_1 , первым потому, что оно будет зафиксировано и загружено после преобразования input_0 . Также обратите внимание, что делитель дает тактовую частоту, вероятно, намного медленную, чем максимальный тактовый сигнал, который DAC714 может принять.

Вот и всё! Надеюсь, статья оказалась полезной. Оставляйте комментарии!

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

Все эти устройства имеют шкалу, которую мы используем для фиксации их показаний. Рассмотрим простой пример — определение температуры с помощью обычного градусника. Человек решает эту задачу очень просто: мы смотрим, к какому из делений ближе всего приблизился уровень жидкости в градуснике. Полученное таким образом значение и будет измеренной температурой. Иными словами, мы осуществляем преобразование аналоговой непрерывной величины в дискретную, которую можно записать на бумаге с помощью цифр.

Чтобы автоматизировать процесс измерения аналоговых величин, и возложить эту задачу на электронные приборы, инженеры создали особое устройство, называемое аналого-цифровым преобразователем (АЦП). Это устройство позволяет превращать аналоговый сигнал в цифровой код, пригодный для использования в ЭВМ.

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

Конструктивно, АЦП может находиться в одном корпусе с микропроцессором или микроконтроллером, как в случае Arduino Uno. В противном случае, как и все современные электронные устройства, АЦП может быть оформлен в виде отдельной микросхемы, например MCP3008:

Следует отметить, что существует и устройство с обратной функцией, называемое цифро-аналоговым преобразователем (ЦАП, DAC). Оно позволяет переводить цифровой сигнал в аналоговый. Например, во время проигрывания мелодии на мобильном телефоне происходит преобразование цифрового кода из MP3 файла в звук, который вы слышите у себя в наушниках.

Для лучшего понимания работы АЦП нам потребуется интересная задачка. В качестве оной, попробуем сделать устройство для измерения оставшегося заряда обычных пальчиковых батареек — самый настоящий цифровой вольтметр.

1. Функции работы с АЦП

На этом уроке изучать работу АЦП мы будем с помощью платформы Arduino. В используемой нами модели Arduino Uno, наряду с обычными выводами общего назначения (к которым мы уже подключали и ) есть целых шесть аналоговых входов . В других версиях Arduino таких входов может быть и больше, например, у Arduino Mega их 16.

На карте Arduino Uno аналоговые входы имеют буквенно-цифровые обозначения A0, A1, …, A5 (снизу слева).

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

результат = analogRead(номер_контакта);

после вызова этой функции, микроконтроллер измерит уровень аналогового сигнала на заданном контакте, и сохранит результат работы АЦП в переменную «результат». При этом результатом функции analogRead будет число от 0 до 1023.

2. Разрядность АЦП

Надо заметить, что число 1023 здесь появилось неспроста. Дело в том, что у каждого устройства АЦП есть такой важный параметр как разрядность . Чем больше значение этого параметра, тем точнее работает прибор. Предположим, что у нас есть АЦП с разрядностью 1. Подавая на вход любое напряжения от 0 до 2,5 Вольт, на выходе мы получим 0. Любое же напряжение от 2,5 до 5 вольт даст нам единицу. То есть 1-битный АЦП сможет распознать только два уровня напряжения. Графически это можно изобразить следующим образом:

АЦП с разрядностью 2 распознает уже четыре уровня напряжения:

  • от 0 до 1,25 — это 0;
  • от 1,25 до 2,5 — это 1;
  • от 2,5 до 3,75 — это 2;
  • наконец, от 3,75 до 5 — это 3.

На следующих двух картинках изображена работа АЦП с разрядностью 2 и 3 бит:

В Arduino Uno установлен 10-битный АЦП, и это значит, что любое напряжение на аналоговом входе в диапазоне от 0 до 5 вольт будет преобразовано в число с точностью 1/1024 вольта. На графике будет сложно изобразить столько ступенек. Имея такую точность, 10-битный АЦП может «почувствовать» изменение напряжение на входе величиной всего 5 милливольт.

3. Опорное напряжение

Есть нюанс, который может стать причиной ошибки измерения с помощью АПЦ. Помните тот диапазон от 0 до 5 вольт в котором работает устройство? В общем случае этот диапазон выглядит иначе:

от 0 до опорного напряжения

Это изменение повлечет за собой изменение формулы расчет точности АЦП:

точность = опорное напряжение/1024

Опорное напряжение определяет границу диапазона, с которым будет работать АЦП.

В нашем примере опорное напряжение будет равно напряжению питания Arduino Uno, которое дал USB порт компьютера. У моем конкретном случае это напряжение было 5.02 Вольта, и я могу смело заявить, что измерил заряд батарейки с высокой точностью.

Что если вы питаете микроконтроллер от другого источника? Допустим у вас есть четыре NiMh аккумулятора на 1.2 Вольта. В сумме они дадут 4.8 Вольта (пусть они немного разряжены, ведь в действительности их заряжают до 1.4 Вольта). Точность измерения будет равна 4.8/1024. Это следует учесть в нашей программе.

Наконец рассмотрим случай, когда мы питаем Arduino Uno одним напряжением, а в качестве опорного хотим установить совсем другое, например, 3.3 Вольта. Что делать? Для такого варианта на Arduino Uno есть специальный вывод Vref. Чтобы решить проблему, нам нужно подать на этот контакт напряжение 3.3 Вольта, и разрешить использование внешнего источника опорного напряжения функцией:

AnalogReference(EXTERNAL);

которую следует вызвать внутри функции setup нашей программы.

Также следует учитывать, что результат измерения значения напряжения не может превышать границы диапазона. Если мы выбираем в качестве опорного напряжения 3.3 Вольта, а поступающий сигнал будет с большим напряжением, то мы получим неправильное значение напряжения, поскольку АЦП «не знает» о наличии более высокого напряжения.

4. Программа

Наша первая программа с использованием АЦП будет крайне простой: каждую секунду мы будем измерять аналоговое значение на входе A0, и передавать его в последовательный порт.

Int val = 0; void setup() { Serial.begin(9600); pinMode(A0, INPUT); } void loop() { val = analogRead(A0); Serial.println(val); delay(1000); }

Теперь загружаем программу на Arduino, и переходим к измерениям.

5. Подключение

Чтобы измерить напряжение на батарейке, мы должны подключить её к нашей Arduino всего двумя контактами. Для примера используем щелочную батарейку на 1.5 Вольта.

Теперь откроем окно COM-монитора в Arduino IDE, и посмотрим какие значение выдает нам АЦП:

Что означает число 314? Вспомним, что 10-битный АЦП разбивает диапазон от 0 до 5 вольт на 1024 части. Значит точность 10-битного АЦП — 5/1024. Зная точность, мы можем записать формулу для преобразования показаний АЦП к вольтам:

V = (5/1024)*ADC

где V — измеренное напряжение на батарейке;
ADC — результат работы функции analogRead.

Подставим эту формулу в программу и снова попробуем измерить заряд батарейки!

Int val = 0; void setup() { Serial.begin(9600); pinMode(A0, INPUT); } void loop() { val = analogRead(A0); Serial.println((5/1024.0)*val); delay(1000); }

Результат измерений:

Уже больше похоже на правду.

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 В

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

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

Решим, что вольтметр должен измерять напряжение в пределах не меньше 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

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


float u1, u2; // измеренные напряжения

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

MsTimer2::start(); // разрешение прерывания
}

void loop() {

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

//

// чтение кода канала 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

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

На сайте появился еще один урок (

Цель работы: Рассмотрение особенностей ввода и отображения широкополосных сигналов.
Задача работы: Построение канала ввода, обработки и отображения сигналов на максимальной частоте преобразования АЦП контроллера Arduino.
Приборы и принадлежности: Контроллер Arduino UNO, пакет Simulink МатЛАБ (R2012).

ВВЕДЕНИЕ

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

Хорошим примером мощной специализированной среды для работы с сигналами является МатЛАБ. Для анализа сигналов зачастую требуется наблюдать его спектр в максимально широкой полосе частот. Для этого контроллер должен принимать сигналы на максимальной частоте преобразования АЦП.

Построение рабочего канала «Arduino UNO – МатЛАБ» для наблюдения и обработки сигналов в реальном времени на предельной частоте преобразования АЦП подробно излагается в этой работе. Особенностью этого канала является то, что такты реального времени задаются не МатЛАБ, а контроллером Arduino. Такое построение не требует компиляции Simulink модели с библиотекой реального времени (rtwin.tlc), что позволяет использовать в модели практически любые блоки библиотеки Simulink.

Рис. 1. Сравнение средств разработки алгоритмов. Для проектирование алгоритмов на уровне специализированной среды необходим канал передачи данных между контроллером и средой проектирования.

ОБЩИЕ СВЕДЕНИЯ

Средства накопления, анализа, обработки и отображения сигналов
В этой работе используется среда Simulink для приёма и отображения данных контроллера Arduino.

Simulink – это интерактивная среда и язык программирования для имитационного моделирования, позволяющая при помощи блок-диаграмм строить динамические модели процессов. Simulink интегрирована в среду MatLAB. Интеграция позволяет использовать уже готовые библиотеки блоков, встроенные математические алгоритмы, мощные средства обработки и графического отображения данных для решения всего спектра задач от разработки концепции модели до тестирования, проверки, генерации кода и аппаратной реализации.

Состав пакетов расширения библиотеки Simulink на примере пакета цифровой обработки сигнала “DSP System Toolbox” показан на Рис. 2.


Рис. 2. Пример дополнительного пакета расширения Simulink для моделирования систем обработки сигналов: DSP System Toolbox . В пакете используются новейшие алгоритмы спектрального анализа. Выделено содержимое раздела Power Spectrum Estimation - блоки для спектральной оценки сигнала.

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

Примером применения такой организации может быть модуль E14-440 для многоканального ввода, вывода и обработки аналоговой и цифровой информации, подключаемый к компьютеру через шину USB.

Входные данные сначала заносятся в первую половинку FIFO буфера АЦП. После ее заполнения данные начинают передаваться в PC, в тоже время не прекращается сбор данных во вторую половинку FIFO буфера. После накопления данных во второй половинке FIFO буфера опять начинается передача данных в PC и параллельно продолжается сбор данных уже в первую половинку.

ПОСТРОЕНИЕ ОСЦИЛЛОГРАФА НА БАЗЕ КОНТРОЛЛЕРА ARDUINO

Максимальная скорость накопления данных АЦП
Используя вывод результата на монитор редактора Arduino на максимальной частоте (57600 бит/с) напишем программу подсчета преобразований АЦП за фиксированный период.

Программа измерения скорости преобразования АЦП:



Void setup() {
Serial.begin (57600); // 9600, 19200, 38400, 57600 and 115200 bit/s
}

Void loop(){
time_start = millis();
for (int i = 0; i < 1024; i++) {

}
time_end = millis();

Serial.println(period);


Рис. 3. Время (в мсек) 1024 и 512 преобразований АЦП. Среднее время преобразования АЦП: 0.1123 мсек (как 115/1024).

Время масштабирования данных АЦП
Для перевода 10 разрядных данных АЦП в 8 разрядные используется функция
map(val, 0, 1023, 0, 255);
где val – int переменная c 10 значимыми разрядами.
Программа измерения времени преобразования АЦП с масштабированием и записи в массив:

Const int adc_5 = A5; // ADC port number
unsigned long time_start; // Start of capturing, ms
unsigned long time_end; // End of capturing, ms

Void setup() {

}

Void loop(){
time_start = millis();
for (int i = 0; i < 1024; i++) {
int val = analogRead(adc_5);
}
time_end = millis();
int period = time_end - time_start;
Serial.println(period);
}


Рис. 4. Время (в мсек) 1024 преобразований АЦП, перевода 10 р. данных в 8 разрядные и запись в массив. Период АЦП преобразования с масштабированием: 0.1611 мсек (как 165/1024).

Поскольку время преобразования АЦП 0.13 мсек, то один перевод 10 разрядных данных в байтовый формат (масштабирование) составляет 0.0424 мсек.

Скорость канала последовательной передачи данных
Для определения скорости побайтовой передачи в последовательный канал в цикле передается код символа Serial.write(1) который не отображается на мониторе.

Основной блок программы определения скорости передачи:

Void loop(){ //Do stuff here
Serial.write(1);
rate = rate + 1;
if (time > set_time) {
set_time = set_time + 30; // 30 ms RT clock
Serial.println(rate);
rate = 0;
}
}

Рис. 5. Тестовые данные: количество переданных байт в последовательный канал за 30 мсек на скорости 57600 бит/сек.
Тест показал, что передача 173 байт занимает 30 мсек, с другой стороны за 30 мсек на скорости 57600 бит/с можно передать 1728 бит. Следовательно, на передачу одного байта расходуется время передачи 10 бит. Используя это отношение для режима передачи
Data bits: 8
Parity: none
Stop bits: 1
Flow control: none
можно подсчитать время потоковой передачи массива данных на разных скоростях.
Передача, например, 256 байт на скорости 9600 бод (бит/c) занимает 267 мсек, на скорости 57600 бод – 44 мсек; и на скорости 115200 бод – 22 мсек (как 256*10/115200).

Размер массива для накопления и передачи данных
Размер оперативной (SRAM) памяти Arduino UNO составляет 2 Кбайт. Тестирование программы циклического считывания АЦП, масштабирования 10 разрядных данных до 8 разрядных, тактирования и побайтной передачи данных показало, что максимальный размер массива для накопления и отправки данных не должен превышать 1800 байт.

Более сложные программы могут потребовать и большей дополнительной памяти SRAM. Поэтому массив для накопления и передачи данных АЦП ограничен 1024 байтами или 512 словами.


Рис. 6. Кусок провода, подсоединенный к аналоговому входу А5 контроллера Arduino для усиления наблюдаемой наводки сети 50 Гц.

Таблица 1 . Времена операций программы с учетом нестабильности циклов

Пример настройки канала отображения 256 масштабированных значений АЦП при максимальной скорости накопления и передачи данных.
Код программы контроллера Arduino:
const int adc_5 = A5; // ADC port number
byte adc_bytes; // Buffer for scaled ADC data

Void setup() {

}

Void loop(){

// ADC data capturing
for (int i = 0; i < 256; i++) {
int val = analogRead(adc_5);
adc_bytes[i] = map(val, 0, 1023, 0, 255);
}


for (int i = 0; i < 256; i++) {
Serial.write(adc_bytes[i]);
}

If (time > set_time) {
set_time = set_time + 70; // RT clock is 70 ms
}
}


Рис. 7. Определение номера порта а среде Arduino.


Рис. 8. Simulink модель для приёма АЦП данных контроллера, масштабирования вектора данных по времени, отображения данных в реальном времени и сохранения потока данных в памяти workspace.


Рис. 9. Параметры COM порта в среде Simulink (блок модели: Serial Configuration)


Рис. 10. Параметры блоков Simulink модели и режима моделирования.

Модель запускается нажатием на кнопку Start simulation:

Рис. 11. Кнопка запуска модели.


Рис. 12. Вид сетевой наводки (подключение показано на Рис. 6) с наложением кадров (левое окно) и в отдельном кадре (правое окно). Причина смещения сигнала при наложении кадров – отсутствие синхронизации отображения. Примечание: Simulink имеет достаточно средств для построения канала синхронизации.

ПРИМЕРЫ ПОЛУЧЕНИЯ ПРОВЕРЕННЫХ РЕЗУЛЬТАТОВ И ВАРИАНТЫ ДЛЯ САМОКОНТРОЛЯ

Задание 1 . Накопление, передача и отображение отмасштабированных данных (см. пример и таблицу времён на стр. 8).
1. Напишите для контроллера Arduino UNO программу циклического считывания показаний АЦП, масштабирования, записи данных в массив 1024 байт и передачи массива в последовательный канал. Программа должна выполняться с максимальной скоростью. Символ A – заголовок передаваемого массива.

Пример программы:

Const int adc_5 = A5; // ADC port number
unsigned long set_time; // Time of next clock
byte adc_bytes; // Buffer for ADC data

Void setup() {
Serial.begin (115200); // bit/s
}

Void loop(){
unsigned long time = millis(); // Current time in ms

// ADC data capturing
for (int i = 0; i < 1024; i++) {
int val = analogRead(adc_5);
adc_bytes[i] = map(val, 0, 1023, 0, 255);
}

// send ADC data into serial port
Serial.print(«A»); // «A» is header
for (int i = 0; i < 1024; i++) {
Serial.write(adc_bytes[i]);
}

If (time > set_time) {
set_time = set_time + 270; // RT clock is 270 ms
}
}

2. В среде МатЛАБ составьте программу из Simulink блоков для приема и отображения данных контроллера в реальном времени. Скорость, размер пакета, период принимаемых данных и такт работы модели должны соответствовать соответствующим параметрам контроллера. Отмасштабируйте время отображаемых данных.


Рис. 13. Simulink модель для приема данных на максимальной частоте: 115200 бод. Объединение векторов (Vector Concatenate) используется для масштабирования сигнала по шкале времени кадра (frame).

3. Проверьте качество канала «Вход АЦП – дисплей МатЛАБ», например по периоду сетевой 50 Гц наводки на входе АЦП. Для увеличения амплитуды наводки ко входу АЦП подсоедините кусок провода (см. Рис. 6). Амплитуда наводки зависит от расстояния между проводом и вашей рукой.


Рис. 14. Наложение 4 кадров при сканировании частоты 50Гц на входе АЦП контроллера Arduino.


Рис. 15. Частота сети на входе АЦП контроллера, 4 кадр.

Задание 2 . Накопление, передача и отображение 10 разрядных данных АЦП.
1. Для контроллера Arduino UNO напишите программу циклического считывания показаний АЦП, записи данных в массив 512 слов и побайтной передачи данных массива в последовательный канал. Программа должна выполняться с максимальной скоростью.

Пример программы:

Const int adc_5 = A5; // ADC port number
unsigned long set_time; // Time of next clock in ms
word adc_int; // Buffer for ADC data
int val;
byte val_Lo, val_Hi;

Void setup() {
Serial.begin (115200); // bit/s
}

Void loop(){
unsigned long time = millis();

// ADC data capturing
for (int i = 0; i < 512; i++) {
adc_int[i] = analogRead(adc_5);
}

// send ADC data into serial port
// first low bytes then high bytes
Serial.print(«A»); // «A» is header
for (int i = 0; i < 512; i++) {
val = adc_int[i];
val_Lo = (val << 1) & 0xFE;
Serial.write(val_Lo); // Lo byte
}
for (int i = 0; i < 512; i++) {
val = adc_int[i];
val_Hi = (val >> 6) & 0xE;
Serial.write(val_Hi); // Hi byte
}

If (time > set_time) {
set_time = set_time + 160; // RT clock is 160 ms
}
}

2. Составьте программу Simulink для приема восстановления и отображения АЦП данных контроллера. Скорость, размер пакета и период принимаемых данных должны соответствовать соответствующим параметрам контроллера. Отмасштабируйте время отображаемых данных.


Рис. 16. Программа Simulink для приёма, восстановления и отображения массива данных АЦП контроллера Arduino UNO.
3. Запишите сетевые 50 Гц наводки.


Рис. 17. Наложение 15 кадров при сканировании сетевой наводки 50Гц на входе АЦП контроллера. Период программы: 160 мсек. Время заполнения массива данными АЦП: 58 мсек. Время передачи 512х2 байт на частоте 115200 бод: 89 мсек.


Рис. 18. Последний 15 кадр. Время 3.5 циклов 50 Гц сигнала: 70 мсек.

Задание 3 . Обработка сигнала m-программой МатЛАБ
1. Сохраните отображаемые в реальном времени данные в workspace памяти МатЛАБ, например, при помощи блока simout (см. Рис. 13).
2. Скопируйте сохраненные данные в рабочий каталог, например:
save("simout_50Hz","simout");
3. Разработайте m-программу МатЛАБ для отображения архивного АЦП сигнала контроллера.

Пример кода:

Clear all
load("simout_50Hz");


size_frame = size(simout.Data,1);
sampling = d_frame/(size_frame + 163*4); % dt
data_size = size(simout.Data,1)*size(simout.Data,2)*size(simout.Data,3);

% time = (0:data_size-1)*sampling;
time = ;
for i = 1:length(simout.Time)
time = ;
end

Adc = uint8();
for i = 1:size(simout.Data,3)
adc = ;
end

% frame_num = length(simout.Time) % or size(adc,3) % is 54 frame

If 1 %
figure
plot(time, adc, "b")
grid on
xlabel("Time, s");


end


Рис. 19. Покадровое изменение 50 Гц наводки на входе АЦП контроллера Arduino UNO: 24 кадра по 0.27 сек.

4. Разработайте m-программу для вычисления параметров сигнала, например, периода в заданном кадре.

Пример кода:

Clear all
load("simout_50Hz");

D_frame = simout.Time(2)-simout.Time(1);
sampling = d_frame/((256 + 176)*4); % dt
data_size = size(simout.Data,1)*size(simout.Data,2)*size(simout.Data,3); % <256 x 1 x 54>

%FRAME number
i = 5;
time = (0:1023)*sampling+simout.Time(i);
adc = simout.Data(:,:,i)";
if 1 %
figure
plot(time, adc, "b")
grid on
xlabel("Time, s");
ylabel("ADC , bit");
title("8 bit ADC frame against Time");
end
% period
comp_level = 60;
j = 1;
for i = 2:length(adc)
if (adc(i) >= comp_level) && (adc(i-1) < comp_level)
cell_num(j) = i;
j = j + 1;
end
end
s_period = diff(time(cell_num));


Рис. 20. Непрерывное и поточечное изменение сигнала в выбранном кадре. Время 5 кадра: 1.08… 1.24 сек. Размер вектора: 1024 байт. Следовательно время одного считывания и масштабирования сигнала АЦП: 0.156 мсек.


Рис. 21. Период наводки сети 5 кадра: 19.2… 19.4 мсек.

Задание 4 . Построение спектра сигнала в реальном времени.
1. Для наблюдения частотного спектра сигнала подключите к отображаемому сигналу модели блок быстрого преобразования Фурье (Spectrum Scope: FFT) из раздела библиотеки Simulink > DSP System Toolbox > Sinks.


Рис. 22. Модель со спектроскопом.


Рис. 23. Спектр сетевой наводки. Сигнал кадра включает 1024 амплитуды и 163x4 нулевых значений.

2. Выделите основную гармонику сигнала: 50 Гц.


Рис. 24. Гармоника сигнала на частоте 50 Гц.

3. Подключите блок Spectrum Scope: FFT к неотмасштабированному (по времени) сигналу.


Рис. 25. Перенос точки подключения спектрографа. На входе неотмасштабированный сигнал с меньшей зоной нулевых значений в конце массива (вектора).

4. Настройте блок. Выберите тип отображаемого спектра: Spectrum Type.


Рис. 26. Параметры спектрометра неотмасштабированного сигнала из 1024 амплитуд.

Задание 5 . Построение канала скоростной потоковой передачи и обработки 8р данных в реальном времени без пропуска данных.
1. Напишите для контроллера Arduino UNO программу циклического считывания показаний АЦП, масштабирования и передачи в последовательный канал 2048 байт с заголовком. Программа должна считывать показания АЦП на постоянной частоте без перерывов.

Пример программы:
const int adc_5 = A5; // ADC port number

Void setup() {
Serial.begin (115200); // bit/s
}

Void loop(){
for (int i = 0; i < 2048; i++) {
if (i == 0) Serial.print(«A „); // “A» is header
int val = analogRead(adc_5);
byte adc_byte = map(val, 0, 1023, 0, 255);
Serial.write(adc_byte);
}
}

2. Настройте модель Simulink (МатЛАБ) на прием данных контроллера.


Рис. 27. Пример модели для отображения непрерывного потока данных. Кадр содержит 2048 байт.

3. Настройте время моделирования модели (Меню > Simulation > Configuration Parameters > Solver > Fixed-step size) и такт блока Serial Receive > Block Sample Time, (см. Рис. 10) по периоду 50 Гц сети.
Расчетное время кадра по данным Таблица 1: 254 мсек (для 1024 байт) => 508 мсек для 2048 байт, В действительности, время кадра программы (в которой считывание АЦП и передача выполняются поочередно) составляет 375 мсек.


Рис. 28. Кадр графопостроителя Vector Scope. В кадре 18.75 периодов 50 Гц волны. Следовательно, время кадра должно быть 375 мсек, а период преобразования АЦП, масштабирования и передачи данных: 0.1831 мсек.

4. В командном окне МатЛАБ наберите команду формирования 5 кадрового сигнала.
sgnl = ;

5. Постройте график 5 первых кадров сигнала.


Рис. 29. Пять кадров входного сигнала модели.

6. Рассмотрите качество стыков кадров.


Рис. 30. Стыки пяти кадров. Имеются заметные искажения в первом байте каждого кадра. Заменой первых байт средними значениями между ближайшими точками можно существенно снизить искажения.

7. Подключите ко входному сигналу модели анализатор спектра. Наблюдайте спектр сигнала в реальном времени.

Рис. 31. Модель отображения спектра входного сигнала (АЦП Arduino UNO) в реальном времени.


Рис. 32. Спектр сетевой наводки на входе АЦП контроллера Arduino.

8. Подключите ко входному сигналу модели осциллограф Time Scope из библиотеки Simulink > DSP System Toolbox > Sinks.

Рис. 33. Осциллограф в модели для отображения входного сигнала контроллера Arduino.

9. Настройте осциллограф на отображение содержимого текущего кадра и частоты сигнала.


Рис. 34. Настройка осциллографа Time Scope > Menu > View > Properties.

10. Запустите модель и наблюдайте стабильность сигнальных параметров.


Рис. 35. Отображение сигнала и его параметров в реальном времени на осциллографе Simulink модели.

Последний вариант канала контроллер Arduino – МатЛАБ в сравнении с предыдущими вариантами имеет следующие преимущества.
не используется память контроллера для накопления АЦП данных;
обеспечивается малый такт преобразования АЦП с масштабированием, который чуть больше такта преобразования АЦП с масштабированием при отсутствии передачи;
не требуется масштабирование сигнала по времени в Simulink модели;
модель содержит меньше блоков;
практически не ограничен размер вектора и время кадра.

Задание 6. Увеличение частоты сэмплирования сигнала АЦП.

Частоту сэмплирования АЦП контроллера Arduino можно повысить до 15 кГц в 10-разрядном режиме и до 77 кГц в 8-разрядном режиме , заменив библиотечные функции на более быстрый вариант использования регистров микроконтроллера.
Функцию пользователя можно создать в программе *.ino или в системном файле контроллера
...\arduino-1.0.6\hardware\arduino\cores\arduino\ wiring_analog.c, зарегистрировав её в
...\arduino-1.0.6\hardware\arduino\cores\arduino\ Arduino.h

Для построения 8-разрядного высокоскоростного канала Arduino – МатЛАБ необходимо выполнить следующее.
1. Напишите программу определения времени заполнения массива АЦП данными с отображением результата в окне «Serial Monitor». Размер массива должен быть достаточно большим, например, в половину памяти SRAM. Для увеличения точности следует измерять время многократного заполнения массива.
Пример программы:

void setup() {
Serial.begin (57600); // bit/s

ADCSRA = (1 << ADEN) // Включение АЦП
|(1 << ADPS2); // Установка предделителя преобразователя на 8
ADMUX = (1<< ADLAR) | (1 << REFS0) // Подключение внешнего ИОН
|(1 << MUX2) |(0 << MUX1) |(1 << MUX0); // подключение АЦП A5 == 101
}

Void loop(){
unsigned long time_start = millis();
for (int j = 0; j < 100; j++) {
for (int i = 0; i < 1024; i++) {
ADCSRA |= (1 << ADSC); // Запуск преобразования АЦП
while ((ADCSRA & (1 << ADIF)) == 0);//Ожидание флага окончания преобразования
adc_bytes[i] = ADCH; // Считывание полученного значения
}
}
unsigned long time_end = millis();
unsigned int dt = time_end - time_start;
Serial.println (dt);
}

Сто заполнений массива из 1024 байт выполнено за 1542 мсек .

2. Дополните однократное заполнение массива данными АЦП последующей передачей всего массива в последовательный порт на максимальной скорости.
Пример программы:

Byte adc_bytes; // Резервирование массива для АЦП данных

Void setup() {
Serial.begin (115200); // bit/s
ADCSRA = (1 << ADEN) // Включение АЦП
|(1 << ADPS2); // Установка предделителя преобразователя на 8
ADMUX = (1<< ADLAR) | (1 << REFS0) // Подключение внешнего ИОН
|(1 << MUX2) |(0 << MUX1) |(1 << MUX0); // подключение АЦП A5 == 101
}

Void loop(){
for (int i = 0; i < 1024; i++) {
ADCSRA |= (1 << ADSC); // Запуск преобразования АЦП
while ((ADCSRA & (1 << ADIF)) == 0); //Ожидание флага окончания преобразования
adc_bytes[i] = ADCH; // Считываем полученное значение
}
// send ADC data into serial port
Serial.print(«A»); // «A» is header
for (int i = 0; i < 1024; i++) {
Serial.write(adc_bytes[i]);
}
}
3. В модели Simulink (Рис. 36) в формате 0.01542 пропишите экспериментальное значение времени записи в массив, а именно, в строке “Block sample time” блока “Serial Receive” и в строке меню > simulation > Configuration Parameters > Fixed-step size (fundamental sample time).


Рис. 36. Модель Simulink для приема и отображения данных из COM порта.

4. Подключите на вход АЦП тестовый сигнал. Запустите программу Arduino и, затем, модель Simulink (MatLAB). Сравните известные параметры сигнала с параметрами наблюдаемого сигнала. Проверить работу тракта можно по отображению поочередно подключаемых выходных напряжений платы Arduino: 0В; 3.3В и 5В.


Рис. 37. Отображение в реальном времени сетевой 50 Гц наводки. Кадр включает 1024 точки. Время кадра 15.42 мсек. Частота сэмплирования 66 КГц (как 1/(0.01542_сек/1024)). Отображаемый сигнал имеет разрывы: процесс записи прерывается передачей кадра в последовательный канал.


Рис. 38. Отображения в режиме реального времени пилообразного сигнала 0… 3.3 В, сформированного на контроллере Teensy 3.1 с 12 разрядным ЦАП и подключенного к шестому АЦП (A5) контроллера Arduino.


Рис. 39. Сигнал 500 Гц контроллера Teensy 3.1. Ошибка такта (15.42 мсек) Simulink модели без дополнительной корректировки меньше 1% (как 100%*(504.72Гц - 500Гц)/500Гц). Погрешность можно уменьшить корректировкой RT такта, как показано в п.3 этого задания.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Сравните периоды преобразования АЦП первого и последнего заданий.
2. Почему для построения спектра сигнала рекомендуется брать выборку размером кратную двум?
3. Какова задержка потоковой передачи 1024 байт на частоте 115200 бит/c при следующих параметрах передачи?
Data bits: 8
Parity: none
Stop bits: 1
Flow control: none