Все про Assembler українською мовою на сайті net.kr.ua

 

:: Меню ::

Головна
Введення
Архітектура реального режиму
Основи програмування
Команди і алгоритми
Розширені можливості сучасних мікропроцесорів
Система команд процесорів Intel
Книга для гостей
Контакти
Добавити у вибране

:: Друзі ::

 
 

:: Лічильники ::

=

 

 

 

 

fff00e50

Двійково-десяткові числа

У гл. 2 вже мовилося про двійково-десяткові числа - спеціальний формат зберігання даних, використовуваний у ряді технічних застосувань. Часто ці числа називають BCD-числами (від binary-coded decimal, двійково-кодовані десяткові числа). Для обробки BCD-чисел (складання, віднімання, множення і ділення) в МП 86 передбачені спеціальні команди. Розглянемо це питання на комплексному прикладі обробки свідчень КМОП-ЧАСОВ реального часу.
Як відомо, в сучасних комп'ютерів є два незалежні таймери. Одін з них ("годинник реального часу") включений до складу мікросхеми з дуже низьким споживанням струму, харчується від батареї або акумулятора, що знаходиться на системній платі, і працює навіть на вимкненій з мережі машині. У цьому таймері зберігається і автоматично нарощується поточний календарний час (рік, місяць, день, година, хвилина і секунда).
Після включення комп'ютера вступає в роботу інший таймер, який зазвичай називають системним. Датчиком сигналів часу для нього служить кварцевий генератор, що працює на частоті 1,19318 Мгц, сигналів від якого, після перерахунку відносно 65536:1, поступають в контроллер переривань і ініціюють переривання через вектор 8 з частотою 18,2065 Гц. Ці переривання активізують програму BIOS, що періодично виконує інкремент вмісту чотирьохбайтового елементу пам'яті з поточним часом, що знаходиться за адресою 46ch. Після включення машини програми BIOS прочитують з годинника реального часу поточний час доби, перетворять його в число тактів системного таймера (тобто в число інтервалів по 1/18,2065 з) і записують в осередок поточного часу. Далі вміст цього осередку нарощується вже системним таймером, що працює в режимі переривань.
Для визначення поточного часу прикладна програма може викликати відповідні функції переривання 21h DOS (конкретно, з номером 2ah для отримання дати і 2ch для отримання часу доби), а може прочитати час безпосередньо з годинника реального часу за допомогою переривання lah BIOS. При цьому переривання 1а1г дозволяє, окрім читання поточного часу (функція 02h) і поточної дати (функція 04h), виконувати і цілий ряд інших функцій, серед яких ми відзначимо тільки можливість встановити "будильник", тобто записати в мікросхему годинника значення календарного часу, коли годинник повинен видати сигнал апаратного переривання. Цей сигнал через вектор 70h ініціює обробник переривань, що входить до складу BIOS, який перевіряє, чи виникло дане переривання в результаті досягнення часу установки будильника (годинник реального часу може ініціювати переривання і по інших причинам), тестує заразом батарейне живлення мікросхеми, а потім посилає в обидва контроллери переривань команди кінця переривань і завершується командою iret. Проте по ходу свого виконання обробник переривання 70h виконує команду hit 4ah, яка передає управління на обробник цього переривання, що теж входить до складу BIOS. Системний обробник переривання 4ah нічого особливо корисного не робить, по суті будучи просто програмою-заглушкою. Проте програміст має можливість записати у вектор 4ah адресу прикладного обробника переривань, який активізуватиметься перериванням будильника. Функції прикладного обробника визначає програміст.
У прикладі 3-9 встановлюється прикладний обробник переривання 4all, який сам по собі викликатися ніколи не буде, оскільки за умовчанням будильник годинника реального не працює. Якщо, проте, прочитати системний час за допомогою функції 02h переривання lah, додати до нього деяку величину, наприклад, 1 секунду, і встановити будильник на цей час (за допомогою функції 06h переривання lah), то через одну секунду буде активізований наш обробник. У прикладі 3-9 цей процес зроблений нескінченним: у обробнику переривань будильника знову виконується читання часу, збільшення до нього 1 секунди і установка будильника на новий час. В результаті наш обробник викликатиметься кожну секунду до завершення всієї програми.
Окрім службової функції установки будильника на наступну секунду, обробник переривань виконує і корисну роботу: він виводить поточний час в певне місце екрану. Оскільки обробник активізується кожну секунду, значення часу, що виводиться, оновлюватиметься кожну секунду.
Як вже мовилося, в годиннику реального часу значення часу зберігається у вигляді упакованих двійково-десяткових чисел. При виконанні арифметичних операцій з числами BCD (а нашому випадку операції полягають в збільшенні 1) необхідно використовувати призначені для цього команди процесора. У прикладі проілюстровано використання однієї з цих команд, конкретно, команди daa.
Для того, щоб вивести на екран значення часу, його треба перетворити в послідовність код ASCII. Процедура перетворення упакованих двійково-десяткових чисел в рядок символів також включена в даний приклад.

Приклад 3-9. Читання і обробка показань годинників реального часу

.586   ;Будут використовуватися додаткові команди

assume Cs:code,ds:data 

code segment use 16

main proc

mov Ax,data      ;Настроим DS наш

mov Ds,ax            ;сегмент даних

;Збережемо початковий вектор 4ah

mov Ax,354ah

int 21h

mov word ptr old_4a,bx

mov word ptr old_4a+2,es

;Встановимо наш обробник переривань 4ah

mov Ax,254ah

push DS                            ;Сохраним DS

push CS                            ;Настроим DS на сегмент 

pop DS                             ;команд

mov Dx,offset new_4a: Ds:dx->new_4a

int 21h

pop DS                             ;Восстановим DS

;Встановимо будильник

movah,02h                      ;Чтение поточного часу

int 1ah

call add_time                   ;Прибавим 1 секунду

mov Ah,06h                     ;Установим будильник на цей час

int 1ah

;Зупинимо програму, щоб спостерігати переривання

mov Ah,01h                  ;Функция введення з клавіатури

int 21h

;Завершимо програму, прибравши за собою

mov Ah,07h                 ;Сброс будильника

int 1ah

Ids  Dx,old_4a/ds:dx=ісходний вектор

mov Ax,254ah       ;Установим початковий вектор

int 21h

mov Ax,4c00h       ;Завершим програму

int 21h

main endp

;Наш обробник переривання від будильника new_4a proc

push а                        ;Сохраним всі регістри

push DS                    ;Сохраним ще і

push ES                    ;сегментные регістри

mov AX,seg hour     ;Настроим DS на наш

mov Dx,ax                ;сегмент даних

mov Ah,02h               ;Прочитаем поточний час

int 1ah                        ;из годинника реального часу

push CX                     ;Сохраним отримане

push DX                     ;текущее час

У прикладі 3-9 використовуються декілька команд, відсутніх в МП 86: команди збереження в стеку і відновлення всіх регістрів загального призначення pusha і рора, а також команда зрушення shl з числовим операндом. Для того, щоб ці команди розпізнавалися асемблером, в програму включена директива .586 (можна було б обійтися і директивою .386). В цьому випадку необхідно обидва сегменти оголосити з описувачем use16.
Програма складається з головної процедури main, процедури new_4a обробника переривань від будильника, а також трьох допоміжних процедур-підпрограм add_time, add_unit і conv. Головна процедура зберігає початковий вектор переривання 4ah, встановлює новий обробник цього переривання, читає поточний час і встановлює будильник на якийсь час, віддалене від поточного на 1 секунду, а потім зупиняється в очікуванні натиснення будь-якої клавіші. Поки програма стоїть, обробляються переривання від будильника і в правий верхній кут екрану кожну секунду виводиться поточний час. Після натиснення будь-якої клавіші програма завершується, заздалегідь скинувши будильник і відновивши початковий вміст вектора 4ah.
Легко бачити, що в запропонованому варіанті програма має мало практичного сенсу, оскільки вона не виконує, окрім виведення часу, ніякої корисної роботи. В той же час, поки ця програма не завершилася, запустити іншу програму не можна, оскільки DOS є однозадачною системою. Якщо, проте, написати нашу програму у форматі .сом і зробити її резидентною, ми дістанемо можливість запускати будь-які програми і одночасно спостерігати на екрані поточний час. Такого засобу в DOS немає, і в якійсь ситуації воно може виявитися корисним. Методика розробки резидентних програм описана вищим; читач може виконати необхідні перетворення самостійно.
Розглянемо тепер програму обробника переривань будильника. Перш за все в нім командою pusha (push all, зберегти все) зберігаються всі регістри загального призначення і, крім того, два сегментні регістри DS і ES, які використовуватимуться в обробнику. Далі регістр DS настроюється на сегментну адресу того сегменту, в який входить осередок hour, тобто фактично на наш сегмент команд. На перший погляд ця дія може показатися безглуздим. Адже на початку процедури main в регістр DS вже була поміщена адреса нашого сегменту даних data. Навіщо ж цю операцію повторювати? Річ у тому, що процедура new_4a, будучи формально обробником програмного переривання 4ah, фактично є обробник апаратного переривання від годинника реального часу, який, як і будь-яке апаратне переривання, може прідті у будь-який момент часу. Програма, що в принципі переривається, у цей момент може виконувати будь-які дії, і вміст регістра DS може бути будь-яким. Якщо ж говорити про нашу програму, то вона знаходиться в циклі очікування натиснення клавіші. Цей цикл організовує функція 01h DOS, яка, між іншим, час від часу звертається до свого драйвера клавіатури, а той - до програм BIOS введення символу з клавіатури. Цілком імовірно (а насправді так воно і є), що при виконанні згаданих операцій використовується регістр DS, який в цьому випадку указує вже не на наш сегмент даних, а на різні системні області. Іншими словами, при вході в обробник переривання вміст регістра DS невідомий, і його слід ініціалізувати наново, обов'язково зберігши початкове значення. Якщо перед виходом з обробника це початкове значення не відновити, буде неминуче зруйнована DOS.
Зберігши регістри і набудувавши DS, ми викликаємо функцію 02h переривання lah читання поточного часу. Час повертається, як вже мовилося, в упакованому двійково-десятковому форматі (по дві цифри в байті) в регістрах СН (годинник), CL (хвилини) і DH (секунди). Нам цей час знадобиться ще раз в кінці обробника для установки будильника наново, і щоб другий раз не викликати функцію 02h, отриманий час (тобто вміст регістрів СХ і DX) зберігається в стеку.
Далі виконується послідовне перетворення BCD-цифр, складових час, в коди ASCII відповідних символів. Число годинника (дві упаковані BCD-цифры) переноситься в регістр AL, і викликається підпрограма conv, яка перетворить старшу цифру годинника в код ASCII і повертає його в регістрі АН. Цей код поміщається в оголошену в сегменті даних рядок-шаблон hour, в якій заготовлено порожні поки місця для символів цифр, складових час, а також є розділові двокрапки. Для зручності звернення до елементів цього рядка, вона розділена на частини і кожна частина забезпечена власним ім'ям - min для поля хвилин і sec для поля секунд.
Підпрограма conv перетворення BCD-цифры в код ASCII складається всього з трьох пропозицій, не рахуючи завершальної команди ret. Два розрядне BCD-число передається в підпрограму в регістрі AL. Після обнулення регістра АН, який служитиме приймачем для утворення кінцевого результату, вміст AL зрушується командою shl вліво на 4 битий, внаслідок чого старший півбайт регістра AL, тобто старша цифра числа, переміщається в регістр АН (мал. 3.9). Двійково-десяткова цифра є просто двійковим представленням цифри; збільшення до її коду коди символу "0" (числа 30h) дає код ASCII цієї цифри.
Ми перетворили поки тільки старший півбайт регістра СН. Для виділення молодшого півбайта на регістр СН накладається маска 0fh

Мал. 3.9. Алгоритм роботи підпрограми conv.

яка обнуляє старший півбайт, не зачіпаючи молодшого. Збільшення коди ASCII нуля до коду десяткової цифри утворює код ASCII цієї цифри, який і переноситься потім в рядок-шаблон. Описана процедура повторюється потім для регістрів CL (хвилини) і DH (секунди).
Для виведення рядка з часом на екран використовується пряме звернення у відеопам'яті. У регістр ES заноситься сегментна адреса відеобуфера Bs00h, а в регістр DI - необхідний зсув відеопам'яті до того місця, починаючи з якого ми хочемо вивести рядок. У регістр SI заноситься адреса рядка-джерела, в регістр СХ - число кроків, а в регістр АН - вибраний нами атрибут символів (червоні символи по синьому полю). Оскільки переміщення і по рядку-шаблону, і по екрану повинно здійснюватися вперед, командою сld скидається прапор DF. Нарешті, циклічне виконання пари команд

lodsb stosw

приводить до виводу в задане місце екрану всього рядка hour.
Виконавши вивід на екран поточного часу, треба знову встановити будильник. Для цього спочатку забороняється робота раніше встановленого будильника, відновлюється поточний час в регістрах DX і СХ, і викликом процедури add_time до поточного часу додається 1 секунда. Далі викликом функції 06h наново встановлюється будильник, відновлюються збережені на початку програми обробника регістри, і, нарешті, командою iret обробник завершує свою роботу.
Розглянемо тепер процедуру збільшення 1 до поточного часу. Вона складається з двох компонентів - підпрограми add_time, яка організовує правильне складання чисел, що позначають час, щоб збільшення 1 секунди до 59 секунд дало 0 секунд і збільшило на 1 число хвилин (і те ж саме для хвилин) і підпрограми add_uuit, що виконує збільшення 1 до упакованого коду BCD.
Підпрограма add_time переносить число секунд з DH в AL, за допомогою підпрограми add_unit збільшує його на 1 і повертає в DH. Підпрограма add_unit сигналізує установкою прапора CF про необхідність перенесення 1 в наступний розряд часу (число секунд складало 59). Тому після повернення з add_iuit перевіряється прапор CF і, якщо він скинутий, тобто наступний розряд часу модифікувати не треба, підпрограма add_time завершується. Якщо ж прапор CF встановлений, виконується аналогічна процедура збільшення 1 до хвилин, яке знаходиться в регістрі CL. Далі знову аналізується прапор CF, і якщо він встановлений (поточний час був 59 мін 59 з), додається 1 до годинника. Нарешті, підпрограма завершується командою ret.
Підпрограма add_unit отримує упаковане двійково-десяткове число, до якого треба додати 1, в регістрі AL. Командою add до нього додається 1, після чого в деяких випадках утворюється правильна сума, а в деяких - неправильна. Так, 14h + 1 = 15h, що правильно, проте 19h + 1 = lah, що невірно. Такого двійково-десяткового числа не існує, а після збільшення 1 до 19 повинно вийти 20 (і записано у вигляді 20h). Корекцію після складання BCD-чисел здійснює команда daa, яка в приведеному прикладі перетворить lah в 20h, і яка повинна завжди слідувати за командою складання.
Наші двійково-десяткові числа специфічні в тому відношенні, що вони не можуть перевищувати 59. Тому після корекції результат порівнюється з 60h. Якщо сума менше 60h, прапор CF скидається і виконується команда ret. Якщо сума рівна 60h, регістр AL обнуляється, прапор CF встановлюється, сигналізуючи про перенесення 1 в наступний розряд часу (хвилин або годинника) і виконується та ж команда ret. Таким чином, прапор CF процесора в точці повернення з підпрограми add_unit говорить не про наявність або відсутність арифметичного перенесення, а виконує роль прапора "виняткової ситуації" - переходу часу на наступну хвилину або на наступну годину Таке нестандартне використання прапора CF є загальновживаним прийомом.

-

:: Наша кнопка ::

Отримати код:

Підтримайте наш сайт і розмістіть нашу кнопку на своєму ресурсі.


:: Популярне ::

-


:: Посилання ::

-


 

 

 


Copyright © net.kr.ua, 2019-2025 (assem.us)