Автор работы: Пользователь скрыл имя, 12 Февраля 2011 в 19:27, курсовая работа
Изучить устройство и принцип работы системного таймера, а так же для демонстрации использовать самостоятельно разработанную программу.
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ УКРАИНЫ
ХАРЬКОВСКИЙ
НАЦИОНАЛЬНЫЙ УНИВЕРСИТЕТ
имени В.Н. Каразина
Курсовой
проект
Дисциплина: архитектура ПК
Тема: «Программирование системного таймера»
Выполнил:
Шульга Сергей Олегович
студент ФКН гр. КС-31
Харьков
2010
Цель
Изучить
устройство и принцип работы системного
таймера, а так же для демонстрации
использовать самостоятельно разработанную
программу.
Основные
сведения
Программирование таймера на уровне портов
Таймеру соответствуют четыре порта ввода/вывода со следующими адресами:
40h - канал 0;
41h - канал 1;
42h - канал 2;
43h
- управляющий регистр.
Приведем формат
управляющего регистра:
Все управление
таймером осуществляется путем вывода
одного байта в порт 43h. Рассмотрим
назначение бит в этом байте.
биты 7 – 6: если не 11 — это номер канала, который будет программироваться
00,01,10 = канал
0,1,2
биты 5 – 4:
00 — зафиксировать текущее значение счетчика для чтения (в этом случае биты 3 – 0 не используются)
01 — чтение/запись только младшего байта
10 — чтение/запись только старшего байта
11 — чтение/запись
сначала младшего, а потом старшего
байта
биты 3 – 1: режим работы канала
000: прерывание IRQ0 при достижении нуля
001: ждущий мультивибратор
010: генератор импульсов
011: генератор прямоугольных импульсов (основной режим)
100: программно запускаемый одновибратор
101: аппаратно
запускаемый одновибратор
бит 0: формат счетчика:
0 — двоичное 16-битное число (0000 – FFFFh)
1 — двоично-десятичное
число (0000 – 9999)
Если биты 7 – 6 равны 11, считается, что байт, посылаемый в порт 43h, — команда чтения счетчиков, формат которой отличается от команды программирования канала:
биты 7 – 6: 11 (код
команды чтения счетчиков)
биты 5 – 4: режим чтения:
00: сначала состояние канала/потом значение счетчика
01: значение счетчика
10: состояние
канала
биты 3 – 1: команда
относится к каналам 3 – 1
Таймер и динамик
Одно из наиболее
распространенных применений таймера
- генерация звуковых сигналов и
воспроизведение музыки. Таймер позволяет
воспроизводить музыку в фоновом режиме,
т.е. во время работы программы может звучать
музыка.
Как мы уже говорили,
канал 2 микросхемы 8254 связан с громкоговорителем
компьютера. Однако громкоговоритель
не просто соединен с выходом OUT канала
2. Порт вывода 61h также используется
для управления громкоговорителем.
Младший бит порта 61h подключен ко входу
GATE канала 2 таймера. Этот бит при установке
в 1 разрешает работу канала, т.е. генерацию
импульсов для громкоговорителя.
Дополнительно
для управления громкоговорителем
используется бит 1 порта 61h. Если этот
бит установлен в 1, импульсы от канала
2 таймера смогут проходить на громкоговоритель.
Таким образом, для включения звука надо выполнить следующие действия:
запрограммировать канал 2 таймера на нужную частоту (т.е. загрузить регистр счетчика канала нужным значением);
для включения
звука установить в 1 два младших
бита порта 61h.
Так как остальные
6 битов порта 61h используются для
других целей, установка младших
битов должна выполняться таким
образом, чтобы значения остальных
битов не были изменены. Для этого вначале
надо считать байт из порта 61h в рабочую
ячейку памяти, установить там нужные
биты, затем вывести новое значение байта
в порт 61h.
Очевидно, что
для выключения звука надо сбросить
два младших бита порта 61h в 0 (при
этом нельзя изменять значение остальных
битов этого порта).
Мелодия (одноголосая),
как известно, состоит из нот, разделенных
или не разделенных паузами. При
проигрывании мелодии необходимо для
каждой ноты программировать
Пример функции:
void tm_sound(int freq, int time) {
int cnt, i;
outportb(0x43, 0xb6); // Задаем режим канала 2 таймера
cnt = 1193180L / freq; // Вычисляем задержку для загрузки в
// регистр счетчика таймера
outportb(0x42, cnt & 0x00ff); // Загружаем регистр счетчика таймера - //сначала младший, затем старший байты
outportb(0x42, (cnt &0xff00) >> 8);
outportb(0x61, inp(0x61) | 3); // Включаем громкоговоритель. Сигнал от
// канала 2 таймера теперь будет проходить
// на вход громкоговорителя.
delay(time); // Выполняем задержку.
outportb(0x61, inp(0x61) & 0xfc); // Выключаем громкоговоритель.
}
Изменение частоты генерации IRQ0.
Таймер имеет кварцевый генератор (или часы), которые "тикают" ровно 119318 раз в секунду. Нулевой канал, который мы будем рассматривать, имеет так называемый счетчик. Этот счетчик возрастает на 1 каждый "тик" часов. Когда он достигает некоторого граничного значения (которое можно установить программно), он сбрасывается, и генерируется прерывание 8.
Схематическое
представление:
BIOS
устанавливает это граничное
значение в 0, что для таймера
обозначает 65536. То есть каждый 65536-й
"тик" часов генерируется
прерывание 8, оно "срабатывает"
примерно 119318/65536=18.2 раз в секунду.
outportb(0x43,6); /* channel state */
outportb(0x40,(char)cnt);
outportb(0x40,(char)(cnt>
Запустим подобную программу на выполнение, скажем, со значнием cnt=65536/8. Когда она отработает, мы увидим, что часы станут идти в 8 раз быстрее! Это происходит оттого, что после выхода из программы необходимо установить старый счетчик, который устанавливал BIOS, то есть 0.
Однако
и это не решит до конца проблему.
Ведь на период работы программы часы
все равно будут идти в 8 раз быстрее, то
есть после выхода из программы собьется
время. Этого можно избежать, если написать
"заплату" на 8-е прерывание таким
образом, чтобы BIOS-обработчик вызывался
не каждый раз, а лишь каждый восьмой. При
этом в остальные семь раз необходимо
посылать в 20h-й порт значение 20h, чтобы
разрешить следующие прерывания от таймера
(если этого не делать, прерывание 8 вызовется
только один раз.
Далее
пример как избежать этого:
unsigned BIOSTimerSpeed=1;
unsigned
TimerFreq=(unsigned)(1193181L/
void
interrupt (*SvInt08)(…)=NULL;
void Set8254Counter(unsigned cnt)
{ long l=cnt;
if(!cnt) l=65536L; /* если 0, то на самом деле 65536 */
BIOSTimerSpeed=(unsigned)(
outportb(0x43,6);
outportb(0x40,(char)cnt);
outportb(0x40,(char)(cnt>>8));
}
void interrupt NewInt08(…)
{ static cnt=0;
cnt++; /* увеличить счетчик пропущенных тиков */
/* если пора вызывать обработчик BIOS...*/
if(cnt>=BIOSTimerSpeed) { cnt=0; SvInt08(); }
/ иначе разрешить следующие прерывания */
else outportb(0x20,0x20);
}
void DeactivateTimer(void); /* предварительное описание */
int SetTimer(unsigned cnt)
{ /* если передается 0, то отключить нашу
процедуру обработки */
if(!cnt)
{ Set8254Counter(0);
/* отключить от прерывания */
if(SvInt08) setvect(8,SvInt08);
return 0;
}
TimerFreq=1193181L/cnt;
Set8254Counter(cnt);
SvInt08=getvect(8); setvect(8,NewInt08);
atexit(DeactivateTimer);
return 1;
}
void SetTimerFreq(unsigned freq)
{ SetTimer((unsigned)(1193181L/
void DeactivateTimer(void)
{ SetTimer(0); }
Демонстрационная
программа:
#include <dos.h>
#include <math.h>
#include <stdlib.h>
#include <graphics.h>
#include <time.h>
#include <conio.h>
#include <bios.h>
#include
<stdio.h>
unsigned BIOSTimerSpeed=1;
unsigned
TimerFreq=(unsigned)(1193181L/
void
interrupt (*SvInt08)(...)=NULL;
int mary[] = {
330, 294, 262, 294, 330, 330, 330,
294, 294, 294, 330, 392, 392,
330, 294, 262, 294, 330, 330, 330, 330,
294, 294, 330, 294, 262, 0
};
int del[] = {
500, 500, 500, 500, 500, 500, 1000,
500, 500, 1000, 500, 500, 1000,
500, 500, 500, 500, 500, 500, 500, 500,
500, 500, 500, 500, 2000
};
void Set8254Counter(unsigned cnt)
{ long l=cnt;
if(!cnt) l=65536L;
BIOSTimerSpeed=(unsigned)(
outportb(0x43,6);
outportb(0x40,(char)cnt);
outportb(0x40,(char)(cnt>>8));
}
void interrupt NewInt08(...)
{ static cnt=0;
cnt++;
if(cnt>=BIOSTimerSpeed) { cnt=0; SvInt08(); }
else outportb(0x20,0x20);
}
void DeactivateTimer(void);
int SetTimer(unsigned cnt)
{
if(!cnt)
{ Set8254Counter(0);
if(SvInt08) setvect(8,SvInt08);
return 0;
}
TimerFreq=1193181L/cnt;
Set8254Counter(cnt);
SvInt08=getvect(8);