Программирование МПС на языке ассемблер

(Скачать)

WML Версия

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

Рассмотрим основные моменты практического программирования простроенной нами МПС на примере задачи по вводу в память системы внешнего импульсного сигнала амплитуды ТТЛ с последующим воспроизведением этого сигнала. Идея заключается в следующем (рис. 1.):

Рис. 1.

Через равные промежутки времени будем опрашивать порт ввода, выделяя бит, по которому поступает внешний импульсный сигнал, и записывать полученные байты последовательно в доступный нам блок оперативного запоминающего устройства (ОЗУ) МПС. Частота опроса порта подбирается экспериментально исходя из соображений теоремы Котельникова: минимальный интервал изменения сигнала должен быть опрошен программой хотя бы два раза для того чтобы это изменение было нами зафиксировано.

Обратимся к техническим характеристикам нашей МПС:

Карта памяти была разделена нами посредством дешифратора на участки по 2048 байт (07FFh байт или 2Кбайт) с целью применения микросхем ROM и RAM соответствующей емкости. С нулевого адреса (0000h) мы поместили микросхему ПЗУ (ROM BIOS), содержащую подпрограммы начальной инициализации и системные вызовы. В пространстве адресов ПЗУ находится вектор (адрес) для обслуживания единственного предусмотренного нами прерывания - 0038h, что соответствует программному вызову RST7 (код 0FFh или 1111.1111b). Предполагается, что это прерывание будет использовано в нашей системе для обслуживания матрицы клавиш 8х3 и регенерации семисегментного 8-разрядного индикатора, выполняющего функции алфавитно-цифрового дисплея. Диапазон адресов 0800h ÷ 37FFh отведен в МПС под ОЗУ, причем ячейки в интервале 0800h ÷ 08FFh (256 байт) будут выполнять роль системного ОЗУ, где мы разместим изменяемые вектора системных вызовов, буфер клавиатуры и экрана и служебные ячейки BIOS. Ячейки в интервале 3700h ÷ 37FFh (256 байт) отведем под стек, указав при старте вершину стека по адресу 37FFh (LXI SP, 37FFH). Диапазон адресов 3800h ÷ 3FFFh отведен нами для устройств ввода-вывода, подключаемых в данной конфигурации как ячейки памяти, и физически представляющих два параллельных 8-битных регистра, один из которых работает в этом диапазоне на вывод, а второй - на ввод. Пространство устройств ввода-вывода для схемотехнической простоты не было пока разбито на более мелкие интервалы, что позволяет обратиться к нашим портам и командами ввода вывода (к примеру OUT 38H и IN 38H).

Таким образом, наша программа и вводимые данные должны располагаться в области пользовательского ОЗУ, занимающего интервал памяти 3800h ÷ 3FFFh, ячейки ОЗУ в диапазоне 4000h ÷ 0FFFFh системой не используется.

Таблица 1. Карта памяти учебной МПС.

0FFFFh

 

 

Неиспользуемая область

 

 

4000h

3FFFh

Устройства ввода-вывода

3800h

3700h Область стека 37FFh

36FFh

 

5 RAM - ОЗУ пользователя

 

1000h

0FFFh

1 RAM - ОЗУ пользователя

0900h

0800h Служебное ОЗУ 08FFh

07FFh

ROM BIOS

0000h

Наряду с картой памяти нас также интересует информация о структуре портов ввода-вывода и о назначении их отдельных битов. Биты 3 ÷ 0 порта вывода, доступного для записи по адресу 3800h, обслуживают дешифратор типа "код-код", управляющий включением сегментов a, b, c, d, e, f, g индикатора. Биты 6 ÷ 4 этого же порта подключены ко входам дешифратора типа "код-позиция", управляющего подсветкой разрядов индикатора активным уровнем "0" на выходах и одновременно осуществляющего сканирование столбцов матрицы клавиатуры 8х3, "бегущим нулём". Бит 7 порта отвечает за последовательную передачу данных и подключен ко входу операционного усилителя, трансформирующего выходные сигналы амплитуды ТТЛ в двуполярные сигналы амплитудой ±5 В для согласования с внешними устройствами (к примеру - интерфейсом RS-232).

Бит 7 порта, доступного для чтения по адресу 3800h подключен к выходу компаратора, осуществляющего ввод от внешних устройств последовательных сигналов как цифрового, так и аналогового характера, и будет использован нами для ввода цифровой последовательности. Разряды 3 ÷ 0 этого же порта подключены через резисторы 10 кОм к линии питания +5 В и осуществляют опрос столбцов матрицы клавиатуры 8х3, причем бит 3 ответственен за отслеживание нажатия клавиши "Shift". Эта клавиша будет осуществлять управление в нашей программе. Биты 6 ÷ 4 порта для информативного ввода не используются, но могут быть использованы в дальнейшем.

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

;------------------------------------------------------------;

; Ввод данных импульсной последовательности в МПС

; через бит 7 параллельного порта ввода 3800H

;------------------------------------------------------------;

ORG 0900H; - начало ОЗУ пользователя

START: ;------------- Цикл ввода ----------------------------

;______ Начальная инициализация

CALL INIT; загрузим регистровые пары

MVI A,00H; байт управления консолью: 0000.0000b

STA PORT; включаем O, в позиции 0 и столбец 0

CALL KEYB; опросим клавиши на нажатие "Shift"

MET1: LDA PORT; читаем порт ввода

ANI 80H; выделяем бит 7 (1000.0000b)

MOV M,A; заносим байт в ОЗУ по адресу [HL]

INX H; укажем следующую ячейку ОЗУ

LXI B,01FFH; загрузим в [BC] длительность задержки

CALL LOOP; выполним задержку

CALL DEND; уменьшим счетчик длины и проверим конец

JNZ MET1; если не конец - повторим ввод байта

;------------------ Цикл вывода ----------------------------

;______ Начальная инициализация

CALL INIT; загрузим регистровые пары

CALL KEYB; опросим клавиши на нажатие "Shift"

MET2: MOV A,M; читаем байт из ОЗУ по адресу [HL]

ANI 80H; выделяем бит 7 для симметрии вывода

STA PORT; выводим байт в порт вывода

INX H; укажем следующую ячейку ОЗУ

LXI B,01FFH; загрузим в [BC] длительность задержки

CALL LOOP; выполним задержку

CALL DEND; уменьшим счетчик длины и проверим конец

JNZ MET2; если не конец - повторим вывод байта

JMP START; переход на цикл ввода

INIT: LXI Н,0B00H; укажем начало области ОЗУ для записи

LXI D,2D00H; укажем размер области ОЗУ для записи

RET; (3800h-0B00H = 2D00H)

LOOP: DCR C; уменьшим младший байт счетчика задержки

JNZ LOOP; повтор, если не 0

DCR B; уменьшим старший байт счетчика задержки

JNZ LOOP; повтор, если не 0

RET; возврат из задержки

DEND: DCR E; уменьшим младший байт счетчика длины

RNZ; возврат, если не 0

DCR D; уменьшим старший байт счетчика длины

RET; возврат с признаком (Z или NZ)

KEYB: LDA PORT; читаем порт ввода

ANI 04H; выделяем бит 3 (0000.1000b)

JNZ KEYB; повторяем, если бит 3 не = 0

RET; выходим - нажат "Shift"

;------ Внешние метки -------------------------------------

SYST: EQU 0000H;___Холодный старт системы_______________

PORT: EQU 3800H;___Регистры ввода-вывода________________

END;_____________ укажем транслятору конец программы

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

Оценить длительность задержки можно, просчитав по тактам цикл ввода, и, зная длительность такта (500 нс при рабочей частоте процессора 2 МГц), определить суммарную задержку. Но существует менее трудоемкий путь: выполняя следующую программу,

ORG 0900H; - начало ОЗУ пользователя

START: ;------------- Оптимизация задержки -------------------------

XRA A; обнулим значение аккумулятора

LXI B,01FFH; загрузим в [BC] длительность задержки

LOOP: DCR C; уменьшим младший байт счетчика задержки

JNZ LOOP; повтор, если не 0

DCR B; уменьшим старший байт счетчика задержки

JNZ LOOP; повтор, если не 0

STA PORT; выводим байт в порт вывода

CMA; инвертируем все биты аккумулятора

JMP LOOP; повторим задержку и вывод

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

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

;------------------------------------------------------------;

; Программа системной ПЗУ

;------------------------------------------------------------;

ORG 0000H; - начало системной ПЗУ

BEGIN: ;--------Холодный старт системы -----------------------

LXI SP,37FFH; укажем стек системы

JMP INIT; переход к программе инициализации

ORG 0008H; другие RST0-6

. . . ;

ORG 0038H; адрес RST7, DI - автоматически

CONS: PUSH H; сохраним [HL]

LHLD VECT7; загрузим в [HL] адрес обработки прерывания

PCHL; переход на этот адрес

;______ Начальная инициализация

INIT: MVI A,00H; байт управления консолью: 0000.0000b

STA PORT; включаем 0, в позиции 0 и столбец 0

. . . ; другие стартовые действия…

LXI H,KEYB; укажем в [HL] адрес обработки консоли

SHLD VECT7; сохраним его как изменяемый вектор

MVI A,00H; зададим счетчик прерываний 256

STA CNT; запишем в ячейку счетчика

WAIT: EI; разрешим прерывания

HLT; остановим программу для ожидания прерывания

WAI1: ; этот адрес будет запомнен, как возврат

JPM WAIT; ожидание следующего прерывания

KEYB: POP H; восстановим [HL]

LDA CNT; читаем счетчик прерываний

DCR A; уменьшаем его

STA CNT; сохраняем счетчик

RNZ; выход из прерывания (не 256-ое)

MVI A,00H; зададим счетчик прерываний 256

STA CNT; восстановим в ячейке счетчика

KEY0: LXI H,BUFF; укажем на начало буфера строки

MOV A,M; берем первый символ

ANI 0FH; выделим младший ниббл

. . . ; другие действия консоли

RET; завершение работы подпрограммы

Основная идея механизма прерываний заключается в том, что какое-либо устройство, требующее мгновенной реакции, выдает сигнал INT, по которому процессор прекращает текущую программу и ждет по шине данных от одного до трёх байт инструкций прерывания, качестве которых удобно использовать RST0 ÷ RST7 или CALL ADDR. По этим инструкциям процессор запоминает в стеке адрес возврата на следующую ячейку и переходит к выполнению подпрограммы обработки прерывания. Команды RST0 ÷ RST7 удобны тем, что автоматически выполняют инструкцию DI - запрещение прерывания, в других случаях эта инструкция должна быть первой в подпрограмме обработки прерывания. Прерывания разрешаются далее командой EI, если критический участок подпрограммы обработки прерывания пройден и допустимо вложение прерываний, либо перед выходом из подпрограммы.

В качестве источника прерывания используем простой внешний генератор по схеме мультивибратора на логических элементах с частотой генерации 12800 Гц (максимальная частота прерываний для нашей системы ~35000 Гц). При старте системы прерывания запрещены. Процессор устанавливает стек и уходит в программу инициализации, где выполняет настройку устройств ввода-вывода и записывает в служебное ОЗУ адреса переходов по прерываниям и счетчик прерываний. После этого прерывания разрешаются и процессор останавливается по HLT. Каждый следующий импульс генератора будет возбуждать прерывания и после каждого 256-го мы будем обслуживать индикатор и клавиатуру (т.е. 50 раз в секунду - что вполне достаточно).

Переписав ячейку VECT7 новым адресом, указывающим на другую подпрограмму мы можем осуществлять опрос порта с частотой 12800, 6400 Гц и т.д. Рассмотрим практическую реализацию такой программы.

;------------------------------------------------------------;

; Ввод данных импульсной последовательности в МПС

; через бит 7 параллельного порта ввода 3800H

; по обработке прерывания RST7 (адрес 0038H)

;------------------------------------------------------------;

ORG 0900H; - начало ОЗУ пользователя

START: ;------------- Цикл ввода/вывода ----------------------

DI; запретим прерывания

XRA A; очистим A

STA TRIG; запишем в ячейку-переключатель

SWITCH: LDA TRIG; прочитаем переключатель

CMA; инвертируем (переключим)

STA TRIG; запишем в ячейку-переключатель

ORA A; если переключатель = 0

JZ SW1; укажем прерывание на чтение блока

LXI H, MET1; укажем адрес прерывания на запись блока

JMP SW2; перепишем вектор

SW1: LXI H, MET2; укажем адрес прерывания на чтение блока

SW2: SHLD VECT7; перепишем вектор

;______ Начальная инициализация регистров

CALL INIT; загрузим регистровые пары

MVI A,00H; байт управления консолью: 0000.0000b

STA PORT; включаем 0, в позиции 0 и столбец 0

CALL KEYB; опросим клавиши на нажатие "Shift"

ST1: EI; разрешим прерывания

HLT; ждём прерывание

CALL DEND; уменьшим счетчик длины и проверим конец

JNZ ST1; если не конец - повторим ввод байта

JMP SWITCH;

MET1: POP H; восстановим [HL]

LDA PORT; читаем порт ввода

ANI 80H; выделяем бит 7 (1000.0000b)

MOV M,A; заносим байт в ОЗУ по адресу [HL]

INX H; укажем следующую ячейку ОЗУ

RET; выход из прерывания

MET2: POP H; восстановим [HL]

MOV A,M; читаем байт из ОЗУ по адресу [HL]

ANI 80H; выделяем бит 7 для симметрии вывода

STA PORT; выводим байт в порт вывода

INX H; укажем следующую ячейку ОЗУ

RET; выход из прерывания

INIT: LXI Н,0B00H; укажем начало области ОЗУ для записи

LXI D,2D00H; укажем размер области ОЗУ для записи

RET; (3800h-0B00H = 2D00H)

DEND: DCR E; уменьшим младший байт счетчика длины

RNZ; возврат, если не 0

DCR D; уменьшим старший байт счетчика длины

RET; возврат с признаком (Z или NZ)

KEYB: LDA PORT; читаем порт ввода

ANI 04H; выделяем бит 3 (0000.1000b)

JNZ KEYB; повторяем, если бит 3 не = 0

RET; выходим - нажат "Shift"

TRIG: DB 00H; ячейка-переключатель

;------ Внешние метки --------------------------------------

PORT: EQU 3800H;___Регистры ввода-вывода_________________

END;_____________ укажем транслятору конец программы

Набранный в программе-редакторе ассемблерный текст подлежит трансляции в объектный код и дальнейшей отладке. Рассмотрим процессы отладки и практического применения разработанной нами программы.

Hosted by uCoz