Как сделать задержку выполнения программы? - Assembler
Формулировка задачи:
У Финогенова К.Г. в книге самоучителя приведет пример очень компактной задержки вот код
в комментариях к коду написано что это небольшая задержка. Вопрос - что значит небольшая? На сколько она небольшая 1нс, 1мкс, 1мс, 1сек? (просто все в нашем мире относительно) И как она работает? jmp это же безусловный переход, куда уходит код после такой команды и каким образом он где то ненадолго задерживается?
jmp $+2
Решение задачи: «Как сделать задержку выполнения программы?»
textual
Листинг программы
; tasm /m delay.asm ; tlink /t /x delay.obj .MODEL Tiny .286 .CODE ORG 100h LOCALS Start: call InitTimer ; Выполняем один раз! (для PTDelay) call InitPasDelay ; Выполняем один раз! (для PasDelay) mov al,'.' int 29h ; Выводим точку mov ax,1000 call PTDelay ; Ожидаем 1 секунду mov al,'1' int 29h ; Выводим '1' mov ax,1000 call PasDelay ; Ожидаем 1 секунду mov al,'2' int 29h ; Выводим '2' mov ax,1000 call TickDelay ; Ожидаем ~ 1 секунду mov al,'3' int 29h ; Выводим '3' mov ax,1000 call Int15Delay ; Ожидаем 1 секунду mov al,'4' int 29h ; Выводим '4' mov al,'.' int 29h ; Выводим точку xor ah,ah int 16h ; Ждём нажатия клавиши int 20h ; Выходим из программы ; -- Процедуры --------------------------------------------------------------; ; Инициализировать таймер (установить режим и коэффициент пересчёта) InitTimer proc cli ; На всякий случай запретим прерывания mov al,34h ; Режим 2 канала 0 out 43h,al xor al,al ; 65536 циклов между IRQ out 40h,al out 40h,al sti ret InitTimer endp ; Получить значение таймера в DX:CX:AX (AX младшее слово) GetTimerValue proc cld xor ax,ax mov es,ax mov si,46Ch ; ES:SI = 0000h:046Ch = системный счётчик BIOS mov cx,es:[si+0] ; CX = первое значение счётчика BIOS (младшее слово) out 43h,al ; "Защёлкиваем" канал 0 таймера i8253/i8254 cli mov bx,es:[si+0] mov dx,es:[si+2] ; DX:BX = второе значение счётчика BIOS in al,40h mov ah,al in al,40h xchg ah,al ; AX = значение таймера i8253/i8254 sti cmp cx,bx ; Первое значение счётчика BIOS равно второму ? je @@Ok ; Да! Оставляем как есть (DX:CX), иначе... or ax,ax ; Счётчик изменился ПОСЛЕ "защёлкивания" (между OUT и CLI) ? js @@Swap ; Нет! Идём переносить BX в CX, иначе... or bx,bx ; При изменении таймера изменилось старшее слово ? jne @@NoOverflow ; Нет! dec dx ; Да, корректируем старшее слово (DX) @@NoOverflow: cmp ax,? ; Здесь это аналогично jmp @@Ok (только короче и быстрее) ORG $-2 @@Swap: mov cx,bx ; DX:CX = DX:BX, если счётчик изменился между MOV CX и OUT @@Ok: not ax ; Обратный отсчёт -> Прямой отсчёт ret GetTimerValue endp ; Осуществить задержку AX миллисекунд ; Использует GetTimerValue PTDelay proc or ax,ax jz @@Exit ; Если AX=0, то сразу выходим mov bp,sp ; Сохраняем SP в BP push ax ; Сохраняем нужное нам число в стеке call GetTimerValue ; Получаем текущее значение таймера xchg bx,ax mov si,cx mov di,dx ; Сохраняем его в DI:SI:BX finit ; Инициализируем сопроцессор fild word ptr [bp-2]; Загружаем в стек кол-во миллисекунд push 012h push 034DDh ; Заносим в стек число 1193181 fild dword ptr [bp-6]; Загружаем его в сопроцессор fmul ; Перемножаем эти числа push 1000 fild word ptr [bp-8] fdiv ; И делим на 1000 fistp dword ptr [bp-4]; А затем помещаем обратно в стек (округлив) mov ax,[bp-4] mov dx,[bp-2] ; Читаем из стека результат в DX:AX mov sp,bp ; Восстанавливаем SP add bx,ax adc si,dx adc di,0 ; Добавляем DX:AX к DI:SI:BX @@Repeat: push bx si call GetTimerValue ; Получаем текущее значение таймера pop si bx sub ax,bx sbb cx,si sbb dx,di ; Вычитаем DI:SI:BX из DX:CX:AX or dx,dx js @@Repeat ; Повторяем, если получилось < 0 @@Exit: ret PTDelay endp PasDelayCnt dw 0 ; Используется в процедуре PasDelay ; Инициализировать переменную PasDelayCnt для работы процедуры PasDelay ; Использует PasDelayLoop InitPasDelay proc xor ax,ax mov es,ax mov di,46Ch mov bl,es:[di] @@Wait: cmp bl,es:[di] je @@Wait ; Ждём следующий тик mov bl,es:[di] mov ax,-28 cwd ; DX = 0FFFFh call PasDelayLoop ; Ждём следующий тик not ax not dx ; Инвертируем DX:AX mov cx,55 xchg bx,ax ; Сохранить AX в BX xchg ax,dx ; Записать DX в AX cwd ; DX = 0 div cx ; Делим DX:AX на CX (55) mov PasDelayCnt[2],ax ; Сохраняем старшее слово xchg ax,bx ; Восстанавливаем AX из BX div cx ; Делим DX:AX на CX (55) mov PasDelayCnt[0],ax ; Сохраняем младшее слово ret InitPasDelay endp ; Ожидать либо DX:AX циклов, либо изменения байта по адресу ES:[DI] ; (в зависимости от того, что произойдёт раньше) PasDelayLoop proc @@Repeat: sub ax,1 sbb dx,0 jc @@Exit cmp bl,es:[di] je @@Repeat @@Exit: ret PasDelayLoop endp ; Осуществить задержку AX миллисекунд (этот метод используется в языке Pascal) ; Использует PasDelayLoop PasDelay proc xchg cx,ax ; Заносим AX в CX jcxz @@Exit ; Если CX=0, то сразу выходим mov ax,40h mov es,ax xor di,di mov bl,es:[di] ; BL = байт по адресу 0040h:0000h (не меняется) @@Repeat: mov ax,PasDelayCnt[0] mov dx,PasDelayCnt[2] call PasDelayLoop ; Ждём DX:AX циклов loop @@Repeat ; CX раз @@Exit: ret PasDelay endp ; Осуществить задержку AX миллисекунд (с точностью до 55 миллисекунд) ; Ничего не использует, весьма простой способ TickDelay proc mov cx,1193 mul cx ; Делим AX на 55 и прибавляем 1 inc dx ; Получаем в DX кол-во тиков таймера xor ax,ax mov es,ax mov si,46Ch mov bx,es:[si+0] mov cx,es:[si+2] ; Читаем таймер BIOS в CX:BX add bx,dx adc cx,ax ; Добавляем DX к CX:BX (AX=0) @@Repeat: mov ax,es:[si+0] mov dx,es:[si+2] ; Читаем таймер BIOS в DX:AX sub ax,bx sbb dx,cx ; Вычитаем CX:BX из DX:AX js @@Repeat ; Повторяем, если получилось <= 0 ret TickDelay endp ; Осуществить задержку AX миллисекунд через int 15h ; Ничего не использует, самый простой, но не всегда надёжный способ ; Устанавливает флаг CF=CY=1, если функция занята Int15Delay proc mov cx,1000 mul cx ; Умножаем AX на 1000 mov cx,dx mov dx,ax ; Переносим DX:AX в CX:DX mov ah,86h int 15h ; Вызываем int 15h ret Int15Delay endp END Start
ИИ поможет Вам:
- решить любую задачу по программированию
- объяснить код
- расставить комментарии в коде
- и т.д