Как вводить ascii символы на клавиатуре

Обновлено: 02.07.2024

Для ввода символов с клавиатуры используется функция 01h прерывания INT 21h:

  1. по адресу 100h введите INT 21h
  2. в регистр AH занесите номер функции 01h
  3. выполните прерывание командой "p"

В результате появится мерцающий курсор - это приглашение операционной системы (OS) ввести с клавиатуры любой символ. Например, введите букву "h":

После выполнения прерывания, в регистре AL появилось число 68h (ASCII код буквы "h").

При определении ASCII кода клавиши [F1], в регистр AL заносится число 00h, а рядом с приглашением Debug появляется символ ";". Почему получается такой результат? Следующий раздел поможет разобраться в этом.

ASCII и Scan коды

Каждая клавиша клавиатуры при нажатии вырабатывает код сканирования Scan code:

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

В отличие от Scan кода, ASCII код описывает не клавишу, а символ, который изображен на ней. Например, при нажатии клавиши с буквой "Z" вырабатывается Scan код 2Сh. Далее этот код преобразуется в один из ASCII кодов:

Преобразование кода зависит от состояния клавиш [Shift], [Caps Lock] и от текущей кодовой страницы.

Управляющие клавиши: [F1] . [F12], [Ctrl], [Alt] и др., не предназначены для ввода символов, и вырабатывают только Scan код. Для каждой из этих клавиш посылается два кода, один за другим. Первый код всегда 0h, он означает, что следом идет Scan код специальной клавиши.

Чтобы Scan код клавиши [F1] попал в регистр AL, надо выполнить INT 21h дважды:

Проверьте функцию в регистре AH и запустите программу командой "g 104". На приглашение OS надо ответить нажатием клавиши [F1], после чего вы увидите:

Регистр AL содержит Scan код клавиши [F1].

  1. Определите Scan коды функциональных клавиш: [F2] . [F10]
  2. Определите Scan коды клавиш управления курсором: [←], [↑], [→]

Позже мы используем Scan коды в программе редактирования секторов диска, для операций:

  • управление псевдокурсором;
  • листинг секторов диска;
  • запуск отдельных фрагментов кода.

Считывание одной шестнадцатеричной цифры

Любой символ, запрашиваемый функцией 01h прерывания INT 21h, отображается в регистре AL в виде ASCII кода. Например, клавиатурный ввод символа "9" завершается записью в AL числа 39h. Для преобразования кода в цифру достаточно выполнить вычитание:

Этот алгоритм можно использовать для ввода с клавиатуры любой десятичной цифры. Ввод шестнадцатеричных цифр A . F выполняется аналогично, с той лишь разницей, что код символа уменьшается на 37h.

Следующая программа выполняет запрос символа с клавиатуры, и преобразует этот символ в соответствующее шестнадцатеричное число:

Инструкция JLE (Jump if Less or Equal to) - перейти, если меньше или равно.

Программу следует выполнять в два этапа: "g 10C" и "g". Остановка по адресу 10Сh нужна для просмотра числа, введенного в регистр AL, до того, как прерывание INT 20h вернет все регистры в исходное состояние.

Проведите следующие эксперименты с программой:

  • проверьте ввод не числовых символов ("k", "z", "w" . );
  • проверьте ввод строчных числовых символов ("a" . "f").

Данная программа работает корректно только с символами "0". "9" и "A". "F". Позже мы исправим этот недостаток, а пока ввод символов надо контролировать самостоятельно.

Считывание двузначного шестнадцатеричного числа

Алгоритм ввода многозначного числа сводится к последовательному запросу всех его цифр: от старшего разряда к младшему. Далее цифры легко объединяются в исходное число.

Например, надо ввести число B9h. После ввода первой цифры, в регистре AL окажется число Bh. Скопируем это число в регистр DL и сдвинем влево на четыре бита:

Сдвиг выполняется инструкцией SHL (Shift Left - сдвиг влево, работает аналогично SHR). После сдвига, в DL получается число B0h. Далее вводится вторая цифра (в AL попадает 9h). Остается выполнить сложение DL + AL:

Программа ввода двузначного шестнадцатеричного числа:

Запустите программу командой "g 121". Введите двузначное шестнадцатеричное число. Результат ввода должен отобразиться в регистре DL. Завершите программу командой "g".

Испытайте программу с различными двузначными числами. Для ввода шестнадцатеричных чисел используйте только заглавные буквы: "A" . "F"

Процедуры

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

Для работы с процедурами используется две инструкции:

В программе вывода на экран двузначного шестнадцатеричного числа, дважды повторялась последовательность из пяти инструкций. Ниже показано, как этот повторяющийся фрагмент оформить в виде процедуры:

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

Программа вывода на экран строки символов от "A" до "J":

Каждый вызов инструкции LOOP сопровождается уменьшением регистра CX на единицу, и проверкой: пока CX > 0, выполняется переход на адрес, указанный в инструкции LOOP. Использование регистра CL (вместо CX) сокращает длину кода на 1 байт, но подвергает программу опасности: если до запуска программы в CH будет записано некоторое число, то LOOP выполнит не 10, а значительно большее количество циклов.

Инструкция INC (Increment) увеличивает содержимое регистра на единицу. Для уменьшения содержимого регистра на единицу используется инструкция DEC (Decrement).

Запустите программу командой "g". Далее выполните трассировку программы. Не забывайте использовать точку останова (или команду "p") для запуска прерываний.

  1. Используя инструкцию DEC, модифицируйте последнюю программу так, чтобы она выводила на экран строку символов: "9876543210"
  2. Используя процедуру печати шестнадцатеричной цифры, напишите программу, которая будет выводить на экран содержимое регистровой пары SS:SP

Инструкция RET передает управление на адрес, следующий за вызовом CALL. Предположим, в программе используется несколько вызовов CALL, передающих управление единственной процедуре. То есть, при каждом вызове, инструкция RET должна возвращать управление на различные адреса. Как RET определяет адрес возврата?

До передачи управления процедуре, CALL копирует адрес возврата в специальную область ОЗУ, называемую STACK (СТЕК). Инструкция RET извлекает адрес возврата из стека, и передает управление инструкции, расположенной по этому адресу.

Стек работает подобно стопке подносов на пружине: когда на верх стопки ставят очередной поднос, то вся стопка немного опускается, а верхний поднос является первым кандидатом на выход. Другими словами, работа стека организована по принципу LIFO ("Last In, First Out" - последним пришел, первым ушел).

При реализации некоторых алгоритмов возникает необходимость написать одну процедуру внутри другой. Такую организацию называют вложенной структурой (вложенным вызовом):

В данном примере выполняется три вложенных вызова: CALL 200, CALL 300, CALL 400. В стек заносятся адреса 103, 203, 303:

вершина стека (SS:SP) > 0303
0203
0103
.

По адресу 400 находится инструкция RET, которая извлекает адрес c вершины стека, и передает управление инструкции по этому адресу. Картина в стеке меняется:

вершина стека (SS:SP) > 0203
0103
.
.

Очередной RET вновь извлекает значение с вершины стека, и передает управление на адрес 203.
Последний RET забирает из стека адрес инструкции INT 20, и передает ей управление.

Вершина стека всегда хранится в регистровой паре SS:SP:

Чтобы лучше разобраться с работой стека, выполните трассировку приведенного примера.
Каждый трассировочный шаг сопровождайте анализом регистров SP и IP.

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

Использование инструкций PUSH и POP

Инструкция CALL помещает адрес возврата в стек, а RET извлекает и передает его в регистр IP (так управление возвращается из процедуры в программу). Аналогичные действия можно выполнить, используя инструкции PUSH и POP:

Инструкции PUSH и POP позволяют использовать стек в качестве временного хранилища данных. Например, в стеке очень удобно сохранять значения регистров.

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

На помощь приходят инструкции PUSH и POP. В начале каждой процедуры нужно сохранить текущие значения регистров, которые будут использованы в процедуре. В конце процедуры (перед возвращением в программу) восстановить значения регистров из стека, например так:

Сохранение и восстановление регистров AX, CX и DX позволяет использовать их в качестве локальных переменных внутри процедуры.

Работать со стеком надо очень аккуратно. Кроме значений регистров, в стеке хранится адрес возврата из процедуры. Т.е., если хоть один регистр не будет восстановлен, то адрес возврата вовремя не окажется на вершине стека, инструкция RET передаст в IP неправильный адрес, и нормальный ход программы будет нарушен.

Организация пауз в программах

Один из вариантов использования вложенных структур - это организация фиксированных по времени пауз. Наиболее простая процедура задержки выглядит так:

Цикл LOOP адресован сам на себя, и выполняет FFFFh - "пустых оборотов". При частоте процессора 1 GHz, такая процедура будет выполнена за несколько десятков микросекунд.

Для более длительной паузы надо поместить данную процедуру внутрь аналогичного цикла:

Выполним приблизительную оценку длительности паузы:

Читайте также: