dk lab
Homepage Карта сайта Версия для печати Статьи:
. .
Apache
Содержание
Основы работы с векторами прерываний
Программирование клавиатуры
Программирование таймера
dkLab | Статьи | Программирование таймера
Система Orphus

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

Дмитрий Котеров

На странице:

1. Введение
2. Механизм работы таймера 8254
3. Программирование таймера

Квартирный переезд газель грузчик. | Качественные логические модули logo siemens с массой преимуществ.

1. Введение

Иногда бывает необходимо замерять промежутки времени, меньшие 55 мс (это стандартная скорость BIOS-таймера). Например, в играх скорость синхронизируется именно по таймеру. Таким образом, если скорость должна быть, скажем, 24 кадра в секунду, необходимо убыстрять таймер (например, до скорости 140 Гц, как это сделано в DOOM-е). Чем быстрее "тикает" таймер, тем с большей точностью можно замерять время. Есть и другие приложения программирования таймера, например, работа с PC-SPEAKER-ом (со встроенным динамиком) в wave-режиме (скорость таймера тогда достигает величин порядка 10-20 КГц!)
Одним словом, если вы умеете программировать, то давно уже столкнулись с проблемой убыстрения таймера. Вот только литературу подходящую найти очень трудно. И простейшая проблема убыстрения таймера становится самой сложной и непостижимой задачей для программиста.

2. Механизм работы таймера 8254

Таймер 8254 устроен довольно сложно. Он имеет 3 так называемых канала, которые работают одновременно и "тикают" с разной скоростью. Например, 0-й канал вызывает генерацию прерывания 8, 1-й используется DMA, а 2-й - для генерации звука. Каждый канал может работать в нескольких режимах. Но вся эта информация практически не представляет ценности.
Рассмотрим, как же работает таймер. Он имеет кварцевый генератор (или часы), которые "тикают" ровно 119318 раз в секунду. Нулевой канал, который мы будем рассматривать, имеет так называемый счетчик. Этот счетчик возрастает на 1 каждый "тик" часов. Когда он достигает некоторого граничного значения (которое можно установить программно), он сбрасывается, и генерируется прерывание 8.
BIOS устанавливает это граничное значение в 0, что для таймера обозначает 65536. То есть каждый 65536-й "тик" часов генерируется прерывание 8, оно "срабатывает" примерно 119318/65536=18.2 раз в секунду.

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

Если мы хотим установить свое граничное значение cnt, можно воспользоваться командами:

Си

outportb(0x43,6);      /* channel state */
outportb(0x40,(char)cnt);      /* low byte */
outportb(0x40,(char)(cnt>>8)); /* hi byte */

Паскаль

Port[$43]:=6;
Port[$40]:=Lo(cnt);     { младший байт значения }
Port[$40]:=Hi(cnt)      { а затем старший байт }
Запустим подобную программу на выполнение, скажем, со значнием cnt=65536/8. Когда она отработает, мы увидим странную вещь: часы в Нортоне станут идти в 8 раз быстрее! Это происходит оттого, что после выхода из программы необходимо установить старый счетчик, который устанавливал BIOS, то есть 0.
Однако и это не решит до конца проблему. Ведь на период работы программы часы все равно будут идти в 8 раз быстрее, то есть после выхода из программы собьется время. Этого можно избежать, если написать "заплату" на 8-е прерывание таким образом, чтобы BIOS-обработчик вызывался не каждый раз, а лишь каждый восьмой. При этом в остальные семь раз необходимо посылать в 20h-й порт значение 20h, чтобы разрешить следующие прерывания от таймера (если этого не делать, прерывание 8 вызовется только один раз!!! Помню, я один раз целый день выяснял, почему не работает программа - тогда я не знал этого приема).
Приведем пример на Си, иллюстрирующий работу с таймером.

unsigned BIOSTimerSpeed=1;
unsigned TimerFreq=(unsigned)(1193181L/65536L);
void interrupt (*SvInt08)(void)=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(void)
 { 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); }
Для инициализации таймера нужно из main() вызвать либо SetTimer() с граничным значением в параметре, либо (что лучше использовать при очень большой частоте таймера, чтобы избежать ошибок округления), функцию SetTimerFreq() с параметром частоты таймера в Гц.
На Паскале это делается совершенно аналогично.

3 ноября 2000, 17:21
Дмитрий Котеров
dkLab, ©1999-2016