[an error occurred while processing this directive] [an error occurred while processing this directive]
Только сейчас нашел .
(«Телесистемы»: Конференция «Микроконтроллеры и их применение»)
[an error occurred while processing this directive] [an error occurred while processing this directive]

Отправлено spectr 09 апреля 2002 г. 11:52
В ответ на: Ответ: Действительно, очень любопытно отправлено DmitryRyvkin 09 апреля 2002 г. 11:43


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


На странице:

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


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
Дмитрий Котеров
Лаборатория

Составить ответ  |||  Конференция  |||  Архив

Ответы



Перейти к списку ответов  |||  Конференция  |||  Архив  |||  Главная страница  |||  Содержание  |||  Без кадра

E-mail: info@telesys.ru