Подпрограмма 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 LDH, #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
LDB, (D_NUM+1) ;
SLA C ; умножить его на 2
RL B ;
ADD HL, BC ; теперь адрес в HL
LDA, 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 ;
POPBC ;
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 ;
PUSHAF ;
; часы реального времени
LD HL, TIME ;
INC (HL) ; инкремент 1/16 секунд
LDA, (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 ;
JRC, 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 ;
JRC, 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 ;
JRC, IR5_H1 ;
XOR A ; если счетчик минут=60
LD (HL), A ; то обнуление минут
INC HL ; и инкремент часов
INC (HL) ;
LD A, (HL) ; с проверкой часов на 24
CP 24 ;
JRC, 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 ;
DJNZIR1_H1 ;
LD C, L ; в результате C=число групп по 16
PUSH BC ; сохранить С в стеке
LD HL, #1000 ;
LD DE, 0 ; DE – начальная сумма групп
PUSH DE ; отправить ее в стек, C станет второй в стеке
PUSH DE ; DE – начальная сумма отдельной группы,
; отправить®в стек, сумма групп вторая в стеке
; C – третья в стеке
IR1_H4 LDB, 16 ;
IR1_H2 LD E, (HL) ; читаем в DE элемент массива
INC HL ;
LD D, (HL) ;
INC HL ;
EX (SP), HL ; текущую сумма в HL, текущий адрес в стеке
ADDHL, DE ;
EX (SP), HL ; новая сумма в стеке, текущий адрес в HL
DJNZIR1_H2 ;
; в итоге сумма одной группы по 16 – в стеке
; начальный адрес следующей группы – в HL
POP DE ;
LD B, 4 ; находим среднее одной группы,
IR1_H3 SRL D ; деля сумму в DE на 16
RR E ;
DJNZ IR1_H3 ;
POP HL ; берем из стека сумму групп
ADDHL, 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
LDD, 4 ; DE=491H
LD E, #91 ;
LD B, H ; сохранить Ud в BC