HEX-дешифратор для 2-значного 7-сегментного LED-индикатора на одном(!) GAL16V8

756de4ed83ffccda653a669dbefaf297.jpg

Те, кто увлекается ретрокомпьютингом в области самостоятельной сборки компьютеров на базе 8-битных процессоров (i8080/i8085/z80/z180/6502/6809 и т.п.) или микроконтроллеров, обычно сталкиваются с необходимостью отображения в процессе отладки и/или «эксплуатации» какой-либо информации (содержимого шин адреса, данных и др.) на 7-сегментных индикаторах в шестнадцатеричном представлении.

В принципе, задача отображения двух шестнадцатеричных разрядов с лёгкостью решается парой «умных» индикаторов TIL311. Эти хорошо известные индикаторы, разработанные компанией Texas Instruments задолго до того, как вымерли динозавры, ещё производятся и их можно найти на Aliexpress.

Несмотря на удобство использования, эти индикаторы имеют и существенные недостатки, а именно:

  • Относительная дороговизна — даже на Aliexpress эти индикаторы стоят совсем не дёшево (по сравнению с обычными 7-сегментными индикаторами) и их не станешь покупать впрок по принципу «авось пригодятся»;

  • Отсутствие в шаговой доступности — их надо специально заказывать и ждать, т.е. совсем не тот случай, когда нужно что-то смакетировать на быструю руку;

  • Они существуют только в красном свечении — не самый важный недостаток, но иногда может иметь существенное значение.

Для увлечений в области ретрокомпьютинга дешёвой и доступной альтернативой является использование обычных, везде и всюду распространённых, 7-сегментных LED-индикаторов любого цвета и размера с дополнительным дешифратором. На роль компактного дешифратора лучше всего, конечно, подходят простые микросхемы программируемой логики (напр. GAL16V8) — эти микросхемы всё ещё распространены, достаточно дёшевы и в силу своей простоты (и при этом гибкости) необычайно полезны, что является достаточным основанием для того, чтобы иметь их в хозяйстве хотя бы десяток.

Конечно же использовать 1 микросхему GAL16V8 в качестве дешифратора 1-го цифрового 16-ричного разряда слишком расточительно, поэтому пришлось спроектировать логику дешифратора сразу двух 16-ричных разрядов на одной микросхеме GAL16V8 (готовых решений подобного типа в сети я не нашёл).

Часть 1: А так можно?

febbaa06aea84ac9d6975c41f3d6fa1f.jpg

Составим функциональную таблицу соответствия активных/неактивных сегментов стандартного 7-сегментного LED-индикатора соответствующим значениям отображаемых нибблов — колонке A в таблице соответствует самый старший разряд ниббла, колонке D соответствует самый младший разряд ниббла, активные сегменты индикатора залиты зелёным, неактивные — жёлтым. 

Фактически, таким образом мы задали таблицы истинности семи (по числу сегментов индикатора) разных булевых функций, зависящих от 4-х аргументов — ƒ(A, B, C, D):

54c136d8f1d756ea440114190c7b6a05.jpg

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

Так, например, для сегмента a склеиваемые по единицам термы могут иметь 3 цвета (салатовый, зелёный и болотный) и 4 вида границы (двойную '═══', сплошную толстую '───',  прерывистую '−−−' и штрих-пунктирную '−∙−∙').

Справа от карты Карно каждого сегмента отображён результат минимизации, т.е. минимальная дизъюнктивная нормальная форма (ДНФ) булевой функции соответствующего сегмента. Минимизированные термы сложены вертикально, с отображением той группировки (цвета или границы), по которой производилась минимизация. 

Здесь и далее в выражениях ДНФ используются следующие обозначения:

a-g — булевы функции четырёх переменных F (A, B, C, D),
* — операция логического 'И' (логическое умножение, конъюнкция),
+ — операция логического 'ИЛИ' (логическое сложение, дизъюнкция),  
\ — операция логического 'НЕ' (логическое отрицание)

Остальные сегменты под спойлером

Эти результаты хорошо и многократно описаны в сети и позволяют легко реализовать на GAL16V8 управление одним 7-сегментным индикатором. Однако управлять двумя 7-сегментными индикаторами одной микросхемой GAL16V8 эти результаты не помогут. Почему?

60e6be3316b5345a640efa79f6ce69ba.jpg

Если ответ на вопрос »почему с помощью этих функций нельзя управлять двумя индикаторами одним GAL16V8? » понятен, то можно листать вниз до 2-ой части, где я рассказываю как надо сделать, чтобы было можно. А сейчас чуть подробнее о том, почему это нельзя.

Зачем нужна минимизация?

Для начала выясним — а зачем вообще нужна вся эта суета с картами Карно и минимизацией булевых функций?

Вернёмся к исходной таблице соответствия активности сегментов индикатора отображаемым шестнадцатеричным цифрам. Для примера рассмотрим колонку сегмента 'e'.
Видно, что этот сегмент должен загораться в 10 случаях из 16-ти — когда на входе цифра 0 или 2 или 6 или 8 илиA или B или C или D или E или F.

Если в двоичном коде, то это 0000 или 0010 или 0110 или 1000 или 1010 или 1011 или 1100 или 1101 или 1110 или 1111.

Ну, а в виде булевой функции то же самое очевидным образом выглядит как

e(A, B, C, D) = \A*\B*\C*\D + \A*\B*C*\D + \A*B*C*\D + A*\B*\C*\D +
A*\B*C*\D + A*\B*C*D + A*B*\C*\D + A*B*\C*D + A*B*C*\D + A*B*C*D

Логические слагаемые (т.е. выражения, связанные операцией ИЛИ) в булевой функции называются термами.  Переменные в терме связаны операцией И. Такая форма записи булевых функций называется дизъюнктивной нормальной формой (ДНФ). Вспомним, что после минимизации та же самая булева функция содержит уже не 10, а всего 4 терма:

e(A, B, C, D) = \B*\D + C*\D +  A*B + A*C*D

Посмотрим теперь как устроены микросхемы программируемой логики на примере гипотетической микросхемы GAL4V1 (в обозначении, как и у всамделишных GAL-ов, первое число обозначает максимально возможное число входов, второе — максимально возможное количество выходов):  

Упрощённое устройство микросхем GAL на примере гипотетической микросхемы GAL4V1Упрощённое устройство микросхем GAL на примере гипотетической микросхемы GAL4V1

Микросхема GAL4V1 состоит из:

  • входного каскада, формирующего с помощью инверторов для каждого входного сигнала 'X' его логическое отрицание '\X';

  • матрицы программируемых перемычек, позволяющих формировать термы (максимум 5 термов) из любых комбинаций входных сигналов, соединённых операцией 'И';

  • дизъюнктора, объединяющего полученные термы операцией 'ИЛИ';

  • выходного каскада, позволяющего с помощью программируемого инвертора на базе элемента 'Исключающее ИЛИ' (eXclusive OR — XOR) формировать прямой или инвертированный результат.

Очевидно, что с точки зрения схемотехники эта микросхема предлагает реализацию булевых функций, представленных в дизъюнктивных нормальных формах. Красные перемычки показывают, что эта микросхема уже кем-то запрограммирована на реализацию ДНФ рассматриваемой нами булевой функции e (A, B, C, D) в её минимизированном варианте (см. спойлер выше).

Любые ли булевы функции, заданные в ДНФ, можно реализовать микросхемой GAL4V1?  Глядя на количество входов (4), выходов (1) и на количество объединяемых дизъюнктором термов (5), можно сформулировать правильный ответ — любую одну, при условии, что функция зависит не более чем от 4-х переменных и количество термов в ДНФ функции не превышает 5-и.

Не будь дизъюнктивная нормальная форма функции e(A, B, C, D) предварительно минимизирована, «засувать» её в эту микросхему не получилось бы, а булеву функцию сегмента 'a' на этой микросхеме не реализовать даже в её минимальной ДНФ (7 термов).
Впрочем, не стоит пока торопиться с окончательными выводами… 

А вот вопрос о необходимости минимизации ДНФ полагаю можно считать закрытым))

Немного о реальных GAL-ах

Сильно ли отличаются всамделишные GAL-ы от рассмотренного гипотетического GAL4V1? С точки зрения способа реализации булевых функций — отличий нет, но реальные GAL-ы имеют дополнительные возможности и/или особенности:

  • Больше максимальное количество выходов, т.е. булевых функций, которые может реализовать микросхема (до 8 выходов у GAL16V8 и GAL20V8, до 10 выходов у GAL18V10 и GAL22V10, до 12 выходов у GAL26V12 и GAL26CV12);

  • Большее количество термов на каждый выход (7–8 у GAL16V8 и GAL20V8, 8–10 у GAL18V10, 8–16 у GAL22V10 и GAL26v12, 8–12 у GAL26CV12);

  • Больше максимальное количество входов, т.е. независимых переменных, из которых можно формировать термы или использовать в других целях (до 16 у GAL16V8, до 18 у GAL18V10, до 20 у GAL20V8, до 22 у GAL22V10 и до 26 у GAL26V12 и GAL26СV12);

  • Возможность программирования некоторых (или всех) выходов на работу в режимах «только выход», «выход-вход» или «вход» (таким способом за счёт ненужных выходов увеличивается количество входов);

  • Возможность перевода выводов в состояние высокого импеданса.

Принципиальным же отличием реальных микросхем GAL является наличие у каждого выхода 1-битного регистра памяти (D-триггера), которые можно активировать/деактивировать индивидуально для каждого выхода в определённом режиме (GAL16V8 и GAL20V8) или без каких-либо особых режимов (GAL18V10, GAL22V10 и GAL26V12/GAL26CV12). 

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

Совокупность 1-битного регистра, дизъюнктора и коммутационной логики выходного каскада называется у GAL-ов «макроячейкой выходной логики» (OLMC — Output Logic MacroCell).

Опять GAL4V1

Пусть на выходе Y необходимо отображать состояние входа A (т.е. просто сделать повторитель сигнала). Булева функция тривиальна и содержит 1 терм:

Y (A) = A

Теперь пусть на выходе Y необходимо отображать состояние входа A только в том случае, если на разрешающем входе B присутствует сигнал низкого уровня:

Y (A, B) = A * \B

Эта функция хоть и зависит теперь от 2-х переменных, но всё ещё содержит 1 терм.

Чуть усложним задачу — пусть на выходе Y необходимо отображать состояние входа A в том и только в том случае, если на разрешающем входе B присутствует разрешающий сигнал высокого уровня, а на разрешающем входе C сигнал низкого уровня:

Y (A, B, C) = A * B * \C

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

А вот теперь попробуем мультиплексировать входные сигналы на один и тот же выход:  если на управляющем входе C ноль, то необходимо на выход Y передавать информацию со входа A, а если на входе C единица, то на выход Y передавать информацию со входа B:

Y (A, B, C) = A * \C + B * C

Получился простейший мультиплексор сигналов. Булева функция также зависит от 3-х переменных, но для нас здесь важно то, что мультиплексирование 2-х сигналов требует в 2 (два) раза больше термов, чем передача 1-го сигнала. Почему? А потому, что появилась альтернатива ИЛИ — «или этот вариант или тот».

Зачем мультиплексировать?

Для управления (включения/выключения) каждого сегмента 7-сегментного индикатора требуется отдельный выход дешифратора. Для управления двумя индикаторами без использования мультиплексирования потребовалось бы 7+7=14 управляющих выходов (а если нужно использовать и индикаторы точек, разделяющих разряды, то и все 16). 

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

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

А что же GAL16V8?

Даташит на GAL16V8 объясняет, что все макроячейки выходной логики (т.е. совокупность дизъюнктора, 1-битного регистра памяти и коммутационной логики на каждом выходе) могут работать в 3-х разных режимах, т.н. Registered, Complex и Simple. Опуская несущественные здесь детали, разницу между ними можно описать следующим образом.

В режиме Simple каждый выход (а их может быть 8) аналогичен рассмотренной ранее гипотетической GAL4V1, но дизъюнктор каждого выхода объединяет уже 8 термов и количество входных переменных — не менее 10. Возможность перевода выходов в состояние высокого импеданса в этом режиме отсутствует.

В режиме Complex выходы работают подобно режиму Simple, но появляется возможность переводить их в состояние высокого импеданса сигналом Output Enable (\OE), формируемым с помощью отдельного терма. Общее количество термов на каждом дизъюнкторе не может превышать 7 термов. Количество независимых входных переменных — не менее 10.

В режиме Registered на каждом (любом или всех сразу) выходе между программируемым XOR-инвертором и выходным буфером активируется 1-битный синхронный регистр (D-триггер), запоминающий состояние выходного сигнала по фронту сигнала CLK, общему для всех триггеров и подаваемому на выделенный в этом режиме контакт 1 микросхемы. Есть возможность переводить регистровые выходы в состояние высокого импеданса сигналом \OE, подаваемым на выделенный в этом режиме контакт 11 микросхемы. Максимальное количество термов на регистровых дизъюнкторах — 8. Если выход не использует регистр, то работает подобно режиму Complex и имеет не более 7 термов. Количество независимых входных переменных — не менее 8.

И какой из этого вывод?

Любой из этих режимов позволяет построить на GAL16V8 дешифратор одной шестнадцатеричной цифры, т.к. количество термов в булевых функциях сегментов находится в пределах от 4 (сегмент 'e') до 7 (сегмент 'a') и не превышает возможностей любого из режимов.

Возвращаясь к теме данной статьи, т.е. к управлению двумя 7-сегментными индикаторами одним GAL16V8, становится понятным, что если для декодирования и отображения одной шестнадцатеричной цифры может потребоваться до 7 термов (в случае сегмента 'a'), то для декодирования и отображения 2-х шестнадцатеричных цифр в режиме мультиплексирования выходов потребуется в 2 раза больше термов.

36e9047221b622d9ab607deb651ca507.jpg

Собственно, на этом можно закончить разъяснение того,  почему с помощью даже минимизированных ДНФ булевых функций, описанных в начале статьи и реализующих дешифратор одного 7-сегментного индикатора,  нельзя построить дешифратор двух 7-сегментных индикаторов на одной микросхеме GAL16V8.

Оставшаяся часть статьи объясняет как можно построить дешифратор двух 7-сегментных индикаторов на одной микросхеме GAL16V8

Часть 2: Вот так — можно!

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

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

1a9636799da228efa72cbc434abac43f.jpg

Вспомним, что в начале 1-ой части статьи минимизация ДНФ производилась по единицам, т.е. по тем входным термам, значение которых дает логическую единицу на выходе сегмента.

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

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

Проведём минимизацию обратных функций каждого сегмента с помощью тех же карт Карно. Для обозначения «склеиваемых» термов по нулям я использовал уже не различные цвета и типы границ, а выделение жирным и два вида перечёркивания ячеек ('\' и '/'). Одиночные, ни с кем не «склеиваемые» нули я никак не выделял :  

8b00f0c1f2a057d7624a58c0a0cb0b17.jpgОстальные сегменты под спойлером

Как видно, количество термов в любой из 7-ми обратных функций \a(A, B, C, D), \b(A, B, C, D), …, \g(A, B, C, D) не превышает 4-х, что нам и требовалось!

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

Пора проверить результат на практике.

Программируем GAL16V8D

В качестве иллюстрации к этой статье нарисовал простую схему — набираем любые 2 шестнадцатеричные цифры в двоичном коде на DIP-переключателе и тут же видим эти цифры на индикаторе:

54d5ffc3f71cb4f782b2a0bc03283c73.jpg

Простейший генератор меандра — на обычном 555 (особая точность здесь не нужна). Транзисторы работают в режиме ключа — подойдут любые PNP с допустимым током коллектора не менее 100 мА. Индикаторы — любые 7-сегментные с общим анодом.

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

Можно было бы задействовать XOR-инверторы выходных каскадов GAL16V8 и выдавать единицы на LED-индикаторы с общим катодом, но выходные буферы GAL16V8D имеют небольшую нагрузочную способность по току логической единицы, зато более чем достаточную по току логического нуля:

cb92ed374cb9f28194bfcbff36f670e1.jpg

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

До установки 2-значного LED-индикатора собранная на короткой макетке схема выглядела вот так (для генератора 200Гц-меандра не хватило всего 4-х рядов, пришлось размещать на отдельной макетке):

33b093cb1c23341d89061fe5eb40f97b.jpg

Код для прошивки GAL16V8 набирался и компилировался в WinCUPL, соответственно и синтаксис такой:

be18468625870f015d0c932338554fa0.jpg

Я постарался добавить комментарии (к сожалению, кириллицу WinCUPL не всегда любит), поэтому хочется думать что после прочтения всего предыдущего текста код должен быть очевидным…

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

Тут дело в следующем — в связи с тем, что используется режим динамической индикации (т.е. когда цифры светятся поочерёдно, в свои определённые промежутки времени), то в общем случае, когда цифр может быть больше 2-х, для указания периода свечения (и декодирования) конкретной цифры сигнал EN_i должен быть таким же прямоугольным входным сигналом требуемой скважности, как и EN_j, но с другой фазой :

c7d37f038dd319546a6fbe1864405579.jpg

В данном же конкретном случае, когда цифр только две и они декодируются/светятся поочерёдно, можно генерировать второй меандр для EN_L из меандра EN_H в противофазе силами самого GAL16V8, что и делается оператором «EN_L  =  ! EN_H» :  

eb5837e05c89053b5cff71df2b3c3d3b.jpg

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

Для прошивки в GAL16V8D JED-файла конфигурации (получившегося в результате компиляции исходного кода) использовался широкоизвестный программатор MiniPro TL866A.

Ну и да — разумеется, всё работает так, как и должно — один GAL16V8 работает дешифратором для двух 7-сегментных индикаторов:

28bf89fe3a6e3f419c6d22a449430d00.jpg

В процессе создания этой статьи я наткнулся на хорошую публикацию на Хабре — PAL, GAL и путешествие в цифровое ретро / Хабр (habr.com) и подумал, что мой кейс может быть интересен тем, кто хочет разобраться как оно на самом деле устроено и кто не считает, что булки растут в магазине, вода возникает из крана, а электричество из розетки.

© Habrahabr.ru