Вновь приветствую читателей «Хабра»!
Присказка.
Честно сказать, хотел написать статью несколько другого содержания, которая затрагивала бы тему применения и использования сдвиговых регистров, когда сам, даже не думал что в моих проектах это станет необходимым.
Но так однажды случилось, что я решил втянуть в область программирования микроконтроллеров своего друга, который во многих вопросах с легкостью разберется сам, а в других....
Заказал для него стартовый комплект из Digispark'ов и 10-ти ваттных RGB светодиодов, потому что его первой же идеей, стало создание свето-динамической установки. Но проблема пришла откуда не ждали: недостаток задействованных аппаратных ШИМ — стал первой проблемой. А второй — друг захотел управлять одновременно несколькими светодиодами.
Выход конечно прост — задействовать более продвинутую плату с другим микроконтроллером. Но я вспомнил про сдвиговые регистры, принцип управления которыми изучил давно, а вот в схемах никогда не использовал.
Сел за Atmel Studio и начал писать многоканальный софт-ШИМ, через 74HC595. За пару дней было написано несколько режимов управления, и реализованы все основные функции для организации многоканальных ШИМ, БАМ, а так же механизмы работы со светодиодными уровнями и семи-сегментными индикаторами.
Собственно на эту тему я и хотел написать развернутую статью с приложенными исходниками, но объем получился довольно большой, а статья получалась очень обширной. А после финальной реализации описанной ниже, пришлось полностью пересмотреть концепцию управления, что привело к мгновенному устареванию всех наработок на эту тему.
Если у читателей появится огонь желания получить такую статью, я займусь. А без этого, мы же все помним, насколько я ленивый. :)
В общем другу была создана библиотека для использования в среде Ардуино, а мне стало скучно и я стал размышлять о применении полученных наработок для управления LCD (HD44780).
Итог этой работы я и хотел бы сейчас продемонстрировать, а заодно, поделиться с Вами знаниями и исходными кодами.
Итак, начнем:
Что такое сдвиговый регистр, микроконтроллер и жидко-кристаллический экран все знают, а если не знают, но имеют к этому интерес, получат эти знания с легкостью, поэтому самые базовые знания я опущу.
Для работы был выбран сдвиговый регистр 74HC595, ввиду наличия защелки, позволяющей организовать вывод данных по-требованию. Дисплей совместимый с HD44780 (для работы в 4-х битном режиме), и микроконтроллер ATtiny13A.
Такой выбор был обусловлен не отсутствием более мощных микроконтроллеров, а сужением рамок поставленной задачи, количеством выводов, объемами памяти и ОЗУ, и просто — ради спортивного интереса.
Цель: создание кода, способного выполняться на заданном оборудовании, не в ущерб производительности, удобности использования и полноте функций.
Первое что необходимо было организовать, это вывод данных через сдвиговый регистр с приемлемой скоростью, что и было проделано с использованием трех ног «Тиньки».
Изображение описанного подключения таким образом:
Затем была написана функция для вывода данных в формате приемлемом для LCD. Эта функция является своеобразным ядром или драйвером библиотеки, на котором базируется весь последующий вывод. А так же дополнительная (вспомогательная функция), которая организует дробление данных на нибблы (полубайты), и отправляет их в LCD через сдвиговый регистр, вызывая основную функцию передачи данных.
Как только эта часть работы была выполнена и протестирована, я приступил к организации нижнего уровня функций управления LCD, которые описаны в ДШ к устройству, как необходимые для первоначальной инициализации LCD, а так же, непосредственно саму функцию первоначальной инициализации, без которой LCD даже не включится.
И здесь я нашел метод инициализации, который никто не использует, но подробно описан в ДШ фирмы HITACHI (если мне не изменяет память). Заключается он в том, что для переключения разрядности линии данных в LCD, необходимо чтобы первые ДВЕ команды были 4-х битными, и все!
Когда же я изучал вопросы управления LCD, читая различные сайты, там был описан известный многим стандартный режим инициализации.
Результатом стало написание двух функций инициализации, для избежания проблем при работе с LCD других производителей. Режим определяется директивой в начале программы.
Так как для вывода LCD предназначена всего одна функция вывода (драйвер описанный выше), то все остальные функции легко организуются через ее вызов с передачей соответствующих параметров. А значит, чтобы сэкономить память программ, все остальные функции можно определить через директиву #define.
Таким образом, был определен сет функций, расширяющий базовый набор функций управления LCD, в который так же вошла функция позиционирования курсора, для двух типов устройств LCD — 1602 и 2004 (переключение типа организовано директивой в начале программы), и функция вывода символов:
Далее был написан вспомогательный набор функций верхнего уровня, для организации вывода на экран данных стандартных типов, как то: байт, слово, байт в HEX-виде, строка и т.п.
Для вывода числовых данных, были написаны функции быстрого деления на 10 и на 100, а так же вспомогательные макросы, которые «выгребают» остаток от соответствующего деления (трюк). Таким образом вместо 5-ти делений для вывода uint16, требуется меньшее количество делений — 4 вместо 8-ми, а для uint8 — 2 вместо 4-ёх. Так же написаны функции определения новых символов, функция перекодировки русского текста для строк в RAM, вывод строк из программной памяти с перекодировкой текста.
Вот этот набор:
Куда вошли две вспомогательные функции, организующие вывод битовой карты байта, и маскированный вывод битовой карты байта, для возможности контроля состояния определенных бит, заданных маской. В видео, представленном ниже, это продемонстрировано.
Однако, для отображения в реальном железе, коды символов придется изменить на другие, либо создать альтернативные символы, и использовать их коды. В комментариях к этим функциям указано где это изменить.
Ну и в заключении, весь этот «винегрет» был расширен самым верхним набором функций,
организующим вывод данных в определенной позиции экрана. Сделано это было так же, через директиву #define:
Представленные выдержки кода показывают, насколько подробно код описан комментариями, чтобы не возникло проблем. Итогом оптимизации стала возможность отображения довольно богатого вывода, при очень скромном размере кода, однако скорость работы меня не впечатляла. А так же, после анализа использования стека и уровней вложенности, было решено переписать функцию ядра вывода, чтобы избавиться от вспомогательной функции (которая принимала 2 параметра), что сильно разгрузило стек, сократило код на 100 байт, но не очень ускорило вывод.
И в этот момент я наткнулся на пример немцев 10-ти летней давности, которые организовали вывод данных в сдвиговый регистр, с использованием RC-цепочки. Во-первых реализация данного метода позволила освободить одну ногу микроконтроллера, а во-вторых, это подтолкнуло меня к новым размышлениям.
Так, проанализировав протокол передачи данных я понял, что вывод сигнала «Е»-LCD и вывод защелки сдвигового регистра совпадают!
А это значит, что можно освободить одну линию сдвигового регистра, и ускорить вывод данных в ДВА РАЗА!
Впоследствии вывод RS-LCD, был так же перенесен на линию данных, что позволило освободить еще одну линию сдвигового регистра, а протокол передачи данных был переписан учитывая этот факт.
Итогом явилась единственная функция ядра, которая принимает на вход данные и флаг (вывод команды или функции), которая сама разбирает нибблы и выводит их на линию данных с необходимыми задержками, и не превышает 100 байт.
Шина данных LCD (4 бита) занимает половину вывода одного сдвигового регистра, вторая половина может использоваться для индикации, что я и показал в демонстрационном примере.
Позже, я наткнулся на упрощенное описание использование RC-цепочки для сдвигового регистра на сайте DIHALT'a — easyelectronics.ru, и хотел разместить весь материал там, но не смог зайти под своим аккаунтом, хотя учетные данные ввел правильно. А может перепутал сайты — основной и сайт сообщества. В общем расстраиваться сильно не стал, DI все-равно привет :)
А тем кто чувствует недостаток знаний в описываемой мной области, предлагаю посетить этот ресурс для устранения этого недостатка. DIHALT и члены сообщества очень подробно описали все устройства, о которых здесь идет речь.
Заключение.
Выносить код в отдельную библиотеку я не стал, созданный код легко преобразуется для любой разработки связанной с выводом данных на LCD. К недостаткам можно отнести невозможность переконфигурирования шины данных LCD, сидящей на сдвиговом регистре, она всегда занимает линии с 0 по 3. А вот выводы микроконтроллера могут быть сконфигурированы любые, причем для любого МК, но с оговоркой: линии должны быть на одном порту.
Код содержит пару-тройку хитрых трюков, которые могут быть полезны не только для указанной области (например деления на 10 и 100 с остатком).
Код не использует прерывания и другую периферию, кроме аппаратного ШИМ, который изначально был инициализирован для диагностических целей, а впоследствии оставлен для демонстрационного примера (линии порта PB0 — ШИМ, PB1 — инверсный ШИМ, меняется от состояния светодиодов). С помощью этого можно, например, контролировать яркость подсветки дисплея — программно.
Таким образом, это самая маленькая библиотека вывода данных на LCD (и пожалуй самая быстрая, и самая документированная в коде :)
Хочется добавить, что в железе тестирование не производилось, но исходя из измеренных отклонений параметров RC-цепочки, думаю проблем возникнуть не должно.
Если найдутся смельчаки которые проверят это дело в железе, буду крайне признателен.
Так же отмечу, что работоспособность проверена для частот МК 9.6 МГц и 4.8 МГц (для последнего необходимо изменить сопротивление RC-цепочки на 9к).
Конденсатор в железе должен быть 100 пф (10 пф — допуск на емкость ног порта МК).
Отдельно создана секция INIT3, для предварительной инициализации параметров МК и запуска дисплея после включения устройства, выглядит она так:
Для демонстрации работоспособности, возможностей и скорости обработки, был написан демо-режим, который использует далеко не все функции, тем не менее достаточный для понимания их набор.
Видео эмуляции в программе Proteus это демонстрирует:
Извиняюсь за озвучку, забыл отключить звук на втором мониторе, надеюсь она не отвлекает от просмотра.
Из этих данных:
понятно, что демонстрационный пример использует 480 байт в функции main. Остальной код — использованные функции библиотеки, а последние 4 фрагмента кода по 7 байт, это строковые константы для вывода режимов: «Red», «Green», «Blue», «Yellow» (выровненные пробелами). 1 байт RAM — занимает переменная состояний флагов светодиодов.
В видео, использованные символы встроены в библиотеку Proteus, которая была полностью перерисована.
Поэтому в железе картинка будет отличаться. :)
Но работоспособность от этого не пострадает!
Временные параметры сигналов пульса и пакета данных для желающих:
Из которых видно, что длительности сигнала «0» и сигнала «1» (первый рис.), полностью совпадают по времени, так как функция вывода данных была оптимизирована для отсутствия джиттера.
Кому интересна функция вывода, она здесь:
Если кто-то решится испытать моё творение в железе, и испытает проблемы с запуском, рекомендую поиграть с задержкой (указана директивой #define в секции описания глобальных данных). Будет необходима помощь, обращайтесь с вопросами в комментариях к статье, с радостью помогу. Чуть не забыл добавить: При использовании прерываний, придется обеспечить барьеры в функции вывода данных на линию, самостоятельно (в конце функции есть примеры восстановления SREG под комментарием). Иначе возникнет проблема вызова прерывания во время функции передачи данных, что приведет к отказу работоспособности.
Как всегда — постскриптум:
Решил я проверить, насколько адаптируем код для новых разработок связанных с выводом данных, и первое что пришло на ум, это реализация вольт-метра на ATtiny13A.
Разработка представленной реализации заняла не более 30 минут, большую часть которых заняло проектирование схемы.
Двух-канальный ампер-, вольт-метр:
1 канал: 0-60V, 0-40A
2 канал: 0-15V, 0-10A
Размер кода: 760 байт
Использованная периферия: ADC0, ADC1, ADC2, ADC3
RESET — запрещён, используется весь PORTB.
Возможно в статье допущены неточности, так же, неточности возможны в результатах профилирования указанных в коде на полях, код переписывался неоднократно. Относительно работоспособности кода могу заверить что все функции протестированы неоднократно.
Исходный код демонстрационного примера для Atmel Studio 7.0, и проект для Proteus 8.3 sp2 — прилагается, дальнейшие изменения выкладываться не будут.
Как всегда, всем желаю успехов в творчестве… да и в прочих начинаниях!
Скорейшей весны!
Использование в коммерческих проектах, перепродажа исходного кода, использование с целью наживы и любых корыстных целях, запрещено. Исходные тексты распространяются бесплатно как есть, в случае использования на других сайтах, либо в других источниках, указание автора и уведомление о размещении — обязательно!
Автор: DolphinSoft