ГЛАВА 20 Изобретаем велосипед
Настольные часы и термометр-барометр на микроконтроллере
— В таком случае, купите мне, сударь, часы, — попросил Планше.
— Возьми вот эти, — сказал Атос, со свойственной ему беспечной щедростью отдавая Планше свои часы.
А. Дюма. Три мушкетера
«Изобретением велосипеда» я называю в первую очередь занятие по конструированию часов — если измеритель давления-температуры еще можно придумать оригинальный (бытовые метеостанции, имеющиеся в продаже, не выдерживают никакой критики — ни с точки зрения удобства пользования и дизайна, ни с точки зрения метрологических качеств), то готовых конструкций часов предлагается много и на все вкусы, включая весьма экзотические. И даже если вы захотите сделать что-то оригинальное, чего в продаже не встретишь (а зачем иначе что-то делать самому?), то на универсальных микроконтроллерах электронные часы все равно делать смысла не имеет. Как минимум по той причине, что если собственно часы-минуты-секунды отсчитывать еще относительно просто, то реализация функций будильника, не говоря уж о календаре, окажется настолько сложной (и в первую очередь, в отладке), что будет уже в полной мере изобретением велосипеда.
Правильный путь к конструированию часов — применение какой-нибудь из универсальных микросхем часов реального времени (RTC), где все эти функции реализованы и проверены, предусмотрен автономный режим резервного хода с микропотреблением и т. д., а микроконтроллер выступает лишь в качестве интерфейса между такими часами и индикацией или еще каким-то способом представления времени. Именно так, в частности, устроены часы в компьютере: когда он выключен, время отсчитывается в автономной микросхеме RTC с резервной батарейкой, при включении оно оттуда считывается и далее уже индицируется программно. Такими часами мы займемся в следующей главе — на платформе Arduino они реализуются, как говорится, «с полпинка». Здесь же мы покажем пример того, как можно отсчитывать время, что называется, «в лоб», — это же решение годится и для индикации любых значений (например, показаний каких-нибудь датчиков).
Часы со счетом времени на МК
Часы мы сделаем на основе светодиодных индикаторов — поскольку схема все равно будет потреблять довольно много, то так или иначе потребуется сетевой источник питания, и слепые ЖК-индикаторы ставить нет особого смысла. Также договоримся, что секунды мы не показываем (в настольных часах этого никто и не делает, заменяя их отсчет миганием разделительной точки или двоеточия).
Для выбора МК из предлагаемых фирмой Atmel просто подсчитаем, сколько нам требуется выводов. Во-первых, надо управлять четырьмя разрядами индикации (ЧЧ: ММ). Это мы будем делать в режиме динамической индикации, когда в каждый отдельный момент времени напряжение питания подается только на один разряд индикаторов, и в это же время на сегменты, которые все соединены между собой параллельно, подается код, соответствующий именно этому разряду. При четырех разрядах непосредственное управление предполагает 74 = 28 задействованных выводов, а динамическое — всего 7 + 4 = 11.
Затем нам надо засвечивать разделительный символ — в часах это традиционно двоеточие. Наконец, часы нужно устанавливать. Для этого минимально необходимы две кнопки (включение режима установки и собственно установка). Итого получилось по минимуму 14 выводов.
Остановимся на знакомом нам МК ATtiny2313 — он выпускается в 20-выводном корпусе (см. рис. 19.2), в котором 5 выводов занято под системные нужды (два питания, Reset и два вывода для подключения кварца). Итого нам остается на все про все 15 выводов, что нас устраивает (выводы для программирования тоже задействуем). Мы даже вроде бы получаем один резервный вывод, но далее увидим, что на самом деле под все желательные дополнительные функции выводов нам будет не хватать, и придется изворачиваться (конечно, можно остановиться на ATmega8, у которого 28 выводов корпуса, но мы делаем схему в учебных целях, и тут дефицит даже полезнее).
Общее построение схемы
Теперь общая схема. Выбираем индикаторы большого размера (с цифрой 1 дюйм, или 25,4 мм высотой), с общим анодом, т. е. типа SA10, если ориентироваться на продукцию Kingbright. Лично я предпочитаю желтое свечение (например, SC10-21Y), но это не имеет значения. Так как падение напряжения у них может достигать 4 В, то от того же питания, что требует МК (5 В), питать их нельзя, поэтому нам потребуется два питания: одно стабилизированное +5 В и второе нестабилизированное (пусть будет +12 В). Управлять разрядами индикаторов мы будем от транзисторных ключей с преобразованием уровня (когда на выходе МК уровень +5 В, ключ подает +12 В на анод индикатора), а сегментами — от простых транзисторных ключей (при уровне +5 В вывод сегмента коммутируется на «землю» — поскольку питание индикаторов повышенное, то, к сожалению, управлять прямо от выводов процессора не получится).
В обоих случаях управление получается в положительной логике — включенному индикатору и сегменту соответствует логическая единица (это совершенно не принципиально, но удобно для простоты понимания того, что именно мы делаем).
Токоограничивающие резисторы в управлении сегментами примем равными 470 Ом, тогда пиковый ток через сегмент составит примерно 20 мА, а средний для четырех индикаторов — 5 мА. Всех восьмерок на часах быть не может, максимальное число одновременно горящих разрядов равно 24 («20:08»), потому общее максимальное потребление схемы составит 24-5 = 120 мА, плюс примерно 10 мА на схему управления, итого 130 мА. Исходя из этого, будем рассчитывать источник питания.
Теперь подумаем о том, как сделать, чтобы часы продолжали идти при сбоях в электрической сети. Нет ничего ужасней бытового прибора, который не может сохранить установки даже при секундном пропадании напряжения питания, — вероятно, вы не раз с такими мучились. Конструкторов, выпускающих видеоплееры, музыкальные центры, магнитофоны, микроволновые печи, метеостанции и электроплиты, в которых часы при малейшем сбое в подаче электроэнергии приходится устанавливать заново, следует расстреливать без суда и следствия.
При питании в пределах 4–5 В контроллер типа 2313 потребляет около 5 мА, так что можно рассчитывать на непрерывную работу от щелочной батарейки типа АА с емкостью порядка 2 А·ч в течение не менее 2–3 недель. Так как на непрерывную работу мы не рассчитываем, а лишь на поддержку при сбоях длительностью максимум в несколько минут или часов (при длительном отключении от сети батарейку придется извлекать), то это нас устраивает. Для обеспечения работы понадобятся три таких элемента, соединенных последовательно, тогда их общее напряжение составит 4,5 В.
Переключать питание с сетевого питания на батарейки можно автоматически, с помощью простой схемы из двух развязывающих диодов (чтобы уменьшить потери, диоды должны быть с переходами Шоттки, на них меньше падение напряжения).
Чтобы сделать схему совсем «юзабельной», добавим также небольшой узел для сигнализации при необходимости замены резервной батарейки — пусть это будет наше ноу-хау, т. к. такого почти ни у кого нет[36]. Хотя есть специальные микросхемы, которые «мониторят» питание (мы о них уже говорили в главе 18 % здесь в образовательных целях мы без них обойдемся. Схему такого узла удобно реализовать, «не отходя от процессора», на встроенном компараторе, если сравнивать напряжение батарейки с каким-то фиксированным напряжением.
Кроме того, потребуется некий монитор питания для того, чтобы контроллер «знал», что он подключен к батарейке — при этом придется отключать выводы управления индикацией, чтобы не было дополнительного потребления (вообще-то можно и задействовать режим пониженного энергопотребления, но в нашей конструкции он не имеет особого смысла). В этом случае у нас набирается уже целых 18 функций (12 для осуществления индикации, 2 кнопки установки, 2 входа компаратора, 1 для сигнализации состояния батарейки и еще 1 для входа монитора питания), а использовать контроллер большего размера только для этой цели не хочется. И уж совсем не хочется добавлять какие-то внешние схемы лишь для того, чтобы контролировать батарейку, которая, может быть, сядет этак лет через пять…
Поэтому мы объединим функции выводов: используем один из входов компаратора также и под вторую кнопку, как обычный вывод порта. А на другой вход компаратора, который подсоединен к опорному источнику, «повесим» дополнительно функцию монитора — сигнализировать о пропадании внешнего питания, все равно опорный источник подключен к напряжению питания. Остается придумать, как обеспечить сигнализацию разряда батареи. Тут мы сделаем просто: пусть разделительный символ (двоеточие) мигает, когда все нормально, а когда батарея разряжена — горит все время. Таким образом мы получим наиболее экономичную схему с минимумом внешних элементов.
Теперь поглядим на схему разводки выводов ATtiny2313 (см. рис. 19.2) и выберем, что и к чему мы будем подсоединять. Во-первых, удобно использовать вход внешнего прерывания, например, INT1 (вывод 7) под кнопку, которая будет вводить часы в режим установки. От порта D (портов А и С в этом микроконтроллере нет) осталось шесть разрядов, четыре из которых мы задействуем под управление разрядами индикаторов (в скобках указаны номера выводов): PD0 (2), PD1 (3), PD2 (6) и PD4 (8). Из восьми выводов порта В два задействованы под входы компаратора AIN+ (вывод 12 — к нему мы подсоединим опорный источник для контроля батареи и с него же будем снимать информацию о состоянии питающего напряжения и второй кнопки) и A1N- (вывод 13 — к нему подключим батарейку). Для управления миганием разделительного двоеточия удобно использовать вывод ОС1 (15), который управляется автоматически от таймера (см. главу 19). Под управление сегментами мы задействуем оставшиеся выводы: PD5 (9), PD6 (11), РВ2 (14) и РВ4-РВ7 (16–19). То, что выводы для управления индикаторами расположены не по порядку — это, конечно, не здорово, нам фактически придется управлять каждым разрядом по отдельности, но обойдемся.
Схема
Вот, собственно, и все предварительные наметки — можно рисовать схему платы управления. Она показана на рис. 20.1. Некоторую громоздкость схеме придают ключи управления индикаторами, однако все равно ее можно без проблем уместить на плату примерно 70x100 мм, а с некоторыми усилиями — и на меньшую.
Рис. 20.1. Схема часов на МК ATtmy2313 (плата управления)
Как мы говорили ранее, в ней можно без внесения изменений заменить ATtiny2313 на старый AT90S2313.
Игольчатый разъем X1 типа IDC с 10-ю контактами — программирующий, рассчитанный на описанный в главе 19 программатор от Argussoft. Его можно заменить на стандартный 6-контактный, как и указывалось в главе 19. Все остальные внешние соединения, кроме питания от сети, осуществляются через такой же разъем, но с 16-ю контактами, два из которых: контакты «земля» и питание.
Обратите внимание, что программирующие выводы (кроме Reset) здесь работают в двух режимах. В нормальном режиме эти выводы работают, как выходы на нагрузку 5,1 кОм. Не помешает ли это процессу программирования? Нет, не помешает — такая нагрузка для программатора вполне приемлема. Более того, «чистые» (более нигде не задействованные) выводы программирования все равно следует нагружать «подтягивающими» резисторами, иначе не исключены сбои (об этом мы говорили в главе 18). Здесь же роль гасящей помехи нагрузки играют базовые резисторы ключей управления транзисторами, и дополнительных мер принимать не приходится.
Плату индикации мы делаем отдельно (рис. 20.2).
Рис. 20.2. Схема часов на МК AT90S2313 (плата индикации)
На ней мы ставим четыре индикатора и две управляющих кнопки (о них далее), а также в точности такой же разъем IDC-16, как и на плате контроллера, причем он должен стоять на стороне платы, противоположной индикаторам. Разводка у него также должна быть идентичной. Эти разъемы мы соединим плоским кабелем. Изготовить такой плоский кабель с разъемами IDC-16F самостоятельно без наличия специального инструмента практически невозможно, потому либо придется такой инструмент приобрести, либо попросить установить разъемы на ваш кабель в любой фирме, которая занимается сборкой и ремонтом компьютеров. Можно употребить и готовый кабель даже с большим количеством линий, если на плате использовать разъемы PLD (т. е., если не установлен кожух). Это решение не очень красивое, т. к. при этом кабельная часть разъема будет выходить за пределы разъема на плате, и это нужно предусмотреть в раскладке платы, иначе разъем кабеля может во что-нибудь упереться.
Рассмотрим подробнее работу схемы платы управления. При включении питания цепочка R1C1 обеспечивает надежный Reset. Напомню (см. главу 18), что ставить эту цепочку необязательно — производитель МК гарантирует нормальный Reset и без каких-либо внешних элементов, однако для лучшей защиты от помех это не помешает, ведь часы у нас должны работать по идее годами в круглосуточном режиме.
После установления питания диод VD2 «запрет» батарею, которая имеет напряжение заведомо ниже, чем на выходе стабилизатора. Оба диода — с переходом Шоттки, падение напряжения на них не превышает 0,2–0,4 В.
Теперь разберемся с нашими компараторными примочками. В нормальном режиме кнопка Кн2 (S3 на плате индикации — см. рис. 20.2) разомкнута и на работу схемы не влияет. Напряжение батареи фактически напрямую (делитель R4/R5 делит сигнал в отношении 300/301) попадает на инвертирующий вход компаратора. Это напряжение сравнивается с напряжением на стабилитроне VD3, равном примерно 3,9 В. Стабилитрон обязательно должен быть маломощный, типа КС139Г, в стеклянном корпусе, или соответствующий импортный, в противном случае сопротивление резистора R35 надо снизить примерно в два-три раза. Когда напряжение батареи упадет ниже этого уровня (выбранного с некоторым запасом — при 3 В МК еще может нормально работать, но часть напряжения батареи упадет на диоде VD2, кроме того, следует учитывать, что смена батарейки может произойти не сразу), то компаратор перебросится в состояние логической единицы по выходу.
Программа (см. далее) зарегистрирует падение напряжения батарейки, и разделительное двоеточие (пара светодиодов VD1 и VD2 на схеме по рис. 20.2, подключенных к выводу мигания от Timer 1) перестанет мигать и будет гореть постоянно. Восстановление произойдет сразу, как только батарею сменят на свежую. Та же реакция будет, если просто отключить батарею тумблером «Бат» (S1 на рис. 20.1) или удалить ее. Для того, чтобы в этих случаях вход компаратора не оказывался «висящим в воздухе», и предназначен резистор R5. Ток через него настолько мал (около 1,5 мкА), что на разряд батареи это не оказывает влияния. С8 защищает вход от наведенных на этом резисторе помех.
Разумеется, отличить нажатие кнопки Кн2 от внезапного выключения батарейки МК не в состоянии, но «правильная» реакция на нажатие Кн2, как мы увидим далее, происходит только в режиме установки часов — когда предварительно была нажата кнопка Кн1 (S2 на рис. 20.2). Нажатие Кн2 и в самом деле будет восприниматься, как отключение батарейки — ив режиме установки, и в рабочем режиме, но только на время ее нажатия, а после отпускания состояние МК сразу восстановится. Поэтому функции друг другу не мешают, за исключением невероятного совпадения, если батарейка «захочет» разрядиться как раз в момент установки времени (и при разряженной или отключенной батарейке, увы, установку времени производить нельзя).
При пропадании внешнего питания запирается диод VD1, а диод VD2 открывается, и напряжение батареи попадает на питание МК. Резистор R6 вкупе с развязывающим конденсатором С2 служат для большей устойчивости работы МК в момент перепада напряжений при переключении питания, для той же цели служит конденсатор С7, установленный параллельно кнопке Кн1 (иначе при перепадах напряжения может спонтанно возникать прерывание, и часы войдут в режим установки, о котором далее). Одновременно с переключением питания становится равным нулю напряжение на стабилитроне, а так как при этом стабилитрон представляет собой обрыв в цепи, то установлен резистор R36, который служит тем же целям, что и резистор R5. Компаратор работать перестает (точнее, он всегда будет показывать «нормальную» батарею), но нас это не волнует, т. к. индикации все равно нет.
Тумблер «Бат» (S1 на рис. 20.1) нужен для отключения батареи в случае, если вы хотите остановить часы надолго, а вот тумблер для включения сетевого питания тут совершенно не требуется (разве что на время отладки).
Программа
Полный текст программы часов можно скачать с сайта автора по ссылке: http://revich.lib.ru/AVR/clock.zip. Все подробности приведены в качестве комментариев к тексту программы, здесь мы рассмотрим только общее ее построение и принцип работы.
При включении питания процессора программа начинает работу с команды по метке RESET. Здесь она устанавливает соответствующие порты на выход (все, кроме двух входов компаратора и входа кнопки Кн1), затем делает нужные установки для таймеров и разрешает соответствующие прерывания.
Восьмиразрядный Timer 0 у нас будет по событию переполнения управлять разрядами в режиме динамической индикации. При заданной частоте на входе Timer 0, равной 1/8 от тактовой частоты (4 МГц), частота управления разрядами получится равной 1/256 от 4 МГц/8 = 500 кГц, т. е. чуть меньше 2 кГц, а каждый из четырех разрядов будет включаться с частотой почти 500 Гц, что однозначно превышает порог заметности мигания.
* * *
Заметки на полях
Заметим, что при проектировании питания подобных устройств следует учитывать еще одно обстоятельство: в динамическом режиме нельзя использовать для питания индикаторов пульсирующее напряжение (как в схеме со статической индикацией вроде термометра из главы 17) — обязательно возникнут биения между частотой питающего напряжения и частотой переключения разрядов, и яркость свечения будет пульсировать. Потому напряжение +12 В необязательно должно быть стабилизированным, но для него обязательно наличие сглаживающего фильтра. На самом деле в данной конструкции это условие соблюдается автоматически, т. к. те же +12 В подаются и на вход стабилизатора + 5 В, но могут встретиться конструкции, в которых питание индикаторов осуществляется от отдельной обмотки трансформатора, и нам об этом забывать не следует.
* * *
16-разрядный Timer 1 у нас будет управлять собственно отсчетом времени по прерыванию сравнения, как это делалось в главе 19. Для этого в регистры сравнения загружается число 62 500, а предварительный коэффициент деления задается равным 1/64, тогда прерывание таймера будет возникать с частотой 4 МГц/64/62 500 = 1 Гц. На практике число для сравнения подгоняется под конкретный кварц, и обычно почему-то меньше теоретической величины 62 500 (так, в моем случае оно было равно 62 486).
* * *
Подробности
Как быстро подобрать коэффициент деления? Можно воспользоваться высокоточным частотомером для измерения длительности секундного импульса на выводе ОС1А. При отсутствии такого прибора (мультиметры, позволяющие измерять частоту, не подойдут решительно, а большинство радиолюбительских частотомеров могут использоваться лишь для ориентировочной прикидки) нужно воспользоваться следующим приемом: установить часы с каким-то определенным коэффициентом (например, с теоретическим значением 62 500) по точным часам, например, по компьютерному времени, которое несложно выставить через Интернет очень точно. Так как небольшая ошибка все равно может сохраниться (см. далее процедуру установки), то после установки отметьте точную разницу в секундах между моментом смены показаний минут нашей конструкции и точных часов и запишите ее. Потом выдержите часы достаточно длительный промежуток времени (чем длиннее, тем точнее). Снова точно установите время в компьютере и опять запишите разницу в момент смены минут.
Таким образом вы получите величину ухода часов — пусть, например, она составляет 200 секунд в месяц в сторону отставания. Это значит, что у нас секундный интервал длиннее необходимого на 200/2 592 000 = 7,7·10-5 часть, т. е. на 77 микросекунд (число 2 592 000 есть число секунд за 30 дней, проверьте). Эту же величину мы можем получить и с помощью частотомера. На 77 микросекунд и следует уменьшить период «тиков» таймера, для чего нужно уменьшить наш коэффициент деления на величину 62 500·7,7·10-5 ~= 5, т. е в регистры таймера необходимо записать число 62 495. То же число можно получить, исходя из того, что при коэффициенте деления 1:64 и кварце 4 МГц каждый такт таймера длится 16 микросекунд. Отметьте, что несмотря на кажущуюся достаточно высокую величину коэффициента деления 62 500, изменение его всего на единицу изменит ход часов на целых 40 секунд в месяц, т. е. более, чем на секунду в сутки — это является следствием использования 16-разрядных счетчиков-таймеров и крупнейшим недостатком использования МК для отсчета времени. Для более тонкой подстройки придется изощряться, придумывая всякие хитрости.
* * *
Кроме этого, в процедуре инициализации разрешается прерывание от кнопки Кн1 (INT1). Для кнопки Кн2 отдельного прерывания не требуется, ее состояние отслеживается непосредственно в процессе установки (см. далее). По окончании установок разрешаются прерывания (команда sei), и далее программа переходит к выполнению бесконечного цикла, во время которого производится мониторинг состояния определенных узлов.
Основная логика работы часов следующая. Каждую секунду, когда происходит прерывание Timer 1, счетчик секунд sek увеличивается на 1 (см. процедуру обработки прерывания TIM1 по метке mtime). Если его значение не равно 60, то больше ничего не происходит, если равно, то регистр sek обнуляется, и далее по цепочке обновляются значения текущего времени, хранящиеся в регистрах emin, dmin, ehh и dhh (см. их определения в начале программы).
Прерывание по переполнению Timer 0 для управления разрядами происходит независимо от прерывания Timer 1 и использует установленные в последнем значения часов. По Timer 0 обнуляются все выходы всех портов, управляющие индикацией, затем проверяется значение счетчика POS, отсчитывающего последовательные номера разрядов (от 0 до 3). Чтобы не тратить время на всякие проверки и обнуления, для организации счетчика до 4 здесь используется тот факт, что число 4 совпадает с числом комбинаций первых двух битов. Тогда для последовательного непрерывного счета (0-1-2-3-0-1…) достаточно каждый раз увеличивать счетчик на единицу (см. команду inc POS в конце процедуры), а в начале ее лишь обнулять старшие шесть битов (команда andi POS,3). Далее в зависимости от значения счетчика (cpi POS….) устанавливаем питание нужного индикатора (sbi PortD….) и вызываем процедуру установки маски сегментов SEG_SET, где в зависимости от значения данного разряда в часах устанавливается и маска.
В процедуре SEG_SET и, собственно, в процедурах установки маски (OUT_х) я предлагаю вам разобраться самостоятельно, Есть и другие способы — например, непосредственного задания маски рисунков цифр через загрузку констант командой lpm для чтения констант из памяти, тогда не потребуется длинной процедуры установки битов по отдельности (см. далее). Но такую маску удобно использовать, если у вас выводы управления разрядами идут подряд (к примеру, когда биты 0–7 порта D соответствуют битам маски 0–7). Тогда маску достаточно приложить к регистру порта, и программа резко сокращается. А здесь это сделать трудно — перестраивание маски под выводы различных портов займет не меньше места, чем простая и понятная прямая установка выводов.
Процедура установки часов накладывается на всю эту картину и работает следующим образом. При коротком нажатии на Кн1 возникает прерывание INT1 (процедура по метке INTT1), в котором первым делом проверяется, есть ли сетевое питание (бит 1 регистра Flag, см. далее), иначе и сама установка не требуется. Далее запрещается само прерывание INT1 во избежание дребезга. Разрешается оно в прерывании Timer 1 (см. в исходном тексте начало процедуры TIM1), которое, как мы уже знаем, происходит каждую секунду. Таким образом время нечувствительности, в течение которого можно отпустить кнопку без последствий (без перескока на произвольный разряд), составляет случайную величину от 0 до 1 с. На самом деле это не совсем верное решение, и сделано так только для простоты, — по-хорошему следовало бы пропустить одну секунду, и только потом разрешать, иначе вероятность дребезга все-таки остается большой.
Далее в прерывании INT1 устанавливается отдельный счетчик разрядов set_up, который будет считать от 1 до 4 (если он больше, то выходим из режима установки), и признак режима установки (бит 0 регистра Flag). Если этот признак установлен, то разряд, соответствующий установленному номеру в счетчике set_up, станет мигать. Это достигается с помощью вспомогательного счетчика count (см. процедуру TIM1 по метке CONT1). В этом же месте программы отслеживается состояние Кн2 — если она нажата и удерживается, то каждую секунду происходит увеличение значения выбранного разряда на 1 в тех пределах, в которых это допускается (для единиц минут — от 0 до 9, для десятков минут — от 0 до 5, для десятков часов — от 0 до 2, причем предел единиц часов зависит от значения десятков), далее значение опять обращается в 0. Отпустив кнопку Кн2, вы фиксируете установленное значение, а нажав кратковременно на Кн1, переходите к следующему разряду. После прохождения всех разрядов, при последнем (пятом) нажатии Кн1 режим установки отменяется, т. е. бит 0 регистра Flag сбрасывается (см. процедуру по прерыванию INT1).
Немаловажная особенность этой конструкции — то, что во время установки счет времени прекращается, а при выходе из режима установки счетчик секунд устанавливается в состояние 59 (команда idi sek,59), т. е. счет сразу же начинается с новой минуты. Окончание установки — это довольно важный момент, который можно организовать по-разному, но данный способ наиболее удобен, т. к. вам достаточно дождаться окончания текущей минуты по образцовым часам, и в этот момент сделать последнее нажатие, выйдя из режима установки, чтобы довольно точно синхронизировать время. Сравните, например, как неудобно исполнена ручная установка часов в Windows, где часы продолжают идти и во время установки. А если бы мы обнуляли счетчик секунд вместо его установки в максимальное значение, то нам пришлось каждый раз устанавливать число минут на единицу большее текущего, что неудобно.
Теперь об обеспечении режима автономной работы. Программа контроллера в непрерывном цикле опрашивает значение логического уровня на выводе номер 12 (РВО, он же AIN+), и когда оно становится равным нулю, принимает меры к снижению потребления, в первую очередь за счет отключения внешних портов (см. процедуру Disable). Как только внешнее питание восстанавливается, автоматически возобновляется нормальный режим работы (Restore).
При перебрасывании компаратора в любою сторону происходит прерывание ACOMPI. В нем вывод 15 (ОС1) отключается от таймера Timer 1 и устанавливается навсегда в единичное состояние, если состояние компаратора есть логическая единица (т. е. когда истощается или отключается батарейка). Тогда двоеточие горит постоянно. И наоборот, вывод этот опять подключается к автоматическому миганию, когда компаратор перебрасывается обратно в нулевое состояние.
Детали и конструкция
В качестве источника питания мы используем внутренности блока со встроенной вилкой, с номинальным напряжением питания 10 В и током не менее 500 мА (такие продаются для некоторых игровых консолей). Напряжение на холостом ходу у него будет составлять примерно 13–14 В, под нагрузкой 130 мА оно сядет как раз примерно до 11–12 В.
В качестве кнопок Кн1 и Кн2 с легким нажатием удобно использовать обычные микропереключатели (известные в отечественном варианте под названием МП-1), но со специальной металлической лапкой-рычагом, которая предназначена для того, чтобы уменьшить усилия нажатия и увеличить зону срабатывания (вообще-то такие кнопки предназначены для использования в качестве концевых выключателей). Подойдут импортные кнопки типа SM5 (см. рис. 20.3). Тогда нам не придется портить внешний вид фалыдпанели кнопками или устанавливать их где-то сзади, а установить их прямо на плату индикаторов и просверлить в дымчатом оргстекле напротив них маленькие отверстия, через которые кнопку можно нажимать зубочисткой или другим острым предметом. Чтобы отверстие в оргстекле выглядело «фирменно», сверлить следует осторожно, на малых оборотах, затем вручную сверлом или зенковкой сделать аккуратную фаску с лицевой стороны и обработать отверстие маслом, чтобы оно не белело. Подобное решение хорошо еще и тем, что случайное нажатие кнопок — беда почти всех бытовых электронных устройств — совершенно исключено.
Рис. 20.3. Кнопка SM5 c лапкой-рычагом
После изготовления платы индикации сначала следует установить с обратной стороны разъем, а затем «обдуть» лицевую часть платы черной эмалью из баллончика, не слишком густо (достаточно одного слоя), чтобы краска не затекла в отверстия. Потом на черную плату уже монтируются индикаторы, светодиоды разделительной точки и кнопки. Светодиоды нужно выбирать, естественно, того же цвета свечения, что и индикаторы. Имейте в виду, что сама по себе характеристика «желтый» или «зеленый» еще ни о чем не говорит, — только в таблице, приведенной в главе 7, два зеленых цвета и три красных, а у разных изделий разных фирм их может быть еще больше. И чтобы разница не бросалась в глаза, приготовьтесь к тому, что покупать придется несколько разновидностей и подбирать оттенок по месту. Под индикаторы указанного типоразмера (1 дюйм) подойдут светодиоды диаметром 3 мм, обычные 5-миллиметровые будут слишком выделяться (а под меньшие индикаторы потребуются светодиоды с еще меньшим диаметром). Светодиоды при этом желательно иметь с диффузным рассеиванием, чтобы их было одинаково видно со всех углов зрения. Так что вопрос их подбора может оказаться непростым.
Для каждого типа светодиодов придется подобрать резистор R34 (см. рис. 20.1) согласно необходимой яркости (для прозрачных номинал его будет больше, для диффузных — меньше). Устанавливать эту пару диодов следует не прямо друг над другом, а с некоторым наклоном, соответственно наклону цифры индикатора. Неплохо будут выглядеть и прямоугольные светодиоды (5x2 мм), также под наклоном, только их боковые грани придется закрасить густой черной краской или аккуратно обернуть их непрозрачной липкой лентой.
Я останавливаюсь на всех этих подробностях потому, что они имеют решающее значение для того, будет ли ваша конструкция выглядеть фирменно или напоминать продукт творчества членов кружка юных техников из деревни Гадюкино. Затрачивать столько сил и средств на конструирование и пренебречь при этом нюансами внешнего вида просто не имеет смысла — если вы, конечно, конструируете бытовой прибор, а не утилитарную схему для рабочих нужд. Но и в последнем случае гораздо удобнее брать в руки аккуратную и удобную в работе конструкцию, а не голую плату с болтающимися проводами.
Когда мы соединим плату управления с платой индикации кабелем и подключим питание, схема заработать сразу не сможет, потому что нужно запрограммировать МК. Для этого вы должны подключить к разъему XI программатор и загрузить hex-файл с программой. Часы должны «затикать» светодиодами и показать все нули на индикаторах. Потом можно браться за установку времени.
Без сомнения, вы легко сможете доделать эту конструкцию, добавив в нее, к примеру, функции будильника. Причем это можно сделать даже без переделки схемы, если «повесить» функции установки и включения будильника на те же кнопки, разделив их с простой установкой за счет отсчета времени удержания кнопки (т. е. между нажатием и отпусканием). Сложнее, правда, будет обеспечить выход на «пищалку», но ее можно «повесить» на тот же вывод «мигалки» управления разделительными светодиодами, если при срабатывании будильника заполнять включенное состояние мигалки частотой 2 кГц, предназначенной для управления разрядами, — например, переключая с этой частотой вывод ОС1 то на вход, то на выход (при этом в обычном режиме «пищалка» будет еле слышно тикать). Но, разумеется, никто вас не заставляет жаться и применять именно 2313 — возьмите модель Mega8 или Mega8515, где выводов гораздо больше, и все окажется куда проще. Тем более, что в этом случае можно придумать и еще что-то, например, добавить маленькие разряды секундомера в углу передней панели, а будильник дополнить «полицейской» мигалкой, переключая красный и синий светодиоды попеременно.
Измеритель температуры и давления на AVR
Прежде чем непосредственно заняться этой относительно сложной конструкцией, нам придется углубиться в теорию и понять, как в восьмиразрядном контроллере производить арифметические действия с многобайтовыми числами, и к тому же получать результат в десятичной системе счисления. Без этого никакой измеритель с индикацией спроектировать невозможно, т. к. АЦП контроллера выдает абстрактные численные результаты, а нам нужны физические величины. Подгонять выходную шкалу с помощью регулирования соотношений опорного и измеряемого напряжения, как мы это делали в цифровом термометре из главы 17, при наличии процессора — не просто глупое, но и крайне неудобное занятие: для термометра нужна одна шкала, для датчика давления — совсем другая (а если бы мы еще пару датчиков других величин придумали вставить?).
Поэтому для начала поучимся оперировать в контроллере большими числами и представлять их в десятичной форме. В следующей главе мы перейдем к Arduino, где таких проблем не существует вовсе, — любые арифметические действия программируются «прозрачно» для пользователя, а сопутствующие проблемы за вас уже решили создатели компилятора AVRGCC. Зато когда вы поглядите на объем получающегося кода, то оцените преимущества программирования на ассемблере. И дело даже не в самом объеме (аналогичная программа для Arduino просто не влезла бы в память mega8535), а в скорости исполнения: к этой программе мы спокойно можем добавить еще часы с будильником, запись в память, общение с компьютером, и все это будет спокойно выполняться на частоте 4 МГц с максимально возможной скоростью и без потерь.
Арифметика многобайтовых чисел в МК
Сложение и вычитание больших чисел в МК не представляет трудностей. Корректная операция сложения двух 16-разрядных чисел будет занимать две команды:
add RL1,RL2
adc RH1,RH2
Здесь переменные RL1 и RL2 содержат младшие байты слагаемых, a RH1 и RH2 — старшие. Если при первой операции результат превысит 255, то перенос запишется во все тот же флаг переноса С и учтется при второй операции. Общий результат окажется в паре RH1:RL1. Совершенно аналогично выглядит операция вычитания. Примеры операций с большим числом слагаемых вы найдете в тексте программ далее.
А вот с умножением и делением несколько сложнее. Выполнение типовых операций на AVR для чисел с различной разрядностью, вообще говоря, приводится в фирменных руководствах по применению: «аппнотах» (Application Notes, в данном случае номер 200). Но эти процедуры для наших целей все равно придется творчески переработать. Поэтому мы не будем на них останавливаться, а сразу воспользуемся тем обстоятельством, что для контроллеров семейства Mega определены аппаратные операции умножения. Тогда и алгоритм сильно упрощается и легко модифицируется для любого размера операндов и результата. Вот так выглядит алгоритм для перемножения двух 16-разрядных сомножителей с получением 24-разрядного результата (в названиях исходных переменных отражен факт основного назначения такой процедуры — для умножения неких данных на некий коэффициент[37]):
Как видите, если нужно получить полный 32-разрядный диапазон, просто добавьте еще один регистр для старшего разряда (temp3, к примеру) и одну строку кода перед командой ret:
adc temp3,r01
Естественно, можно просто обозвать r01 через temp3, тогда и добавлять ничего не придется.
Деление — значительно более громоздкая процедура, чем умножение, требует больше регистров и занимает больше времени. Операции деления двух чисел (и 8- и 16-разрядных) приведены в той же «аппноте» 200, но они не всегда удобны на практике: часто нам приходится делить результат какой-то ранее проведенной операции умножения или сложения, а он нередко выходит за пределы двух байтов.
Здесь нам потребуется вычислять среднее значение для уточнения результата измерения по сумме отдельных измерений. Если даже само измерение укладывается в 16 разрядов, то сумма нескольких таких результатов уже должна занимать три байта. В то же время делитель — число измерений — будет относительно небольшим и укладывается в один байт. Но мы не будем здесь заниматься построением «настоящих» процедур деления (интересующихся отсылаю к моей книге [21]). Многие подобные задачи на деление удается решить значительно более простым и менее громоздким методом, если заранее подгадать так, чтобы делитель оказался кратным степени 2. Тогда все деление сводится, как мы знаем, к сдвигу разрядов вправо столько раз, какова степень двойки.
Для примера предположим, что мы некую величину измерили 64 раза и хотим узнать среднее. Пусть сумма укладывается в 2 байта, тогда вся процедура деления будет такой:
He правда ли, гораздо изящнее и понятнее? Попробуем от радости решить задачку, которая на первый взгляд требует по крайней мере знания высшей алгебры — умножить некое число на дробный коэффициент (вещественное число с «плавающей запятой»). Теоретически для этого требуется представить исходные числа в виде мантисса-порядок, сложить порядки и перемножить мантиссы. Нам же неохота возиться с этим представлением, т. к. мы не проектируем универсальный компьютер, и в подавляющем большинстве реальных задач все конечные результаты у нас есть целые числа.
На самом деле эта задача решается очень просто, если ее свести к последовательному умножению и делению целых чисел, представив реальное число в виде целой дроби с оговоренной точностью. В десятичной форме это выглядит так: представим число 0,48576 как 48 576/100 000. И если нам требуется на такой коэффициент умножить, к примеру, число 976, то можно действовать, не выходя за рамки диапазона целых чисел: сначала умножить 976 на 48 576 (получится заведомо целое число 47 410 176), а потом поделить результат на 105, чисто механически перенеся запятую на пять разрядов. Получится 474,10176 или, если отбросить дробную часть, 474. Большая точность нам и не требуется, т. к. и исходное число было трехразрядным.
Улавливаете, к чему я клоню? Наше ноу-хау будет состоять в том, что мы для того, чтобы «вогнать» дробное число в целый диапазон в микроконтроллере, будем использовать не десятичную дробь, а двоичную — деление тогда сведется к той же самой механической процедуре сдвига разрядов вправо, аналогичной переносу запятой в десятичном виде.
Итак, чтобы умножить 976 на коэффициент 0,48576, следует сначала последний вручную умножить, например, на 216 (65 536), и тем самым получить числитель соответствующей двоичной дроби (у которой знаменатель равен 65 536) — он будет равен 31 834,76736, или, с округлением до целого, 31 835. Такой точности хватит, если исходные числа не выходят, как у нас, за пределы 3–4 десятичных разрядов. Теперь мы в контроллере должны умножить исходную величину 976 на константу 31 835 (см. процедуру перемножения ранее) и полученное число 31 070 960 (оно оказывается 4-байтовым — $01DA1AF0, потому нашу процедуру Mui1x16 придется чуть модифицировать, как сказано при ее описании) сдвинуть на 16 разрядов вправо:
В результате, как вы можете легко проверить, старшие байты окажутся нулевыми, а в ddM: ddL окажется число 474 — тот же самый результат. Но и это еще не все — такая процедура приведена скорее для иллюстрации общего принципа. Ее можно еще больше упростить, если обратить внимание на то, что сдвиг на восемь разрядов есть просто перенос значения одного байта в соседний (в старший, если сдвиг влево, и в младший — если вправо). Итого получится, что для сдвига на 16 разрядов вправо нам надо всего-навсего отбросить два младших байта и взять из исходного числа два старших ddHH: ddH — это и будет результат. Проверьте — $01DA и есть 474. Никаких других действий вообще не требуется!
Если степень знаменателя дроби, как в данном случае, кратна 8, то действительно никакого деления, даже в виде сдвига, не требуется, но чаще всего это не так. Однако и тогда приведенный принцип может помочь — например, при делении на 215 вместо пятнадцатикратного сдвига вправо результат можно сдвинуть на один разряд влево (умножив число на два), а потом уже выделить из него старшие два байта. В программе далее мы будем делить на 210 = 1024, отбрасывая младший байт (деление на 8) и еще дважды сдвигая результат вправо. Вот такая специальная арифметика в МК.
Операции с числами в формате BCD
О двоично-десятичных числах или числах в формате BCD было подробно рассказано в главе 14. Как ясно из сказанного там, упакованные BCD-числа удобны для хранения данных, но неудобны для отображения и для выполнения арифметических операций с ними. Поэтому перед отображением упакованные BCD-числа распаковывают, перемещая старший разряд в отдельный байт и заменяя в обоих байтах старшие полубайты нулями. А перед проведением арифметических действий их переводят в обычный формат, после чего опять преобразуют в упакованный формат BCD. Вот этими операциями мы и займемся. Следует отметить, что в системе команд процессора 8051 (а также и знаменитого 8086) есть специальные команды десятичной коррекции, но в AVR их нет, и придется изобретать им замену самостоятельно.
В области двоично-десятичных преобразований (BCD-преобразований) есть три основные задачи:
□ преобразование двоичного/шестнадцатеричного числа в упакованный BCD-формат;
□ распаковка упакованного BCD-формата для непосредственного представления десятичных чисел с целью их вывода на дисплей;
□ обратное преобразование упакованного BCD-формата в двоичный/шестнадцатеричный с целью, например, произведения арифметических действий над ним.
Некоторые процедуры для преобразования в BCD-формат содержатся в фирменной Application notes 204. Приведем здесь вариант такой процедуры, более экономичный в части использования регистров. Исходное hex-число находится в регистре temp, распакованный результат — в tempi: temp. Процедура довольно короткая:
Заодно приведем одно из решений обратной задачи — преобразование упакованного BCD в hex-число, после чего с ним можно производить арифметические действия (хотя в программе далее это нам не понадобится). По сравнению с «фирменной» BCD2bin8 эта процедура хоть и немного длиннее, но понятнее и более предсказуема по времени выполнения:
Более громоздкая задача — преобразование многоразрядных чисел. Преобразовывать BCD-числа, состоящие более чем из одного байта, обратно в hex-формат приходится крайне редко, зато задача прямого преобразования возникает на каждом шагу. В программе далее нам понадобится преобразование 16-разрядного hex-числа в упакованный BCD. Реализацию этой задачи нет смысла рассматривать подробно — она во всем аналогична рассмотренному случаю, с готовой процедурой bin2BCD16 вы можете ознакомиться в исходном тексте программы TPjmeter (см. далее).
Хранение данных в ОЗУ
В проектируемом измерителе для всех операций переменных-регистров не хватит, и часть данных придется хранить в ОЗУ (SRAM). Познакомимся с общими принципами обращения к ячейкам этой памяти.
Для чтения и записи SRAM предназначены регистры х, y и z — т. е. пары r27:r26, r29:r28 и r31:r30, которые по отдельности еще именуют XH: XL, YH: YL, ZH: ZL — в том же порядке (т. е. старшим в каждой паре служит регистр с большим номером). Если обмен данными производится между памятью и другим регистром общего назначения, то достаточно задействовать только одну из этих пар (любую), если же между областями памяти — целесообразно задействовать две. Независимо от того, какую из пар мы используем, чтение и запись происходят по идентичным схемам, меняются только имена регистров.
Покажем основной порядок действий при чтении из памяти в случае использования регистра z (r31:r30). Чтение одной ячейки с заданным адресом Address, коррекция ее значения и обратная запись производятся так:
Режимы с преддекрементом и постинкрементом используются, когда нужно прочесть/записать целый фрагмент из памяти. Схема действий аналогичная, только команды выглядят так:
Абсолютно аналогично выглядят команды чтения:
А вот как можно в цикле записать одно и то же значение из temp в 16 идущих подряд ячеек памяти, начиная с нулевого адреса старших 256 байтов памяти:
Напомним, что область пользовательского ОЗУ начинается с адреса $60 (9610). При попытке записать что-то по меньшему адресу вы обязательно попадаете в какой-то регистр, и результат окажется непредсказуем. Также не следует забывать о том, что последние адреса ОЗУ заняты под стек, который обязательно задействуется, если в программе применяются прерывания. Так, в ATmega8535 имеется 512 байтов SRAM, потому последний адрес (RAMEND) будет равен 96 + 512 — 1 = 607 ($25F), но не стоит занимать адреса ОЗУ выше примерно 592 ($250).
Использование встроенного АЦП
Встроенный АЦП последовательного приближения входит в состав почти всех МК семейства Mega и большинства МК семейства Tiny, кроме простейших младших моделей и, увы, знакомого нам Tiny2313. Мы не будем жаться (от батареек термометру-барометру работать не придется, и экономить тут нечего) и выберем ATmega8535 в корпусе с 40 выводами, у которого имеются четыре порта А, В, С и D полностью (каждый по 8 выводов) и некоторая часть выводов задействована только под альтернативные функции.
Сначала несколько общих слов о встроенных АЦП. Во всех моделях AVR общего назначения они многоканальные и 10-разрядные (за некоторым исключением, например, в ATmega8 из 6 каналов только четыре имеют разрешение 10 разрядов, а оставшиеся два — 8, а в новейшем семействе Xmega АЦП имеет 12 разрядов).
Многоканальность означает, что имеется только одно ядро преобразователя, которое по желанию программиста может подключаться к одному из входов через аналоговый мультиплексор, наподобие 561КП2, рассмотренного в главе 15. Если вы, как чаще всего и бывает, задействуете лишь часть входов, то остальные могут использоваться, как обычные порты ввода/вывода.
Точность АЦП номинально составляет ±2 LSB, плюс еще 0,5 LSB за счет нелинейности по всей шкале. То есть фактически такой АЦП с точки зрения абсолютной точности соответствует 8-разрядному. При соблюдении всех условий эту точность, впрочем, можно повысить, правда, условия довольно жесткие и включают в себя как «правильную» разводку выводов АЦП, так и, например, требование остановки цифровых узлов на время измерения, чтобы исключить наводки (специальный режим ADC Noise Reduction, которого мы здесь касаться не будем).
Чтобы не углубляться в детали этого процесса и не усложнять задачу, мы в дальнейшем поступим проще: предпримем ряд мер, чтобы обеспечить стабильность результата, а абсолютную ошибку скомпенсируем за счет калибровки, которая все равно потребуется. Для этой цели погрешности встроенного АЦП нам хватит и без особых ухищрений, важно только, чтобы показания не «дребезжали». Уменьшение дребезга почти до нуля у нас будет достигаться тем, что, во-первых, на входе канала мы поставим конденсатор для фильтрации неизбежных в совмещенных аналого-цифровых схемах наводок на внешние цепи (фирменное руководство рекомендует еще последовательно с ним включать индуктивность порядка 10 мкГн, но мы без этого обойдемся). Во-вторых, мы будем измерять несколько раз и усреднять значения отдельных измерений.
АЦП в МК AVR могут использовать три источника опорного напряжения на выбор: внешний, встроенный и напряжение питания аналоговой части. Последний вариант, как самый простой, мы и применим — все равно подгонкой масштабов мы заниматься не будем, а все рассчитаем в цифровом виде. Отметим, что выводы аналогового питания сделаны отдельно от цифрового (хотя в простейших случаях это может быть и одно и то же питание, но мы их также разделим). Применение встроенного опорного источника при нестабильном общем питании мы рассмотрим в главе 22 на примере Arduino.
Пару слов о самой организации измерений. АЦП последовательного приближения (см. главу 17) должен управляться определенной тактовой частотой, для чего в его состав входит делитель тактовой частоты самого МК, подобный предделителю у таймеров. Рекомендуется подбирать этот коэффициент деления так, чтобы тактовая частота АЦП укладывалась в промежуток от 50 до 200 кГц. Например, для тактовой частоты МК 4 МГц подойдет коэффициент деления 32, тогда частота АЦП составит 125 кГц. Преобразование может идти в непрерывном режиме (после окончания преобразования сразу начинается следующее), запускаться автоматически по некоторым прерываниям (не для всех типов AVR) или каждый раз запускаться по команде. Мы воспользуемся только последним «ручным» режимом, т. к. нам для осреднения результатов тогда удобно будет точно отсчитывать число преобразований. В таком режиме на одно преобразование уходит 14 тактов, потому для приведенного примера с частотой 125 кГц время преобразования составит приблизительно 9 мс.
По окончании процесса преобразования возникает прерывание АЦП, в обработчике которого результат измерения читается из соответствующих регистров. Поскольку число 10-разрядное, то оно займет два байта, у которых старшие 6 разрядов равны нулю. Это удобно, т. к. мы можем без опасений суммировать до 64 (26) результатов в рамках двухбайтового числа, не привлекая дополнительных регистров, и затем простым сдвигом, как мы обсуждали ранее, вычислять среднее.
Датчики температуры и давления
Аналоговая часть схемы измерения температуры совпадает с описанной в главе 17, за исключением диапазона выходных сигналов и, соответственно, несколько иных параметров. Чтобы использовать диапазон встроенного АЦП полностью, нам надо подавать сигнал от 0 до 5 В (точнее, до значения опорного напряжения, которое здесь совпадает с аналоговым питанием), причем с отрицательными напряжениями на входе в данном случае АЦП работать «не умеет» (в некоторых моделях AVR есть АЦП с дифференциальным режимом, и даже с предварительными усилителями, но точность при этом значительно снижается). При указанных на схеме (рис. 20.4) номиналах резисторов диапазон выходных напряжений всей схемы составит около 4,9 В, т. е. мы задействуем весь диапазон АЦП с некоторым запасом. Резистор R4, который устанавливает нижнюю границу диапазона, нужно выбирать равным не сопротивлению датчика при 0°, как в схеме по рис. 17.9, а равным его сопротивлению при нижней требуемой температуре.
С датчиком атмосферного давления все еще проще — ряд фирм выпускают готовые датчики давления. Мы возьмем барометрический датчик МРХ4115 фирмы Motorola, питающийся от напряжения 5 В и имеющий удобный диапазон выхода примерно от 0,2 до 4,6 В. При этом учтем, что большая абсолютная точность нам не требуется, только стабильность — для небольших высот над уровнем моря можно считать, что при изменении высоты на каждые 10–12 м давление меняется примерно на 1 мм рт. ст. Так что в пределах такого города, как Москва, с естественными перепадами высот до 100 и более метров[38], оно само по себе будет «гулять» в пределах как минимум 10 мм рт. ст., даже без учета этажности зданий. И нам все равно целесообразно будет подогнать результат «по месту» так, чтобы не иметь крупных расхождений с прогнозом погоды по телевидению, — иначе показания прибора окажутся никому не нужны.
Схема
С учетом всего сказанного схема термометра-барометра будет выглядеть так, как показано на рис. 20.4 (напомним, что ОУ МАХ478 можно заменить, например, на ОР293, см. главу 12). Чтобы не загромождать схему, здесь не показан узел индикации, т. к. он аналогичен тому, что используется в часах из предыдущего раздела, за исключением того, что должен содержать не четыре, а шесть разрядов (показания в формате «33,3»° и «760» мм рт. ст.). К ним можно добавить постоянно горящие индикаторы, показывающие единицы измерения, подобно тому, как это делалось в главе 17 (рис. 17.9).
Рис. 20.4. Схема измерителя температуры и давления на МК ATmega8535
На рис. 20.5 показан внешний вид табло такого измерителя, где дополнительные индикаторы изготовлены на основе шестнадцатисегментных PSA-05 красного свечения, в то время как основные семисегментные цифры — зеленого свечения. Минус, как и в главе 17, изготовлен из плоского светодиода.
Рис. 20.5. Размещение индикаторов измерителя температуры и давления
Так как здесь выводов портов хватает, то можно назначить для управления сегментами разряды подряд, для чего выбран порт С (семь его битов из восьми). Тогда для упрощения программы можно применить следующий прием: где-либо в программе определяются константы, соответствующие маске сегментов для рисунка цифр (зажженному сегменту соответствует единица, младший бит соответствует сегменту а, далее по порядку):
Затем в процедуре индикации мы читаем эти константы с помощью инструкции lpm, которая специально предназначена для чтения констант из памяти программ. Инструкция находит их по адресу, в данном случае по метке OUT_N (т. к. адресация в памяти производится байтами, а нумерация команд выполняется словами, то адрес метки приходится умножать на два). После чего выводим в порт С непосредственно маску цифр:
Маски расположены по порядку цифр от 0 до 9. Поэтому перед выполнением этой последовательности команд у нас в рабочем регистре temp должно содержаться значение, соответствующее цифре, выводимой в текущем такте индикации. Так мы избавляемся от процедур рисования знаков. Разряды РВ0-РВ5 назначаем для управления разрядами индикации, а вывод PD7 — для управления знаком температуры.
Не показан на схеме и программирующий разъем, который одинаков для любой схемы на AVR и приведен на рис. 19.2 (соответствующие выводы для ATmega8535 названы на схеме рис. 20.4).
То, что вывод MOSI (вывод 6) совпадает с выводом индикации единиц давления, вас смущать уже не должно. Однако незадействованные в других функциях выводы программирования (в данном случае MISO и SLK, выводы 7 и 8) следует не забыть подсоединить к питанию (в нашем случае к цифровому питанию +5 Вц) «подтягивающими» резисторами номиналом от 1 до 10 кОм, как и показано на рис. 19.2.
Схема источника питания показана на рис. 20.6.
Рис. 20.6. Схема источника питания для измерителя температуры и давления
Измеритель имеет четыре питания (+5 Вц, +5 Ва, — 5 Ва и +12 В для индикации) и три «земли», причем обычным значком «
» здесь обозначена аналоговая «земля» GNDa. Линия цифровой «земли» обозначена GNDц, кроме этого, имеется еще общий провод индикаторов GNDи. Все три «земли» соединяются только на плате источника питания. Отмечу, что готовый трансформатор с характеристиками, указанными на схеме, вы можете не найти. Поэтому смело выбирайте тороидальный трансформатор мощностью порядка 10–15 Вт на напряжение вторичной обмотки 10–12 В (которое будет использоваться для индикаторов и стабилизатора +5 Вц), измерьте на нем количество витков на вольт (как описано в главе 9) и домотайте три одинаковых обмотки на 7–8 В, каждая поверх существующих, проводом не тоньше 0,3 мм в диаметре. Удобнее всего их мотать одновременно сложенным втрое проводом заранее рассчитанной длины.
Программа
Чтобы перейти к обсуждению непосредственно программы измерителя, нам нужно решить еще один принципиальный вопрос. Передаточная характеристика любого измерителя температуры, показывающего ее в градусах Цельсия, должна «ломаться» в нуле — ниже и выше абсолютные значения показаний возрастают. Так как мы тут действуем в области положительных напряжений, то этот вопрос придется решать самостоятельно (в АЦП типа 572ПВ2, напомним, oпpeделeниe абсолютной величины и индикация знака производились автоматически).
Это несложно сделать, если представить формулу пересчета значений температуры в виде уравнения N = K·|x — Z|, где N — число на индикаторе, х — текущий код АЦП, Z — код АЦП, соответствующий нулю градусов Цельсия (при наших установках он должен соответствовать примерно середине диапазона). Чтобы вычислить значение абсолютной величины, нам придется сначала определять, что больше — х или Z, и вычитать из большего меньшее. Заодно при этой операции сравнения мы определяем значение знака. Если в регистрах AregH: AregL содержится значение текущего кода АЦП х, а в регистрах KoeffH: KoeffL значение коэффициента Z, то алгоритм выглядит примерно вот так:
Здесь разряд 7 порта D (вывод 21 контроллера) управляет плоским светодиодом «минус», который горит, если температура ниже нуля, и погашен, если выше. Давление занимает только положительную область значений, поэтому там такой сложной процедуры не понадобится. Если вы посмотрите на характеристику датчика в фирменном описании, то выясните, что он работает не с начала шкалы — нулевому напряжению на выходе (и, соответственно, нулевому коду АЦП) будет соответствовать некоторое значение давления. В результате можно ожидать, что в формуле пересчета значений давления, представленной в виде N = K(x + Z), все величины будут в положительной области.
Физический смысл коэффициента К — крутизна характеристики датчиков в координатах «входной код АЦП — число на индикаторах». Умножение на коэффициент К мы будем производить описанным ранее методом — через представление его в виде двоичной дроби (за основу берется 210 = 1024, этого будет достаточно). Вычисление ориентировочных значений коэффициентов К и Z поясняется далее, при описании процедуры калибровки.
Теперь можно окинуть взглядом собственно программу. Целиком ее текст и результирующий hex-файл можно скачать с сайта автора по адресу http://revich.lib.ru/AVR/TPineter.zip. При всей своей видимой «навороченности», программа TPmeter занимает в памяти программ контроллера всего 632 байта — сравните со многими килобайтами и даже десятками килобайт, которые будет занимать аналогичная программа на Arduino.
Как вы видите из таблицы прерываний, здесь используется всего один, самый простой Timer 0, который срабатывает с частотой около 2000 раз в секунду. В его обработчике по метке TIM0 и заключена большая часть функциональности. В каждом цикле сначала проверяется счетчик cRazr, который отсчитывает разряды индикаторов (от 0 до 5). В соответствии с его значением происходит формирование кода индицируемого знака и затем на нужный разряд подается питание. После формирования цифры программа переходит к довольно запутанному, на первый взгляд, алгоритму работы АЦП. На самом деле он не так уж и сложен.
Управляют этим процессом две переменных: счетчик циклов countcyk и счетчик преобразований count. Первый из них увеличивается на 1 каждый раз, когда происходит прерывание таймера. Когда его величина достигает 32 (т. е. когда устанавливается единица в бите 5, см. команду sbrs countcyk, 5), то значение счетчика сбрасывается для следующего цикла, и происходит запуск преобразования АЦП, причем для канала, соответствующего значению бита в регистре Flag, указывающего, что именно мы измеряем сейчас: температуру или давление. Таким образом измерения равномерно распределяются по времени.
Сами преобразования отсчитываются счетчиком count до 64 (поэтому цикл одного измерения занимает чуть более секунды: 32x64 = 2048 прерываний таймера, а в секунду их происходит примерно 1953). Когда это значение достигается, то мы переходим к обработке результатов по описанным ранее алгоритмам: сумма измерений делится на 64 (т. е. вычисляется среднее за секунду), затем вычитается или прибавляется значение коэффициента Z и полученная величина умножается на коэффициент К, точнее — на его целый эквивалент, полученный умножением на 1024. Затем произведение делится на это число (отбрасывается младший байт и оставшиеся сдвигаются на два разряда вправо) и преобразуется к распакованному двоично-десятичному виду, отдельные цифры которого размещаются в памяти для последующей индикации. Как только очередной такой цикл заканчивается, меняется значение бита в регистре Flag, поэтому давление и температура измеряются попеременно. В целом выходит, что значение каждой из величин меняется примерно раз в две секунды и представляет собой среднее за половину этого периода. Собственно результат измерения читается в прерывании АЦП (процедура по метке readADc), которое происходит автоматически по окончании каждого преобразования. В нем увеличивается значение счетчика count, извлекается из памяти предыдущее значение суммы показаний (в зависимости от регистра Flag — температуры или давления), считываются значения АЦП, суммируются с предыдущими значениями, и сумма записывается обратно в память. Практически весь алгоритм мы описали — осталось только понять, как получить значения коэффициентов преобразования К и Z и затем произвести точную калибровку.
Калибровка
Для того чтобы прибор заработал, в него необходимо ввести предварительные значения коэффициентов преобразования К и Z, причем такие, желательно, чтобы они были достаточно близки к настоящим, и измеритель не показал бы нам сразу «погоду на Марсе». В программе «зашиты» некие значения коэффициентов (см. процедуру Reset, Секцию Запись коэффициентов в самом конце программы), которые вы можете использовать, если в точности воспроизведете схему по рис. 20.4 и используете тот же самый датчик давления. Как они получены?
Схема датчика температуры при указанных параметрах должна выдавать, как вы можете подсчитать, значение от 0 до 5 В в диапазоне температур примерно от -47 до 55 °C. То есть на 102 °C у нас приходится 1024 градации АЦП, и крутизна характеристики, если считать градусы с десятичными долями, составит 1020/1024 = 0,996 тысячных долей градуса на единицу кода АЦП. Для вычислений в МК эту величину мы хотим умножить на 1024, так что можно было бы и не делить — ориентировочное значение коэффициента К и так будет 1020.
Величину Z, соответствующую 0 °C, вычислить также несложно. Мы полагаем, что нулевому значению кода соответствует температура -47°, тогда значение кода в нуле должно составить величину 470, поделенную на крутизну: 470/0,996 = 471.
Теперь разберемся с давлением. «Если повар нам не врет», то диапазон датчика, соответствующий изменению напряжения на его выходе от 0 до 4,6 В, составляет примерно 850 мм рт. ст. Диапазон 0–4,6 В будет соответствовать изменению кодов примерно от 0 до 940 единиц, т. е. крутизна К равна 850/940 = 0,904 мм рт. ст. на единицу кода. В приведенном для наших расчетов виде это составит 0,904 — 1024 = 926. «Подставка» Z есть значение кода на нижней границе диапазона датчика, которая равна около 11 мм. рт. ст., соответственно, Z = 11/0,904 = 12 единиц. Полученные величины «по умолчанию» и «зашиваем» в программу.
Для уточнения этих величин необходимо произвести калибровку. Откалибруем уже отлаженный прибор сначала по температуре. Для этого следует запустить прибор и поместить датчик температуры в воду, записав для двух значений температур (как можно ближе к 0°, но не ниже его, и около 30–35 °C) показания датчика (t) и реальные значения температуры по образцовому термометру (t'). Они, естественно, будут различаться.
Для расчета новых (правильных) значений коэффициентов K' и Z' достаточно решить относительно них систему уравнений:
Здесь величины со штрихами относятся к правильным (новым) значениям, а без штрихов — к старым, причем значение коэффициента К нужно подставлять в изначальной форме (а не умноженным на 1024). Система четырех уравнений содержит четыре неизвестных, два из которых (величины кодов x1 и х2) вспомогательные.
Если вы забыли, как решаются такие простые системы — обратитесь к любому справочнику по математике для средней школы (или к пособию по использованию Excel в алгебраических расчетах). Вычисленные значения (не забудьте К умножить на 1024!) «забейте» в программу и перепрограммируйте контроллер.
Аналогично калибруется канал давления, только коэффициент Z в уравнениях не вычитается, а прибавляется к х. Но самое сложное здесь — получить действительные значения давления. Далеко не все научные лаборатории располагают образцовыми манометрами для измерения столь малых давлений с необходимой точностью. Поэтому самый простой, хотя и долгий метод, — сравнивать показания датчика с данными по давлению, которые публикуются в Интернете. Данные радио и телевидения лучше не использовать, т. к. текущие значения могут сообщаться с опозданием на полсуток либо вообще отсутствовать, а по завтрашнему прогнозу, естественно, вы ничего не откалибруете.
Для получения двух точек дождитесь, пока давление на улице не станет достаточно низким, а затем, наоборот, высоким — экстремальные значения давления в европейской части России составляют примерно 720 и 770 мм рт. ст. Чем дальше будут отстоять друг от друга значения, тем точнее калибровка. Для повышения точности можно усреднить коэффициенты, рассчитанные по нескольким парам значений давления, но это стоит делать, только если у вас хватит терпения вести наблюдения в течение нескольких месяцев, когда будет пройдено несколько минимумов и максимумов. Средние значения давления при калибровке лучше не учитывать, т. к. ошибка ее из-за узкого интервала и так достаточно велика.
Можно ли объединить часы, описанные в первом разделе этой главы, с измерителем температуры и давления? Конечно, но я предоставляю читателям сделать это самостоятельно. Одно только замечание: общее количество индикаторов составит 10 штук (6 для измерителя и 4 для часов), и это почти предельная величина для динамической индикации. Увеличивать частоту обхода индикаторов нельзя до бесконечности — у контроллера может просто не хватить быстродействия, и он начнет терять прерывания, сбиваясь в опросе датчиков или, что еще хуже, в отсчете времени (правда, это отчасти решается увеличением тактовой частоты). Но и быстродействие транзисторных ключей тоже ограничено, и при слишком высокой частоте обхода будут подсвечиваться ненужные и терять яркость нужные сегменты. Потому, возможно, схему придется продумывать более тщательно и применять индикаторы со встроенным контроллером-драйвером, позволяющим обойтись меньшим числом соединений и без дополнительных ключей. Такие индикаторы мы увидим в следующей главе, где будем конструировать настоящую метеостанцию с часами, выносным радиодатчиком и сохранением данных на флэш-карте.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОК