Смекни!
smekni.com

Программирование системного таймера

МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ УКРАИНЫ

ХАРЬКОВСКИЙ НАЦИОНАЛЬНЫЙ УНИВЕРСИТЕТ

имени В.Н. Каразина

Курсовой проект

Дисциплина: архитектура ПК

Тема: «Программирование системного таймера»

Выполнил:

Шульга Сергей Олегович

студент ФКН гр. КС-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 (при этом нельзя изменять значение остальных битов этого порта).

Мелодия (одноголосая), как известно, состоит из нот, разделенных или не разделенных паузами. При проигрывании мелодии необходимо для каждой ноты программировать соответствующим образом канал 2 таймера и включать громкоговоритель (с помощью порта 61h) на определенное время, равное длительности ноты. Затем программа должна выключить динамик и выдержать паузу перед проигрыванием следующей ноты, если такая пауза требуется.

Пример функции:

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); /* low byte */

outportb(0x40,(char)(cnt>>8)); /* hi byte */

Запустим подобную программу на выполнение, скажем, со значнием cnt=65536/8. Когда она отработает, мы увидим, что часы станут идти в 8 раз быстрее! Это происходит оттого, что после выхода из программы необходимо установить старый счетчик, который устанавливал BIOS, то есть 0.

Однако и это не решит до конца проблему. Ведь на период работы программы часы все равно будут идти в 8 раз быстрее, то есть после выхода из программы собьется время. Этого можно избежать, если написать "заплату" на 8-е прерывание таким образом, чтобы BIOS-обработчик вызывался не каждый раз, а лишь каждый восьмой. При этом в остальные семь раз необходимо посылать в 20h-й порт значение 20h, чтобы разрешить следующие прерывания от таймера (если этого не делать, прерывание 8 вызовется только один раз.

Далее пример как избежать этого:

unsigned BIOSTimerSpeed=1;

unsigned TimerFreq=(unsigned)(1193181L/65536L);

void interrupt (*SvInt08)(…)=NULL;

void Set8254Counter(unsigned cnt)

{ long l=cnt;

if(!cnt) l=65536L; /* если 0, то на самом деле 65536 */

BIOSTimerSpeed=(unsigned)(65536L/l);

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/freq)); }

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/65536L);

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)(65536L/l);

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);

//setvect(8,NewInt08);

//atexit(DeactivateTimer);

return 1;

}

void SetTimerFreq(unsigned freq)

{ SetTimer((unsigned)(1193181L/freq)); }

void DeactivateTimer(void)

{ SetTimer(0); }

void printtime()

{

struct time t;

gettime(&t);

printf("The current time is: %2d:%02d:%02d.%02d&bsol;n",

t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund);

}

void rnd_set(int bound) {

outportb(0x43, 0xb6);

outportb(0x42, bound & 0x00ff);

outportb(0x42, (bound &0xff00) >> 8);

outportb(0x61, inp(0x61) | 1);

}

int rnd_get(void) {

int i;

outportb(0x43, 0x86);

i = inportb(0x42);

i = (inportb(0x42) << 8) + i;

return(i);

}

void tm_sound(int freq, int time)

{

int cnt, i;

outp(0x43, 0xb6);

cnt = 1193180L / freq;

outp(0x42, cnt & 0x00ff);

outp(0x42, (cnt &0xff00) >> 8);

outp(0x61, inp(0x61) | 3);

delay(time);

outp(0x61, inp(0x61) & 0xfc);

}

void main()

{

int i;

for(i=0 ;mary[i] != 0 ;i++)

tm_sound(mary[i], del[i]);

//printtime();

SetTimerFreq(182);

puts("Press any key for continue");

getch();

for(i=0 ;mary[i] != 0 ;i++)

tm_sound(mary[i], del[i]);

//delay(50000);

//printtime();

SetTimer(0);

puts("Press any key for continue");

getch();

int j;

printf("&bsol;nRandom number generator"

"&bsol;nPress any key"

"&bsol;n");

for(int k=0;k<10;k++) {

rnd_set(80);

getch();

j = rnd_get();

for(i=0; i < j; i++) putchar(219);

printf("&bsol;n");

}

puts("Thank you&bsol;n");

getch();

}

Заключение:

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


Литература:

1. «Современные операционные системы», Э. Таненбаум, Издательство «Питер», 2010 г.

2. «Операционные системы», В.Столингс, 2002 г.

3. http://dklab.ru/

4. http://www.lib.csu.ru/