Arduino. Работа с АЦП. Подключение переменного резистора. Аналого-цифровые преобразования — АЦП

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

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

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

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

Конструктивно, АЦП может находиться в одном корпусе с микропроцессором или микроконтроллером, как в случае 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 поддерживает обработку аналоговых сигналов. Для входных сигналов мы имеем АЦП (аналогово-цифровой преобразователь), в случае выходного сигнал — возможна модуляция ШИМ (широтно-импульсная модуляция).

В Arduino, сердцем которой является микроконтроллер Atmega, имеется один 10-битный АЦП. Это означает, что считанное значение напряжения может находиться в диапазоне от 0 — 1023. В зависимости от опорного напряжения 1024 значений будут распределены на соответствующий диапазон. В результате мы можем получить различную точность и различный диапазон напряжений, считываемых аналого-цифровым преобразователем.

Если выбрать опорное напряжение равное 1,024В, то каждое последующее значение, считанное с аналогового входа будет соответствовать 1мВ. Если опорное напряжение задать равным 5В, то каждое последующее значение будет соответствовать приблизительно 5 мВ.

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

Примечание: Arduino имеет несколько (в зависимости от версии) аналоговых входов, однако АЦП в ней только один. Это означает, что одновременно может быть считано значение только с одного из датчиков, подключенных к аналоговым входам A0… A5 (A0… A15 для Arduino MEGA).

Функция analogReference()

Для правильной работы АЦП требуется опорное напряжение (эталон). Для Arduino опорное напряжение может быть в диапазоне 0…5В (или 0… 3,3В для Arduino с напряжением питания 3,3В). В зависимости от типа используемого микроконтроллера у нас могут быть разные виды опорного напряжения.

Мы можем использовать внутренний или внешний источник опорного напряжения. Функция AnalogReference() предназначена для того, чтобы установить соответствующий источник опорного напряжения. Доступны следующие параметры этой функции:

  • DEFAULT: опорное напряжение составляет 5В или 3,3В (в зависимости от питания) — то есть, оно равно напряжению питания микроконтроллера;
  • INTERNAL: опорное напряжения составляет 1,1В для ATmega168, ATmega328 и 2,56В для ATmega8;
  • INTERNAL1V1: опорное напряжение составляет 1,1В — только для Arduino MEGA;
  • INTERNAL2V56: опорное напряжение составляет 2,56В — только для Arduino MEGA;
  • EXTERNAL: внешнее опорное напряжение, приложенное к выводу AREF — от 0 до 5В.

Параметр DEFAULT выбираем, когда хотим воспользоваться опорным напряжением 5В (питание системы). Это самый простой и одновременно наименее точный способ. Здесь требуется хорошая стабильность питания.

Использование INTERNAL является хорошим вариантом, в ситуации, когда мы создаем проект, предназначенный для конкретной версии Arduino. Внутренние опорное напряжение является относительно стабильным и достаточным в большинстве случаев.

Наиболее точным вариантом является использование внешнего источника опорного напряжения. Существуют специальные источники опорного напряжения (ИОН). Плюсом является возможность получения необходимого точного опорного напряжения, например, 1,024В или 2,048В, что облегчает интерпретацию данных, считываемых АЦП. К недостаткам применения внешнего источника опорного напряжения можно отнести возможное увеличение стоимости проекта.

Синтаксис функции analogReference() показан в следующем примере:

AnalogReference(DEFAULT); //опорное напряжение = напряжение питания analogReference(INTERNAL); //опорное напряжение = 1,1В или 2,56В analogReference(EXTERNAL); //опорное напряжение = напряжение на AREF выводе

Функция analogRead()

Функция analogRead() обеспечивает считывание значения с одного из аналоговых входов. Считанное значение находится в диапазоне 0 — 1023 (10-битный АЦП). Необходимо указать номер аналогового входа, с которого будет происходить чтение данных.

Следующий пример иллюстрирует использование аналоговых входов:

#define analogPin 0 // потенциометр подключен к A0 int val = 0; // val — переменная, хранящая считанное значение void setup() { Serial.begin(9600); // инициализация последовательного порта } void loop() { val = analogRead(analogPin); // чтение значения напряжения с порта A0 Serial.println(val); // отправка измеренной величины на терминал }

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

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

Функция analogWrite()

Функция analogWrite() позволяет управлять выходом с помощью сигнала ШИМ. ШИМ часто используется в качестве замены обычного аналогового сигнала. Количество доступных выводов ШИМ зависит от типа используемого микроконтроллера в Arduino.

Так у Arduino на микроконтроллере:

  • Atmega8 — выводы 9, 10, 11;
  • Atmega128, Atmega168 и Atmega328 — выводы 3, 5, 6, 9, 10, 11;
  • Atmega1280 — выводы 2…13 и 44…46.

Частота переключения ШИМ большинства контактов составляет 490 Гц. Степень заполнения формируется числом от 0 до 255 (0 — без заполнения, 255 – полное заполнение).

Если мы подключим светодиод к контакту PWM и будем менять заполнение ШИМ, мы увидим изменение интенсивности свечения светодиода. Ниже приведен пример программы изменения свечения светодиода при помощи потенциометра:

#define ledPin 11 // светодиод подключен к контакту 9 #define analogPin 0 // потенциометр на А0 int val = 0; // val — переменная, хранящая значение A0 void setup() { pinMode(ledPin, OUTPUT); // устанавливаем контакт 9 как выход } void loop() { val = analogRead(analogPin); // чтение с потенциометра analogWrite(ledPin, val / 4); // пишем в ШИМ }

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

Чтобы ШИМ работал пропорционально вращению потенциометра, значение, полученное с A0, следует разделить на четыре. Это связано с тем, что данные с потенциометра лежат в диапазоне от 0 до 1024, а ШИМ принимает диапазон данных от 0 до 255.

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

Несколько слов в продолжение рассуждений о точности АЦП (см. предыдущую ). На эту тему написан не один десяток хороших книг, поэтому выделим основную проблему: для АЦП 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 может принять.

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

Очень полезный модуль в составе микроконтроллера — аналого-цифровой преобразователь. Он позволяет микроконтроллеру измерять произвольное напряжение.
В мы описывали, как можно считать логическое состояние входа, то есть "0" или "1". Аналого-цифровой преобразователь считывает величину напряжения на выводах A0-A5. Это дает возможность считать данные с датчика освещенности, измерить напряжение питания и т.д.

Подготовка к работе

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

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

Первый пример

Для начала попробуем просто считать напряжение на выводе микроконтроллера A0 и отправить его в COM-порт.
Делается это при помощи функции analogRead() . Этой функции нужно передать номер вывода, напряжение на котором должно быть измерено и она вернет текущее значение.
Загрузите на плату следующий пример:

int val; void setup() { Serial. begin(9600 ) ; } void loop() { val = analogRead(A0) ; Serial. println(val) ; delay(1000 ) ; }

В микроконтроллере Atmega8A, который используется на нашей плате , есть модуль АЦП с разрешением 10 бит и возможностью мультиплексирования шести входов. Эти входы пронумерованы A0-A6 (или 14-19).
Измерение производится относительно напряжения питания. Ни в коем случае нельзя подавать на вход отрицательное напряжение или напряжение, превышающее питание! Мы подключили ко входу переменный резистор и наше входное напряжение точно не выйдет за рамки питания.
Теперь разберемся с тем, что нам будет присылать плата. Раз разрешение 10 бит — в десятичном виде значение будет меняться от 0 до 1023. Измерение производится относительно 5-ти вольт, поэтому изменение показаний на 1 соответствует фактическому напряжению 5/1023=4.9мВ. То есть средствами встроенного АЦП микроконтроллера можно измерить напряжение с точностью до 4.9мВ.
Вернемся к скетчу. В результате выполнения строчки …

Val = analogRead(A0) ;

… в переменную val будет записано оцифрованное напряжение, считанное на выводе A0. Откройте монитор порта (Ctrl+Shift+M) и посмотрите, как меняются показания АЦП при вращении вала переменного резистора. Обратите внимание, что нет нужды настраивать вывод при этом на вход.
Теперь пора немного улучшить работу с модулем аналого-цифрового преобразователя. На практике младшие разряды АЦП могут сильно флуктуировать из-за шумов и их обычно отбрасывают, причем сразу два разряда. При этом остается 8ми-битное число с которым гораздо удобнее работать. Точность при этом получается 5/255=19.6мВ, чего вполне достаточно для большинства ситуаций.
Измените код так, чтобы он присылал 8ми-битное значение. Замените строку с чтением состояния АЦП на это:

Val = analogRead(A0) > > 2 ;

Теперь переменной val мы присваиваем значение считанное из АЦП сдвинутое на два бита вправо. Остальные биты просто отбрасываются.

Второй пример

Теперь мы можем плавно изменять яркость светодиода при помощи ШИМ-модуляции, задавая ее переменным резистором. Установите перемычку "color" так, как описано в . Этим самым вы подключены к 9му, 10му и 11му выводу сегменты трехцветного светодиода.
Для начала попробуем изменять яркость только одного светодиода:

# define BLUE 9 int val; void setup() { pinMode(BLUE, OUTPUT) ; } void loop() { analogWrite(BLUE, (analogRead(A0) > > 2 ) ) ; }

Довольно простой код, если вы помните, как работает функция . При вращении вала переменного резистора светодиод будет менять свою яркость от минимума до максимума.
Кстати, если вы уберете сдвиг на два бита, при выполнении функции analogWrite() будет наступать переполнение, так как она может принимать только значения от 0 до 255. Попробуйте убрать этот сдвиг и посмотрите, что получится.
И в заключении добавим управление всеми тремя светодиодами. Остается только найти отвертку, чтобы покрутить двумя остальными подстроечными резисторами.

# define BLUE 9 # define ORANGE 10 # define GREEN 11 int val; void setup() { pinMode(BLUE, OUTPUT) ; pinMode(ORANGE, OUTPUT) ; pinMode(GREEN, OUTPUT) ; } void loop() { analogWrite(BLUE, (analogRead(A0) > > 2 ) ) ; analogWrite(ORANGE, (analogRead(A1) > > 2 ) ) ; analogWrite(GREEN, (analogRead(A2) > > 2 ) ) ; }

Индивидуальные задания

  1. Оставьте на шилде только перемычку от резистора на выводе A0 и подключите пьезоизлучатель также, как
Понравилась статья? Поделиться с друзьями: