Sources
Delphi Russian Knowledge Base
DRKB - база знаний по Дельфи в рунете, составленная Виталием Невзоровым

О таймере

01.01.2007

Компонент timer (таймер) служит для отсчета интервалов реального времени. Его свойство interval определяет интервал временив миллисекундах, который должен пройти от включения таймера до наступления события ontimer. Таймер включается при установке значения true в его свойство enabled. Единожды включенный таймер все время будет возбуждать события ontimer до тех пор, пока его свойство enabled не примет значения false.

Следует учесть, что в силу специфики реализации стандартного аппаратного таймера ibm-совместимого компьютера минимальный реально достижимый интервал отсчета времени не может быть меньше 55 мс (этот интервал называется тиком), более того, любой интервал времени, отсчитываемый с помощью таймера, всегда кратен 55 мс. Чтобы убедиться в этом, проведите эксперимент, в котором подсчитывается среднее время между двумя срабатываниями таймера (timer.dpr):

Начните новый проект с пустой формой и положите на нее компонент ttimer. Установите в свойство enabled таймера значение false. Напишите такой модуль главной формы (листинг 4):

Листинг 4

unit unit1; 

interface 

uses 
windows, messages, sysutils, classes, graphics, controls, forms, 
dialogs, stdctrls, buttons, extctrls; 

type 
  tfmexample = class(tform) 
  panel1: tpanel; 
  bbrun: tbitbtn; 
  bbclose: tbitbtn; 
  edinput: tedit; 
  lboutput: tlabel; 
  mmoutput: tmemo; 
  timer1: ttimer; 
  procedure bbrunclick(sender: tobject); 
  procedure timer1timer(sender: tobject); 
  procedure formactivate(sender: tobject); 
  private 
  begtime: tdatetime; // Начальное время цикла 
  counter: integer; // Счетчик цикла 
end; 

var fmexample: tfmexample; 

implementation 

{$r *.dfm} 

procedure tfmexample.bbrunclick(sender: tobject); 
// Запускает таймер. edinput содержит период его срабатывания. 
var delay: word; 
begin 
  // Проверяем задание интервала 
  if edinput.text='' then exit; 
  try 
    delay := strtoint(edinput.text); 
  except 
    showmessage('Ошибка в записи числа'); 
    edinput.selectall; 
    edinput.setfocus; 
    exit 
  end; 
  counter := 0; // Сбрасываем счетчик 
  timer1.interval := delay; // Устанавливаем интервал 
  begtime := time; // Засекаем время 
  timer1.enabled := true; // Пускаем таймер 
  screen.cursor := crhourglass 
end; 

procedure tfmexample.timer1timer(sender: tobject); 
  var h, m, s, ms: word; // Переменные для декодирования времени 
  const maxcount = 55; // Количество срабатываний таймера 
begin 
  counter := counter + 1; // Наращиваем счетчик срабатываний 
  if counter=maxcount then // Конец цикла? 
  begin // - Да 
    timer1.enabled := false; // Останавливаем таймер 
    // Находим среднее время срабатывания: 
    decodetime((time-begtime)/maxcount, h, m, s, ms); 
    mmoutput.lines.add( // Выводим результат 
    format('Задано %s ms. Получено %d ms.', [edinput.text, ms])); 
    edinput.text := ''; // Готовим следующий запуск 
    edinput.setfocus; 
    screen.cursor := crdefault 
  end; 
end; 

procedure tfmexample.formactivate(sender: tobject); 
begin 
  edinput.setfocus 
end; 

end. 

Необходимость нескольких (maxcount) срабатываний для точного усреднения результата связана с тем, что системные часы обновляются каждые 55 мс. После запуска программы и ввода 1 как требуемого периода срабатывания в редакторе mmoutput вы увидите строку

Задано 1 ms. Получено 55 ms.

в которой указывается, какое реальное время разделяет два соседних события ontimer. Если вы установите период таймера в диапазоне от 56 до 110 мс, в строке будет указано 110 ms и т.д. (в силу дискретности обновления системных часов результаты могут несколько отличаться в ту или иную сторону).

В ряде практически важных областей применения (при разработке игр, в системах реального времени для управления внешними устройствам и т.п.) интервал 55 мс может оказаться слишком велик. Современный ПК имеет мультимедийный таймер, период срабатывания которого может быть от 1 мс и выше, однако этот таймер не имеет компонентного воплощения, поэтому для доступа к нему приходится использовать функции api.

Общая схема его использования такова. Сначала готовится процедура обратного вызова (call back) с заголовком:

procedure timeproc(uid, umsg: uint; dwuser, dw1, dw2: dword); stdcall; 

Здесь uid - идентификатор события таймера (см. об этом ниже);
umsg - не используется;
dwuser - произвольное число, передаваемое процедуре в момент срабатывания таймера;
dw1, dw2 - не используются.

Запуск таймера реализуется функцией:

function timesetevent(udelay, uresolution: uint;
                      lptimeproc: pointer;
                      dwuser: dword;
                      fuevent: uint): uint; stdcall; external 'winmm.dll'; 

Здесь udelay - необходимый период срабатывания таймера (в мс);
uresolution - разрешение таймера (значение 0 означает, что события срабатывания таймера будут возникать с максимально возможной частотой; в целях снижения нагрузки на систему вы можете увеличить это значение);
lptimeproc - адрес процедуры обратного вызова;
dwuser - произвольное число, которое передается процедуре обратного вызова и которым программист может распоряжаться по своему усмотрению;
fuevent - параметр, управляющий периодичностью возникновения события таймера: time_oneshot (0) - событие возникает только один раз через udelay миллисекунд;
time_periodic (1) - события возникают периодически каждые udelay мс.

При успешном обращении функция возвращает идентификатор события таймера или 0, если обращение было ошибочным.

Таймер останавливается, и связанные с ним системные ресурсы освобождаются функцией:

function timekillevent(uid: uint): uint; stdcall; external 'winmm.dll'; 

Здесь uid - идентификатор события таймера, полученный с помощью timesetevent.

В следующем примере (timer.dpr) иллюстрируется использование мультимедийного таймера (листинг 5).

unit unit1; 

interface 

uses 
windows, messages, sysutils, classes, graphics, controls, forms, 
dialogs, stdctrls, buttons, extctrls; 

type 
  tfmexample = class(tform) 
  panel1: tpanel; 
  bbrun: tbitbtn; 
  bbclose: tbitbtn; 
  edinput: tedit; 
  lboutput: tlabel; 
  mmoutput: tmemo; 
  procedure bbrunclick(sender: tobject); 
  procedure formactivate(sender: tobject); 
end; 

var fmexample: tfmexample; 

implementation 

{$r *.dfm} 
// Объявление экспортируемых функций: 

function timesetevent(udelay, ureolution: uint; lptimeproc: pointer; 
dwuser: dword; fuevent: uint): integer; stdcall; external 'winmm'; 

function timekillevent(uid: uint): integer; stdcall; external 'winmm'; 

// Объявление глобальных переменных 
var 
  ueventid: uint; // Идентификатор события таймера 
  begtime: tdatetime; // Засекаем время< 
  counter: integer; // Счетчик повторений 
  delay: word; // Период срабатывания 

procedure proctime(uid, msg: uint; dwuse, dw1, dw2: dword); stdcall; 
// Реакция на срабатывание таймера (процедура обратного вызова) 
var h, m, s, ms: word; // Переменные для декодирования времени 
const maxcount = 55; // Количество повторений 
begin 
  timekillevent(ueventid); // Останавливаем таймер 
  counter := counter+1; // Наращиваем счетчик 
  if counter=maxcount then // Конец цикла? 
  begin // - Да: декодируем время 
    decodetime((time-begtime)/maxcount, h, m, s, ms); 
    fmexample.mmoutput.lines.add( // Сообщаем результат 
    format('Задано %s ms. Получено %d ms', 
    [fmexample.edinput.text,ms])); 
    fmexample.edinput.text := ''; // Готовим повторение 
    fmexample.edinput.setfocus 
  end 
  else // - Нет: вновь пускаем таймер 
    ueventid := timesetevent(delay,0,@proctime,0,1); 
end; 

procedure tfmexample.bbrunclick(sender: tobject); 
// Запускает таймер. edinput содержит требуемый период. 
begin 
  // Проверяем задание периода 
  if edinput.text='' then exit; 
  try 
    delay := strtoint(edinput.text) 
  except 
    showmessage('Ошибка ввода числа'); 
    edinput.selectall; 
    edinput.setfocus; 
    exit 
  end; 
  counter := 0; // Сбрасываем счетчик 
  begtime := time; // Засекаем время 
  // Запускаем таймер: 
  ueventid := timesetevent(delay,0,@proctime,0,1); 
  if ueventid=0 then 
    showmessage('Ошибка запуска таймера') 
end; 

procedure tfmexample.formactivate(sender: tobject); 
begin 
  edinput.setfocus 
end; 

end. 
Previous page:
Использование команды RDTSC процессора Pentium для работы с малыми временными интервалам
Top:
DRKB
Next page:
Класс-оболочка для объекта синхронизации WaitableTimer