Микропроцессорная система управления, предназначенная для использования на лесопильном заводе
то ошибка ПЗУ… ; иначе тест ПЗУ успешен, продолжаем тесты
Подпрограмма E_ROM выводит на два самых левых индикатора условный код ошибки ПЗУ (“E2”), а остальные индикаторы гасит. Индикация такой ошибки говорит о необходимости проверки и перепрограммирования микросхемы ПЗУ.
Листинг 4: процедура E_ROM, вызываемая после ошибки ПЗУ
; – – – обработка ошибки ПЗУ (код “E2”)
E_ROM LD A, #79 ; A¬“E”
OUT (#0A), A ;
LD A, E ; A¬код ошибки (2)
OUT (#09), A ;
XOR A ; A¬“0”
LD C, #08 ;
LD D, #18 ;
LD B, 6 ; счетчик
E_ROM1 OUT (C), A ; последние шесть индикаторов гасим
OUT (D), A ;
DEC C ;
DEC D ;
DJNZ E_ROM1 ;
JR E_ROM ;
Тест фотоэлементов
Перед началом работы нужно проверить прохождение света от ламп к фотоэлементам. Если один из фотоприемников не чувствует света, это значит, что или перегорела лампа фотоэлемента, или в момент пуска бревно уже лежит между фотоэлементами и его длина не может быть измерена, или повредился канал от фотоэлемента к процессору. В обоих случаях микропроцессорная система не начнет свою работу (т.к. нет возможности отличить второй случай от остальных). При обнаружении такой ошибки на индикаторы выводится признак ошибки фотоэлемента в виде условного кода “E3”.
В программе этого теста читается порт ввода статуса фотоэлементов (адрес 0B) и проверяются на ноль два его младших бита. Если хотя бы один не равен нулю, то происходит переход на обработчик ошибки. Причем для обработки ошибки можно использовать ту же процедуру E_ROM, перед вызовом загрузив в регистр E код ошибки 3 (экономия памяти).
Листинг 5: тест фотоэлементов
; – – – проверка двух младших битов порта ФЭЛ на 0
TST_FEL IN A, (#0B) ; прочесть слово статуса ФЭЛ
AND 3 ; наложить на него маску 000000112
LD E, 3 ;
JP NZ, E_ROM ; если не ноль, то ошибка
… ; иначе продолжаем тесты
Инициализация программируемого контроллера прерываний
Для инициализации контроллера надо переслать ему два управляющих слова ICW1 и ICW2, первое по адресу 0CН (A0=0), второе – по адресу 0DН (A0=1).
Пересылаются следующие управляющие слова:
Рис. 6 Применяемые управляющие слова инициализации ПКП
Слово ICW1 установит одиночный (без каскадного соединения) режим работы ПКП (бит 1), 4-хбайтный интервал для начальных адресов обработчиков прерываний (бит 2).
Биты 5–7 слова ICW1 вместе со всем словом ICW2 сообщат контроллеру, что первый обработчик (запроса IR0) начинается с адреса 0020Н.
Контроллер накладывает определенные ограничения на расположение обработчиков в памяти. Первое из них в том, подпрограммы обработки прерываний должны располагаться по порядку, начиная с адреса обработчика запроса IR0, и с постоянным интервалом, т.е. образовывать в памяти таблицу. Интервал расположения может составлять 4 байта (если бит 2 ICW1 равен единице, как в нашем случае), или 8 байт (если бит 2 равен нулю). При постоянном интервале адреса всех обработчиков определятся расположением первого (IR0).
Адрес первого обработчика составляется из полного слова ICW2 (старший байт) и битов 7,6,5 слова ICW1 (старшие три бита младшего байта адреса).
В нашем случае старший байт равен 00Н, младший байт равен 0010 0000 = 20Н. Обработчики имеют начальные адреса: 0020H, 0024H, 0028H, 002CH, 0030H, 0034H (6 обработчиков).
Процедура INI_PIC инициализирует контроллер.
Листинг 6: инициализация программируемого контроллера прерываний
; – – – переслать 38H в порт 0CH и 0 в порт 0DH
INI_PIC LD A, #38 ;
OUT (#0C), A ;
XOR A ;
OUT (#0D), A ;
Инициализация переменных системы
В системе за некоторыми ячейками памяти закреплена функция хранения переменных. Например, подсчитанный суммарный объем древесины VS в двоичном формате хранится в отдельных двух байтах памяти с адресами V_SUM, V_SUM+1.
Требуют инициализации только две переменные (инициализируются нулем):
VS (2 байта) – начальный адрес V_SUM;
Время (4 байта) – начальный адрес TIME.
Ясно, что эти имена (V_SUM и TIME) лишь условные обозначения (также, как, например, имена меток в листингах программ). При переводе в машинный код эти имена транслируются в двухбайтный адрес.
Листинг 7: инициализация ячеек суммарного объема и времени
; – – – начальное обнуление объема и времени
INI_VAR XOR A ;
LD (V_SUM), A ;
LD (V_SUM+1), A ;
LD (V_SUM+2), A ;
LD (TIME), A ;
LD (TIME+1), A ;
LD (TIME+2), A ;
LD (TIME+3), A ;
… ; следует продолжение переходит к основному циклу работы (описание см. в п.4.4).
Арифметические подпрограммы
Микропроцессорной системе необходимо “уметь” выполнять арифметические операции: сложение, вычитание, умножение, деление и косинус. В этом же разделе приведем и неарифметические процедуры для осуществления индикации: преобразования данных в двоично-десятичный код и в семисегментный.
Основным форматом чисел в МП системе является двухбайтный формат с фиксированной точкой вида 1байт , 1байт . Формат беззнаковый, предполагается, что числа положительны. Одно число умещается в одной регистровой паре. Минимальное представимое число – 0,01H=1/256=3,9Ч10-3, максимальное – FF,FFН=256,996.
Общие правила для всех вычислительных процедур:
программист вызывающей программы сам следит, чтобы операнды и результат не выходили за пределы представления и чтобы при вычитании не образовалось отрицательных величин;
операнды при сложении, вычитании, умножении, делении хранятся в регистровых парах HL и DE, результат в HL. Процедура косинуса получает операнд и выводит результат в паре HL.
Сложение
В микропроцессоре Z80 есть команда сложения регистровых пар ADD HL, DE. Однако, чтобы сохранить единообразие, оформим ее все же как подпрограмму.
Листинг 8: подпрограмма PLUS
; – – – подпрограмма сложения
; HL+DE®HL
; сохраняет A, BC, DE
PLUS ADD HL, DE ;
RET ;
Вычитание
В Z80 есть команда вычитания регистровых пар с учетом переноса SBC HL, DE. Используем ее в подпрограмме вычитания, обнулив прежде флаг CY.
Листинг 9: подпрограмма MINUS
; – – – подпрограмма сложения
; HL–DE®HL
; сохраняет A, BC, DE
MINUS OR A ; A не меняется, но флаг CY=0
SBC HL, DE ;
RET ;
Умножение
При умножении первый множитель хранится в паре BC (скопируем его туда из HL в начале), второй множитель хранится в DE. Результат первоначально получается в четырехбайтном виде HL.DE, затем “обрезается” до H.L.
Результат произведения накапливается в HLDE. В течение 16 циклов сдвигов HLDE второй множитель DE постепенно выдвигаясь, уходит вправо и на его место приходит из HL готовые биты произведения. Они получаются при суммировании первого множителя (BC) и левой половины накопленной суммы HLDE (HL). Суммирование делается в случае, если перед этим при сдвиге HLDE вправо из DE был выдвинут единичный бит.
Листинг 10: подпрограмма MUL
; – – – подпрограмма умножения
; HLґDE®HL
; все регистры меняются
MUL LD B, H ; BC=1-й множитель
LD C, L ;
LD HL, 0 ; HL=0
LD A, 16 ; счетчик цикла
MUL2 SRL H ; сдвиг 0®HLDE®CY
RR L ;
RR D ;
RR E ;
JR NC, MUL1 ; если выдвинут 0, то на конец цикла
ADD HL, BC ; если выдвинута 1, то сложить
MUL1 DEC A ;
JR NZ, MUL2 ;
LD H, L ; получено произведение HL.DE
LD L, D ; которое “обрезаем” до L.D и переносим в H.L
RET
Деление
При делении делимое хранится в HL, делитель в DE. Результат получается в виде трех байт [стек.L] (где “стек” – содержимое вершины стека), в конце программы младший байт стека переносится в H и окончательный результат имеет формат H.L. Регистровая пара BC в начале обнуляется.
Деление производится в течение 24-х циклов. В каждом цикле делается сдвиг BC¬HL¬0, т.е. делимое HL выдвигается влево в пару BC. Затем BC сравнивается с DE (путем вычитания BC–DE и проверки флага переноса). Если BC>DE, то младший разряд HL устанавливается единицей, в противном случае он остается нулем. Это один из разрядов частного.
После 16 сдвигов в делимое HL полностью сдвинется влево и на его место придет частное целочисленного деления, в BC будет остаток деления, DE сохранит делитель. На этом этапе целая часть частного в HL заносится в стек, а HL обнуляется. В оставшихся 8-ми сдвигах BC¬HL в BC будут сдвигаться только нули. После всех 24-х циклов в регистре L будет дробная часть частного, а в стеке целая часть.
Листинг 11: подпрограмма DIV
; – – – подпрограмма деления
; HL/DE®HL
; сохраняет DE, использует стек (2 байта)
DIV LD BC, 0 ;
LD A, 24 ; А – счетчик циклов
DIV4 PUSH AF ; сохранить счетчик А в стеке
ADD HL, HL ; BC¬HL¬0
RL C ;
RL B ;
LD A, C ;
SUB E ;
LD C, A ;
LD A, B ;
SBC D ;
LD B, A ;
JR NC, DIV1 ; если BC>DE, то переход
LD A, C ;
ADD E ;
LD C, A ;
LD A, B ;
ADC D ;
LD B, A ;
JR DIV2 ;
DIV1 INC HL ; если BC>DE: установить последний бит
; частного в 1
DIV2 POP AF ; извлечь счетчик А из стека
CP #09 ; проверить, 16-й цикл идет или нет
JR NZ, DIV3 ;
PUSH HL ; если 16-й цикл, то в HL целая часть частного,
; сохранить ее в стеке, чтобы извлечь
; в конце программы
LD HL, 0 ;
DIV3 DEC A ;
JR NZ, DIV4 ; конец цикла
POP BC ; извлекаем из стека целую часть частного
LD H, C ; получаем частное в H.L
RET ;
Косинус
Чтобы вычислить косинус, используется ограниченный четырьмя членами ряд Тейлора:
, угол a изменяется от 0 до p/2. (6)
Эта формула дает косинус с максимальной погрешностью 8,9Ч10-4, это меньше, чем ошибка разрядности используемого формата [1].[1] дробных чисел.
(7)
Представление (7) формулы (6) гораздо удобнее для вычисления косинуса программно. Достаточно сделать процедуру для вычисления , где n – целое число, b – дробное число. Перед обращением к этой процедуре (названной COS_A) операнды содержатся:
HL = a2;
DE = b;
BC = n в формате n.0 (B=n, C=0).
Результат – в HL
Эта подпрограмма использует предыдущие арифметические программы.
Листинг 12: подпрограмма COS_A
; – – – вспомогательная подпрограмма для косинуса
; операнды: HL, DE, BC
; изменяются все регистры
COS_A PUSH BC ; сохранить в стеке n перед вызовом MUL
CALL MUL ; HL=HLґDE
POP DE ; DE=n
CALL DIV ; HL=HL/DE
LD D, 1 ; DE=1.0
LD E, 0 ;
EX HL, DE ;
CALL MINUS ; HL=HL-DE
RET ;
Подпрограмма косинуса вычисляет косинус угла в паре HL.
Листинг 13: подпрограмма COS
; – – –подпрограмма косинуса
; операнд и результат в HL
; изменяются все регистры
COS LD D, H ;
LD E, L ;
CALL MUL ; HL=a2
PUSH HL ; a2 в стеке
LD D, 1 ; DE=1.0
LD E, 0 ;
LD B, #1E ; BC=1E.0H=30
LD C, 0 ;
CALL COS_A ; HL=1-a2/30
EX HL, DE ;
POP HL ; HL=a2
PUSH HL ; a2 в стеке
LD B, #0C ; BC=C.0H=12
LD C, 0 ;
CALL COS_A ; HL=1-a2/12(1-a2/30)
EX HL, DE ;
POP HL ; HL=a2
LD B, #02 ; BC=2.0
LD C, 0 ;
CALL COS_A ; HL=1-a2/2[1-a2/12(1-a2/30)]
RET ;
Преобразование двоичный®двоично-десятичный код
В управляющей программе надо переводить двухбайтные числа формата [12].[34] в двоично-десятичный код вида [123.45] размером в пять тетрад для последующего перевода в семисегментный код и индикации.
Для этого нужны две отдельных подпрограммы: одна (по имени B2D) для перевода целой части числа [12] и вторая (имя B2D_F) для перевода дробной части числа [34].
Подпрограмма B2D переводит целое двоичное число в регистре C (0..FFH) в 2-10 код, расположенный в регистровой паре HL. Перевод производится в соответствии с формулой:
HL=(…((c7)Ч2+c6)Ч2+…+c1)Ч2+c0,
в которой ci – разряды числа в регистре C, а удвоение и сложение с битами ci происходит по правилам десятичной арифметики (с командой DAA после операции).
Листинг 14: подпрограмма B2D
; – – – перевод байта (целого) в 2-10 код
; операнд C – переводимое число, результат в HL
; сохраняет DE
B2D LD B, 8 ;
B2D1 SLA C ; CY¬C (получаем последний бит операнда)
LD A, L ; удвоение HL с учетом переноса CY
ADC L ; по правилам десятичной арифметики
DAA ;
LD L, A ;
LD A, H ;
ADC H ;
DAA ;
LD H, A ;
DJNZ B2D1 ; конец цикла
RET ;
Подпрограмма B2D_F переводит дробное число в формате 0.L в 2-10 код из трех тетрад в формате 0.ABC (учитываются три цифры после запятой). Регистры A, B, C содержат в конце каждый по одной десятичной цифре. Перевод происходит так. Число 0.L умножается на 10, результат в паре H.L. Его целая часть (H) и будет первой цифрой A результата. Затем H обнуляется, полученная дробь 0.L снова умножается на 10 и т.д.
Для быстрого умножения на 10 сделана отдельная подпрограмма MUL10, умножающая пару HL (где H=0) и получающая результат в той же HL. Она использует равенство:
10ЧHL=2ЧHL+8ЧHL, а умножения на 2 и на 8 делаются с помощью команды ADD HL, HL.
Листинг 15: подпрограммы B2D_F и MUL10
; – – – перевод байта (дробного) в 2-10 код
; операнд C – число с фиксированной перед старшим разрядом точкой, результат в ABC
; изменяются все регистры
B2D_F LD H, 0 ;
CALL MUL10 ; получить в H первую цифру
LD A, H ; скопировать ее в A
LD H, 0 ; и обнулить H
CALL MUL10 ;
LD B, H ; вторую цифру – в регистр B
LD H, 0 ;
CALL MUL10 ; третью – в C
LD C, H ;
RET
; – – – умножение на 10
; операнд в HL (имеет значение только L) и результат в HL
; сохраняет A, BC
MUL10 ADD HL, HL ; HLЧ10=HLЧ2+HLЧ8
LD D, H ;
LD E, L ;
ADD HL, HL ;
ADD HL, HL ;
ADD HL, DE ;
RET ;
Преобразование двоичный®семисегментный код
Это преобразование с помощью таблицы перекодировки уже встречалось в тестовой программе ОЗУ. Сейчас оформим ее как отдельную подпрограмму (в тесте ОЗУ нельзя вызывать подпрограммы, т.к. команда вызова CALL использует стек). Операнд подпрограммы – двоично-десятичная цифра в регистре A (0..9, старшая тетрада нулевая).
Листинг 16: подпрограмма D27
; – – – перевод байта в семисегментный код
; операнд (0..9) и результат в A
; сохраняет BC, DE
D27 LD H, #07 ;
LD L, A ;
LD A, (HL) ;
RET ;
Обработчики прерываний
Всего их шесть – по числу прерываний. Обработчик обязательно должен сохранить в стеке все изменяемые им регистры и в конце восстановить их. Выход из обработчика выполняется не стандартным RET, а командой RETI.
Обработчик IRQ0 (начало измерений)
Функция обработчика – обнулить таймеры T1 и T2, а также специальную ячейку памяти D_NUM (2 байта). Эта ячейка инкрементируется всякий раз после чтения напряжения Ud с датчика диаметра. По приходу запроса IRQ0, когда приходит новое бревно, она должна быть обнулена.
Листинг 17: обработчик запроса IRQ0
; – – – обнулить T1, T2, D_NUM
IR0_H PUSH BC ;
PUSH AF ;
LD BC, 0 ;
LD (T1), BC ; обнулить T1
LD (T2), BC ; обнулить T2
LD (D_NUM), BC ; обнулить D_NUM
POP AF ;
POP BC ;
RETI ;
Обработчик IRQ2 (информация с АЦП готова)
Функция обработчика – если бревно сейчас под пластиной датчика (можно судить, прочтя порт фотоэлементов с адресом 0B), считать два байта напряжения из портов 00 (младший) и 01 (старший). Записать их в массив напряжений, под который отведена область памяти начиная с адреса 0100Н до конца ОЗУ (всего 2К). Перед записью проверить, не заполнен ли этот массив. Инкрементировать ячейку D_NUM, содержащую число элементов этого массива.
Листинг 18: обработчик запроса IRQ2
; – – – считать и обработать байт с АЦП
IR2_H PUSH BC ;
PUSH HL ;
PUSH AF ;
IN A, (#0B) ;
AND #02 ; наложить маску 000000010
JR Z, IR2_H1 ; если второй бит нулевой, то выход
LD HL, #1000 ; рассчитать адрес очередного элемента массива
LD C, (D_NUM) ; считать D_NUM
LD B, (D_NUM+1) ;
SLA C ; умножить его на 2
RL B ;
ADD HL, BC ; теперь адрес в HL
LD A, H ;
CP #18 ;
JR NC, IR2_H1 ; если вышли за пределы массива, то выход
IN A, (#00) ; считать первый байт с АЦП
LD (HL), A ; и отправить его в память
INC HL ;
IN A, (#01) ; считать второй
LD (HL), A ; отправить
INC BC ; увеличить переменную D_NUM на единицу
LD (D_NUM), BC ;
IR2_H1 POP AF ;
POP HL ;
POP BC ;
RETI ;
Обработчик IRQ3 (от генератора 16 Гц)
Функция обработчика – произвести инкремент часов реального времени и условный инкремент таймеров. Часы реального времени – это 4 байта в памяти:
TIME 1/16 секунды (0..15);
TIME+1 секунды (0..59);
TIME+2 минуты (0..59);
TIME+3 часы (0..23);
Все величины хранятся в двоичном формате.
Таймерам T1 и T2 отведено по 2 байта с начальными адресами T1 и T2. Условия, при которых они инкрементируются, были приведены в п. 3.4.
Листинг 19: обработчик запроса IRQ3
; – – – инкремент часов реального времени и условный инкремент таймеров
IR3_H PUSH BC ;
PUSH HL ;
PUSH AF ;
; часы реального времени
LD HL, TIME ;
INC (HL) ; инкремент 1/16 секунд
LD A, (HL) ;
CP 16 ; проверить на достижение максимума JR C, IR3_H1 ; условный выход из подпрограммы
LD (HL), 0 ; иначе обнулить 1/16 секунды и продолжить
INC HL ;
LD B, 2 ; инкремент секунд и минут делается в цикле
IR3_H2 INC (HL) ;
LD A, (HL) ;
CP 60 ;
JR C, IR3_H1 ;
LD (HL), 0 ;
INC HL ;
DJNZ IR3_H2 ; конец цикла
INC (HL) ; инкремент часов
LD A, (HL) ;
CP 24 ;
JR C, IR3_H1 ;
XOR A ; если счетчик часов=24
LD (HL), A ; то обнулить все 4 байта часов реального времени
DEC HL ;
LD (HL), A ;
DEC HL ;
LD (HL), A ;
DEC HL ;
LD (HL), A ;
; таймеры
IR3_H1 IN A, (#0B) ; загрузить слово статуса фотоэлементов
AND 1 ;
JR Z, IR3_H3 ; если не установлен 1-й бит, то выход
LD HL, T2 ; иначе инкремент Т2
INC (HL) ;
JR NZ, IR3_H4 ; если инкремент не обнулил
; первый байт Т2, то идем дальше
INC HL ; иначе увеличить на 1 и второй байт
INC (HL) ;
IR3_H4 IN A, (#0B) ;
AND 2 ; проверить 2-й бит статуса ФЭЛ
JR Z, IR3_H3 ; если он не установлен, то выход
LD HL, T1 ; иначе инкремент Т1
INC (HL) ;
JR NZ, IR3_H3 ;
INC HL ;
INC (HL) ;
IR3_H3 POP AF ;
POP HL ;
POP BC ;
RETI ;
Обработчик IRQ4 (от кнопки “+Час”)
Функция обработчика – увеличить на единицу часы реального времени (ячейка TIME+3).
Листинг 20: обработчик запроса IRQ4
; – – – инкремент часов
IR4_H PUSH HL ;
PUSH AF ;
LD HL, TIME+3;
INC (HL) ; инкремент часов
LD A, (HL) ;
CP 24 ;
JR C, IR4_H1 ;
XOR A ; если счетчик часов=24
LD (HL), A ; то обнулить часы и минуты
DEC HL ;
LD (HL), A ;
IR4_H1 POP AF ;
POP HL ;
RETI
Обработчик IRQ5 (от кнопки “+Мин”)
Функция обработчика – увеличить на единицу минуты реального времени (ячейка TIME+2).
Листинг 21: обработчик запроса IRQ5
; – – – инкремент минут
IR5_H PUSH HL ;
PUSH AF ;
LD HL, TIME+2;
INC (HL) ; инкремент минут
LD A, (HL) ;
CP 60 ;
JR C, IR5_H1 ;
XOR A ; если счетчик минут=60
LD (HL), A ; то обнуление минут
INC HL ; и инкремент часов
INC (HL) ;
LD A, (HL) ; с проверкой часов на 24
CP 24 ;
JR C, IR5_H1 ;
XOR A ; если счетчик часов=24
LD (HL), A ; то обнулить и часы
IR4_H1 POP AF ;
POP HL ;
RETI
Обработчик IRQ1 (от фотоэлемента Фэл2)
Обработчик IRQ1 выполняет самую важную функцию. Его задача – вычислить объем бревна. Последовательность следующая: вычисляем диаметр бревна, длину, вычисляем объем Vi, находим объем VS.
Для вычисления диаметра все значения, прежде считанные в массив напряжений с АЦП, усредняются: суммируются и делятся на количество (D_NUM). При суммировании может произойти переполнение суммы (а она двухбайтная), чтобы этого не было, массив разбивается на группы по 16 измерений в каждой. Если осталась остаточная группа с числом меньше 16, то она отбрасывается. В каждой из них подсчитывается среднее, затем рассчитывается искомое как среднее средних.
Из среднего напряжения находится угол
a=.
Затем находим диаметр d=0,625 – 0,5cos a=00,A0H – 00,80Hcos a. Занести его в ячейку DIAM (2 байта в памяти). Сравнить диаметр с допустимыми пределами [0,2..0,5]=[0,33H..0,8H]. Если он выходит за эти пределы, то выдать на отбраковку (порт 02H) единицу.
Объем Vi находится как Vi=(p/4)d2ЧT1/T2=0,C9HЧd2ЧT1/T2.
Листинг 22: обработчик запроса IRQ1
; – – – найти объем бревна и суммарный объем
; усреднение всех напряжений с датчика диаметра в массиве по адресу 1000H
IR1_H PUSH AF ;
PUSH BC ;
PUSH DE ;
PUSH HL ;
LD L, (D_NUM) ;
LD H, (D_NUM+1) ;
LD B, 4 ; делим D_NUM на 16
IR1_H1 SRL H ;
RR L ;
DJNZ IR1_H1 ;
LD C, L ; в результате C=число групп по 16
PUSH BC ; сохранить С в стеке
LD HL, #1000 ;
LD DE, 0 ; DE – начальная сумма групп
PUSH DE ; отправить ее в стек, C станет второй в стеке
PUSH DE ; DE – начальная сумма отдельной группы,
; отправить®в стек, сумма групп вторая в стеке
; C – третья в стеке
IR1_H4 LD B, 16 ;
IR1_H2 LD E, (HL) ; читаем в DE элемент массива
INC HL ;
LD D, (HL) ;
INC HL ;
EX (SP), HL ; текущую сумма в HL, текущий адрес в стеке
ADD HL, DE ;
EX (SP), HL ; новая сумма в стеке, текущий адрес в HL
DJNZ IR1_H2 ;
; в итоге сумма одной группы по 16 – в стеке
; начальный адрес следующей группы – в HL
POP DE ;
LD B, 4 ; находим среднее одной группы,
IR1_H3 SRL D ; деля сумму в DE на 16
RR E ;
DJNZ IR1_H3 ;
POP HL ; берем из стека сумму групп
ADD HL, DE ;
PUSH HL ; снова отправляем в стек: сначала сумму групп
PUSH DE ; затем сумму одной группы
DEC C ; С – счетчик групп
JR NZ, IR1_H4 ; следующая группа…
POP HL ;
POP HL ; извлечь найденную сумму групп
POP BC ; извлечь счетчик групп C
LD D, C ; DE=C.0
LD E, 0 ;
CALL DIV ; делим сумму групп на число групп
; теперь HL=Ud=среднее всего массива напряжений датчика
; следующий шаг – нахождение угла a, cos a, d
LD D, 4 ; DE=491H
LD E, #91 ;
LD B, H ; сохранить Ud в BC
LD C, L ;
EX DE, HL ;
CALL MINUS ; HL=491H-Ud
EX HL, DE ;
LD H, B ;
LD L, C ;
CALL DIV ; HL= Ud/(491H-Ud)
LD D, 0 ;
LD E, #4F ;
CALL MUL ; HL=a
CALL COS ; HL=cos a
LD D, 0 ;
LD E, #80 ;
CALL MUL ; HL=0,5cos a
LD D, 0 ;
LD E, #A0 ;
EX HL, DE ;
CALL MINUS ; HL=0,625-0,5cos a=d
LD (DIAM), HL ; занести диаметр в память
; формируем сигнал отбраковки
LD A, L ;
CP #33 ;
JR NC, IR1_H5 ; если d>20, то идем дальше
LD A, 1 ; иначе в порт отбраковки записать 1
OUT (#02), A ;
JR IR1_OUT ; и выход, не считая объем
IR1_H5 LD A, L ;
CP #80 ;
JR C, IR1_H6 ; если d<50, то идем дальше
LD A, 1 ; иначе в порт отбраковки записать 1
OUT (#02), A ;
; дальше находим Vi, VS
IR1_H6 LD L, (T1) ;
LD H, (T1+1) ;
LD E, (T2) ;
LD D, (T2+1) ;
CALL DIV ; HL=T1/T2
LD E, (DIAM) ;
LD D, (DIAM+1) ;
PUSH DE ;
CALL MUL ; HL=T1Чd/T2
POP DE ;
CALL MUL ; HL=T1Чd2/T2
LD D, 0 ;
LD E, #C9 ;
CALL MUL ; HL=(p/4)T1Чd2/T2=Vi
EX HL, DE ;
LD L, (V_SUM) ;
LD H, (V_SUM+1) ;
CALL PLUS ; HL=VS
LD (V_SUM), HL ; занести суммарный объем в память
IR1_OUT POP HL ;
POP DE ;
POP BC ;
POP AF ;
RETI ;
Основная исполняемая часть программы
Эту часть, которая, собственно, и является управляющей программой, микропроцессор выполняет большую часть своего времени, изредка отвлекаясь от нее на обработку прерываний.
Ее функции:
прочесть VS из ячейки V_SUM, перевести его в 2-10 код (подпрограммы B2D, B2D_F), перевести каждую цифру в семисегментный код (подпрограмма D27) и вывести в порт индикаторов (03-0A), присоединив десятичную запятую в индикаторе 06Н;
прочесть время из ячейки TIME (TIME+1–секунды, TIME+2–минуты, TIME+3–часы), перевести его в 2-10 код, перевести каждую цифру в семисегментный код и вывести в порт индикаторов (13-1A), присоединив десятичную запятую в индикаторах 13Н, 15Н, 17Н.
Рис. 7 Расположение объема и времени на индикаторах
Листинг 23: основная часть
; – – – вывести на индикаторы объем и время
; вывод объема
MAIN LD E, (V_SUM) ;
LD D, (V_SUM+1) ; D.E – суммарный объем
; выводим целую часть D
LD C, D ;
CALL B2D ; HL=2-10 код D
LD A, H ;
CALL D27 ; в семисегментный код
OUT (#08), A ; вывести в порт
LD A, H ;
AND #0F ;
CALL D27 ;
OR #80 ; примешать десятичную запятую
OUT (#06), A ; вывести в порт
LD A, H ;
RR A ; четырежды сдвигаем вправо
RR A ;
RR A ;
RR A ;
AND #0F ;
CALL D27 ;
OUT (#07), A ; вывести в порт
; выводим дробную часть E
LD L, E ;
CALL B2D_F ; ABC=три цифры 2-10 кода числа 0.L
CALL D27 ;
OUT (#05), A ; вывести в порт
LD A, B ;
CALL D27 ;
OUT (#04), A ; вывести в порт
LD A, C ;
CALL D27 ;
OUT (#03), A ; вывести в порт
; вывод текущего времени
LD HL, (TIME+1) ; HL=адрес секунд
LD B, #13 ; самый правый индикатор
MAIN1 PUSH HL ;
LD A, (HL) ;
LD C, A ;
CALL B2D ; HL=2-10 код секунд/минут/часов (причем Н=0)
LD A, L ;
AND #0F ;
CALL D27 ;
OR #80 ; примешать запятую
OUT (B), A ; вывести в порт
INC B ; следующий индикатор
LD A, L ;
RR A ; четырежды сдвигаем А вправо
RR A ;
RR A ;
RR A ;
AND #0F ;
CALL D27 ;
OUT (B), A ; вывести в порт
INC B ; следующий индикатор
POP HL ;
INC HL ; перейти к следующей ячейке (TIME+2, TIME+3)
LD A, B ;
CP #08 ; проверка конца цикла
JR NZ, MAIN1 ;
JP MAIN ; начинаем все сначала
Общая структура ПЗУ микропроцессорной системы
Наконец, приведем карты ПЗУ и ОЗУ микропроцессорной системы.
ПЗУ
0000 | DI |
0001 |
Начало тестов TST_RAM JR 0038 |
0020 |
Распределитель обработчиков прерываний 0020 JP IR_H0 0024 JP IR_H1 0028 JP IR_H2 002C JP IR_H3 0030 JP IR_H4 0034 JP IR_H5 |
0038 |
Продолжение теста ОЗУ (TST_RAM) TST_ROM TST_FEL INI_PIC INI_VAR |
EI | |
LD SP, #0FFF | |
Главный цикл программы MAIN |
|
Подпрограммы E_RAM E_ROM PLUS MINUS MUL DIV COS_A COS B2D B2D_F MUL10 D27 |
|
Обработчики IR_H0 IR_H1 IR_H2 IR_H3 IR_H4 IR_H5 |
|
Область нулей 00000000 | |
0700 | Таблица перекодировки |
070F | 00000000 |
07FF | Последний байт (дополнение контрольной суммы до нуля) |
Общая структура ОЗУ микропроцессорной системы. ОЗУ
0800 | V_SUM |
0802 | DIAM |
0804 | TIME |
0808 | T1 |
080A | T2 |
080C | D_NUM |
080D | |
0FFF | ОБЛАСТЬ СТЕКА |
1000 |
Массив напряжений с датчика диаметра (2К) |
1FFF | Конец ОЗУ |
Заключение
В результате проделанной работы мы получили систему, отбраковывающие бревна, не годные для распиливания и поэтому отбрасывающиеся в сторону. Если бревно нам подходит, то идет подсчет его объема и подсчет всех бревен, прошедших распил. Также считается время наработки.
ПЕРЕЧЕНЬ ЭЛЕМЕНТОВ
|