- •Системное программирование
- •Контрольные вопросы
- •2. Программная модель микропроцессора 8086
- •2.1. Представление информации
- •2.2. Регистры микропроцессора
- •2.3. Формат машинной команды
- •2.4. Способы задания операндов команды
- •Контрольные вопросы
- •3. Основные понятия языка ассемблера
- •3.1. Предложения
- •3.2. Директивы определения данных
- •3.3. Выражения
- •Контрольные вопросы
- •4. Сегментированная модель памяти
- •4.1. Сегментирование адресов
- •4.2. Директивы сегментации
- •4.3. Общая структура программы
- •4.4. Модели памяти
- •Контрольные вопросы
- •5. Основные группы команд
- •5.1. Соглашению по описанию команд
- •5.2. Команды пересылки данных
- •5.3. Арифметические команды
- •5.4. Логические команды
- •5.5. Команды переходов
- •5.6. Команды организации циклов
- •5.7. Команды обработки строк
- •5.8. Стековые команды
- •5.9. Команды ввода-вывода
- •5.10. Команды прерываний
- •5.11. Команды управления микропроцессором
- •Контрольные вопросы
- •6. Подпрограммы
- •Контрольные вопросы
- •7. Разработка одномодульной программы
- •7.1. Трансляция и компоновка программы
- •7.2. Отладка программы
- •Контрольные вопросы
- •8. Разработка многомодульных программ
- •8.1. Принципы разработки модулей
- •8.2. Расширенное применение директивы сегментации
- •9. Упражнения
- •Контрольные вопросы
- •Программирование микропроцессорных устройств
- •10. Программирование системного таймера
- •10.1. Описание таймера-счетчика 8254
- •10.2. Режимы работы таймера
- •10.3. Структура регистров таймера
- •10.4. Упражнения
- •Контрольные вопросы
- •11. Программирование контроллера прерываний
- •11.1. Механизм обработки прерываний
- •11.2. Типы прерываний
- •11.3. Приоритеты прерываний
- •11.4. Контроллер прерываний 8259
- •11.5. Идентификация прерываний
- •11.6. Прерывания bios и ms-dos
- •11.7. Упражнения
- •Контрольные вопросы
- •12. Программирование параллельного порта
- •12.1. Интерфейс Centronics
- •12.2. Работа с параллельным портом на низком уровне
- •12.3. Стандартные средства работы с параллельным портом
- •12.4. Упражнения
- •Контрольные вопросы
- •13. Программирование последовательного порта
- •13.1. Основы последовательной передачи данных
- •13.2. Последовательный интерфейс rs-232c
- •13.3. Универсальный асинхронный приемо-передатчик 8250
- •13.4. Порты асинхронного адаптера
- •13.5. Стандартные средства программирования последовательного порта
- •13.6. Упражнения
- •Контрольные вопросы
- •Литература
- •141 Кафедра Вычислительной Техники и Программирования Московского Государственного Открытого Университета
10.4. Упражнения
Пример 1. Используя встроенный в среду Borland® Delphi ассемблер, разработать две подпрограммы для определения текущего состояния счетчика и его значения. Обе подпрограммы должны быть функциями. Первая подпрограмма должна использовать регистр задвижки и возвращать текущее значение счетчика. Вторая подпрограмма должна использовать режим «Read back command» и в зависимости от переданного параметра возвращать режим работы счетчика либо его текущее значение.
Решение. Функция LatchCNT() возвращает текущее значение счетчика с помощью задвижки и поэтому может работать с обеими микросхемами таймера 8253(4). Единственным параметром функции является порядковый номер канала CNT, который может принимать значения [0,1,2]. Формат управляющего регистра показан в табл. 88, а алгоритм работы функции – на рис. 38.
Табл. 88. Формат управляющего регистра.
Биты |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Значение |
SC1 |
SC0 |
0 |
0 |
x |
x |
x |
x |
Исходный текст функции приведен ниже.
function LatchCNT(CNT: Byte): Word;
begin
asm
{ Сохранение в стеке используемых регистров }
PUSH AX
PUSH DX
{ Фиксировать текущее значение счетчика }
MOV AL, CNT
SHL AL, 6
MOV DX, 43h
OUT DX, AL
{ Код выбора сначала LSB, а затем MSB }
OR AL, 00110000b
MOV DX, 43h
OUT DX, AL
{ Адрес регистра счетчика }
MOV DX, 40h
ADD DL, CNT
{ Получение младшего байта регистра счетчика }
IN AL, DX
{ Сохранение младшего байта в AH }
MOV AH, AL
{ Получение старшего байта регистра счетчика }
IN AL, DX
{ Обмен младшего и старшего байтов аккумулятора }
XCHG AL, AH
{ Функция возвращает результат }
MOV @Result, AX
{ Восстановление из стека содержимого регистров }
POP DX
POP AX
end
end;
Рис. 38. Алгоритм работы функции LatchCNT().
Функция GetCNT() предназначена для работы только с микросхемой 8254, т.к. использует режим «Read back command». Первый параметр CNT по-прежнему задает номер канала, а параметр Mode – запрашиваемое действие. При Mode = 0 функция возвращает текущее значение, при Mode = 1 – режим работы указанного канала (табл. 89).
Табл. 89. Формат управляющего регистра.
Значение счетчика | ||||||||
Биты |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Значение |
1 |
1 |
0 |
1 |
C2 |
C1 |
C0 |
X |
Статус канала | ||||||||
Биты |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Значение |
1 |
1 |
1 |
0 |
C2 |
C1 |
C0 |
X |
Коды выбора счетчика перечислены в табл. 90. Для упрощения не используется комбинация (C2=1, C1=1, C0=1).
Табл. 90. Коды выбора счетчика.
Счетчик |
C2 |
C1 |
C0 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
2 |
1 |
0 |
0 |
Алгоритм работы функции GetCNT() может быть следующим (рис. 39). Вначале код канала CNT сдвигается влево на CNT+1 разрядов. Затем код необходимо сложить по маске 0E0h (получить статус канала) или по маске 0D0h (получить значение счетчика). Полученный управляющий код должен быть выведен в порт 43h. После этого может быть прочитано содержимое регистра счетчика, вначале младший значимый байт, а затем старший.
Исходный текст функции приведен ниже.
function GetCNT(CNT,Mode: Byte): Word;
begin
asm
{ Сохранение в стеке используемых регистров }
PUSH AX
PUSH BX
PUSH DX
{ Сохранение номера счетчика в AL }
MOV AL,CNT
{ Сдвиг на CNT+1 влево содержимого AL для размещения
номера канала в разрядах D3D2D1 управляющего кода }
MOV CL, CNT
INC CL
SHL AL,CL
{ Определение запрашиваемого действия }
CMP Mode, 0
JNE @@1
{ Сложение по маске $0E0 }
OR AL, 0E0h
JMP @@2
{ Сложение по маске $0D0 }
@@1: OR AL, 0D0h
@@2: MOV DX, 43h
{ Передача кода в управляющий регистр }
OUT DX, AL
{ Получить адрес счетчика }
MOV DH, 0
MOV DL, 40h
ADD DL, CNT
{ Получение младшего байта регистра счетчика }
IN AL, DX
MOV AH, 0
{ Вновь определение запрашиваемого действия }
CMPMode, 0
JNE@@3
{ Сохранение младшего байта в AH }
MOV AH, AL
{ Получение старшего байта регистра счетчика }
IN AL, DX
{ Обмен младшего и старшего байтов аккумулятора }
XCHGAL,AH
{ Функция возвращает результат }
@@3: MOV @Result, AX
{ Восстановление из стека содержимого регистров }
POP DX
POP BX
POP AX
end;
end;
Рис. 39. Алгоритм работы функции GetCNT().
Пример 2. Используя ассемблер TASM, разработать программу определения внутренней тактовой частоты микропроцессора в МГц. Использовать режим работы ассемблера IDEAL и упрощенные директивы сегментации. Для подсчета количества тактов процессора применить команду RDTSC (код операции 0F31h).
Решение. Команда RDTSC предназначена для загрузки в регистровую пару EDX:EAX содержимого 64-разрядного регистра MSR (Model Specific Register). Он используется для хранения значения счетчика тактовых импульсов TSC (Time Stamp Counter). Значение счетчика увеличивается на единицу с приходом очередного такта микропроцессора, а обнуление происходит при системном сбросе. Заметим, что команда RDTSC работает на микропроцессорах Intel Pentium и выше.
Внутренняя тактовая частота микропроцессора может быть рассчитана исходя из количества тактов, прошедших за временной интервал, равный периоду следования импульсов. Число тактов определяется с помощью команды RDTSC по содержимому регистра MSR в моменты изменения значения таймера. Эти изменения можно отследить с помощью переменной BIOS, расположенной по адресу 0040:006Ch. На рис. 40 показан алгоритм вычисления тактовый частоты.
Установить
начальные значения параметров для
интервалов
Нет
Получить
текущее время, выраженное в 1/18.2 долях
секунды
Да
Расчет
длительности интервалов в тактах
Задержка
на время 1/18.2
доли секунды
Расчет
среднего значения
длительности интервала
в тактах
Выполнить команду
RDTSC
Расчет
тактовой частоты
в МГц
Сохранить
значение счетчика TSC
Выход
в MS-DOS
Рис. 40. Алгоритм расчета внутренней тактовой частоты процессора.
Для повышения точности вычислений необходима выборка, по которой будут рассчитаны длительности каждого интервала в тактах, а также среднее значение длительности интервалов. Учитывая частоту генератора и максимальное значение коэффициента делителя для нулевого канала, расчетная формула внутренней частоты процессора будет иметь вид:
или
Количество замеров для вычисления среднего значения длительности интервалов может быть произвольным, исходя из принципа «чем больше, тем лучше». Однако в случае реализации на ассемблере оптимальным является выбор значения, на единицу большего числа, кратного степени 2. Связано это с тем, что операция деления на 2n в двоичной арифметике реализуется простым сдвигом на n разрядов вправо. Число итераций равно 33, а количество интервалов 32.
Возможный вариант программы, реализующей алгоритм вычислений, представлен ниже.
IDEAL
.386
MODEL MEDIUM
STACK 100h
DATASEG
; Массив для хранения значений отсчетов счетчика
; тактов в моменты срабатывания системного таймера
TimeCounter DD 33 DUP(?)
; Разность между соседними замерами
DeltaT DD 32 DUP(?)
; Среднее значение длительности интервала
AverageTime DD ?
; Предыдущее значение системного таймера
Time DD ?
; Результат вычислений
FreqResultDD?
ENDS
CODESEG
PROC ProcFrequency
; Настроить регистр DS на глобальный сегмент данных
MOV AX, DGROUP
MOV DS, AX
; Настроить сегментный регистр ES на область данных BIOS
MOVAX, 0
MOVES,AX
; Инициализация начальных параметров
MOV BX, 33
MOV DI, OFFSET TimeCounter
; Текущее время, выраженное в 1/18.2 долях секунды
MOV EAX, [ES:046Ch]
MOV [Time], EAX
; Сохранение значений счетчика TSCкаждые 1/18.2 сек
@@t0:CALLWaitTimerStateChange
; Выполнить команду RDTSC
DB0Fh, 31h
; Сохранить значение в массиве
MOV[DI],EAX
; Переход к следующему элементу массива
ADDDI, 4
DECBX
; Число замеров равно 33
JNZ@@t0
; Вычислить длительность каждого интервала в тактах
MOV BX, 32
MOV DI, OFFSET TimeCounter
MOV SI, OFFSET DeltaT
MOV EDX, 0
; Из значения счетчика в момент t+1 вычесть значение в момент t
@@t1:MOV EAX, [DI+4]
SUB EAX, [DI]
MOV[SI],EAX
; Накопление суммы значений
ADDEDX, EAX
; Индексирование массивов
ADDDI, 4
ADD SI, 4
DEC BX
JNZ @@t1
; Вычислить среднюю длительность интервала в тактах
SHREDX, 5
MOV[AverageTime],EDX
; Умножить среднее значение на частоту генератора
MOV EAX, [AverageTime]
MOV EDX, 1193180
MULEDX
; Разделить результат на делитель таймера (65536)
SHRDEAX, EDX, 16
; Вычислить частоту микропроцессора в МГц
XOREDX,EDX
; Разделить результат на 106
MOVEBX, 1000000
DIVEBX
; Сохранить результат вычислений
MOV [FreqResult],EAX
; Выход в MS DOS
MOV AX, 4C00h
INT 21h
ENDP ProcFrequency
; Процедура ожидания очередного изменения значения таймера
PROC WaitTimerStateChange NEAR
MOV EAX, [Time]
; Цикл до тех пор, пока текущее значение времени
; не будет отличаться от ранее сохраненного значения
@@T: CMP EAX, [ES:046Ch]
JE@@T
; Сохранить новое значение времени
MOV EAX,[ES:046Ch]
MOV [Time], EAX
RET
ENDP WaitTimerStateChange
ENDS
END
Пример 3. Разработать подпрограмму, организующую задержку на заданный временной интервал. Минимальный временной интервал может быть меньше периода следования импульсов от системных часов.
Решение. При разработке измерительных приложений часто требуется организовать временные задержки для выполнения различных программных операций. Такая необходимость может быть связана, например, с ожиданием окончания переходных процессов, получением данных, синхронизацией критичных по времени операций.
Самый простой способ организовать задержку – определить пустой цикл. В этом случае может потребоваться достаточно много времени для того, чтобы добиться нужного времени задержки. Однако даже если и удастся определить требуемую длительность, нельзя быть уверенным, что программа будет давать нужное время задержки при всех условиях. Длительность цикла может меняться в зависимости от используемого компилятора, скорости процессора и других факторов.
Даже цикл на языке ассемблера может приводить к различным временам задержки. Поэтому разумно определять время программной задержки непосредственно по часам. Частота отсчета в 18.2 Гц, используемая в модификации счетчика времени суток, должна вполне удовлетворять большинство потребностей.
Чтобы обеспечить задержку заданной продолжительности, программа должна подсчитать требуемое число импульсов счетчика времени суток. Значение добавляется к считанному текущему значению счетчика. Затем программа постоянно считывает текущее значение счетчика и сравнивает его с запомненным значением. Когда достигается равенство, то считается, что требуемая задержка прошла. Значение счетчика времени суток хранится по адресу 0040:006Ch.
Более высокую дискретность отсчетов, требуемую по условиям задачи, можно достичь, воспользовавшись счетчиком тактовых импульсов TSC. Однако в этом случае требуется предварительная калибровка таймера. Первым шагом алгоритма будет вычисление среднего числа тактов за период системного таймера и расчет продолжительности одного такта (см. задачу 2).
Дело в том, что использование паспортных данных процессора приведет к неточности, т.к. всегда имеется некоторый технологический разброс значений параметра. Лучший выход в данной ситуации – определение значения экспериментальным путем. Далее применим адаптированный для счетчика TSC приведенный выше алгоритм. Описанные алгоритмы организации временных задержек реализуйте самостоятельно на языке ассемблера, используя TASM.