В последнее время мне всё больше нравится собирать 8-битные компьютеры из ТТЛ чипов. Я вдохновился замечательными роликами Бена Итера с ютуба, а также различными проектами с сайта Hackaday. В процессе проектирования и сборки собственного 8-битного компьютера я задумался над тем, насколько сложно было бы реализовать UART-трансивер на базе ИС серии 7400.
Итоговая схема: готовый UART-трансивер, собранный из ИС серии 7400
В первую очередь разберёмся, что такое UART. Это универсальный асинхронный приёмопередатчик – простой протокол, позволяющий асинхронно отправлять и получать 8-битные данные, благодаря чему процессор или компьютер могут общаться с внешним миром. Это и само по себе полезно – мой 8-битный компьютер может общаться с ноутбуком и использовать программу для мониторинга последовательного порта (типа putty) в качестве интерфейса для ввода и вывода текста. Что ещё интереснее, я могу запрограммировать загрузчик ОС для своего 8-битного компьютера, а потом программировать его через UART соединение с ноутбука! Поскольку Bluetooth модули типа HC-05, по сути, общаются с CPU через UART, я даже могу использовать Bluetooth модуль для программирования своего 8-битного компьютера на расстоянии! Это было бы очень круто.
Некоторые пуристы сочли бы программирование 8-битного компьютера при помощи гораздо более мощного компьютера мошенническим подходом – но это мой проект, и он живёт по моим правилам! Пожалуйста, программируйте собранную вручную машину при помощи DIP-переключателей, если вам больше нравится вводить данные, чем программировать, и хочется получить аутентичный опыт тяжёлой работы.
Как бы там ни было с программированием, я, по крайней мере, решил ограничить себя при разработке компьютера простыми ТТЛ чипами – никаких Arduino, Raspberry Pi, ESP8266 и иных полных по Тьюрингу модулей (иначе в чём был бы интерес?).
Перед вами – структура сигнала UART. У него есть начальный бит, обозначаемый переходом с высокого на низкий уровень сигнала, за которым идёт байт данных (LSB first), а потом бит остановки, переводящий сигнал на высокий уровень. Иногда есть ещё и бит чётности, однако он не обязателен, поэтому я его опустил из соображений простоты. Время передачи каждого бита определяется скоростью передачи в бодах (в данном случае – в битах в секунду). К примеру, скорость 9600 бод означает, что бит передаётся за
1/9600 = 104 мкс. Форма сигнала довольно простая, поэтому мы можем реализовать её полностью в железе на логических чипах.
Мне нужно было выбрать кварцевый генератор, дающий мне доступ к стандартным скоростям передачи в бодах, предпочтительно делящихся на степени двойки, чтобы с ним было удобно работать при помощи двоичного счётчика. Подумав, я решил использовать генератор на 2,4576 МГц, поскольку он позволял передавать на 38400 б/с (делением на 64), или 9600 б/с (делением на 256).
Список компонентов:
Схему работы UART передатчика понять проще всего. По сути, это регистр сдвига с параллельной загрузкой и последовательным выходом. Он загружает байт данных, отслеживает начальные и конечные биты, и синхронизирует его с желаемой скоростью передачи. На схеме ниже показан этот процесс. В части (1) кварцевый генератор на 2,4576 МГц замедляется до 38 400 Гц при помощи двух 4-битных счётчиков 74LS161. В части (2) 16-битный сдвиговый регистр 74LS674 используется для синхронизации данных для UART. Я использую этот регистр потому, что он уже был у меня под рукой. Понимаю, что эта ИС дороговата, и её, возможно, трудно найти, но она определённо упростила всю мою схему.
При помощи всего трёх таких ИС (двух 4-битных счётчиков и регистра сдвига) можно выдавать непрерывный поток символов на передатчике UART со скоростью 38 400 б/с (без бита чётности)! Да-да, именно непрерывный поток – я не учёл, что сдвиговый регистр обновляет буфер загрузки по кругу – упс. Такое поведение мне не было нужно – я хотел, чтобы процессор отправлял по байту за раз. Всё усложняется тем, что тактовые импульсы процессора и UART не синхронизированы, и мне не хотелось строить предположения о том, чей таймер работает быстрее, какой сигнал в какой момент будет актуальным, и т.п. Поскольку мне нужно было надёжно обрабатывать асинхронность, я решил использовать следующую, неплохо работающую схему:
Обратите внимание, что я сдвигаю 16 бит вместо 10 бит сигнала передатчика UART – в основном из-за удобства использования бита переноса для отключения передающей схемы. Я мог использовать и десятичный счётчик (к примеру, 74LS162), однако у меня не было такого под рукой, когда я собирал схему на макетной плате. Возможно, в окончательной схеме я на него перейду.
Список компонентов:
Мне кажется, что если описанный выше UART передатчик понять легко, то приёмник будет несколько более сложным. Однако что хорошо с цифровой логикой – её можно разбить на отдельные модули, и тогда всё уже не кажется таким сложным!
Формы сигнала в левом нижнем углу схемы, приведённой ниже, показывают, что нужно учесть при приёме единственного цифрового бита передатчика. Как мы определим, что нам отправляют байт? Легко – стартовый бит обозначен переходом от высокого уровня сигнала к низкому, так что мы можем это инвертировать и использовать переход от низкого уровня к высокому для установки D-триггера (74LS74) (2).
Теперь нам нужно начать записывать сигнал, сдвигая его в сдвиговые регистры и делая выборку в центре последовательности битов данных. Что важно понять: поскольку мы не знаем, когда начнём получать данные с UART, этот процесс не будет синхронным с нашими тактовыми импульсами. Поэтому чем быстрее будут наши импульсы, тем ближе мы подойдём к истинному началу сигнала передатчика. Для удобства моя тактовая частота в 16 раз превышает скорость в бодах (1). Это значит, что каждый передаваемый бит проходит за 16 импульсов этого генератора. Поэтому, чтобы взять выборку примерно в середине передаваемых данных, мы должны делать это на счёт 8 – для этого мы генерируем сигнал SAMPLING_CLK (3).
Затем по нарастающему фронту этого нового тактового сигнала мы можем синхронизировать передаваемый сигнал с двумя связанными 8-битными регистрами сдвига с последовательным параллельным выходом (SIPO) в середине каждого бита данных. На 16-м счёте мы заканчиваем с цифровым битом, поэтому мы увеличиваем ещё один счётчик, отслеживающий общее количество бит, синхронизированных в (5). Когда этот счётчик достигает 16 (это мог бы быть и десятичный счетчик), схема приёма отключается путем очистки D-триггера. Уф! Схему привожу ниже, и надеюсь, что у вас получится отследить логику её работы при помощи моего описания.
У меня, к сожалению, нет осциллографа, а изначально моя схема давала какие-то загадочные результаты, принимая один байт, а потом принимая другой уже по-другому. Я поменял генератор на 2,4576 МГц на 1-секундный генератор 555, чтобы проверить логику подсчёта, и обнаружил проблему с плавающим входом на контакте одного из счётчиков (отладкой занимался при помощи светодиодов). Я привязал оба контакта сброса счётчиков к сигналу RX_active, в результате чего счётчики переключаются между состояниями включения и сброса, что подчищает их выход в конце каждого цикла получения данных. Теперь счётчики работают, как и ожидалось, и когда я поставил генератор на 2,4576 МГц обратно, всё стало работать правильно и надёжно.
У итоговой схемы компьютера на макетной плате будет выходной регистр для контроля выводимых на шину данных. Наконец, я использовал лишний D-триггер на 74LS74 для реализации сигнала RX_READY, который процессор может считывать, чтобы проверять готовность байта к считыванию (он истинный, только когда байт полностью принят).
Ниже привожу фотографию собранного и работающего компьютера. Интерфейс UART-USB – это донгл справа вверху. На средней плате стоит кварцевый генератор и 4-битные счётчики, генерирующие различные тактовые импульсы. Сверху рядом с питанием USB стоит 16-битный сдвиговый регистр. На левой плате расположена логика управляемой отправки одного байта (UART TX). Можно заметить кнопочку, которой я симулировал управляющий сигнал процессора и таймер 555, выполняющий роль генератора тактовых импульсов процессора. На правой плате живёт модуль UART RX. Зелёные светодиоды отмечают поступление байта на вход, жёлтые – получение данных (сигнал занятости UART RX), а красный включается, когда байт готов к чтению процессором.
Нужны макетные платы покрасивее и навык разводки проводов
Я немного оптимизировал схему (попутно выучив урок о разнице обработки асинхронных и синхронных событий в дискретной логике ИС). Я хотел уменьшить количество чипов, используя десятичный счётчик, который будет считать входящие биты, причём считать по 10 битов, а не по 16. Тогда мне удалось бы убрать сдвиговый регистр.
Сначала я попробовал счётчик 74LS162. Для одного байта всё работало, однако я быстро обнаружил, что у него синхронный механизм сброса – то есть, для сброса сигнала должен пройти один такт. Поскольку тактовые импульсы прекращались после получения последнего бита, счётчик не очищался. У 4-битного счётчика 74LS161, который я убрал, был асинхронный сброс, поэтому раньше всё работало. Хорошо, что нашёлся десятичный счётчик с асинхронным сбросом — 74LS160. С ним всё работает отлично – см. обновлённую схему.
Для простоты я не добавлял проверку ошибок в полученном байте. Можно представить, что мы добавили бит чётности и переключаем триггер каждый раз при получении «1». Тогда мы бы знали, чётное или нечётное число битов получили, и могли бы сравнить его с битом чётности, взведя флаг при несовпадении. Кроме того, сюда можно включить и проверку подтверждения того, что бит остановки равнялся «1». Из экономии места я не стал добавлять эту функциональность, однако хочется добавить её в будущем. Модульность проекта позволяет сделать это по необходимости.
Мне нравятся 8-битные компьютеры на макетных платах, и я с удовольствием занимался этим мини-проектом. Я довольно долго проектировал эту схему, и всё равно был шокирован, когда я её собрал и всё заработало. Это прямо магия какая-то! Ну, почти.
Итоговая схема: готовый UART-трансивер, собранный из ИС серии 7400
В первую очередь разберёмся, что такое UART. Это универсальный асинхронный приёмопередатчик – простой протокол, позволяющий асинхронно отправлять и получать 8-битные данные, благодаря чему процессор или компьютер могут общаться с внешним миром. Это и само по себе полезно – мой 8-битный компьютер может общаться с ноутбуком и использовать программу для мониторинга последовательного порта (типа putty) в качестве интерфейса для ввода и вывода текста. Что ещё интереснее, я могу запрограммировать загрузчик ОС для своего 8-битного компьютера, а потом программировать его через UART соединение с ноутбука! Поскольку Bluetooth модули типа HC-05, по сути, общаются с CPU через UART, я даже могу использовать Bluetooth модуль для программирования своего 8-битного компьютера на расстоянии! Это было бы очень круто.
Некоторые пуристы сочли бы программирование 8-битного компьютера при помощи гораздо более мощного компьютера мошенническим подходом – но это мой проект, и он живёт по моим правилам! Пожалуйста, программируйте собранную вручную машину при помощи DIP-переключателей, если вам больше нравится вводить данные, чем программировать, и хочется получить аутентичный опыт тяжёлой работы.
Как бы там ни было с программированием, я, по крайней мере, решил ограничить себя при разработке компьютера простыми ТТЛ чипами – никаких Arduino, Raspberry Pi, ESP8266 и иных полных по Тьюрингу модулей (иначе в чём был бы интерес?).
Протокол UART и ограничения проекта
Перед вами – структура сигнала UART. У него есть начальный бит, обозначаемый переходом с высокого на низкий уровень сигнала, за которым идёт байт данных (LSB first), а потом бит остановки, переводящий сигнал на высокий уровень. Иногда есть ещё и бит чётности, однако он не обязателен, поэтому я его опустил из соображений простоты. Время передачи каждого бита определяется скоростью передачи в бодах (в данном случае – в битах в секунду). К примеру, скорость 9600 бод означает, что бит передаётся за
1/9600 = 104 мкс. Форма сигнала довольно простая, поэтому мы можем реализовать её полностью в железе на логических чипах.
Мне нужно было выбрать кварцевый генератор, дающий мне доступ к стандартным скоростям передачи в бодах, предпочтительно делящихся на степени двойки, чтобы с ним было удобно работать при помощи двоичного счётчика. Подумав, я решил использовать генератор на 2,4576 МГц, поскольку он позволял передавать на 38400 б/с (делением на 64), или 9600 б/с (делением на 256).
UART передатчик
Список компонентов:
- 2,4576 МГц кварцевый генератор
- 3 x 74LS161 4-битные счётчики
- 74LS674 16-битный сдвиговый регистр
- 74LS06 AND
- 74LS74 D-триггер
- 74LS04 NOT
- Diode 1N4001
- 470 мкФ (!) конденсатор (сглаживание питания)
Схема
Схему работы UART передатчика понять проще всего. По сути, это регистр сдвига с параллельной загрузкой и последовательным выходом. Он загружает байт данных, отслеживает начальные и конечные биты, и синхронизирует его с желаемой скоростью передачи. На схеме ниже показан этот процесс. В части (1) кварцевый генератор на 2,4576 МГц замедляется до 38 400 Гц при помощи двух 4-битных счётчиков 74LS161. В части (2) 16-битный сдвиговый регистр 74LS674 используется для синхронизации данных для UART. Я использую этот регистр потому, что он уже был у меня под рукой. Понимаю, что эта ИС дороговата, и её, возможно, трудно найти, но она определённо упростила всю мою схему.
При помощи всего трёх таких ИС (двух 4-битных счётчиков и регистра сдвига) можно выдавать непрерывный поток символов на передатчике UART со скоростью 38 400 б/с (без бита чётности)! Да-да, именно непрерывный поток – я не учёл, что сдвиговый регистр обновляет буфер загрузки по кругу – упс. Такое поведение мне не было нужно – я хотел, чтобы процессор отправлял по байту за раз. Всё усложняется тем, что тактовые импульсы процессора и UART не синхронизированы, и мне не хотелось строить предположения о том, чей таймер работает быстрее, какой сигнал в какой момент будет актуальным, и т.п. Поскольку мне нужно было надёжно обрабатывать асинхронность, я решил использовать следующую, неплохо работающую схему:
- (3) Процессор отправляет сигнал «передача байта» без синхронизации с тактовыми импульсами процессора и UART.
- В следующий момент возрастания сигнала процессора внутренний сигнал устанавливается в «истину». Это синхронизирует тактовые импульсы процессора с передающим сигналом (я использую AND 74LS06 и D-триггер 74LS74).
- В следующий момент возрастания сигнала UART установленный в «истину» внутренний сигнал включает сдвиговый регистр и 4-битный счётчик 74LS161. Теперь сигнал отправки синхронизирован с тактовыми импульсами UART.
- (4) Счётчик считает до 16 и потом использует инвертированный бит переноса для отключения логики отправки данных, отключая сдвиговый регистр и счётчик.
Обратите внимание, что я сдвигаю 16 бит вместо 10 бит сигнала передатчика UART – в основном из-за удобства использования бита переноса для отключения передающей схемы. Я мог использовать и десятичный счётчик (к примеру, 74LS162), однако у меня не было такого под рукой, когда я собирал схему на макетной плате. Возможно, в окончательной схеме я на него перейду.
UART приёмник
Список компонентов:
- 2,4576 МГц кварцевый генератор (можно использовать тот же генератор, что и у приёмника)
- 3 x 74LS161 4-битные счётчики (можно использовать одну из ИС из приёмника)
- 74LS74 D-триггер
- 74LS04 NOT (можно использовать ИС из приёмника)
- Диод 1N4001
- 470 мкФ (!) конденсатор (сглаживание питания)
- Резисторы на 220 Ом и светодиоды для красоты.
Мне кажется, что если описанный выше UART передатчик понять легко, то приёмник будет несколько более сложным. Однако что хорошо с цифровой логикой – её можно разбить на отдельные модули, и тогда всё уже не кажется таким сложным!
Формы сигнала в левом нижнем углу схемы, приведённой ниже, показывают, что нужно учесть при приёме единственного цифрового бита передатчика. Как мы определим, что нам отправляют байт? Легко – стартовый бит обозначен переходом от высокого уровня сигнала к низкому, так что мы можем это инвертировать и использовать переход от низкого уровня к высокому для установки D-триггера (74LS74) (2).
Теперь нам нужно начать записывать сигнал, сдвигая его в сдвиговые регистры и делая выборку в центре последовательности битов данных. Что важно понять: поскольку мы не знаем, когда начнём получать данные с UART, этот процесс не будет синхронным с нашими тактовыми импульсами. Поэтому чем быстрее будут наши импульсы, тем ближе мы подойдём к истинному началу сигнала передатчика. Для удобства моя тактовая частота в 16 раз превышает скорость в бодах (1). Это значит, что каждый передаваемый бит проходит за 16 импульсов этого генератора. Поэтому, чтобы взять выборку примерно в середине передаваемых данных, мы должны делать это на счёт 8 – для этого мы генерируем сигнал SAMPLING_CLK (3).
Затем по нарастающему фронту этого нового тактового сигнала мы можем синхронизировать передаваемый сигнал с двумя связанными 8-битными регистрами сдвига с последовательным параллельным выходом (SIPO) в середине каждого бита данных. На 16-м счёте мы заканчиваем с цифровым битом, поэтому мы увеличиваем ещё один счётчик, отслеживающий общее количество бит, синхронизированных в (5). Когда этот счётчик достигает 16 (это мог бы быть и десятичный счетчик), схема приёма отключается путем очистки D-триггера. Уф! Схему привожу ниже, и надеюсь, что у вас получится отследить логику её работы при помощи моего описания.
У меня, к сожалению, нет осциллографа, а изначально моя схема давала какие-то загадочные результаты, принимая один байт, а потом принимая другой уже по-другому. Я поменял генератор на 2,4576 МГц на 1-секундный генератор 555, чтобы проверить логику подсчёта, и обнаружил проблему с плавающим входом на контакте одного из счётчиков (отладкой занимался при помощи светодиодов). Я привязал оба контакта сброса счётчиков к сигналу RX_active, в результате чего счётчики переключаются между состояниями включения и сброса, что подчищает их выход в конце каждого цикла получения данных. Теперь счётчики работают, как и ожидалось, и когда я поставил генератор на 2,4576 МГц обратно, всё стало работать правильно и надёжно.
У итоговой схемы компьютера на макетной плате будет выходной регистр для контроля выводимых на шину данных. Наконец, я использовал лишний D-триггер на 74LS74 для реализации сигнала RX_READY, который процессор может считывать, чтобы проверять готовность байта к считыванию (он истинный, только когда байт полностью принят).
Ниже привожу фотографию собранного и работающего компьютера. Интерфейс UART-USB – это донгл справа вверху. На средней плате стоит кварцевый генератор и 4-битные счётчики, генерирующие различные тактовые импульсы. Сверху рядом с питанием USB стоит 16-битный сдвиговый регистр. На левой плате расположена логика управляемой отправки одного байта (UART TX). Можно заметить кнопочку, которой я симулировал управляющий сигнал процессора и таймер 555, выполняющий роль генератора тактовых импульсов процессора. На правой плате живёт модуль UART RX. Зелёные светодиоды отмечают поступление байта на вход, жёлтые – получение данных (сигнал занятости UART RX), а красный включается, когда байт готов к чтению процессором.
Нужны макетные платы покрасивее и навык разводки проводов
Дополнение
Я немного оптимизировал схему (попутно выучив урок о разнице обработки асинхронных и синхронных событий в дискретной логике ИС). Я хотел уменьшить количество чипов, используя десятичный счётчик, который будет считать входящие биты, причём считать по 10 битов, а не по 16. Тогда мне удалось бы убрать сдвиговый регистр.
Сначала я попробовал счётчик 74LS162. Для одного байта всё работало, однако я быстро обнаружил, что у него синхронный механизм сброса – то есть, для сброса сигнала должен пройти один такт. Поскольку тактовые импульсы прекращались после получения последнего бита, счётчик не очищался. У 4-битного счётчика 74LS161, который я убрал, был асинхронный сброс, поэтому раньше всё работало. Хорошо, что нашёлся десятичный счётчик с асинхронным сбросом — 74LS160. С ним всё работает отлично – см. обновлённую схему.
Проверка ошибок в полученном байте
Для простоты я не добавлял проверку ошибок в полученном байте. Можно представить, что мы добавили бит чётности и переключаем триггер каждый раз при получении «1». Тогда мы бы знали, чётное или нечётное число битов получили, и могли бы сравнить его с битом чётности, взведя флаг при несовпадении. Кроме того, сюда можно включить и проверку подтверждения того, что бит остановки равнялся «1». Из экономии места я не стал добавлять эту функциональность, однако хочется добавить её в будущем. Модульность проекта позволяет сделать это по необходимости.
Заметки
Мне нравятся 8-битные компьютеры на макетных платах, и я с удовольствием занимался этим мини-проектом. Я довольно долго проектировал эту схему, и всё равно был шокирован, когда я её собрал и всё заработало. Это прямо магия какая-то! Ну, почти.