Pull to refresh

УМК на ATmega16

Reading time 16 min
Views 20K
Спойлер: данная статья не выдавит ностальгической слезы.

Решив не так давно познакомиться поближе (после Arduino) с микроконтроллерами вообще
и с микроконтроллерами ATmega в частности, я быстро понял, что просто читать о МК,
прогонять код в эмуляторе, «моргать светодиодом» — это как-то не то. Поэтому я решил
«запилить» какой-нибудь — пусть даже бесполезный — проект, и разбираться со всем
уже по ходу дела. На ум пришли лабораторные работы по микропроцессорным системам
и учебный стенд, на котором эти работы выполнялись — так называемый учебный микропроцессорный
комплект (УМК), который у нас звался просто «Умкой».

Про УМК


Итак, если не вдаваться в подробности (если вдаваться — то по ссылкам ниже), то
«Умка» это микро-ЭВМ на базе микропроцессора К580ИК80А, выпускался в 80-х годах
прошлого века (мне же посчастливилось работать с ним в не совсем далёком 2009 году),
представляет из себя весомый такой ящик. Более подробную информацию об истории «Умок»
и прочем можно найти по второй ссылке ниже. Вот так он выглядит.


(Фото наглым образом взято по первой ссылке ниже, больше фото — по той же
ссылке)

Что тут есть: индикация шин данных и адреса, индикация регистра статуса процессора,
дисплей для отображения содержимого ячеек памяти и вводимых данных, кнопки ввода
данных. Два столбика кнопок левее — это так называемые директивы вшитой в «Умку»
программы «Монитор» — они-то и стали целью моего проекта (директивы, не кнопки).
Коротко о директивах. Тут ничего особенного нет.
«П» — просмотр содержимого ячейки памяти: вводим адрес ячейки, смотрим, что там
лежит, при желании — записываем другое значение.
«РГ» — просмотр содержимого регистров: аналогично просмотру содержимого ячеек. В моей
«версии» эта директива отсутствует: в ней нет особой необходимости, потому как в
ATmega ОЗУ, регистры общего назначения и регистры периферийных устройств находятся в
одном адресном пространстве, и предполагается, что к ним можно обратиться через
директиву «П».
«КС» — подсчёт контрольной суммы массива данных: вводим адрес начала массива,
конечный адрес массива, сумма содержимого ячеек массива выводится на дисплей.
«ЗК» — заполнение массива памяти константой: вводим начальный адрес, вводим конечный
адрес массива, вводим переменную — ячейки массива заполняются этой переменной.
«ПМ» — перемещение массива памяти: вводим начальный адрес перемещаемого массива
памяти, вводим его конечный адрес, вводим начальный адрес размещения массива — массив
перемещается в область памяти, начинающуюся с введённого адреса размещения.
Директиву «СТ» — выполнение программы пользователя — я пропустил, об этом ниже.
Кнопка «ВП» — выполнение директивы, кнопка "_" разделяет переменные при вводе, «СБ» — кнопка
сброса. Подробнее о вводе и выполнении директив — тут: практикум.

sfrolov.livejournal.com/136290.html — фото УМК, воспоминания, впечатления.
www.computer-museum.ru/histussr/umk_sorucom_2011.htm — немного истории.

Что планировалось


Целью моего проекта было создание устройства, способного выполнять эти директивы и
выводить информацию на дисплей, эдакая lite-версия «Умки» (ну очень-очень lite).
Что использовалось в проекте:
1. Микроконтроллер ATmega16. Программа написана на ассемблере в AVRStudio.
2. Матричная клавиатура для ввода значений. Код для работы с клавиатурой я нашёл
здесь (спасибо автору), затем просто переписал его на ассемблере. Поскольку ОЗУ ATmega16
ограничивается адресом $045F, то вводятся три последние цифры. Кстати, данные/адреса
вводятся и выводятся в шестнадцатеричном формате.
3. LCD дисплей 16х2 на контроллере HD44780. Разобраться в работе дисплея помогли эти
две статьи — вот и вот (спасибо авторам), правильно же инициализировать дисплей помог даташит.
4. Простые кнопки для директив. Также была добавлена ещё одна кнопка — «Enter» (на
фото она со стрелочкой): нужна для подтверждения ввода каждой цифры адреса или данных
— эдакая защита от дребезга контактов матричной клавиатуры (без которой вполне можно
было обойтись, но умная мысля, как говорится, приходит опосля).

Что в итоге


Удалось достичь всего, что было запланировано: можно просматривать и изменять
содержимое ОЗУ и регистров МК, записывать константу в массив ячеек, подсчитывать
контрольную сумму массива, перемещать массив ячеек (вот развлечение-то *sarcastic*);
запись/перемещение массива куда-нибудь за адрес $0440 (примерно) вносит закономерный
хаос в область стека. Можно выполнить аж шесть пунктов первой лабораторной работы из
практикума по ссылке выше.
Судя по найденной информации, программа «Монитор», зашитая в «Умку», занимает 1Кбайт.
Одной из задач было уложить программу в 1 Кбайт, что успешно удалось (даже с
запасом), но из-за LCD-дисплея и желания сделать всё более информативным код
увеличился практически вдвое.
Настоящая «Умка» (professional edition) позволяла написать свою программу на
ассемблере микропроцессора К580ИК80А, записывая в память коды соответствующих команд,
и затем выполнять её либо пошагово, либо циклически. Я не знаю, как это было
реализовано в «Умке», и решение не кажется мне очевидным, поэтому такой опции в
проекте нет (для начала, впрочем, мне хватило и директив), с удовольствием приму
предложения по реализации этого. За это я ласково прозвал получившееся устройство «Недоумкой».



Это код
.include «m16def.inc»
.device ATmega16
.def count = r18; r18 — счётчик
.def mode = r19; r19 — признаки режимов
.def buf = r25; для ввода значений
.def rLCD = r22; для работы с LCD
.def rKey = r20; значения с клавиатуры и по мелочи

; команды LCD
.equ off = 0b00001000 ;off
.equ clrSc = 0b00000001 ;clear
.equ config = 0b00111000; 8bit, 2 lines
.equ incr = 0b00000110; addr+ (увеличение адреса, статичный экран)
.equ on = 0b00001100 ;on
.equ right = 0b00010100; сдвиг вправо
.equ down = 0b11000000; перейти на вторую строку
.equ up = 0b10000000; на первую строку
.equ cursor = 0b00001111; мигающий курсор
.equ noCursor = 0b00001100
.equ left = 0b00010000; влево

; данные LCD (порт D)
.equ _dp = DDRD
.equ _dpo = PORTD
.equ _dpi = PIND

; управление LCD (порт С)
.equ _cp = DDRC
.equ _cpo = PORTC
.equ rs = 0
.equ rw = 1
.equ e = 7

; клавиатура (порт A)
.equ _mp = DDRA
.equ _mpi = PINA
.equ _mpo = PORTA

; директивы (порт B)
.equ BUT = PINB
.equ BUTddr = DDRB
.equ BUTp = PORTB
.equ P = 0; кнопка П (режим просмотра/изменения содержимого памяти)
.equ quit = 1; выход
.equ exe = 2; кнопка ВП (выполнение)
.equ enter = 3; кнопка ввода
.equ space = 4; кнопка "_"
.equ CF = 5; кнопка ЗК (заполнение константой)
.equ CS = 6; кнопка КС (контрольная сумма)
.equ AM = 7; кнопка ПМ (перемещение массива)
; флаг Т — режим ввода данных

;======================= макрос для опроса матричной клавиатуры====================
.macro matrix
rcall _exit
ldi rKey, 0
ldi r16, 0xff; загрузка единиц в r16
cbi _mpo, @0; очистить бит, соответствующий столбцу
out _mpo, r16; единицы в portd
in r16, _mpi; чтение регистра данных порта D
cpi r16, @1
breq ein
cpi r16, @2
breq zwei
cpi r16, @3
breq drei
cpi r16, @4
breq vier
sbi _mpo, @0; выставить бит, соответствующий столбцу (возврат в исх. состояние)
rjmp @9; если ни одна из кнопок в столбце не была нажата,
; то переходим на метку — макрос с параметрами следующего столбца либо в начало опроса

ein:
ldi rKey, @5; загрузка в rKey соответствующего нажатой кнопке значения
ldi rLCD, @5; загрузка того же значения для вывода на экран
rcall _BF
rcall _charInput
sbi _mpo, @0; выставить бит, соответствующий столбцу (возврат в исх. состояние)
rjmp _check; перейти на метку view

zwei:
ldi rKey, @6
ldi rLCD, @6
rcall _BF
rcall _charInput
sbi _mpo, @0
rjmp _check

drei:
ldi rKey, @7
ldi rLCD, @7
rcall _BF
rcall _charInput
sbi _mpo, @0
rjmp _check

vier:
ldi rKey, @8
ldi rLCD, @8
rcall _BF
rcall _charInput
sbi _mpo, @0
rjmp _check
.endmacro

.cseg

.org 0

; стек
ldi r16, Low(RAMEND)
out SPL, r16
ldi r16, High(RAMEND)
out SPH, r16

sbi _cp, 0 ;rs
sbi _cp, 1 ;rw
sbi _cp, 7 ;e

; инициализация портов
ldi r16, 0b00001111
out _mp, r16; DDRA — вход
ldi r16, 0b11110000
out _mpo, r16; с подтяжкой

ldi r16, 0x00
out BUTddr, r16; DDRB — вход
ldi r16, 0xff
out BUTp, r16; с подтяжкой

; инициализация дисплея
rcall altInit

;================================= основной цикл ==============================
go:
rcall _resetLCD
ldi xl, 0x00
ldi xh, 0x00
ldi yl, 0x00
ldi yh, 0x00
ldi zl, 0x00
ldi zh, 0x00
clt
clc
clh
clr mode
clr buf
ldi count, 1

; ========================== проверка режима работы =============================
modeCheck:
sbis BUT, P; режим П
rjmp _P
sbis BUT, CF; режим ЗК
rjmp _setConst
sbis BUT, CS; режим КС
rjmp _setSum
sbis BUT, AM; режим ПМ
rjmp _setArray
rjmp modeCheck

_setConst:
sbr mode, 1; установка признака режима ЗК
; вывод на экран информации о режиме
rcall _clear
rcall _labelWrite; write
rcall _labelSpace; _
rcall _labelConst; const
rcall _point;.
rcall _down
rcall _labelAddr; address
rcall _labelOne; 1
rcall _colon;:
rcall _labelZero; 0
rcall _cursor
rjmp scan

_setSum:
sbr mode, 5; установка признака режима КС
rcall _clear
rcall _labelSum; checksum
rcall _down
rcall _labelAddr; address
rcall _labelOne; 1
rcall _colon;:
rcall _labelZero; 0
rcall _cursor
rjmp scan

_setArray:
sbr mode, 9; установка признака режима ПМ
rcall _clear
rcall _mArray
rcall _down
rcall _labelAddr
rcall _labelOne
rcall _colon
rcall _labelZero
rcall _cursor
rjmp scan

_check:; после ввода цифры — сюда
sbrc mode, 0; проверяем режим
rjmp _const; если ЗК, КС или ПМ, то сюда
rjmp _view

_P:; режим П
rcall _clear
rcall _browse
rcall _labelSpace
rcall _labelZero
rcall _cursor

scan:; опрос матричной клавиатуры
rcall _exit
rcall _cursor
matrix 0, 0b11101110, 0b11011110, 0b10111110, 0b01111110, 3, 7, 11, 15, next

next:
matrix 1, 0b11101101, 0b11011101, 0b10111101, 0b01111101, 2, 6, 10, 14, next1

next1:
matrix 2, 0b11101011, 0b11011011, 0b10111011, 0b01111011, 1, 5, 9, 13, next2

next2:
matrix 3, 0b11100111, 0b11010111, 0b10110111, 0b01110111, 0, 4, 8, 12, scan

rjmp scan
; ==========================================================================

;========================== ожидание нажатия кнопки ввода====================
_view:
brts _input; переходим на input, если Т-флаг установлен (т.е. режим ввода данных)
rcall _exit
sbic BUT, enter; если не нажата кнопка ввода,
rjmp _view; то замыкаем цикл и ожидаем нажатия кнопок ВЫП., сброса или ввода
cpi count, 1; сравнение значения счётчика: если 1,
breq one; то вводится первая цифра
cpi count, 2; если 2,
breq two; то вводится вторая
cpi count, 3; если 3,
breq three; то вводится последняя цифра
rjmp _view

; ввод первой цифры адреса
one:
mov xh, rKey
inc count
rjmp scan; переход к опросу мат. клав.

; ввод второй цифры адреса
two:
mov xl, rKey
swap xl
inc count
rjmp scan

; ввод третьей цифры адреса
three:
rcall _noCursor; курсор не нужен
add xl, rKey
inc count
rjmp finish; после ввода третьей ццфры, перейти на finish
;==================================================================================

;================================== режим ввода ================================
_input:
rcall _exit
sbic BUT, enter; если нажата кнопка ввода, то перепрыгиваем на сравнение счётчика
rjmp _input
cpi count, 1; если счётчик равен 1, то вводится первая цифра
breq first
cpi count, 2; если 2, то вторая
breq second
rjmp _input

first:; ввод первой цифры
mov buf, rKey
swap buf
inc count
rjmp scan

second:; ввод второй цифры
rcall _noCursor
add buf, rKey
inc count
rjmp _waitFor
;=================================================================================

;======================= ожидание после ввода третьей цифры ======================
finish:; переходим сюда после ввода третьей цифры
brts link; если Т-флаг выставлен, то это режим ввода данных, — переходим на scan
rcall _exit
sbis BUT, exe; кнопка ВЫП.
rjmp view; перейти на view, если нажата ВЫП.
sbic BUT, space; кнопка "_"
rjmp finish
ldi count, 1; сбрасываем счётчик
set; выставить Т-флаг в регистре статуса
; вывести в первой строке «WRITE DATA»
rcall _clear
rcall _labelWrite
rcall _labelSpace
rcall _labelData
; перейти на вторую,
rcall _down
; установить курсор
rcall _cursor
rjmp finish
link:
rjmp scan
; ============================================================================

; =============== просмотр содержимого ячейки (после нажатия кнопки ВЫП. -> сюда)
view:
; вывод надписи «BROWSE XXXX»
rcall _clear
rcall _browse; BROWSE
rcall _labelSpace; _
mov rLCD, xh; первые XX
rcall _charFromMemory
mov rLCD, xl; вторые XX
rcall _charFromMemory
; вывод содержимого ячейки памяти
rcall _noCursor
rcall _down
ld rLCD, X+
rcall _charFromMemory
view1:
rcall _exit
ldi count, 1
sbic BUT, space; если нажата кнопка "_"., то переходим к ячейке с адресом на 1 больше
rjmp view1
rcall _delay1
rjmp view
; =============================================================================

; ========================= запись значения в ячейку памяти =======================
input:
st X, buf
clt; очищаем Т-флаг по завершении режима ввода данных
rjmp go
;==================================================================================

; ========== ожидание нажатия ВЫП. либо "_" для продолжения ввода ===============
_waitFor:; после ввода второй цифры (данные) — сюда
sbrc mode, 0
rjmp _WC
rcall _exit
sbis BUT, exe; если нажата ВЫП., то перейти на input
rjmp input
sbic BUT, space; если нажата "_",
rjmp _waitFor; то перепрыгиваем это,
st X+, buf; пишем в память введённые данные,
ldi count, 1; сбрасываем счётчик
rcall _clear
rcall _labelWrite
rcall _labelSpace
rcall _labelData
rcall _down
rjmp scan

_WC:
rcall _exit
sbic BUT, exe
rjmp _WC
rjmp _execConst
; ==============================================================================

; =============================== режим ЗК (заполнение константой) ==================
_const:
brts to_input
rcall _exit
sbic BUT, enter; если не нажата кнопка ввода,
rjmp _const; то замыкаем цикл и ожидаем нажатия кнопок ВЫП., сброса или ввода
cpi count, 1; сравнение значения счётчика: если 1,
breq _one; то вводится первая цифра
cpi count, 2; если 2,
breq _two; то вводится вторая
cpi count, 3; если 3,
breq _three; то вводится последняя цифра
rjmp _const; в цикл
to_input:
rjmp _input

; ввод первой цифры первого адреса
_one:
sbrc mode, 4; если выставлен 2-й бит (признак ввода третьего адреса),
rjmp _ad31; < — двигаем сюда
sbrc mode, 1; если не выставлен 1-й бит в r19 (признак ввода второго адреса),
rjmp inpConst1; то пропускаем это
mov xh, rKey
inc count
rjmp scan
; ввод первой цифры второго адреса
inpConst1:
mov yh, rKey
inc count
rjmp scan

; ввод второй цифры первого адреса
_two:
sbrc mode, 4; если выставлен 2-й бит (признак ввода третьего адреса),
rjmp _ad32; < — двигаем сюда
sbrc mode, 1; если выставлен 1-й бит в r19 (признак ввода второго адреса),
rjmp inpConts2; то переходим на inpConst1
mov xl, rKey
swap xl
inc count
rjmp scan
; ввод второй цифры второго адреса
inpConts2:
mov yl, rKey
swap yl
inc count
rjmp scan

; ввод третьей цифры первого адреса
_three:
sbrc mode, 4; если выставлен 2-й бит (признак ввода третьего адреса),
rjmp _ad33; < — двигаем сюда
sbrc mode, 1
rjmp inpConst3
rcall _noCursor
add xl, rKey; rKey -> в младшую тетраду мла. байта рег. пары X
inc count
rjmp constEnd; после ввода третьей ццфры, перейти на constEnd
; ввод третьей цифры второго адреса
inpConst3:
rcall _noCursor
add yl, rKey
inc count
rjmp constEnd

constEnd:; переходим сюда после ввода третьей цифры
brts _link; если Т-флаг выставлен, то это режим ввода данных, — переходим на scan
sbrc mode, 1; если выставлен признак ввода второго адреса,
rjmp _ad2; то сюда
sbrc mode, 2; если признак режима «КС» выставлен,
rjmp _ifCheckSum; то сюда
sbrc mode, 3; если признак режима «ПМ» выставлен,
rjmp _ifArray; то сюда
rcall _exit
sbic BUT, space; если нажата кнопка "_",
rjmp constEnd; то перепрыгиваем это,
ldi count, 1; сбрасываем счётчик
sbr mode, 3; в r19 теперь 0b00000011 (признак ввода второго адреса в ЗК)
; облагораживание содержимого экрана
;address2
rcall _down
rcall _labelAddr
rcall _labelTwo
rcall _colon
rcall _labelZero
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _left
rcall _left
rcall _left
rjmp scan

_ad2:; переходим сюда после ввода второго адреса
sbrc mode, 2; если признак «КС» выставлен, то перейти
rjmp _toCheckSum
sbrc mode, 3; если выставлен признак «ПМ»,
rjmp _toArray; < — сюда
rcall _exit
sbic BUT, space; если нажата кнопка "_",
rjmp _ad2; то пропускаем это,
ldi count, 1; сбрасываем счётчик
set; и выставляем признак ввода данных
; облагораживание содержимого экрана
; const
rcall _down
rcall _labelConst
rcall _colon
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _left
rcall _left
rcall _left
rcall _left
rcall _left
rcall _left
rcall _left
rjmp constEnd; -> в цикл

_link:
rjmp scan

_ifCheckSum:; ветка от «ЗК» к «КС», если 2-й бит в mode выставлен
sbic BUT, space
rjmp _ifCheckSum
ldi count, 1
; облагораживание содержимого экрана:
; address2
rcall _down
rcall _labelAddr
rcall _labelTwo
rcall _colon
rcall _labelZero
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _left
rcall _left
rcall _left
sbr mode, 7; признак ввода второго адреса «КС»
rjmp scan

_toCheckSum:; на выполнение «КС»
rcall _exit
sbic BUT, exe; если нажата «ВЫП», то перейти к выполнению «КС»
rjmp _toCheckSum
ldi rKey, 0x00; предварительно обнулить используемые регистры
ldi buf, 0x00
rjmp _SUM

_ifArray:; ветка от «КС» к «ПМ»
sbic BUT, space
rjmp _ifArray
ldi count, 1
; облагораживание содержимого экрана
;address2
rcall _down
rcall _labelAddr
rcall _labelTwo
rcall _colon
rcall _labelZero
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _left
rcall _left
rcall _left
sbr mode, 11
rjmp scan

_toArray:; переход после ввода второго адреса
ldi count, 1
sbr mode, 27; 0b00011011 -> r19 — признак ввода третьего адреса
rcall _exit
sbic BUT, space
rjmp _toArray
; облагораживание содержимого экрана
; address3
rcall _down
rcall _labelAddr
rcall _labelThree
rcall _colon
rcall _labelZero
rcall _labelSpace
rcall _labelSpace
rcall _labelSpace
rcall _left
rcall _left
rcall _left
rjmp scan

; первая цифра третьего адреса (ПМ)
_ad31:
mov zh, rKey
inc count
rjmp scan

; вторая цифра первого адреса
_ad32:
mov zl, rKey
swap zl
inc count
rjmp scan

; третья цифра третьего адреса
_ad33:
rcall _noCursor
add zl, rKey
inc count
rjmp _arrayEnd

; ожидание нажатия ВП после ввода третьего адреса
_arrayEnd:
rcall _exit
sbic BUT, exe
rjmp _arrayEnd
ldi buf, 0x00; предварительное обнуление buf
rjmp _execArray

; =============================== выполнение ЗК =================================
_execConst:
; первое сравнение
_ccp1:; yh > xh?
cp yh, xh
brlo _errConst; if yh < xh
breq _eq1; if yh = xh
rjmp _ccp2

_eq1:; if yh = xh
cp yl, xl; yl > xl?
brlo _errConst; if yl < xl

; если первое число меньше второго,
; то переходим ко второму сравнению
_ccp2:; if yh > xh
cp yh, xh; yh > xh?
breq _ccp22; if yh = xh
st X+, buf
rjmp _ccp2

_ccp22:; if yh = xh
cp yl, xl; yl > xl?
brlo _end; if xl > yl
st X+, buf
rjmp _ccp22

_errConst:; «ERROR», если первое число больше второго
rcall _clear
rcall _labelWrite
rcall _labelSpace
rcall _labelConst
rcall _point
rcall _down
rcall _noCursor
rcall _labelErr
rjmp _wait

_end:; выход
rjmp go

; ====================== подсчёт контрольной суммы массива ========================
_SUM:
; первое сравнение
_scp1:; yh > xh?
cp yh, xh
brlo _errSum; if yh < xh
breq _eq2; if yh = xh
rjmp _scp2

_eq2:; if yh = xh
cp yl, xl; yl > xl?
brlo _errSum; if yl < xl

; если первое число меньше второго,
; то переходим ко второму сравнению
_scp2:; if yh > xh
cp yh, xh; yh > xh?
breq _scp22; if yh = xh
rcall _exCS
brcs _overflow
rjmp _scp2

_scp22:; if yh = xh
cp yl, xl; yl > xl?
brlo _showSum; if xl > yl
rcall _exCS
brcs _overflow
rjmp _scp22

_showSum:; вывод контрольной суммы на экран
rcall _clear
rcall _labelSum
rcall _down
rcall _noCursor
mov rLCD, rKey
rcall _charFromMemory
_wait:
rcall _exit
rjmp _wait

_errSum:; «ERROR», если первое число больше второго
rcall _clear
rcall _labelSum
rcall _down
rcall _noCursor
rcall _labelErr
rjmp _wait

_overflow:; при превышении КС 255 — сюда
; сброс флагов переноса и полупереноса (на всякий случай)
clh
clc
ldi rKey, 0xff; если КС больше, чем 0xFF, то на экран выводится FF
rjmp _showSum

_exCS:; подпрограмма выполнения КС
ld buf, X+
add rKey, buf
;rcall _cpixl
ret

; ============================ перемещение массива ===============================
_execArray:
; первое сравнение
_acp1:; yh > xh?
cp yh, xh
brlo _errArr; if yh < xh
breq _eq3; if yh = xh
rjmp _acp2

_eq3:; if yh = xh
cp yl, xl; yl > xl?
brlo _errArr; if yl < xl

; если первое число меньше второго,
; то переходим ко второму сравнению
_acp2:; if yh > xh
cp yh, xh; yh > xh?
breq _acp22; if yh = xh
rcall _exMA
rjmp _acp2

_acp22:; if yh = xh
cp yl, xl; yl > xl?
brlo _end; if xl > yl
rcall _exMA
rjmp _acp22

_errArr:; «ERROR», если первое число больше второго
rcall _clear
rcall _mArray
rcall _down
rcall _noCursor
rcall _labelErr
rjmp _wait

_exMA:; подпрограмма выполнения ПМ
ld rKey, X+
st Z+, rKey
ret
; ===================================================================

_exit:
sbis BUT, quit; если нажата кнопка выхода,
rjmp go; то выходим из режима на go
ret
; ====================================================================

; работа с экраном

; инициализация LCD
altInit:
rcall _delay1
ldi rLCD, config; настройка дисплея: 8 бит, 2 строки, 5х8
rcall _cWrite

rcall _delay1
ldi rLCD, on; включить дисплей
rcall _cWrite

rcall _delay1
ldi rLCD, clrSc; очистка экрана
rcall _cWrite

rcall _delay1
ldi rLCD, incr; увеличение адреса, экран статичен
rcall _cWrite
ret

; проверка бита занятости
_BF:
rcall _portIn; порт данных на вход
rcall _modeB; режим чтения команды
_loop:
sbi _cpo, e
rcall _delay
cbi _cpo, e
in r24, _dpi; чтени шины данных
andi r24, 0x80; проверка 7-ого бита
brne _loop
ret

; задержка
_delay:
ldi r23, 20
_del:
dec r23
brne _del
ret

//задержка побольше
_delay1:
ldi r24, 0xff ;255
_d:
ldi r23, 0xff ;255
_cmp_d:
dec r23
brne _cmp_d
dec r24
brne _cmp_d
ret

; запись команды
_cWrite:
rcall _modeC
rcall _portOut
out _dpo, rLCD
rcall _delay
cbi _cpo, e
ret

; запись данных
_dWrite:
rcall _modeD
rcall _portOut
out _dpo, rLCD
rcall _delay
cbi _cpo, e
ret

; порт данных на выход
_portOut:
ldi r23, 0xff
out _dp, r23
ret

; порт данных на вход с подтяжкой
_portIn:
ldi r23, 0x00
out _dp, r23
ldi r23, 0xff
out _dpo, r23
ret

; режим записи команд
_modeC:
cbi _cpo, rw
cbi _cpo, rs
sbi _cpo, e
ret

; режим записи данных
_modeD:
sbi _cpo, rs
cbi _cpo, rw
sbi _cpo, e
ret

; режим ожидания BF
_modeB:
sbi _cpo, rw; чтение
cbi _cpo, rs; команды
ret

; формирование кода символа при вводе адресов и данных
_charInput:
cpi rLCD, 0x0a; сравнить с 10
brge _grt; если больше, то перейти на _grtA
rcall _lstA
rjmp _return
_grt:
rcall _grtA
_return:
ret

_lstA:
ldi r21, 0x30; если меньше,
add rLCD, r21; то прибавить 48
rcall _dWrite; записать в LCD
ret

_grtA:; если число от А...F
ldi r21, 0x37; 55 — прибавить к rLCD, чтобы получить код символа
add rLCD, r21;
rcall _dWrite; записать в LCD
ret

; формирование символа при чтении из памяти
; работает через rLCD
_charFromMemory:
push rLCD; отправили в стек
; формирование старшего байта
andi rLCD, 0xf0; очистить младший ниббл
swap rLCD; поменять нибблы местами
rcall _BF
rcall _charInput
; формирование старшего байта
pop rLCD; достать из стека
andi rLCD, 0x0f; очистить старший ниббл
rcall _BF
rcall _charInput
ret

; ========================================== всяческие надписи и настройки экрана ===============================

; "-"
_none:
;rcall _up
ldi rLCD, 0x2D
rcall _BF
rcall _dWrite
ret

; на вторую строку
_down:
ldi rLCD, down
rcall _BF
rcall _cWrite
ret

; надпись «BROWSE»
_browse:
ldi rLCD, 0x42; B
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x4f; O
rcall _BF
rcall _dWrite
ldi rLCD, 0x57; W
rcall _BF
rcall _dWrite
ldi rLCD, 0x53; S
rcall _BF
rcall _dWrite
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
ret

; надпись «MOVE ARRAY»
_mArray:
ldi rLCD, 0x4d; M
rcall _BF
rcall _dWrite
ldi rLCD, 0x4f; O
rcall _BF
rcall _dWrite
ldi rLCD, 0x56; V
rcall _BF
rcall _dWrite
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
rcall _labelSpace
ldi rLCD, 0x41; A
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x41; A
rcall _BF
rcall _dWrite
ldi rLCD, 0x59; Y
rcall _BF
rcall _dWrite
ret

; надпись «DATA»
_labelData:
ldi rLCD, 0x44; D
rcall _BF
rcall _dWrite
ldi rLCD, 0x41; A
rcall _BF
rcall _dWrite
ldi rLCD, 0x54; T
rcall _BF
rcall _dWrite
ldi rLCD, 0x41; A
rcall _BF
rcall _dWrite
ret

; надпись «WRITE»
_labelWrite:
ldi rLCD, 0x57; W
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x49; I
rcall _BF
rcall _dWrite
ldi rLCD, 0x54; T
rcall _BF
rcall _dWrite
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
ret

; надпись «CONST»
_labelConst:
ldi rLCD, 0x43; C
rcall _BF
rcall _dWrite
ldi rLCD, 0x4f; O
rcall _BF
rcall _dWrite
ldi rLCD, 0x4e; N
rcall _BF
rcall _dWrite
ldi rLCD, 0x53; S
rcall _BF
rcall _dWrite
ldi rLCD, 0x54; T
rcall _BF
rcall _dWrite
ret

; надпись «ADDRESS»
_labelAddr:
ldi rLCD, 0x41; A
rcall _BF
rcall _dWrite
ldi rLCD, 0x44; D
rcall _BF
rcall _dWrite
ldi rLCD, 0x44; D
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
ldi rLCD, 0x53; S
rcall _BF
rcall _dWrite
ldi rLCD, 0x53; S
rcall _BF
rcall _dWrite
ret

; надпись «CHECKSUM»
_labelSum:
ldi rLCD, 0x43; C
rcall _BF
rcall _dWrite
ldi rLCD, 0x48; H
rcall _BF
rcall _dWrite
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
ldi rLCD, 0x43; C
rcall _BF
rcall _dWrite
ldi rLCD, 0x4b; K
rcall _BF
rcall _dWrite
ldi rLCD, 0x53; S
rcall _BF
rcall _dWrite
ldi rLCD, 0x55; U
rcall _BF
rcall _dWrite
ldi rLCD, 0x4d; M
rcall _BF
rcall _dWrite
ret

; надпись «ERROR»
_labelErr:
ldi rLCD, 0x45; E
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ldi rLCD, 0x4f; O
rcall _BF
rcall _dWrite
ldi rLCD, 0x52; R
rcall _BF
rcall _dWrite
ret

; «0»
_labelZero:
ldi rLCD, 0x30
rcall _BF
rcall _dWrite
ret

; «1»
_labelOne:
ldi rLCD, 0x31
rcall _BF
rcall _dWrite
ret

; «2»
_labelTwo:
ldi rLCD, 0x32
rcall _BF
rcall _dWrite
ret

; «3»
_labelThree:
ldi rLCD, 0x33
rcall _BF
rcall _dWrite
ret

; пробел
_labelSpace:
ldi rLCD, 0x20
rcall _BF
rcall _dWrite
ret

; очистка обеих строк
_clear:
ldi rLCD, clrSc
rcall _BF
rcall _cWrite
ret

; очистка экрана и вывод символа "-"
_resetLCD:
rcall _clear
rcall _noCursor; без курсора
rcall _BF
;rcall _cWrite
rcall _none
ret

; настройка мигающего курсора
_cursor:
ldi rLCD, cursor
rcall _BF
rcall _cWrite
ret

; отключение курсора
_noCursor:
ldi rLCD, noCursor
rcall _BF
rcall _cWrite
ret

; "."
_point:
ldi rLCD, 0x2e
rcall _BF
rcall _dWrite
ret

; ":"
_colon:
ldi rLCD, 0x3a
rcall _BF
rcall _dWrite
ret

; сдвиг курсора вправо
_right:
ldi rLCD, right
rcall _BF
rcall _cWrite
ret

; сдвиг курсора влево
_left:
ldi rLCD, left
rcall _BF
rcall _cWrite
ret

А это видео, на котором выполняется изменение и просмотр содержимого ячейки памяти и следующих за ней ячеек.
Tags:
Hubs:
+7
Comments 10
Comments Comments 10

Articles