Динамическая индикация. Подключение светодиодной матрицы к микроконтроллеру Примеры динамической индикации pic на си

Сегодня мы попробуем подключить к микроконтроллеру не один одноразрядный индикатор, а два. То есть подключим ещё один такой индикатор. Только если мы подключим его точно так же, как и первый, то мы на это дело задействуем практически все ножки портов контроллера, что сделает наш проект нежизнеспособный.

Поэтому на помощь нам придет в этом случае другой способ — это динамическая индикация .

При динамической индикации в каждый момент времени мы будем выводить цифру только на одном индикаторе. То есть цифры, предназначенные для обоих индикаторов мы будем выводить по очереди. Но выводить по очереди мы их будем с такой частотой, что наш глаз этого не заметит. И нам будет казаться, что индикаторы работают постоянно, а не по очереди. Это позволяет нам подключить сегменты обоих индикаторов, а в последствии и большего количества индикаторов к одним и тем же ножкам порта, то есть параллельно. При таком способе будут сразу на оба индикатора подаваться по очереди сначала цифра, предназначенная для первого индикатора, а затем цифра, предназначенная для второго. И чтобы нам не виделись одновременно две цифры на каждом индикаторе, во время отображения цифры, предназначенной для определённого индикатора, мы будем на его анод положительное напряжение, а на другой анод не будем, а будем соединять его с общим проводом. Поэтому независимо от состояния его катодов светиться не будет ни один из его сегментов. Если на сегменте будет ноль, то вообще не будет разности потенциалов, а если будет единица, то разность потенциалов будет, но ток будет направлен в другую сторону и светодиод при таком направлении не будет светиться.

Новый проект мы создадим также как и обычно, скопировав код в main.c из одноименного файла прошлого проекта. Назовём проект Test08 .

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

Отключим пока от питания общий анод и добавим ещё один такой же индикатор, какой у нас уже есть в проекте.

Для этого достаточно его просто выбрать в левой панели, заново из библиотеки его добавлять не нужно. Затем соединим катоды данного индикатора с одноимёнными катодами первого

Освободившиеся аноды мы подключим к ножкам другого порта. Но подключим мы не напрямую, а через ключевые транзисторы, так как при свечении нескольких сегментов индикатора одновременно через ножку порта, подключенную к аноду индикатора, потечёт слишком большой ток. Порт может выйти из строя. Это нам вовсе не интересно. Поэтому подключим наши аноды к ножкам B0 и B1 через транзисторы. Вот зачем мы перенесли кнопку на другую ножку. Причём перенесли с запасом, так как подключать впоследствии мы будем четыре индикатора.

Добавим из библиотеки транзистор. Включать мы его будем в инверсном режиме, поэтому выбираем структуру p-n-p

Подключим таких два транзистора в схему, добавив ещё два резистора по 2 килоома для ограничения тока базы. Эмиттеры подключим к питанию, а коллекторы — к анодам индикаторов (нажмите на картинку для увеличения изображения)

Также я покажу полную схему нашей сборки (нажмите на картинку для увеличения изображения)

Программирование работы многоразрядного семисегментного индикатора



Алгоритм работы программы

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

Подключение многоразрядного семисегментного индикатора

Давайте еще раз посмотрим схему подключения многоразрядного семисегментного индикатора к микроконтроллеру:

На этой схеме выводы порта РВ (РВ0 — РВ7) микроконтроллера через токоограничительные резисторы подключены к соответствующим сегментам (a-g) многоразрядного семисегментного индикатора. Соответствующие сегменты всех разрядов индикатора соединены параллельно. Катоды (аноды) каждого разряда индикатора подключены через транзисторы к выводам порта PD.

Организация динамической индикации

Работа многоразрядного индикатора осуществляется следующим образом:

1. На управляющий транзистор первого разряда индикатора (7Seg1), с вывода порта микроконтроллера PD0 подается логическая единица, которая открывает транзистор, в результате чего подается напряжение питания на данный разряд индикатора. На базах остальных транзисторов — логический ноль, транзисторы закрыты.
2. На выводах порта РВ0-РВ7 выставляется двоичный код соответствующей десятичной цифры — высвечивается нужная цифра в первом разряде.
3. На управляющий транзистор второго разряда (7Seg2) с вывода порта PD1 подается логическая единица (на остальные транзисторы — логический ноль) — подается питание на второй разряд индикатора.
4. На выводах порта РВ0-РВ7 выставляется двоичный код следующей (второй) десятичной цифры — высвечивается нужная цифра во втором разряде.
5. На управляющий транзистор третьего разряда (7Seg3) с вывода порта PD2 подается логическая единица (на остальные транзисторы — логический ноль) — подается питание на третий разряд индикатора.
6. На выводах порта РВ0-РВ7 выставляется двоичный код следующей (третьей) десятичной цифры — высвечивается нужная цифра во втором разряде.
7. И так, по кругу

Такая работа многоразрядного семисегментного индикатора называется —динамическая индикация .
Частота переключения разрядов должна быть в пределах 100 герц, тогда не будет заметно мерцание разрядов.

Для переключения разрядов можно задействовать (на примере микроконтроллера ATtiny2313) таймер «TIMER 0 «.
Настройка таймера производится следующим образом (при тактовой частоте 1 мГц — заводская установка):
— предделитель таймера устанавливаем в СК/8
— вызов прерывания по переполнению счетчика таймера

Вот так настройка таймера выглядит в программе:
Где:
— SP — настройка стека
— Timer 0 — настройка параметров таймера
— TIMSK — настройка прерывания

Алгоритм работы программы

Рассмотрим алгоритм программы для осуществления динамической индикации и вывода данных в многоразрядный семисегментный индикатор:

Этот алгоритм, в принципе, иллюстрирует организацию динамической индикации данных на многоразрядном индикаторе. При этом надо учитывать, что при первом прерывании выполняется первый «прямоугольник», а затем происходит выход из подпрограммы, при втором прерывании выполняется второй «прямоугольник» и тоже выход из подпрограммы и при третьем прерывании — нижний «прямоугольник» с выходом из подпрограммы, и далее по кругу.

Ну а теперь — самое легкое. Напишем программу вывода данных на многоразрядный семисегментный индикатор с динамической индикацией.

Программа индикации на многоразрядном семисегментном индикаторе

Как я уже писал в другой статье — , и чем продуманнее он будет написан, тем легче будет писать программу.

Назначение переменных:

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

Data0, Data1 и Data2 — переменные, в которые основная программа записывает вычисленное значение (трехзначное)
Data — переменная, в которой записан адрес первой переменной данных — Data0
@Data — эта запись означает, что в переменной Data будет храниться адрес первой переменной данных — Data0
DataIndex — эта переменная хранит текущий номер переменной данных, которая выводилась на индикацию последней (0, 1 или 2, соответственно для Data0, Data1 или Data2)
PortDigits — эта переменная хранит данные о том, какой разряд индикатора зажигался последним

Настройка стека:

Стек настраивается в самом начале основной программы, мы его рассматривать не будем, так как к нашей подпрограмме он не относится

Настройка восьмиразрядного таймера Taimer0:

Taimer0 в подпрограмме используется как средство обеспечивающее динамическую индикацию разрядов индикатора

Настроенный таймер через определенные промежутки времени вызывает прерывание, в результате чего происходит остановка основной программы и осуществляется переход в подпрограмму обработки прерывания. В нашем случае — вызывается подпрограмма вывода данных на индикатор.
Как настраивается таймер: Частота переключения разрядов должна быть в пределах 100 Гц для предотвращения мерцания индикаторов при их поочередном зажигании (дело это в принципе индивидуальное, и зависит от особенностей вашего зрения).
Тактовая частота микроконтроллера — 1 мГц, или 1 000 000 Гц
Устанавливаем внутренний делитель частоты таймера в СК/8 — рабочая частота таймера будет в 8 раз меньше тактовой частоты микроконтроллера
Получаем: 1000 000/8 = 125 000 Гц, или 125 кГц — тактовая частота таймера
Настраиваем вызов прерывания по переполнению счетчика таймера (счетчик таймера восьмиразрядный и считает до 255, после этого сбрасывается в ноль и вызывается прерывание)
Получаем: 125 000/255 = 490 Гц (что соответствует времени приблизительно в 2 миллисекунды)
Мы поочередно зажигаем три разряда:
Получаем: 490/3 = 163 Гц — разряды индикатора будут переключаться с частотой 163 Гц.
Настройка таймера производится соответствующей настройкой соответствующих регистров таймера.
Давайте посмотрим как это происходит в Algorithm Builder:

Инициализация индикатора

Инициализация индикатора — эта фраза подразумевает настройку разрядов портов, к которым подключены выводы индикатора на вывод, а также обнуление переменных данных Data0…2 и запись первоначальных данных в остальные переменные. Процесс инициализации индикатора прописывается в начале основной программы.
Назовем подпрограмму инициализации Ini_Indikator2/
Давайте посмотрим этот процесс на примере:


В первой строке разряды порта РВ с 0 по 6 (к которым подключены семь сегментов индикатора) настраиваются на вывод информации (десятичную точку индикатора не используем).
Во второй строке разряды порта PD с 0 по 2 (к которым подключены управляющие транзисторы) также настраиваются на вывод.
Третьей строкой на выходах порта РВ устанавливается логический ноль — сегменты индикатора погашены для индикаторов с общим катодом).
Четвертая строка — обнуляем переменную DataIndex
Пятая строка — в переменную PortDigits записываем единицу
Следующие три строки — обнуляем переменные данных

Теперь нам необходимо куда-то записать которые будут подаваться на разряды порта PB для высвечивания соответствующей цифры на индикаторе.
В статье по программированию работы одноразрядного семисегментного индикатора, мы эти коды записывали программным путем в ОЗУ микроконтроллера. Сейчас мы сделаем по-другому — запишем двоичные коды в теле самой программы.
Для этого создадим таблицу двоичных кодов и присвоим ей имя, к примеру D0_9:

В этой таблице размещены двоичные коды (хотя и записаны в шестнадцатиричной системе) цифр от 0 до 9.

После проделанной нами предварительной работы, разрешаем микроконтроллеру использовать прерывания и переходим к самому главному — подпрограмме вывода данных на многоразрядный индикатор .

Подпрограмма вывода данных на многоразрядный семисегментный индикатор

Присвоим подпрограмме имя, к примеру Indikator2 , посмотрим на нее и разберем построчно:


Хочу сразу отметить, что в этой подпрограмме вывод данных начинается не с первого разряда индикатора, а со второго — так удобнее реализовать алгоритм.

В переменной DataIndex храниться номер ячейки памяти (0, 1 или 2) с данными (Data0, Data1 или Data2) которые необходимо вывести на разряд индикатора в текущий момент. Первоначально мы записали в нее ноль.
Первой строкой мы записываем содержимое DataIndex в регистр R20 , теперь в нем соответственно то-же ноль.
Во второй строчке мы увеличиваем содержимое регистра R20 на единицу (r20++) , теперь в R20 записана единица, означающая, что данные мы будем брать из переменной Data1. При втором прерывании R20 увеличится еще на единицу, станет равным 2, и соответственно следующие данные мы будем брать из переменной Data2. При следующем прерывании R20 станет равным 3.
Следующей строчкой (r20<3) мы проверяем какая цифра записана в регистре R20 — если меньше трех (0,1 или 2), то переходим по стрелке, а если равно трем, то обнуляем регистр R20 и данные теперь берем из переменной Data0.
Далее записываем содержимое R20 в переменную DataIndex .
Следующей командой @Data -> Y записываем адрес переменной Data0 в двойной регистр Y (R28, R29).
Затем складываем содержимое двойного регистра Y с содержимым R20 (0,1 или 2).
Командой [Y] -> r21 записываем содержимое переменной данных (или Data0, или Data1, или Data2 — в зависимости от значения r20) в рабочий регистр R21. Теперь в регистре R21 записана цифра из соответствующей переменной данных (к примеру цифра 5).
Следующей командой @D0_9*2 -> Z мы загружаем начальный адрес таблицы с двоичными кодами в двойной регистр Z (R30, R31). По начальному адресу у нас находится двоичный код для цифры 0.
Теперь мы складываем содержимое Z с R21 (с пятеркой) и получаем в регистре Z адрес в таблице двоичного кода с цифрой 5.
Следующей командой LPM[Z] -> R21 мы записываем двоичный код цифры 5 в рабочий регистр R21.
Команду NOP — холостой ход , можно и не прописывать — она вставлена для разделения отдельных кусков программы для наглядности.
Следующей командой PortDidgit -> R20 мы загружаем в рабочий регистр R20 содержимое переменной PortDidgit, а в нее мы предварительно записали единицу. Теперь в R20 записана единица (#b 0000 0001).
Следующей командой <Следующей командой R20 -> PortD мы подаем напряжение на второй разряд индикатора. При следующем прерывании произойдет еще один сдвиг влево (#b 0000 0100) и будет подключен третий разряд индикатора.
С помощью команды R20.3=1 записанной в овале, мы проверяем — достигла ли логическая единица при сдвигах третьего разряда регистра, и если — да, то записываем в R20 единицу (начинается новый круг).
Командой R21 -> PortB мы выводим двоичный код соответствующей цифры на подключенный разряд индикатора.
Командой R20 -> PortDigits — мы сохраняем текущее значение в переменной (последний зажженный разряд индикатора).

Практическая работа №2

Тема : Разработка схемы динамической индикации.

Цель : Получить практические навыки проектирования элементов индикации цифровых устройств.

Задание : Разработать электрическую принципиальную схему цифрового семисегментного светодиодного дисплея на основе микроконтроллера в соответствии с техническим заданием.

Краткие теоретические сведения

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

Светодиодный семисегментный индикатор представляет собой восемь светодиодов с соединенными катодами или анодами. Сегменты обозначаются буквами A,B,C,D,E,F,G,H как показано на рисунке 1.

Рисунок 1

Свечение сегмента возникает при подаче на общий вывод положительного напряжения, а на катод - нулевого.

Существует два способа организации интерфейса с дисплеем: статический и динамический. Недостатком первого является необходимость большого количества линий управления (количество разрядов портов микроконтроллера) - по 8 на каждый индикатор.

При динамическом способе индикации требуется количество разрядов равное сумме количества сегментов и количества разрядов.

Динамический способ является импульсным и основан на том, что если «мерцание» производится с частотой 50Гц и более, то свечение представляется человеку постоянным. Этот способ требует минимальных аппаратных затрат, обработка динамического дисплея, в том числе и преобразование кодов производится программно.

В динамическом дисплее одноименные сегменты всех индикаторов включены параллельно, образуя сегментную шину, а общие аноды индикаторов образуют шину выбора индикаторов. Таким образом, требуется значительно меньшее количество линий, чем при статическом методе.

Обработка дисплея заключается в поочерёдной выдаче на шину сегментов позиционных семисегментных кодов символов и синхронным включением (выбором) индикаторов.

Схема четырехразрядного динамического индикатора представлена на рисунке 3 . Сегментная шина подключена к порту С микроконтроллера, а четыре младших разряда порта D являются разрядами выбора разрядов индикатора.

Очевидно, что индикатор должен обрабатываться программой. Программа выдает в порт С семисегментный код символа для правого (младшего) разряда индикатор, а разряд РС0 устанавливает в нулевое состояние. Происходит включение младшего разряда индикатора (нулевой уровень открывает транзистор VT3).

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

При частоте «мерцания» 50Гц период импульсов равен 20мс.

Рисунок 2

Рисунок 3 – Электрическая схема динамической индикации

На рисунке 2 представлены временные диаграммы сигналов выбора для четырехразрядного дисплея. Время свечения одного индикатора 5мс.

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

Амплитуда импульса тока в цепи выбора индикатора должна быть в 8 раз (по числу сегментов) больше амплитуды тока в сегменте.

На рис. 2 представлена схема динамического четырехразрядного дисплея, выполненного на семисегментных цифровых индикаторах.

Шина сегментов SA-SH образована выводами порта Р2 микроконтроллера, а четыре младших разряда порта Р1 образуют шину выбора индикаторов (SELI-SEL4). Дисплей обрабатывается программным модулем следующим образом. В порт Р2 загружается семисегментный код символа правого индикатора (HG4). Усиленный элементом DD2 этот ток поступает на шину сегментов а,Ь,с... Затем в младшую тетраду порта Р1 загружается код выбора (включения) индикатора HG4: SEL1=0 , SEL2 -SEL4=1 (шестнадцатиричное значение Е). Ток логического нуля, усиленный элементом DD2, по проводу 11 протекает через резистор R7 и открывает транзистор VT4. Остальные транзисторы выключены. Напряжение питания +5В поступает через открытый транзистор на общий анод индикатора HG4, вызывая индикацию символа. Такое состояние сохраняется в течение времени 5мс. По окончании этого временного интервала МК выдает на разряд порта SEL1 высокий уровень, транзистор VT4 закрывается и ключ VT3 и индицируется символ в этом разряде свечение разряда прекращается. Затем в порт Р2 выдается семисегментный код символа индикатора HG3, а в состояние низкого уровня устанавливается разряд Р1.1. Тем самым включается дисплея и т.д.

Временной интервал, соответствующий времени свечения индикатора, может использоваться для реализации функции управления объектом.

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

Таблица 2 – Семисегментные коды

Байт данных A B C D E F G H НЕХ -код символа
$9F
$25
$D
$99
$49
$41
$1F

Задание

1. Разработать электрическую принципиальную схему динамического цифрового семисегментного дисплея.

2. Выполнить чертеж схемы электрической принципиальной согласно ГОСТ ЕСКД.

3. Обосновать выбор схемотехнических решений.

4. Исходные данные для проектирования представлены в таблице вариантов 2. Для расширения разрядности портов параллельного ввода-вывода микроконтроллера использовать микросхемы сдвиговых регистров. Для управления линиями выбора индикатора использовать двоично-десятичный индикатор. Для усиления сигналов выбора разряда индикации использовать транзисторные ключи.

Таблица 2 – Исходные данные

Вариант № Тип микроконтроллера Тип индикатора Количество сегментов
ATmega8 BL-S56A-11
ATmega16 BC56-11SRWA
ATtiny13 BL-D56A-21
ATtiny2313 BL-S56B-11
ATmega8 BA56-11GWA
ATmega16 BL-D56B-21
ATtiny13 BL-D56A-21
ATtiny2313 BL-D56A-21
ATmega16 BL-S56B-11
ATtiny13 BL-D56B-21

Иногда требуется подключить к микроконтроллеру несколько семисегментных индикаторов или светодиодную матрицу, при этом для отображения информации используется динамическая индикация. Суть динамической индикации заключается в поочередном выводе информации на индикаторы. Ниже на схеме представлен пример соединения нескольких семисегментных индикаторов (для примера с общим катодом) для реализации динамической индикации, вообще с учетом точки получается 8 сегментов, но по старинке их называют именно так. Все выводы (аноды) одноименных сегментов соединяют вместе, итого 8 линий которые через резисторы подключают к микроконтроллеру. Общий катод каждого индикатора подключают к микроконтроллеру через транзистор.


Алгоритм индикации следующий: сначала устанавливаем на линиях требуемые логические уровни в зависимости от того какие сегменты надо включить на первом индикаторе (индикация слево направо), при этом высокий логический уровень для включения, низкий для выключения сегмента. Далее подаем высокий логический уровень на базу транзистора VT1, тем самым общий катод первого индикатора подключается к общему проводу, в этот момент загораются те сегменты, на анодах которых присутствует логическая единица. Через определенное время (пауза) индикатор отключаем, подав низкий логический уровень на базу транзистора, затем снова меняем логические уровни на линиях в соответствии с выводимой информацией, предназначенной для второго индикатора, и подаем сигнал включения на транзистор VT2. Таким образом, по порядку в круговом цикле коммутируем все индикаторы, вот и вся динамическая индикация.

Для получения цельного изображения без мерцаний, переключение необходимо выполнять с большой скоростью, для исключения мерцания светодиодов частоту обновления необходимо устанавливать от 70 Гц и более, я обычно устанавливаю 100 Гц. Для вышерассмотренной конструкции пауза рассчитывается следующим образом: для частоты в 100 Гц период равен 10 мс, всего 4 индикатора, соответственно время свечения каждого индикатора устанавливаем на уровне 10/4=2,5 мс. Существуют многоразрядные семисегментные индикаторы в одном корпусе, в которых одноименные сегменты соединены внутри самого корпуса, естественно для их использования необходимо применять динамическую индикацию.

Для реализации динамической индикации необходимо воспользоваться прерываниями по переполнению одного из таймеров. Ниже представлен код с использованием таймера TMR0:

;Реализация динамической индикации для 4-х семисегментных индикаторов ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; swapf STATUS,W ; clrf STATUS ; movwf STATUS_TEMP ; ; bcf ind1 ;выключение 1-го индикатора bcf ind2 ;выключение 2-го индикатора bcf ind3 ;выключение 3-го индикатора bcf ind4 ;выключение 4-го индикатора; incf shet,F ;инкремент регистра shet movlw .5 ;проверка содержимого регистра shet xorwf shet,W ;на равенство числу 5 btfss STATUS,Z ; goto met1 ;число в регистре shet не равно 5 movlw .1 ;число в регистре shet равно 5: запись числа 1 movwf shet ;в регистр shet ; met1 movlw .1 ;проверка содержимого регистра shet xorwf shet,W ;на равенство числу 1 btfss STATUS,Z ; goto met2 ;число в регистре shet не равно 1: переход на met2 movf datind1,W ;число в регистре shet равно 1: копирование movwf PORTB ;содержимого регистра datind1 в регистр PORTB bsf ind1 ;включение 1-го индикатора met2 movlw .2 ;проверка содержимого регистра shet xorwf shet,W ;на равенство числу 2 btfss STATUS,Z ; goto met3 ;число в регистре shet не равно 2: переход на met3 movf datind2,W ;число в регистре shet равно 2: копирование movwf PORTB ;содержимого регистра datind2 в регистр PORTB bsf ind2 ;включение 2-го индикатора goto exxit ;переход на метку exxit met3 movlw .3 ;проверка содержимого регистра shet xorwf shet,W ;на равенство числу 3 btfss STATUS,Z ; goto met4 ;число в регистре shet не равно 3: переход на met4 movf datind3,W ;число в регистре shet равно 3: копирование movwf PORTB ;содержимого регистра datind3 в регистр PORTB bsf ind3 ;включение 3-го индикатора goto exxit ;переход на метку exxit met4 movf datind4,W ;копирование содержимого регистра datind3 movwf PORTB ;в регистр PORTB bsf ind4 ;включение 4-го индикатора; movlw .100 ;запись числа 156 в регистр таймера TMR0 movwf TMR0 ; ; movwf STATUS ; swapf W_TEMP,F ; swapf W_TEMP,W ; ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа ................. movlw b"11010011" ;OPTION_REG, тем самым устанавливаем внутренний ;устанавливаем коэффициент предделителя 1:16 ; clrf shet ;обнуление регистра shet, перед запуском;прерываний по переполнению TMR0, выполняется; clrf datind1 ;очистка регистров вывода информации на clrf datind2 ;индикаторы, равнозначно выключению clrf datind3 ;индикаторов, так как индикаторы с общим clrf datind4 ;катодом; bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 bsf INTCON,T0IE ;разрешение прерываний по переполнению TMR0 bsf INTCON,GIE ;разрешение глобальных прерываний; movlw b"00000110" ;пример вывода числа 13,52 movwf datind1 ; movlw b"11001111" ; movwf datind2 ; movlw b"01101101" ; movwf datind3 ; movlw b"01011011" ; movwf datind4 ; ; ................. ; ................. ; ................. ; ; end ;конец всей программы;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Реализация динамической индикации для 4-х семисегментных индикаторов

;Частота тактового генератора для примера 4 МГц, машинный цикл 1 мкс

org 0000h ;начать выполнение программы с адреса 0000h

goto Start ;переход на метку Start

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Подпрограмма обработки прерываний

org 0004h ;начать выполнение подпрограммы с адреса 0004h

movwf W_TEMP ;сохранение значений ключевых регистров

swapf STATUS,W ;

movwf STATUS_TEMP ;

bcf ind1 ;выключение 1-го индикатора

bcf ind2 ;выключение 2-го индикатора

bcf ind3 ;выключение 3-го индикатора

bcf ind4 ;выключение 4-го индикатора

incf shet,F ;инкремент регистра shet

movlw .5 ;проверка содержимого регистра shet

xorwf shet,W ;на равенство числу 5

btfss STATUS,Z ;

goto met1 ;число в регистре shet не равно 5

movlw .1 ;число в регистре shet равно 5: запись числа 1

movwf shet ;в регистр shet

met1 movlw .1 ;проверка содержимого регистра shet

xorwf shet,W ;на равенство числу 1

btfss STATUS,Z ;

goto met2 ;число в регистре shet не равно 1: переход на met2

movf datind1,W ;число в регистре shet равно 1: копирование

movwf PORTB ;содержимого регистра datind1 в регистр PORTB

bsf ind1 ;включение 1-го индикатора

goto exxit ;переход на метку exxit

met2 movlw .2 ;проверка содержимого регистра shet

xorwf shet,W ;на равенство числу 2

btfss STATUS,Z ;

goto met3 ;число в регистре shet не равно 2: переход на met3

movf datind2,W ;число в регистре shet равно 2: копирование

movwf PORTB ;содержимого регистра datind2 в регистр PORTB

bsf ind2 ;включение 2-го индикатора

goto exxit ;переход на метку exxit

met3 movlw .3 ;проверка содержимого регистра shet

xorwf shet,W ;на равенство числу 3

btfss STATUS,Z ;

goto met4 ;число в регистре shet не равно 3: переход на met4

movf datind3,W ;число в регистре shet равно 3: копирование

movwf PORTB ;содержимого регистра datind3 в регистр PORTB

bsf ind3 ;включение 3-го индикатора

goto exxit ;переход на метку exxit

met4 movf datind4,W ;копирование содержимого регистра datind3

movwf PORTB ;в регистр PORTB

bsf ind4 ;включение 4-го индикатора

exxit bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0

movlw .100 ;запись числа 156 в регистр таймера TMR0

swapf STATUS_TEMP,W ;восстановление содержимого ключевых регистров

swapf W_TEMP,F ;

swapf W_TEMP,W ;

retfie ;выход из подпрограммы прерывания

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Основная программа

Start ................. ;первоначальная настройка регистров

................. ;специального назначения

.................

bsf STATUS,RP0 ;запись двоичного числа 11010011 в регистр

movlw b"11010011" ;OPTION_REG, тем самым устанавливаем внутренний

movwf OPTION_REG ;источник тактового сигнала для TMR0

bcf STATUS,RP0 ;включаем предделитель перед TMR0

;устанавливаем коэффициент предделителя 1:16

clrf shet ;обнуление регистра shet, перед запуском

;прерываний по переполнению TMR0, выполняется

;однократно, после включения питания

clrf datind1 ;очистка регистров вывода информации на

clrf datind2 ;индикаторы, равнозначно выключению

clrf datind3 ;индикаторов, так как индикаторы с общим

clrf datind4 ;катодом

bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0

bsf INTCON,T0IE ;разрешение прерываний по переполнению TMR0

bsf INTCON,GIE ;разрешение глобальных прерываний

movlw b"00000110" ;пример вывода числа 13,52

movlw b"11001111" ;

movlw b"01101101" ;

movlw b"01011011" ;

................. ;

................. ;

................. ;

end ;конец всей программы

В основной программе сначала настраиваем таймер с помощью регистра OPTION_REG, ранее я рассказывал про использование таймеров для . Далее очищаем регистр shet, предназначенный для введения счета от 1 до 4, для каждого индикатора. Этот регистр инкрементируется в подпрограмме обработки прерываний и там же корректируется (он будет считать от 1 до 4), поэтому данная очистка выполняется однократно после включения питания. По этому регистру будем определять, какой индикатор включать и выдавать данные соответствующие ему. Следующим шагом будет очистка регистров хранения информации, четыре регистра dataind1,2,3,4 соответствующие четырем индикаторам. Очистка равнозначна выключению индикаторов, так как в подпрограмме обработки прерываний, содержимое этих регистров передается в регистр PORTB, к которому подключены аноды индикаторов. Это необходимо для того чтобы на индикаторах не высвечивался всякий мусор после разрешения прерываний, в принципе этого можно и не делать, если сразу записывать правильную информацию для вывода. Далее сбрасываем флаг прерывания по переполнению таймера, разрешаем прерывания по переполнению TMR0, и наконец, разрешаем глобальные прерывания.

В подпрограмме обработки прерываний, первым делом выключаем все индикаторы (подав низкие логические уровни на базы транзисторов), потому что неизвестно какой из них включен. Производим инкремент регистра shet, с проверкой на равенство числу 5, при наличии такого совпадения записываем в регистр число 1, так как необходимо вести счет от 1 до 4. Далее проверяем, какое именно число лежит в регистре shet, по которому загружаем в PORTB данные из регистров хранения информации (dataind) для соответствующего индикатора и включаем его. После чего сбрасываем флаг прерывания по переполнению TMR0, записываем число 100 в таймер (ниже приведен расчет этого значения), для временной задержки и выходим из обработчика прерываний. При первом прерывании включается первый индикатор, во втором прерывании второй и так по круговому циклу. В основной программе остается только загружать данные в регистры хранения информации для каждого индикатора. В подпрограмме прерываний не забываем сохранять и восстанавливать значения ключевых регистров, об этом я писал в статье про .

Для вывода чисел лучше использовать знакогенератор в виде таблицы данных. Например, чтобы вывести число 3456 на индикаторы, его необходимо разбить на разряды, при этом лучше использовать отдельные регистры для хранения чисел разрядов (от 0 до 9), далее прогнать эти регистры через знакогенератор, получив тем самым правильные байты (загружаемые в регистры dataind) для зажигания соответствующих сегментов.

Частоту тактового генератора примем за 4 МГц, машинный цикл 1 мкс. Частота обновления каждого индикатора пускай составит 100 Гц (период T=10 мс), соответственно необходимая временная задержка равна 10/4 = 2,5 мс. Коэффициент предделителя для TMR0 устанавливаем равным 1:16, при этом максимально возможная задержка равна 256х16 = 4096 мкс, а нам требуется пауза в 2,5 мс. Рассчитаем число для записи в TMR0: 256-((256х2,5)/4,096) = 256-156,25 = 99,75. После округления получим число 100.

Ниже можно скачать модель для программы Proteus, прошивку и исходник с реализацией динамической индикации на 4-х разрядный индикатор с общим катодом с применением микроконтроллера PIC16F628A. Для примера на индикатор выводятся числа 0000; 0001; 0002; 13,52; 9764.

Теперь рассмотрим подключение матрицы с разрешением 8х8 точек (светодиодов). Структуру матрицы обычно рассматривают в виде строк и столбцов. На картинке ниже в каждом столбце соединены катоды всех светодиодов, а в каждой строке аноды. Строки (8 линий, аноды светодиодов) через резисторы подключают к микроконтроллеру. Каждый столбец (катоды светодиодов) подключают к микроконтроллеру через 8 транзисторов. Алгоритм индикации такой же, сначала устанавливаем необходимые логические уровни на строках, в соответствии с тем, какие светодиоды должны гореть в столбце, далее подключаем первый столбец (индикация слева направо). Через определенную паузу выключаем столбец, и изменяем логические уровни на строках для отображения второго столбца, далее подключаем второй столбец. И так поочередно коммутируем все столбцы. Ниже представлена схема подключения матрицы к микроконтроллеру.


Всего для подключения такой матрицы потребуется 16 выводов микроконтроллера, что весьма немало, поэтому для сокращения управляющих линий лучше использовать последовательные сдвиговые регистры.

Самым распространенным последовательным регистром является микросхема 74НС595, которая содержит в себе сдвиговый регистр для загрузки данных, и регистр хранения через который данные передаются на выходные линии. Загружать данные в него просто, устанавливаем логический 0 на входе тактирования SH_CP, далее устанавливаем требуемый логический уровень на входе данных DS, после чего переключаем вход тактирования в 1, при этом происходит сохранение значения уровня (на входе DS) внутри сдвигового регистра. Одновременно с этим происходит сдвиг данных на один разряд. Снова сбрасываем вывод SH_CP в 0, устанавливаем требуемый уровень на входе DS и поднимаем SH_CP в 1. После полной загрузки сдвигового регистра (8 бит), устанавливаем в 1 вывод ST_CP, в этот момент данные передаются в регистр хранения и поступают на выходные линии Q0…Q7, после чего сбрасываем вывод ST_CP. Во время последовательной загрузки, данные сдвигаются от Q0 до Q7. Вывод Q7’ подключен к последнему разряду сдвигового регистра, этот вывод можно подключить на вход второй микросхемы, таким образом можно загружать данные сразу в две и более микросхемы. Вывод OE переключает выходные линии в третье (высокоомное) состояние, при подаче на него логической 1. Вывод MR предназначен для сброса сдвигового регистра, то есть установка низких логических уровней на выходах триггеров регистра, что эквивалентно загрузке восьми нулей. Ниже представлена диаграмма загрузки данных в микросхему 74НС595, установка значения 11010001 на выходных линиях Q0…Q7, при условии, что изначально там были нули:


Рассмотрим подключение матрицы 8×8 к микроконтроллеру PIC16F628A с помощью двух сдвиговых регистров 74HC595, схема представлена ниже:


Данные загружаются в микросхему DD2 (управление логическими уровнями на строках, аноды светодиодов) затем через вывод Q7’ передаются в DD3 (управление столбцами), соответственно сначала загружаем байт для включения столбца, затем байт с логическими уровнями на строках. К выходным линиям DD3 подключены транзисторы коммутирующие столбцы матрицы (катоды светодиодов). Ниже приведен код программы для вывода изображения на матрицу:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Реализация динамической индикации для матрицы с разрешением 8х8 ;Частота тактового генератора для примера 4 МГц, машинный цикл 1 мкс org 0000h ;начать выполнение программы с адреса 0000h goto Start ;переход на метку Start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Подпрограмма обработки прерываний org 0004h ;начать выполнение подпрограммы с адреса 0004h movwf W_TEMP ;сохранение значений ключевых регистров swapf STATUS,W ; clrf STATUS ; movwf STATUS_TEMP ; ; movwf FSR_osn ;в регистр FSR_osn movf FSR_prer,W ;восстановление ранее сохраненного значения movwf FSR ;регистра FSR из регистра FSR_prer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;загрузка содержимого регистра stolb в микросхему movf stolb,W ;копирование содержимого регистра stolb movwf var ;в регистр var met2 btfsc var,0 ;устанавливаем вывод ds в соответствии с btfss var,0 ; bcf ds ; bcf sh_cp ; rrf var,F ;сдвиг регистра var вправо, для подготовки;следующего бита goto met2 ;scetbit не равен нулю: переход на метку met2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;загрузка содержимого регистра INDF в микросхему;74HC595 (последовательный сдвиговый регистр) movf INDF,W ;копирование содержимого регистра INDF movwf var ;в регистр var movlw .8 ;запись числа 8 в регистр scetbit, для отсчета movwf scetbit ;переданных битов met1 btfsc var,7 ;устанавливаем вывод ds в соответствии с bsf ds ;значением 7-го бита регистра var btfss var,7 ; bcf ds ; bsf sh_cp ;тактируем вывод sh_cp, для защелкивания данных bcf sh_cp ; rlf var,F ;сдвиг регистра var влево, для подготовки;следующего бита decfsz scetbit,F ;декремент с условием регистра scetbit goto met1 ;scetbit не равен нулю: переход на метку met1 ; bsf st_cp ;тактируем вывод st_cp, для передачи загруженных bcf st_cp ;байтов на выходные линии микросхем 74HC595 ; bcf STATUS,C ;сброс бита C регистра статус перед сдвигом rrf stolb,F ;сдвиг влево регистра stolb ; incf FSR,F ;инкремент регистра FSR, подготовка следующего;регистра для отправки данных на 74HC595 decfsz shet,F ;декремент с условием регистра shet goto exxit ;регистр shet не равен 0: переход на exxit movlw data1 ;регистр shet равен 0: запись адреса первого movwf FSR ;регистра хранения иннформации в регистр FSR movlw .8 ;запись числа 8 в регистр shet, для ведения movwf shet ;счета столбцов ; exxit bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 movlw .124 ;запись числа 124 в регистр таймера TMR0 movwf TMR0 ; ; movf FSR,W ;сохранение текущего значения регистра FSR movwf FSR_prer ;в регистр FSR_prer movf FSR_osn ,W ;восстановление ранее сохраненного значения movwf FSR ;регистра FSR из регистра FSR_osn ; swapf STATUS_TEMP,W ;восстановление содержимого ключевых регистров movwf STATUS ; swapf W_TEMP,F ; swapf W_TEMP,W ; ; retfie ;выход из подпрограммы прерывания;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Основная программа Start ................. ;первоначальная настройка регистров................. ;специального назначения................. bsf STATUS,RP0 ;запись двоичного числа 11010011 в регистр movlw b"11010010" ;OPTION_REG, тем самым устанавливаем внутренний movwf OPTION_REG ;источник тактового сигнала для TMR0 bcf STATUS,RP0 ;включаем предделитель перед TMR0 ;устанавливаем коэффициент предделителя 1:8 ; movlw .8 ;запись числа 8 в регистр shet, перед запуском movwf shet ;прерываний по переполнению TMR0, выполняется;однократно, после включения питания movlw b"10000000" ;запись двоичного числа 10000000 в movwf stolb ;регистр stolb, для включения 1-го столбца;выполняется однократно, после включения питания; movlw data1 ;запись адреса первого регистра (регистры хранения movwf FSR_prer ;информации) в регистр FSR_prer, выполняется;однократно, после включения питания; movlw .8 ;очистка 8-ми регистров вывода информации на movwf tmp ;матрицу, равнозначно выключению movlw data1 ;матрицы movwf FSR ; met3 clrf INDF ; incf FSR,F ; decfsz tmp,F ; goto met3 ; ; bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0 bsf INTCON,T0IE ;разрешение прерываний по переполнению TMR0 bsf INTCON,GIE ;разрешение глобальных прерываний; m1 movlw data1 ;пример вывода буквы R movwf FSR ; movlw b"00000000" ; movwf INDF ; incf FSR,F ; movlw b"01111111" ; movwf INDF ; incf FSR,F ; movlw b"00001001" ; movwf INDF ; incf FSR,F ; movlw b"00011001" ; movwf INDF ; incf FSR,F ; movlw b"00101001" ; movwf INDF ; incf FSR,F ; movlw b"01000110" ; movwf INDF ; incf FSR,F ; movlw b"00000000" ; movwf INDF ; incf FSR,F ; movlw b"00000000" ; movwf INDF ; ; ................. ; ................. ; ................. ; ; end ;конец всей программы;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Реализация динамической индикации для матрицы с разрешением 8х8

;Частота тактового генератора для примера 4 МГц, машинный цикл 1 мкс

org 0000h ;начать выполнение программы с адреса 0000h

goto Start ;переход на метку Start

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Подпрограмма обработки прерываний

org 0004h ;начать выполнение подпрограммы с адреса 0004h

movwf W_TEMP ;сохранение значений ключевых регистров

swapf STATUS,W ;

movwf STATUS_TEMP ;

movf FSR,W ;сохранение текущего значения регистра FSR

movwf FSR_osn ;в регистр FSR_osn

movf FSR_prer,W ;восстановление ранее сохраненного значения

movwf FSR ;регистра FSR из регистра FSR_prer

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;74HC595 (последовательный сдвиговый регистр)

movf stolb,W ;копирование содержимого регистра stolb

movwf var ;в регистр var

movlw .8 ;запись числа 8 в регистр scetbit, для отсчета

movwf scetbit ;переданных битов

met2 btfsc var,0 ;устанавливаем вывод ds в соответствии с

bsf ds ;значением 7-го бита регистра var

bsf sh_cp ;тактируем вывод sh_cp, для защелкивания данных

rrf var,F ;сдвиг регистра var вправо, для подготовки

;следующего бита

decfsz scetbit,F ;декремент с условием регистра scetbit

goto met2 ;scetbit не равен нулю: переход на метку met2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;74HC595 (последовательный сдвиговый регистр)

movf INDF,W ;копирование содержимого регистра INDF

movwf var ;в регистр var

movlw .8 ;запись числа 8 в регистр scetbit, для отсчета

movwf scetbit ;переданных битов

met1 btfsc var,7 ;устанавливаем вывод ds в соответствии с

bsf ds ;значением 7-го бита регистра var

bsf sh_cp ;тактируем вывод sh_cp, для защелкивания данных

rlf var,F ;сдвиг регистра var влево, для подготовки

;следующего бита

decfsz scetbit,F ;декремент с условием регистра scetbit

goto met1 ;scetbit не равен нулю: переход на метку met1

bsf st_cp ;тактируем вывод st_cp, для передачи загруженных

bcf st_cp ;байтов на выходные линии микросхем 74HC595

bcf STATUS,C ;сброс бита C регистра статус перед сдвигом

rrf stolb,F ;сдвиг влево регистра stolb

incf FSR,F ;инкремент регистра FSR, подготовка следующего

;регистра для отправки данных на 74HC595

decfsz shet,F ;декремент с условием регистра shet

goto exxit ;регистр shet не равен 0: переход на exxit

movlw data1 ;регистр shet равен 0: запись адреса первого

movwf FSR ;регистра хранения иннформации в регистр FSR

movlw .8 ;запись числа 8 в регистр shet, для ведения

movwf shet ;счета столбцов

movlw b"10000000" ;запись двоичного числа 10000000 в

movwf stolb ;регистр stolb, для включения 1-го столбца

exxit bcf INTCON,T0IF ;сброс флага прерывания по переполнению TMR0

movlw .124 ;запись числа 124 в регистр таймера TMR0

movf FSR,W ;сохранение текущего значения регистра FSR

movwf FSR_prer ;в регистр FSR_prer

movf FSR_osn ,W ;восстановление ранее сохраненного значения

movwf FSR ;регистра FSR из регистра FSR_osn

swapf STATUS_TEMP,W ;восстановление содержимого ключевых регистров

swapf W_TEMP,F ;

swapf W_TEMP,W ;

retfie ;выход из подпрограммы прерывания

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Основная программа

Start ................. ;первоначальная настройка регистров

................. ;специального назначения

.................

bsf STATUS,RP0 ;запись двоичного числа 11010011 в регистр

movlw b"11010010" ;OPTION_REG, тем самым устанавливаем внутренний

movwf OPTION_REG ;источник тактового сигнала для TMR0

bcf STATUS,RP0 ;включаем предделитель перед TMR0

;устанавливаем коэффициент предделителя 1:8

movlw .8 ;запись числа 8 в регистр shet, перед запуском

movwf shet ;прерываний по переполнению TMR0, выполняется

;однократно, после включения питания

movlw b"10000000" ;запись двоичного числа 10000000 в

movwf stolb ;регистр stolb, для включения 1-го столбца

Информация с логическими уровнями для строк каждого столбца, хранится в 8-ми регистрах хранения информации, обращение к которым выполняется через . Адресу первого регистра присвоено название data1. Кроме первоначальной записи регистров shet и stolb, необходимо записать в регистр FSR_prer адрес первого регистра хранения информации (регистр – data1, запись в FSR_prer выполняется однократно, далее корректируется в обработчике), только после этого разрешать прерывания по переполнению TMR0.

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

В подпрограмме обработки прерываний загружаем в микросхему DD2 содержимое регистра stolb (при первом входе в обработчик после разрешения прерываний, в регистре лежит число 10000000, как было сказано выше). Загрузка начинается с младшего бита регистра stolb, который сдвигается в направлении от Q0 к Q7 (внутри микросхемы DD2) по мере загрузки, алгоритм загрузки был рассмотрен выше, так что думаю, разобраться в коде не составит труда. Далее загружаем в DD2 содержимое регистра INDF, это один из регистров хранения информации, адрес которого находится в FSR (при первом входе в обработчик после разрешения прерываний в FSR лежит адрес первого регистра хранения информации с названием data1). Загрузка начинается со старшего бита регистра INDF. После загрузки рассмотренных 2-х байтов, тактируем вывод st_cp, тем самым загруженные данные передаются на выходные линии микросхем DD2, DD3. Таким образом, при первом входе в обработчик коммутируется первый столбец матрицы, в котором загораются светодиоды, на анодах которых присутствует высокий логический уровень, в соответствии с содержимым регистра data1 (первый регистр хранения информации).

Далее сдвигаем регистр stolb вправо на один бит, для того чтобы подготовить к коммутации второй столбец матрицы при следующем входе в обработчик прерываний. Перед сдвигом необходимо очистить флаг C регистра STATUS, так как сдвиг происходит через этот флаг, и его состояние не известно на момент сдвига. После сдвига, инкрементируем регистр FSR, подготавливая следующий регистр хранения информации (после регистра data1) с логическими уровнями строк для второго столбца. Далее декрементируем с условием регистр shet, и если он не равен нулю, сбрасываем флаг прерывания по переполнению TMR0, производим запись числа в таймер, и выходим из обработчика прерываний.

При следующем входе в обработчик включится второй столбец матрицы и так далее. При обнулении регистра shet (после коммутации 8-го столбца), в него записывается число 8 для очередного цикла коммутации столбов, кроме этого корректируется значение регистра stolb, в регистр FSR записывается адрес первого регистра хранения информации (data1).

Выполним расчет временной задержки для таймера TMR0, частота тактового генератора 4 МГц, машинный цикл 1 мкс. Чтобы избежать мерцания светодиодов, примем частоту обновления каждого столбца в 100Гц (период T=10 мс), временная задержка равна 10/8 = 1,25 мс. Коэффициент предделителя TMR0 установим равным 1:8, при этом максимально возможная задержка равна 256х8 = 2048 мкс. Для паузы в 1,25 мс таймер должен отсчитать (256х1,25)/2,048 = 156,25 раз, округляя получим 156 отсчетов. Соответственно в таймер необходимо записать число 256-156 = 100. Но это не совсем правильное значение, так как на выполнение подпрограммы обработки прерываний затрачивается некоторое время, в данном случае на это уходит около 190 мкс, в перерасчете с учетом коэффициента предделителя получаем 190/8 = 23,75 или 24 отсчета. Правильное значение для записи в TMR0 равно: 100+24=124.

В основной программе записываем 8 регистров хранения информации, в соответствии с тем, что хотим вывести на матрицу. Ниже представлена схема поясняющая вывод информации на матрицу для вышерассмотренного кода.


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

Ниже по ссылке можно скачать прошивку и исходник для микроконтроллера PIC16F628A, с реализацией динамической индикации на матрице 8х8 с применением двух сдвиговых регистров 74HC595, схема подключения была рассмотрена выше. На матрицу поочередно выводятся буквы R, L, цифра 46, смайлик, и просто узор в виде креста, эта анимация продемонстрирована в видеоролике ниже.

с метками , . Читать .

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

1 Основы основ, или вступление

Прежде всего, определимся с терминологией, которой будем пользоваться на протяжении всей статьи.

Если требуется управлять дисплеем, состоящим из единственного семисегментного знакоместа, это не вызывает никаких проблем - его можно представить, как 8 независимых светодиодов. Если требуется вывести информации побольше, чем единственный символ, начинаются проблемы: 2 знакоместа составляют 16 светодиодов, три - 24 и т.д., то есть уже для трехразрядного дисплея может элементарно не хватить выводов микроконтроллера, не говоря о 6-и или более разрядных дисплеях и, тем более, матричных индикаторах.

Условимся для простоты, что все наши индикаторы с общим катодом. Решение проблемы достаточно простое: соединяют выводы сегментов всех индикаторов между собой. Теперь, если требуется вывести информацию в первое знакоместо, следует подать на линии сегментов нужные уровни, а общий вывод первого индикатора соединить с общим проводом схемы. Разумеется, на общих катодоах всех прочих индикаторов должны присутствовать высокие уровни. Очевидно, что нужные сегменты первого индикатра засветятся. Для вывода на второй, третий и т.д. индикаторы следует поступить аналогично, т.е. подавая на один из общих катодов логический ноль, мы выбираем текущий индицируемый разряд, а состояние линий сегментов определяет видимый символ.

Чтобы весь дисплей воспринимался, как светящийся непрерывно, следует переключать разряды быстро - чаще 25 раз в секунду. Как видим, уровни всех выводов (которых, кстати, стало существенно меньше, чем при обычном подходе) непрерывно изменяются, т.е. имеют не статические уровни, а динамические, отсюда и название способа индикации - динамическая.

Изображение при динамической индикации

2 Разновидности аппаратной реализации

2.1 Плоские матрицы

Если абстрагироваться от семисегментных индикаторов, то наш дисплей можно представить в виде матрицы отдельных светодиодов, аноды которых объединены в строки матрицы, а катоды - в столбцы. Собственно, именно так оно и есть.

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

  • по строкам;
  • по столбцам;
  • посегментно (попиксельно);
  • смешанным образом.

Вариант по столбцам мы расмотрели в предыдущей главе. Вариант по строкам отличается от него илшь тем, что строки и столбцы нашей матрицы меняются местами. Посегментный способ заключается в том, что в любой момент времени только на одной строке и на однойм столбце присутствует уровень, необходимый для зажигания светодиода. То есть в любой момент времени может светиться лишь один светодиод всей матрицы (в отличие от предыдущих вариантов, когда одновременно может светиться целиком вся строка или весь столбец). Этот способ напоминает развертку телевизора, когда луч обегает весь экран, засвечивая в нужных местах люминофор. Смешанный вариант, как следует из самого названия, состоит в том, что одновременно «активные» уровни могут присутствовать сразу на нескольких строках и столбцах.

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

2.2 Многомерные матрицы

Рассмотренные нами примеры предполагают реализацию монохромного дисплея, т.е. состоящего из одноцветных светодиодов. Как же быть, если требуется получить многоцветный дисплей, например, из RGB-светодиодов? Напрашивается два варианта решения.

Первый - это просто увеличить число строк (или столбцов) нашей матрицы, рассматривая каждый RGB-светодиод как 3 независимых отдельных светодода. Большой минус этого подхода - увеличение в 3 раза числа строк (или столбцов). На простом примере легко видно, во что это выливается практически: при помощи двух восьмиразрядных протров микроконтроллера мы можем иметь монохромную матрицу 8х8 сегментов или цветную 4х4. Согласитесь, что во втором случае ничего вразумительного отобразить практически нельзя...

Второй способ - это перейти от «плоской» матрицы сегментов к «многомерной». Если сигнал каждой строки пропустить через мультиплексор 1х3, то систему дисплея из RGB-светодиодов мы можем представить как 3 независимых матрицы исходной размерности: каждая матрица состоит из светодиодов одного цвета, а сигналами управления мультиплексорами мы выбираем нужную матрицу. Рисунок поясняет сказанное.

Очевидно, что в случае многомерной матрицы так же требуется дополнительное количество линий управления, однако, это число не так велико: на тех же двух портах контроллера мы можем получить цветной дисплей 7х7!!!

2.3 Способы уменьшения размерности матриц

Если число выводов микроконтроллера очень ограничено, нам придется искать способы уменьшить число строк и столбцов нашей матрицы. Разумеется, чудес не бывает, и в этом случае нам придется заплатить тем, что кроме микроконтроллера будут применяться дополнительные микросхемы. Как вы уже догадались, тут можно использовать ранее рассмотренный способ «многомерных» матриц - ведь никто не запретит нам вместо RGB-светодиодов просто использовать утроенное количество одноцветных? Главное, расположить их соответствующим образом...

Итак, уменьшить размерность матрицы мы можем, применив:

  • дешифраторы или мультиплексоры;
  • сдвиговые регистры.

С мультиплексорами мы уже познакомились ранее, дешифратор , как вы можете догадаться, от мультиплексора отличается непринципиально. Следует лишь дополнить, что используя дешифраторы/мультиплексоры и для строк и для столбцов, можно добиться согращения размерности матрицы сразу по обеим измерениям, однако в этом случае может потребоваться использовать только посегментную динамическую индикацию, со всеми ее недостатками.

Сдвиговые регистры могут помочь намного лучше дешифраторов. Рассмотрим схему на рисунке ниже.

Легко видеть, что любое количество строк и столбцов потребует только увеличения числа регистров, а число задействованных линий управления микроконтроллера останется прежним! Небольшим минусом данного подхода является то, что по мере роста числа регистров в цепочке придется увеличивать скорость последовательного вывода информации в них, что не всегда легко достижимо. Так, например, распространенные микроконтроллеры семейства AVR , практически не позволяют превзойти скорость последовательного вывода в 10 мегабит/сек. С другой стороны, если применять иные контроллеры, способные выдавать сигналы быстрее, могут начаться проблемы другого порядка: распространение высокочастотного тактового сигнала по длинной линии (а при большом числе регистров она неизбежно будет таковой) происходит совсем не так, как низкочастотного, поэтому потребуются особые меры при разводке печатной платы и другие вещи, которые в рамках данной статьи мы рассматривать не будем.

3 Способы программной реализации

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

Для всех примеров условимся, что наш дисплей построен на семисегментных индикаторах с общим катодом.

3.1 Простейший способ

Очевидно, что в программе было бы наиболее удобно иметь некий массив, содержимое котрого однозначно определяло бы то, какие сегменты в каких знакоместах дисплея светятся - эдакий аналог экранного ОЗУ.

Введем определение следующих констант:

#define SCR_SZ 6 /* число знакомест дисплея */ #define ROWS PORTB /* порт «строк» дисплея, т.е. управления сегментами */ #define COLS PORTD /* порт управления «столбцами», т.е. общими катодами */

Теперь объявим массив-экран:

Unsigned char SCR;

Для начала будем считать, что каждый элемент массива соответствует знакоместу дисплея, а каждый бит этого элемента соответствует определенному сегменту индикатора. Какой бит какому сегменту соответствует - в данном случае не важно, как не важно и то, как эти биты устанавливаются в байтах нашего массива, просто будем пока считать, что они там уже есть. Так же для простоты будем считать, то общие катоды подключены к выводам порта COLS последовательно: младший бит - самый правый индикатор, затем второй, затем третий и т.д.

Как же заставить этот массив «отобразиться» на дисплее? Напишем такой код:

< SCR_SZ; pos++){ ROWS = SCR; COLS = ~(1 << pos); }

Будет ли он выполнять требуемую функцию? Да. Но плоховато.

Прежде всего, обратите внимание, что мы никак не управляем скоростью обновления содержимого строк и столбцов. Во-вторых, обратите внимание, что к моменту вывода нового элемента массива в ROWS на линиях COLS еще присутствует старое значение! К чему это приведет? Да к тому, что какую-то долю секунды на знакоместе будут отображаться сегменты соседнего знакоместа, т.е. некоторые сегменты будут ложно засвечены.

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

Unsigned char pos; while(1) for(pos = 0; pos < SCR_SZ; pos++){ COLS = 0xFF; ROWS = SCR; COLS = ~(1 << pos); delay(); }

Мы добавили гашение всего дисплея перед тем, как обновить состояние линий сегментов (подав высокий уровень на общие катоды, мы погасим индикатор не зависимо от того, что присутствует на анодах) и ввели задержку в конце цикла. Теперь индикация будет работать существенно лучше. Но хорошую ли программу мы написали? Увы, нет.

Дело в том, что бесконечный цикл индикации while просто не позволит нам сделать что-то еще. Что ж за программа такая у нас выйдет, которая только и умеет, что что-то выводить на индикатор?! Конечно, все плохо не на все 100%, так как что-то полезное можно делать по прерываниям... да и вместо задержки delay() можно выполнять какие-то действия... Но все это очень и очень криво: в обработчиках прерывания не желательно выполнять что-то сложное и громоздкое; с другой стороны, если что-то сложное и громоздкое делать вместо задержки, то сложно обеспечить одинаковость времени вычислений, в противном случае получится, что знакоместа светятся в течение разного промежутка времени, что визуально будет выглядеть как разнояркое их свечение или мерцание.

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

Как же следует поступать? Ответ, как всегда, прост: все процессы, которые должны выполняться незаметно от решения основной задачи (а индикация, безусловно, является таким процессом), следует выполнять по прерываниям от таймера.
Прерывания будут поступать через строго определенные промежутки времени, что обеспечит равномерность свечения знакомест. Фоновая индикация позволит нам в главном цикле просто записать в нужный момент что-то в массив SCR - и оно мгновенно отобразится на дисплее! А все переделки кода сведутся к тому, что вместо циклов мы используем функцию-обработчик прерывания:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; COLS = 0xFF; ROWS = SCR; COLS = ~(1 << pos); if(++pos == SCR_SZ) pos = 0; }

Несколько комментариев.

Переменную pos , обозначающую номер текущего светящегося знакоместа, мы делаем локальной статической переменной, чтобы она сохраняла свое значение от прерывания к прерыванию. В конце функции мы самостоятельно (ведь цикла у нас больше нет) увеличиваем номер знакоместа до тех пор, пока не достигнем предела - в этом случае переходим снова к началу.

В основной программе нам надо будет лишь проинициализировать порты и таймер (в данном случае - Timer 0 ), чтобы он переполнялся через нужные нам промежутки времени, да разрешить прерывания. После этого об индикации можно не вспоминать - она будет тихо-мирно работать сама по себе. Но как определить нужный интервал переполнения таймера? Очень просто. Человеческий глаз воспринимает мерцание с частотой более 25 Гц, как непрерывное свечение. Индикаторов у нас 6, каждый из них должен мерцать с такой частотой, значит, обновление информации на дисплее должно происходить с частотой 25 х 6 = 150 Гц или более. Теперь рассчитаем значение предделителя таймера: поделим тактовую частоту МК на 256 (Timer 0 у всех AVR восьмибитный, а значит, переполняется, досчитав до 256) - это и будет желаемое значение предделителя таймера. Разумеется, маловероятно, что результат получится совпадающим с одним из стандартных значений предделителя - это не проблема, можно взять ближайшиее меньшее подходящее значение. Индикация будет работать на более высокой частоте, но ведь это не ухудшит ее качество! Побочным эффектом будет большая загрузка ядра МК на индикацию. Если это будет сильно мешать основной программе - придется перевести индикацию на другой таймер, например, 16-битный Timer 1 , или ввести счетчик пропускаемых переполнений таймера:

#define SKIP 15 /* число пропускаемых прерываний таймера */ ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; static unsigned char skip = SKIP; if (--skip) return; skip = SKIP; COLS = 0xFF; ROWS = SCR; COLS = ~(1 << pos); if(++pos == SCR_SZ) pos = 0; }

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

COLS |= 0x3F; // так гасим все знакоместа COLS &= ~(1<

Оба оператора не изменяют значение старших битов порта COLS .

3.2 Способ с дешифратором

Дешифратор может использоваться либо для преобразования HEX или BCD кода в семисегментные символы, либо для выбора одного из столбцов матрицы. Оба варианта будут отличаться от рассмотренного ранее простейшего способа лишь тем, как будет организован вывод в порты ROWS и/или COLS , к которым будут подключены входы дешифратора.
Вариант использования дешифратора для получения семисегментного символа:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; COLS |= 0x3F; ROWS = (ROWS & 0xF0) | (SCR & 0x0F); COLS &= ~(1 << pos); if(++pos == SCR_SZ) pos = 0; }

Как видите, изменения минимальны - перед тем, как вывести в ROWS код символа из массива SCR , маскируются старшие биты, после чего младшие устанавливаются в соответствии с кодом символа. То есть мы считаем, что дешифратор подключен к 4-ем младшим битам порта ROWS .

Приводить пример для дешифрации столбцов, надеюсь, смысла нет - все и так понятно.

3.3 Способ с регистрами

Хотя динамическая индикация при помощи сдвиговых регистров принципиально не отличается от ранее рассмотренных способов, есть несколько вариантов ее реализации. Мы рассмотрим наиболее простой - вывод битов чисто программными средствами. А в реализации других (с использованием USI /USART /SPI /TWI ) вы можете попробовать свои силы самостоятельно.

Для варианта ранее выбранного дисплея из 6-и семисегментных знакомест мы используем 2 сдвиговых регистра типа 74HC595 . Управляется этот регистр тремя сигналами: тактовые импульсы последовательного ввода данных CLK , собственно данные DATA и импульс одновременного параллельного вывода записанной в регистр информации SET . Объявим соответствующие макросы (для простоты, все сигналы «заведем» на один порт):

#define CLK _BV(PB0) #define DATA _BV(PB1) #define SET _BV(PB2) #define REG_PORT PORTB

Для записи в регистр удобно написать отдельную функцию:

Static void shift(unsigned char d){ unsigned char i; for (i=0; i < 8; i++){ // устанавливаем нужный уровень DATA if(d & 1) REG_PORT |= DATA; else REG_PORT &= ~DATA; REG_PORT |= CLK; // даем импульс CLK REG_PORT &= ~CLK; d >>= 1; } }

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

Теперь наш обработчик прерывания станет выглядеть так:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; shift(SCR); shift(~(1<< pos)); REG_PORT |= SET; // выдаем импульс SET REG_PORT &= ~SET; if(++pos == SCR_SZ) pos = 0; }

Так как записанные в регистры данные появляются на его выходах одновременно, нет нужды предварительно гасить индикаторы. Следует помнить, что программный последовательный вывод - достаточно долгий процесс, особенно для матриц больших размерностей, поэтому следует максимально оптимизировать его по быстродействию. Лучше всего это можно сделать, используя аппаратные средства последовательного вывода, имеющиеся в МК.

4 Для тех, кому всегда всего мало

Итак, вы познакомились с основами реализации динамической индикации. Но, как обычно, вопросов не убавляется, а прибавляется. Предвидя некоторые из них, попробую сразу же дать нужные ответы.

4.1 Аноды, катоды - что выбрать?

Все, что мы рассматривали ранее, относилось к индикаторам с общими катодами. А если требуется использовать с общими анодами? В общем, все остается по-прежнему, кроме того, что перед выводом нужно будет проинвертировать данные - гашение знакоместа осуществлять выводом нулей в COLS , зажигание - соответственно, единиц, а сегменты в ROWS будут включаться нулями вместо единиц. Так что обработчик прерывания станет примерно таким:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; COLS &= 0xC0; ROWS = ~SCR; COLS |= (1 << pos); if(++pos == SCR_SZ) pos = 0; }

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

#define COMMON_ANODE 1 ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; #if COMMON_ANODE != 1 COLS &= 0xC0; ROWS = ~SCR; COLS |= (1 << pos); #else COLS |= 0x3F; ROWS = SCR; COLS &= ~(1 << pos); #endif if(++pos == SCR_SZ) pos = 0; }

Это хоть и немного громоздко, зато, написав это один раз, вы сможете использовать во всех проектах практически без изменений.

4.2 Мерцание

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

Сделать это очень просто. Введем глобальную переменную, каждый единичный бит которой будет обозначать мигающее знакоместо:

unsigned char blink = 0;

Теперь немного модифицируем обработчик прерывания:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; static unsigned char entry = 0; COLS |= 0x3F; if(!(blink & (1<

Как видите, добавлена всего одна статическая переменная - счетчик входов в обработчик прерывания entry , и оператор проверки условия. Логика проста: вывод очередного знакоместа осуществляется лишь в том случае, если либо в соответствующем бите blink ноль, либо старший бит счетчика entry равен 1. Если, предположим, blink содержит все нули, то данное условие выполняется всегда - выводятся все знакоместа. Если же blink содержит 1 в одном из своих битов, то соответствующее знакоместо будет высвечиваться лишь в то время, когда старший бит счетчика равен 1. Так как счетчик увеличивается каждый раз при входе в обработчик прерываний, то соответствующее знакоместо будет мерцать с частотой в 128 раз меньшей, чем частота прерываний.

4.3 Регулировка яркости сегментов

О регулировке яркости я писал в отдельной статье, которая так и называлась .

4.4 Произвольное распределение выводов

Ранее говорилось, что счастье выделить порт МК целиком под индикацию, выпадает довольно редко. Но еще реже выпадает счастье получить удобную трассировку печатной платы, если использован один порт целиком под строки, а другой порт - под столбцы матрицы дисплея. Гораздо чаще оптимальная трассировка получается лишь в том случае, когда строки и столбцы перемешаны между двумя, а то и более портами микроконтроллера. Жертвовать красотой печатной платы не придется, если организовать программную перестановку битов при индикации.

Рассмотрим некий абстрактный пример. Пусть наилучшая трассировка обеспечивается при следующем распределении сигналов по линиям портов МК:

Сегмент А

Сегмент В

Сегмент H

Сегмент С

Сегмент D

Сегмент G

Сегмент E

Сегмент F

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

Разработку функции динамической индикации для этого случая лучше всего начать с распределения сегментов по битам символа. Раньше мы считали, что в массиве SCR у нас хранятся битовые маски символов, т.е. единичками в байте обозначены светящиеся сегменты. О том, какой бит какому сегменту соответствует, мы не задумывались. Так вот, сейчас пора задуматься об этом.

Удобно разрисовать назначение линий портовв виде трех табличек:

1

A

0

4

H

3

2

B

F

E

5

G

D

C

Мы должны собрать все сегменты в один байт. Делать это придется операциями сдвига, поэтому следует постараться распределить их так, чтобы делать минимум сдвигов. Будем рассуждать.

Если биты сегментов FEGDC оставить в символе так, чтобы они попадали в PORTD без сдвигов, тогда сегмент H так же может остаться в 6-ом бите символа, и его тоже не придется сдвигать перед выводом PORTC , зато для сегментов А и В останутся биты 7 и 3, то есть скорее всего сегмент В придется сдвигать на 3 позиции перед выводом, а сегмент А - на 6. Я остановлюсь на этом варианте, а вы можете продолжить поиск минимума сдвигов (сдвиги на несколько позиций - не такая уж быстрая операция, поэтому желательно свести их число к минимуму).

Итак, в нашем случае получилось такое распределение битов по байту-символу:

A

H

F

E

B

G

D

C

Отметим маски битов для вывода в соответствующие порты:

D

0

0

1

1

0

1

1

1

0x37

B

1

0

0

0

0

0

0

0

0x80

C

0

1

0

0

1

0

0

0

0x48

При помощи этих масок операцией «побитовое И» мы выделим нужные биты для вывода в порт.

Объявим константы масок:

#define MASKD 0x37 #define MASKB 0x80 #define MASKC 0x48

Ранее мы выводили символ в единственный порт ROWS , теперь же эта процедура разделится на три части:

PORTD = (PORTD & ~MASKD) | (SCR & MASKD); PORTB = (PORTB & ~MASKB) | ((SCR & MASKB) >> 6); PORTC = (PORTC & ~MASKC) | ((SCR & _BV(6)) | (((SCR & _BV(3)) >> 3);

Обратите внимание, что для вывода в PORTC один бит надо выводить без сдвига, а второй - со сдвигом, поэтому вместо MASKC пришлось использовать отдельные макросы _BV() .

Теперь осталось решить, как гасить и зажигать соответствующие знакоместа. Объявим константы, соответствующие битам управления знакоместами:

#define COM0 _BV(0) #define COM1 _BV(3) #define COM2 _BV(4) #define COM3 _BV(5) #define COM4 _BV(7) #define COM5 _BV(3) #define COM_D (COM5) #define COM_C (COM2 | COM3 | COM4) #define COM_B (COM0 | COM1)

Для гашения всех знакомест надо вывести в порты соответствующие константы COM_x :

PORTD |= COM_D; PORTC |= COM_C; PORTB |= COM_B;

А вот для включения знакоместа придется мудрить (нет смысла осуществлять вывод во все три порта, ведь активным будет только один-единственный бит в определенном порту в зависимости от значения pos ), например, при помощи оператора switch :

Switch(pos){ case 0: PORTB &= ~COM0; break; case 1: PORTB &= ~COM1; break; case 2: PORTC &= ~COM2; break; case 3: PORTC &= ~COM3; break; case 4: PORTC &= ~COM4; break; case 5: PORTD &= ~COM5; break; }

Это не самый красивый способ, но он работает.

Таким образом, наш обработчик прерывания приобретает следующий вид:

ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; static unsigned char entry = 0; // гасим PORTD |= COM_D; PORTC |= COM_C; PORTB |= COM_B; // выводим PORTD = (PORTD & ~MASKD) | (SCR & MASKD); PORTB = (PORTB & ~MASKB) | ((SCR & MASKB) >> 6); PORTC = (PORTC & ~MASKC) | ((SCR & _BV(6)) | (((SCR & _BV(3)) >> 3); // мигаем if(!(blink & (1<< pos)) || (++entry & 0x80)) { switch(pos){ case 0: PORTB &= ~COM0; break; case 1: PORTB &= ~COM1; break; case 2: PORTC &= ~COM2; break; case 3: PORTC &= ~COM3; break; case 4: PORTC &= ~COM4; break; case 5: PORTD &= ~COM5; break; } } if(++pos == SCR_SZ) pos = 0; }

Теперь осталось разобраться, как же поудобнее описать символы для вывода... Я предлагаю поступить следующим образом: определить константы, соответствующие битам сегментов, а затем из этих констант «сконструировать» нужные символы:

// элементарные сегменты #define _A _BV(7) #define _B _BV(3) #define _C _BV(0) #define _D _BV(1) #define _E _BV(4) #define _F _BV(5) #define _G _BV(2) #define _H _BV(6) // символы цифр #define d_0 (_A | _B | _C | _D | _E | _F) #define d_1 (_B | _C) #define d_2 (_A | _B | _G | _D | _E) // и так далее

Таким образом, если вам понадобится вывести в крайнюю правую позицию дисплея нолик, вы всего-навсего должны написать в нужном месте:

SCR = d_0;

Если в другом проекте вам понадобится иначе распределить биты, вы поменяете только цифры в макросах _BV() для элементарных сегментов, и все символы «переделаются» автоматически. Для описанных в начале простейших случаев не придется больше ничего делать вообще, а для варианта с «перестановкой битов», придется, конечно повозиться.

4.5 Поддержка кнопок

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

Схемотехнически это показано на рисунке.

А программно выглядит следующим образом:

#define keypin() (!(PIND & _BV(KEY))) ISR(TIMER0_OVF_vect){ static unsigned char pos = 0; static unsigned char entry = 0; static unsigned char tmp_key = 0; ROWS = 0; if(keypin()) tmp_key |= 1<< pos; COLS |= 0x3F; if(!(blink & (1<< pos)) || (++entry &0x80)){ ROWS = (ROWS & 0xF0) | (SCR & 0x0F); COLS &= ~(1 << pos); } if(++pos == SCR_SZ){ pos = 0; key = tmp_key; tmp_key = 0; } }

Здесь KEY - это макрос, задающий бит выбранного порта, на котором «соединяются» все кнопки, макрос keypin() возвращает логическое значение ИСТИНА, если на выбранном пине присутствует низкий логический уровень. В примере кнопки подключены к PORTD .

Каждый раз, когда возникает прерывание таймера, сначала гасятся все сегменты - это необходимо для того, чтобы ток через светодиоды не приводил к ошибочному не обнаружению нажатой кнопки. После этого происходит опрос кнопочного входа - если там низкий уровень, значит, нажата кнопка, подключенная к соответствующему pos катоду. В переменной tmp_key накапливаются состояния кнопок, которые переписываются в глобальную переменную key после завершения цикла индикации. Вам остается лишь время от времени анализировать значение key и обрабатывать обнаруженные нажатия:

Static unsigned char get_key(){ unsigned char tmp = 0; tmp = key; _delay_ms(10); if(key == tmp) return tmp; else return 0; }

Эта несложная функция гарантирует отсутствие дребезга кнопок, не смотря на то, что из-за «динамического» характера опроса кнопок вероятность дребезга и без того низкая.

5 Что еще?

Итак, вы освоили достаточно характерные приемы реализации динамической индикации. Думаю, этого хватит вам на первое время, а может, и вообще на всю оставшуюся жизнь. В конце концов, главное - это понимание приемов и алгоритмов, а тонкости и нюансы всегда можно прибавить самостоятельно. Но что же еще может ждать «вблизи» динамической индикации?

Как я уже говорил ранее, можно добавить , причем вплоть до независимого регулирования каждого сегмента.

Можно подумать над оптимальностью обработчика прерывания - в учебных целях я писал достаточно грубый код, например, всюду использовал SCR , хотя оптимальнее было бы один раз считать значение в локальную переменнную, а потом оперировать уже с ее значением. Хотя оптимизатор наверняка выручит и с моим подходом, в целях практики стоит попробовать и самостоятельно пооптимизировать, контролируя себя по размеру получающегося кода и/или быстродействию программы.

Можно помозговать над интересной идеей автоматического регулирования яркости дисплея в зависимости от уровня внешней освещенности. Как известно, светодиодные индикаторы тем хуже различимы, чем темнее вокруг - они просто расплываются. Поэтому в темное время суток разумно снижать яркость индикаторов, повышаяя ее в светолое время. Самое простое - использовать отдельный фоторезистор или светодиод в качестве датчика освещенности, но можно поступить и иначе: известно, что светодиод может работать и в качестве фотодиода, поэтому, если для индикации задействовать порт, соединеннный с входом АЦП , то при определенном желании можно измерить фото-эдс несветящегося сегмента индикатора, и использовать это значение для регулировки яркости...

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

Интересный вариант полностью универсального подхода к динамической индикации, с которым так же рекомендую познакомиться, предложил MOLCHEC . Кратко суть: распределение сегментов по битам символа, назначение портов для управления индикатором и даже тип индикатора - короче говоря, все-все-все параметры, - задаются в виде таблицы конфигурации в EEPROM . Программно на основе этой таблицы организуется все: от инверсии в зависимости типа индикатора, до перестановки битов по разным портам. При этом исходный код программы динамической индикации остается неизменным всегда, а таблица конфигурации составляется конечным пользователем в зависимости от своих предпочтений. Метод действиетльно универсальный и гибкий, однако сопряжен с повышенным расходом памяти программ.


3 Написал(а) ARV , в 06:48 25.08.2010
Миша , я бы на вашем месте не стал бы давать такие безапелляционные заявления "не можете сделать", "никто не написал" или "копирайтят", потому что это во-первых, не вежливо, а во-вторых:
1. я давно сделал бегущую строку на матрице 10х16 (уж какая была) - вы можете найти видео ее работы в этой заметке http://сайт/content/view/160/38/
2. я написал статью (вы ее найдете в новостях - последняя на сегодня) о том, как сделать бегущую строку на ЖКИ. если немного напрячь мозг, то переделать алгоритм для вывода на матрицу - пустячок.
3. на моем сайте нет ни одной статьи, откуда-то скопированной (копипаст - не копирайт, вы опечатались), все материалы полностью оригинальные. на многих сайтах есть копии этих материалов с моего разрешения (или разрешения авторов материалов, которые имеют полное право публиковать свои материалы сразу во многих местах).

Только зарегистрированные пользователи могут оставлять коментарии.
Пожалуйста зарегистрируйтесь или войдите в ваш аккаунт.