Amateur Radio Station R9AL Эиком - электронные компоненты и радиодетали
Соколов Александр, Черников Владимир

     Процессор Zilog Z80 – один, если не самый популярный, из 8-битных микропроцессоров, применявшихся разработчиками персональных компьютеров и других микросистем с конца 70-х по начало 00-х. Слово «8-битный» здесь означает ширину линии данных – 8 бит, тогда как предел объема непрерывно адресуемой памяти у самого процессора – 64 кБ. В нашем случае этот м/п применяется в изготовлении клонов «ZX Spectrum’а» – «Ленинград 48k», поэтому, для упрощения пусконаладочных работ над этими компьютерами, было принято решение изготовить тестер этих микропроцессоров, который также послужит и простейшей демонстрацией работы микропроцессора.



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

     В качестве ПЗУ классически применяются УФ-стираемые (EPROM) микросхемы 27C64, 27C128 или 27C256 а также их электрически стираемые (EEPROM) аналоги с префиксом 28C. Но мы будем использовать плату Arduino Nano на основе микроконтроллера Atmega 328P для удобства отладки и быстрой загрузки программы для микропроцессора.

     Для реализации простейшего порта вывода мы не будем прибегать к какой-либо дешифрации адреса. Нам достаточно сигнала IORQ для записи в порт, поскольку в системе отсутствует ОЗУ и записи в память система осуществлять не будет, соответственно никаких конфликтов на шине данных не возникнет. В качестве «порта» будем использовать бит D0 шины данных и D-триггер 555ТМ2 (74LS74).

     Полная схема системы:

     На выход порта вывода подключен светодиод для индикации его состояния. RESET процессора подтянут к «+» питания через резистор, поскольку в связи с особенностями конкретной схемы иногда возникает необходимость после подачи питания выполнить сброс, а также его необходимо держать в нуле при программировании платы Arduino, поскольку пины RX и TX платы также используются для формирования линии данных микропроцессора. Всего задействовано 6 бит адреса из 16 возможных у микропроцессора, что дает нам объем памяти в 64 байта для нашей программы, чего более чем достаточно.

     Скетч для платы Arduino с комментариями:

//Массив, содержащий машинный код для исполнения микропроцессором
byte mem[64] = {0xEE, 0x01, 0xD3, 0x00, 0x06, 0xFF, 0x10, 0xFE, 0x18, 0xF6};

void setup() {
  //Инициализация портов ввода-вывода платы Arduino. Перевод всех задействованных пинов в режим входа без подтяжки.
  DDRD = 0x00;
  DDRB = 0x00;
  DDRC = 0x00;
  PORTD = 0x00;
  PORTB = 0x00;
  PORTC = 0x00;
}

void loop() {
  //Основной цикл: проверка состояния линии RD#, подключенного на первый бит порта C Arduino.
  //Сравнение по маске: если RD# (бит 1 порта C) в "нуле", переходим в режим выхода
  if ((PINC & 0x01) == 0x00) 
  {
    //Переводим шину данных в состояние выхода
    DDRD = 0xFF;
    //Выводим на шину данных соответствующий байт, согласно адресу, выставленному микропроцессором на пинах линии адреса A0..A5
    PORTD = *(mem + PINB);
  }
  //Если RD# перешел в высокий уровень, снова переводим линию адреса Arduino в режим входа.
  else 
  {
    DDRD = 0x00;
    PORTD = 0x00;
  }
}

     Логика работы скетча проста: пока на входе A0 Arduino (нулевой бит порта C) высокий уровень, процессор не обращается к памяти, и в это время шина данных должна быть свободна, поэтому пины порта D (к которому подключена шина данных микропроцессора) Arduino находятся в режиме входов. Когда же этот вход опускается микропроцессором в низкий уровень, значит микропроцессор обращается к памяти и выставляет на шину адреса адрес следующей команды: Arduino переводит порт D в режим выхода и, согласно выставленному на порте B (вернее на его пинах, соответствующих битам 0..5) микропроцессором адресу, выводит в порт D команду из массива byte, описанного в скетче.

     Теперь перейдем к описанию ассемблерного кода для Z80. Применялся один из самых известных кросс-ассемблеров sjasmplus: https://github.com/z00m128/sjasmplus, проект для VSCode прилагается (необходимо также установить расширение Z80 Macro-Assembler для удобства чтения кода и подсказок, и Microsoft Hex Editor для чтения сформированного ассемблером .bin файла прошивки).

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

    DEVICE ZXSPECTRUM48
    .org $0000
begin:
    xor 0x01 ;Инвертируем нулевой бит аккумулятора
    out 0x00,a ;Выводим значение аккумулятора в порт 0x00
    ld b,0xFF ;Загружаем 0xFF в регистр B
loop:
    djnz loop ;B = B - 1, прыжок на loop пока B != 0
    jr begin ;Прыжок в начало кода
length:
    savebin "rom.bin",begin,length-begin

     Для начала отступление по поводу команд для ассемблера:

     Теперь о коде. Не вдаваясь в подробности устройства регистров и системы инструкций Z80, что может быть темой для отдельной статьи, принцип работы кода можно описать следующим образом. Изначально аккумулятор (регистр а) имеет неизвестное значение, но оно нам абсолютно неважно. Команда xor 0x01 выполняет операцию побитового исключающего «или» по маске 0x01 с аккумулятором. Таким образом, если бит 0 (младший бит, тот что справа) является 0, то он становится 1, если он является 1, то становится 0. Далее следует команда out 0x00,a – тот самый вывод в наш «порт», являющийся светодиодом. Процессор при исполнении этой команды выполняет следующее: выводит на младшие биты линии адреса 0x00, на линию данных – значение аккумулятора, затем переводит в низкий уровень выходы IORQ и WR. Как мы помним, вход CLK нашего D-триггера подключен к линии IORQ. Поэтому при поднятии этой линии назад в «единицу» D-триггер запоминает состояние на линии D0 процессора (да, на шине данных все еще находится то же значение некоторые мгновения после поднятия IORQ , поэтому даже будучи срабатывающим по фронту сигнала D-триггер запоминает верное значение нулевого бита шины данных), светодиод загорается или гаснет, в зависимости от значения. Далее код загружает в регистр B Z80 значение 0xFF (ld b,0xFF)– и начинается цикл djnz loop, банально создающий задержку в 255 исполнений этой команды. Эта команда выполняет следующие действия: декрементировать B, перепрыгнуть на метку loop пока B != 0. После 255 итераций команда «пропускает» процессор дальше (поскольку B == 0) и он совершает безусловный переход по метке begin, где все начинается сначала, но аккумулятор при исполнении команты out будет уже инвертирован командой xor, что даст то самое мигание светодиода. Подключаем питание, подаем на вход CLK процессора с генератора меандр с частотой ~10 кГц, получаем умеренное мигание светодиода. Меняя частоту на генераторе, получим разную частоту мигания светодиода, поскольку скорость исполнения команд процессором напрямую зависит от частоты, поданной на вход CLK. Таким образом схема стабильно работает при тактовой частоте чуть ли не 100 кГц, а возможно и выше. Микропроцессор может работать намного быстрее – с тактовой частотой в единицы, а некоторые его версии и в десятки мегагерц, однако мы упираемся в производительность Arduino относительно вывода на линию данных верных команд, а для тестов с большой скоростью нужно ставить нормальное ПЗУ. Таким образом, мы получили действующий макет минимальной системы на Z80, демонстрирующий его работу, а также примитивный тестер подобных микропроцессоров.

Видео работы:


Файлы:
  1. Код на ассемблере
  2. Скетч для Arduino



Дополнение

Изначально схема тактовалась от лабораторного генератора, но можно встроить простой генератор:



Вопросы, обсуждения, дополнения (Телеграмм, MAX)






Copyright © R9AL 2026 Все права защищены
Рейтинг@Mail.ru