Эксперимент 33. Исследуем окружающий мир
Переключатель либо включен, либо выключен, однако большинство значений, которые мы получаем из окружающего мира, обычно находятся между этими крайностями. Например, терморезистор — это датчик, электрическое сопротивление которого изменяется в широком диапазоне, в зависимости от температуры.
Микроконтроллер был бы очень полезен для обработки такого входного сигнала. Например, получая входной сигнал от терморезистора, микроконтроллер поддерживает заданную температуру, включая нагревательный элемент, если температура опускается ниже минимального значения, и выключая его, если в комнате достаточно тепло.
Микроконтроллер ATmega 328, установленный на плате Arduino Uno, может справиться с этой задачей, поскольку шесть из его выводов относятся к числу «аналоговых входов». Сигнал, поданный на эти входы, не оценивается как «логически высокий» или как «логически низкий». Он преобразуется внутри микросхемы с помощью аналого-цифрового преобразователя или АЦП.
В 5-вольтовой версии платы Arduino аналоговый сигнал на входе должен быть в диапазоне от О до 5 В. На самом деле, верхний предел можно изменять, что вносит некоторую сложность, поэтому я оставлю рассказ об этом на потом. Терморезистор не вырабатывает никакого напряжения, он только изменяет свое сопротивление. Так, необходимо придумать, каким образом изменение сопротивления обеспечит изменение напряжения.
Как только эта проблема будет решена, АЦП внутри микроконтроллера сможет преобразовать напряжение на аналоговом выводе в числовое значение в диапазоне от 0 до 1023. Почему именно такой числовой диапазон? Потому что его можно выразить десятью двоичными цифрами, а число разрядов АЦП в микроконтроллере на плате Arduino равно десяти.
После того как АЦП выдал число, ваша программа может сравнить его с заданным значением и предпринять соответствующие действия, например, изменить состояние вывода, подающего напряжение на твердотельное реле, которое включает комнатный нагреватель.
Эта последовательность операций, начиная с терморезистора и заканчивая числовым значением, проиллюстрирована на рис. 5.86.
Рис. 5.86. Упрощенная последовательность обработки сигнала от терморезистора
Следующий эксперимент покажет, как это осуществить.
Что вам понадобится
• Макетная плата, монтажный провод, инструмент для зачистки проводов, кусачки, тестовые провода, мультиметр
• Терморезистор номиналом 10 кОм, с допуском 1 или 5% (1 шт.) (Он должен быть типа NTC, что означает «сопротивление падает по мере увеличения температуры». Терморезистор типа РТС ведет себя противоположным образом.)
• Плата Arduino Uno (1 шт.)
• Ноутбук или настольный компьютер со свободным USB-портом (1 шт.)
• USB-кабель с разъемами типа А и В на противоположных концах (1 шт.)
• Резистор номиналом 6,8 кОм (1 шт.)
• Исследование терморезистора
Первый шаг — изучить, что собой представляет терморезистор. Он имеет очень тонкие выводы, потому что они не должны проводить тепло к верхней части или забирать его оттуда, поскольку там располагается p-n-переход, реагирующий на изменение температуры. Эти выводы, вероятно, слишком тонкие, чтобы надежно фиксироваться в макетной плате, поэтому я предлагаю вам зажать их парой тестовых проводов с «крокодилами» и подключить к щупам мультиметра, как показано на рис. 5.87.
Терморезистор, который я рекомендую, имеет номинал 10 кОм. Это максимальное значение, когда компонент становится совсем холодным. Его сопротивление меняется слабо, пока температура не повысится до 25 °С. После этого сопротивление будет уменьшаться быстрее.
Вы можете проверить это с помощью мультиметра. При комнатной температуре терморезистор должен иметь сопротивление около 9,5 кОм. Теперь зажмите его между большим и указательным пальцами. Поскольку он поглощает тепло вашего тела, его сопротивление снижается. При температуре тела (приблизительно 37 °С) сопротивление составит около 6,5 кОм.
Как преобразовать этот диапазон сопротивлений в необходимый для микроконтроллера диапазон напряжений от 0 до 5 В?
Вначале примите во внимание то, что максимальное значение, которое соответствует комнатной температуре, должно быть ниже 5 В. Окружающий мир непредсказуем. А вдруг по какой-то непонятной причине терморезистор нагреется гораздо сильнее, чем вы рассчитывали? Возможно, вы положили рядом с ним паяльник или же оставили его на нагретом участке электронного оборудования.
Рис. 5.87. Проверка терморезистора
Отсюда мы извлекаем первый урок аналого- цифрового преобразования: при измерении величин сигналов, поступающих из окружающего мира, предусматривайте неожиданные, предельные значения.
Преобразование диапазона
Самый простой способ преобразовать сопротивление терморезистора в значение напряжения — подобрать резистор, номинал которого приблизительно равен среднему сопротивлению терморезистора в интересующем нас температурном диапазоне. Соедините такой резистор и терморезистор последовательно, чтобы создать делитель напряжения, подайте 5 В на один конец и О В на другой, а затем измерьте напряжение в средней точке между компонентами, как показано на рис. 5.88.
Обычно, чтобы настроить такую схему, вам понадобилось бы подключить стабилизатор, обеспечивающий напряжение 5 В. Однако на плате Arduino есть встроенный стабилизатор напряжения и он выдает на выходе ровно 5 В (см. рис. 5.79). Вы можете сделать отвод от этого выхода и подключить его к макетной плате с помощью перемычки. Вам понадобится также сделать отвод от одного из заземляющих выходов платы Arduino и таким же образом подключить его к макетной плате.
Рис. 5.88. Самая простая схема для снятия напряжения при изменении сопротивления терморезистора
Когда я пробовал это сделать и изменял температуру терморезистора с 25 до 37 °С, мультиметр показывал напряжение от 2,1 до 2,5 В. Вам предстоит самостоятельно проделать аналогичный эксперимент, чтобы проверить мои значения.
Очевидно, что с таким напряжением нашему микроконтроллеру не грозит опасность. Но теперь я вижу другую проблему: этот диапазон слишком мал и не обеспечивает оптимальной точности.
На рис. 5.89 проиллюстрировано преобразование входного напряжения в цифровой эквивалент. Диапазон от 2,1 до 2,5 В обозначен темной вертикальной полосой. Его можно преобразовать в число от 430 до 512, при этом разница составит 82 — это всего лишь небольшая часть полного диапазона от 0 до 1023.
Ограничиться узким диапазоном — все равно что использовать небольшое количество пикселов на фотографии с высоким разрешением.
Рис. 5.89. График преобразования входного напряжения на плате Arduino в значения на выходе АЦП
Мы неизбежно ухудшим детализацию. Было бы неплохо, если бы мы каким-либо образом смогли преобразовать наше напряжение в цифровой диапазон из 500 значений, а не из 82.
Одним из способов добиться этого могло бы стать усиление напряжения, но для этого потребуется дополнительный компонент, например, операционный усилитель. Еще понадобятся резисторы в цепи обратной связи, и вся схема станет сложнее. Сама идея микроконтроллера сохранить простоту!
Есть еще одно решение — воспользоваться функцией платы Arduino, задающей нижнее максимальное напряжение для диапазона. Но для этого нужно подать эталонное значение нового максимального напряжения на один из контактов. Чтобы создать это напряжение, мне понадобился бы еще один делитель напряжения, а затем пришлось бы рассчитать новое преобразование входного напряжения в значения АЦП. В общем, я решил сначала написать простую программу, добиться ее правильного функционирования, а потом уже заняться улучшениями.
Поразмыслив немного, я понял, что диапазон из 82 значений будет пригоден для представления температур в пределах от 25 до 37 °С. При этом точность каждого шага АЦП составит примерно 0,15 градуса. Этого недостаточно для медицинского термометра, но вполне хватит для измерения комнатной температуры.
Сборка макета
Что ж, давайте попробуем. Но сначала определимся, как мы будем собирать макет устройства и подсоединять плату Arduino Uno, на которой установлен микроконтроллер.
Есть три способа соединить все компоненты:
• Приобрести устройство под названием protoshield, похожее на миниатюрную макетную плату, которая устанавливается поверх платы Arduino Uno и подключается к ее разъемам. Мне такой способ не нравится, потому что я предпочитаю собирать прототип на обычной макетной плате.
Рис. 5.90. Подключение терморезистора к плате Arduino
Рис. 5.91. Собранный макет схемы с терморезистором и платой Arduino
• Вынуть микроконтроллер из платы Arduino Uno и вставить в макетную плату, где размещены остальные компоненты вашей схемы. Но если вы сделаете это, то пропадет возможность загрузки программы в микроконтроллер. Понадобится также генератор колебаний, чтобы микроконтроллер работал на той же частоте, какая была у него на плате Arduino.
• Установить терморезистор и резистор на обычной макетной плате, а затем подать сигнал с терморезистора на плату Uno по проводу таким же способом, как вы подавали положительное напряжение и заземление от платы Arduino на макетную плату. Это не слишком изящно, но, похоже, так делают многие. Если вы отладили программу и окончательно переписали ее в микроконтроллер, то затем можно вынуть микросхему и установить в более удобном месте.
На рис. 5.90 показано расположение элементов, на рис. 5.91 — фотография макета установки. Вынужден признать, что здесь как раз тот случай, когда удобны маленькие провода с разъемами на концах, хотя я до сих пор не вполне доверяю им.
А где же выход у схемы?
Теперь вы настроили все для преобразования аналогового входного сигнала в числовое значение. Но погодите, здесь чего-то не хватает. У схемы нет выхода!
В идеальном мире плата Arduino Uno продавалась бы с маленьким алфавитно-цифровым дисплеем, чтобы вы могли использовать ее как настоящий компьютер. В принципе, вы можете раздобыть дисплей, который будет работать с платой Arduino, но опять-таки это внесло бы дополнительную сложность. Микроконтроллер не является устройством «подключи и работай». Чтобы отправлять информацию на дисплей, микроконтроллер нужно сначала запрограммировать.
Поэтому я упрощу задачу. Индикатором в нашем устройстве будет маленький желтый светодиод на плате Arduino. Представим себе, что этот индикатор является комнатным обогревателем, который включается, когда холодно, и выключается, когда тепло.
Гистерезис
Предположим, мы нагреваем теплицу, температура в которой должна составлять 30 °С. Допустим, напряжение комбинации «терморезистор-резистор» при этой температуре составляет 2,3 В. Отыщите его на графике (см. рис. 5.89), и вы увидите, что АЦП внутри микроконтроллера преобразует это напряжение в числовое значение около 470.
Таким образом, наш порог — 470. Если значение снижается до 469, мы включаем нагрев (или имитируем его включением светодиода). Если значение возрастает до 471, мы выключаем нагрев.
Однако, постойте. Имеет ли это смысл? Ведь даже самое небольшое повышение температуры, воспринимаемое терморезистором, будет включать светодиод, а незначительное понижение будет выключать его. Система будет все время включаться и выключаться.
Обычный термостат не реагирует на небольшие изменения температуры, когда кто-то открывает или закрывает дверь. Когда он включается, он остается включенным до тех пор, пока температура не станет чуть выше установленного значения. Затем, когда он прекращает нагрев, он остается выключенным, пока температура не опустится немного ниже указанного значения.
Такое поведение называется гистерезисом, и я расскажу о нем более детально в связи с компонентом, который называется компаратором, в моей следующей книге — продолжении данной: Make: More Electronics.
Как мы можем реализовать гистерезис в программе для микроконтроллера? Нам необходим более широкий диапазон значений, чем числа от 469 до 471. Программа могла бы описывать следующее: «Если светодиод включен, пусть он остается в этом состоянии, пока значение температуры не превысит 490. Затем его следует выключить». А также: «Если светодиод выключен, пусть он будет в таком состоянии, пока значение температуры не упадет ниже 460. Затем его надо включить».
Сможем ли мы это сделать? Да, очень легко. Программа, представленная в листинге 5.1, функционирует именно так. Протестировав эту программу, я сделал снимок экрана в среде Arduino IDE, и поэтому у меня есть веские основания полагать, что она работает.
Листинг 5.1
Эта программа содержит также некоторые новые понятия — но для начала введите ее в среду IDE. Не обязательно включать все строки комментариев, которые я добавил только для пояснения.
В более коротком варианте программы (листинг 5.2) строки комментариев опущены.
Листинг 5.2
Выполните проверку/компиляцию вашей программы и при необходимости исправьте опечатки (возможно, вы где-либо пропустили точку с запятой — это самая распространенная ошибка).
Подключите плату Arduino, загрузите программу, и если температура вашего терморезистора ниже 30 °С, должен зажечься желтый светодиод.
Нагрейте терморезистор, зажав его между пальцами, как будто температура в помещении увеличилась. Спустя несколько секунд светодиод погаснет. Теперь отпустите терморезистор, и он остынет, но светодиод еще продолжит гореть некоторое время, потому что гистерезис в данной системе заставляет выждать, пока температура не станет достаточно низкой. В конечном счете, светодиод загорится снова. Получилось!
Но как же работает эта программа?
Строка за строкой
В программе существует такое понятие, как переменная. Это небольшая область памяти микроконтроллера, где может храниться числовое значение. Вы можете представить ее как «ячейку памяти». Из программы можно обратиться к ячейке при помощи имени переменной. Внутри ячейка содержит числовое значение.
Строка int digitemp = 0; означает, что объявил переменную с именем digitemp. Она является целочисленной (целым числом) и принимает значения начиная с нуля.
В строке int ledstate = 0; я объявил еще одну целочисленную переменную, чтобы отслеживать состояние светодиода на плате (включен или выключен). Нельзя попросить микроконтроллер посмотреть на светодиод и сказать, в каком он состоянии, поэтому я должен самостоятельно предусмотреть все требуемые действия.
Команда pinMode (13, OUTPUT) в секции setup сообщает микроконтроллеру о том, что следует сконфигурировать контакт 13 как выход. Задавать режим работы контакта А0 в качестве входа нет необходимости, потому что аналоговые выводы являются входами по умолчанию.
Теперь перейдем к основной части программы, К циклу. Сначала я задал команду analogRead, чтобы микроконтроллер прочитал состояние аналогового порта. Какого? Я указал 0, что означает аналоговый порт А0. В него вставлен проводник от моей макетной платы.
Что я собираюсь делать с информацией от АЦП после того, как она будет считана с порта? Есть только одно разумное место ее размещения: в переменной digitemp, которую я создал для этой цели.
Теперь, когда переменная digitemp содержит значение, я могу проверить ее. Если нагреватель включен (светодиод горит) И значение digitemp больше 490, то пора выключить нагреватель. Условие «если» проверяется следующим образом:
if (ledstate == 1 && digitemp > 490)
Двойной знак равенства (==) означает «выполнить сравнение и выяснить, одинаковы ли эти два значения». Одиночный знак равенства означает другую операцию: «назначить данное значение переменной».
Двойной символ & — это «логическое И». Да, здесь у нас применяется булева логика, как и в логическом элементе И. Но вместо того чтобы подключать микросхему, мы просто пишем строку кода.
Символ > означает «больше, чем».
Проверка условия «если» помещена в круглые скобки. Если утверждение в круглых скобках истинно, то микроконтроллер выполняет процедуру, расположенную между фигурными скобками. В этой процедуре с помощью команды ledstate = 0 записан тот факт, что светодиод будет выключен. Команда digitalWrite (13, LOW); в действительности выключает светодиод.
Вторая проверка условия «если» очень похожа, за исключением того, что она применяется, если светодиод выключен, а температура сильно снизилась. Тогда мы зажигаем светодиод.
Наконец, введена задержка на десятую долю секунды, поскольку нам не нужно проверять температуру чаще.
Вот и все.
Нюансы программирования
Я объяснил здесь лишь некоторые синтаксические структуры, например, проверку условия «если» и двойной знак равенства, а также логический оператор && без перечисления всего списка конструкций, которые есть в языке С. Необходимые дополнительные сведения вы всегда сможете найти онлайн.
Запомните несколько моментов, относящихся к программе:
• Строки набраны с отступами, чтобы улучшить восприятие логической структуры программы. Компилятор игнорирует дополнительные пробелы, поэтому вы можете спокойно добавлять их в любом количестве.
• Для удобства среда IDE выделяет ошибки в тексте программы цветом.
• Когда вы присваиваете имя переменной, допустимо любое сочетание букв, цифр и символа подчеркивания — при том условии, что эта комбинация не совпадает с зарезервированным словом в языке С. Например, нельзя создать переменную с именем void.
• Кому-то нравится начинать названия с прописной буквы, а кому-то — со строчной. Выбор за вами.
• Каждая переменная должна быть объявлена в начале программы, иначе компилятор выдаст ошибку.
• Целочисленная переменная (объявленная при помощи ключевого слова int) может принимать значение от -32 768 до +32 767. Язык С в этом микроконтроллере разрешает использовать переменные, которые имеют более широкий диапазон значений или которые могут быть дробными. Но до эксперимента 34 большие числа не понадобятся.
Начальные справочные сведения о языке вы можете найти на главном сайте компании Arduino. Выберите вкладку Learning (Обучение), а затем в раскрывающемся меню укажите пункт Reference (Справка). Можно также открыть меню Помощь (Help) в среде Arduino IDE и выбрать пункт Справочник (Reference).
Усовершенствование программы
Предложенная программа решает поставленную задачу, но ее функции очень ограничены. Самое большое ограничение состоит в том, что значения минимальной и максимальной температуры заданы в виде констант. Это похоже на термостат, зафиксированный только в одном положении, которое нельзя настроить. Как улучшить эту программу, чтобы пользователь мог самостоятельно задать пороговые значения температуры для включения и отключения нагревателя?
Думаю, можно было бы добавить потенциометр. Крайние выводы потенциометра следовало бы подключить к клеммам 5 В и 0 В, а движок соединить с другим аналоговым входом микроконтроллера. В результате потенциометр стал бы работать как делитель напряжения и обеспечивал бы полный диапазон напряжения от 0 до 5 В.
Затем я добавил бы еще одну процедуру в цикл, в которой микроконтроллер проверял положение движка потенциометра и переводил его в числовую форму.
В результате получалось бы число в диапазоне от 0 до 1023. Мне потребовалось бы преобразовать его в число, которое соответствует возможному диапазону значений переменной digitemp. Далее я присвоил бы результат новой переменной с именем, скажем, usertemp. Затем мне понадобилось бы выяснить, существенно ли выше или ниже та температура, которую измерил терморезистор, по сравнению с переменной usertemp.
Заметьте, я опустил одну маленькую деталь: каким, собственно, образом я преобразовал бы входной сигнал от потенциометра в диапазон, подходящий для переменной usertemp. Сейчас мы с этим разберемся.
Если диапазон возможных значений терморезистора составлен из чисел от 430 до 512, как я установил ранее, то его можно представить как среднее значение 471 плюс или минус 41. Потенциометр имеет среднее значение 512 плюс или минус 512 до его полного диапазона. Поэтому:
usertemp = 471 + ( (potentiometer - 512) * .08)
где potentiometer — это значение со входа потенциометра, а символ «звездочка» (*) используется в языке С как знак умножения. Результат достаточно близкий.
Да, арифметика рано или поздно проявляется в программировании, так или иначе. И нет способа обойтись без нее. Но уровня математики средней школы для наших задач вполне хватит.
В улучшенной версии программы мне по-прежнему необходимо позаботиться о гистерезисе. Первый оператор сравнения следует преобразовать так:
if (ledstate == 1 && digitemp > (usertemp + 10) )
после чего светодиод выключается. Но
if (ledstate == 0 && digitemp < (usertemp - 10) )
и тогда светодиод загорается. Это дало бы мне диапазон гистерезиса плюс-минус 10, если используются значения от АЦП.
Теперь, когда я описал необходимую модификацию программы, возможно, вы захотите осуществить ее самостоятельно. Не забывайте только объявлять каждую новую переменную, прежде чем вы ее укажете в теле программы.