4. Часы реального времени

Все соверменные компьютеры оснащены часами реального времени. Эти часы работают от аккумулятора, поэтому их показания не пропадают при выключении электропитания.

Доступ к часам реального времени из программы возможен либо через ячейки памяти CMOS, либо через специальные функции BIOS (что более предпочтительно с точки зрения независимости программы от особенностей аппаратуры).

Регистры часов реального времени

Использование регистров памяти CMOS часами реального времени приведено ниже.

Регистры счетчиков

В регистрах 0, 2, 4, 6 и 8 хранится текущее значение времени и дата. Регистры с номерами 1, 3, 5, 7 и 9 – это регистры будильника. Вы можете установить будильник, и когда он сработает, произойдет прерывание.

Регистр Счетчик, который содержится в регистре
0 Секунды
1 Секунды будильника
2 Минуты
3 Минуты будильника
4 Часы
5 Часы будильника
6 День недели (1 - воскресенье)
7 День месяца
8 Номер месяца
9 Последние две цифры текущего года

Регистр состояния A

Адрес первого регистра состояния – 0Ah. Формат битовых полей представлен ниже:

Биты регистра Описание
0-3 Переключатель скорости. По умолчанию установлен в 0110
4-6 22-разрядный делитель. По умолчанию установлен в 010
7 Флаг обновления. Значение 0 означает готовность данных для чтения

Регистр состояния B

Адрес второго регистра состояния – 0Bh. Ниже вы найдете формат битовых полей этого регистра:

Биты регистра Описание
0 Летнее или стандартное время:
1 - летнее время;
0 – стандартное время
1 12 или 24-часовой режим:
0 - 12-часовой режим
1 – 24-часовой режим (установлен по умолчанию)
2 Формат данных:
1 – двоичный;
0 - BCD (установлен по умолчанию)
3 Разрешение прямоугольного сигнала:
1 – включение сигнала, частота которого определяется разрядами 0-3 первого регистра состояния;
0 – сигнал выключен
4 Разрешение прерывания по окончанию изменения данных (по умолчанию сброшен)
5 Разрешение прерывания будильника (по умолчанию сброшен)
6 Разрешение периодических прерываний (по умолчанию сброшен)
7 Запрет счета:
1 – счетчик остановлен;
0 – счетчик запущен

Регистр состояния C

Адрес третьего регистра состояния – 0Сh. Этот регистр доступен только для чтения и содержит биты состояния прерывания.

Регистр состояния D

Адрес четвертого регистра состояния – 0Dh. Если бит 7 этого регистра сброшен, это означает, что разрядился аккумулятор, питающий память CMOS.

Прерывание от часов реального времени

Часы реального времени вырабатывают аппаратное прерывание IRQ8, которому соответствует прерывание с номером 70h. Это прерывание может вырабатываться по трем причинам:

При срабатывании будильника BIOS вырабатывает прерывание INT 4Ah. Программа может подготовить собственный обработчик для этого прерывания.

Функции прерывания INT 1Ah

Для работы с часами реального времени вы можете обращаться непосредственно к перечисленным выше ячейкам памяти CMOS, используя порты 70h и 71h. Однако лучше всего воспользоваться функциями 2 - 7 прерывания INT 1Ah, описанными ниже.

Прочитать показания часов реального времени

Регистры на входе: AH = 02h
Регистры на выходе: CH = часы в BCD-формате (например, 13h означает 13 часов);
CL = минуты в BCD-формате;
DH = секунды в BCD-формате;
CF = CY = 1, если часы реального времени не установлены

Установить часы реального времени

Регистры на входе: AH = 03h
CH = часы в BCD-формате (например, 13h означает 13 часов);
CL = минуты в BCD-формате;
DH = секунды в BCD-формате;
DL = 1, если необходимо использовать летнее время
Регистры на выходе: Регистры не используются

Прочитать дату из часов реального времени

Регистры на входе: AH = 04h
Регистры на выходе: CH = столетие в BCD-формате;
CL = год в BCD-формате (например, CX=1997h означает 1997 год);
DH = месяц в BCD-формате;
DL = число в BCD-формате;
CF = CY = 1, если часы реального времени не установлены

Установить дату в часах реального времени

Регистры на входе: AH = 05h
CH = столетие в BCD-формате;
CL = год в BCD-формате (например, CX=1997h означает 1997 год);
DH = месяц в BCD-формате;
DL = число в BCD-формате;
Регистры на выходе: Регистры не используются

Установить будильник

Регистры на входе: AH = 06h
CH = часы в BCD-формате;
CL = минуты в BCD-формате;
DH = секунды в BCD-формате
Регистры на выходе: CF = CY = 1, если часы реального времени не установлены

Эта функция позволяет установить будильник на заданное время. Когда будильник “зазвенит”, будет вызвано прерывание INT 4Ah (его вызывает BIOS после прихода аппаратного прерывания от часов реального времени IRQ8, то есть прерывания с номером 70h). Программа, использующая функцию будильника, должна подготовить обработчик прерывания INT 4Ah, завершающий свою работу выполнением команды IRET.

Заметим, что можно установить только один будильник.

Сброс будильника

Регистры на входе: AH = 07h
Регистры на выходе: Регистры не используются

Эта функция позволяет сбросить будильник, например, для того чтобы установить его на другое время.

Программа RTCALARM

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

Так как установленное время срабатывания будильника хранится в памяти CMOS, питающейся от аккумулятора, будильник не будет сброшен при случайном выключении компьютера.

Для иллюстрации основных приемов работы с часами мы подготовили программу RTCALARM (листинг 4.1), которая выводит на экран текущую дату и время. Затем программа устанавливает будильник, который должен сработать через одну минуту и подать звуковой сигнал.

Перед установкой будильника программа подключает свой обработчик прерывания 4Ah. Это прерывание вызывается при срабатывании будильника. Перед завершением работы программа сбрасывает будильник и восстанавливает вектор прерывания 4Ah.

Листинг 4.1. Файл rtcalarm\rtcalarm.с

// =====================================================
// Работа с часами реального времени
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <dos.h>

typedef struct _SYSTIMER_ 
{
  char hour;             // часы
  char min;              // минуты
  char sec;              // секунды
  unsigned year;         // год
  char month;            // месяц
  char day;              // число
  char daylight_savings; // флаг летнего времени
} SYSTIMER;

#define RTC_GET_TIME     2 // прочитать показания часов;
#define RTC_SET_TIME     3 // установить часы;
#define RTC_GET_DATE     4 // прочитать дату;
#define RTC_SET_DATE     5 // установить дату;
#define RTC_SET_ALARM    6 // установить будильник;
#define RTC_CLEAR_ALARM  7 // сбросить будильник.

int bcd1bin(char *bcd);
int bcd2bin(char *bcd);
void bin1bcd(int bin, char *bcd);
void _interrupt _far alarm(void);
int timer(char fn, SYSTIMER *tm);

// Выключаем проверку стека и указателей
#pragma check_stack( off )
#pragma check_pointer( off )

// Макро для выдачи звукового сигнала

#define BEEP() _asm { \
_asm mov bx,0 \
_asm mov ax, 0E07h \
_asm int 10h \
}

// Прототип программы-обработчика прерывания
// будильника
void _interrupt _far alarm(void);

// Переменная для хранения старого
// вектора будильника
void (_interrupt _far *old_4a)(void);

union REGS reg;

int main(void) 
{
  char *month_to_text[] = 
  {
  	"январь",
"февраль",
"март",
"апрель",
"май",
"июнь",
"июль",
"август",
"сентябрь",
"октябрь",
"ноябрь",
"декабрь"
  };

  SYSTIMER tmr;

  // Определяем текущие дату и время
  timer(RTC_GET_DATE, &tmr);
  timer(RTC_GET_TIME, &tmr);

  // Выводим дату и время на экран
  printf("\nСейчас %d год, %s, %d число."
    "\n",
bcd2bin((char*)&(tmr.year)),
month_to_text[bcd1bin(&(tmr.month)) - 1],
bcd1bin(&(tmr.day)));

  printf("\nВремя - %02.2d:%02.2d:%02.2d"
    "\n",
bcd1bin(&(tmr.hour)),
bcd1bin(&(tmr.min)),
bcd1bin(&(tmr.sec)));

  // Для установки будильника увеличиваем
  // счетчик минут на единицу. Для упрощения
  // программы мы не проверяем счетчик на
  // переполнение, поэтому если текущее
  // значение счетчика минут равно 59,
  // будильник не сработает. Вы можете сами
  // немного усовершенствовать программу для
  // проверки переполнения
  bin1bcd(bcd1bin(&(tmr.min)) + 1, &(tmr.min));

  // Выводим на экран время, когда сработает
  // будильник.
  printf("\nВремя срабатывания будильника"
    "- %02.2d:%02.2d:%02.2d"
"\n",
bcd1bin(&(tmr.hour)),
bcd1bin(&(tmr.min)),
bcd1bin(&(tmr.sec)));

  // Подключаем свой обработчик прерывания
  // будильника, старое значение вектора
  // 0x4a сохраняем
  old_4a = _dos_getvect(0x4a);

  _dos_setvect(0x4a, alarm);

  // Устанавливаем будильник
  timer(RTC_SET_ALARM, &tmr);

  printf("\nБудильник установлен. Для отмены "
    "и завершения программы нажмите"
"\nлюбую клавишу...");

  getch();

  // Сбрасываем будильник и восстанавливаем
  // вектор прерывания будильника
  timer(RTC_CLEAR_ALARM, &tmr);

  _dos_setvect(0x4a, old_4a);
  
  return 0;
}

// ----------------------------------
// Преобразование однобайтового
// числа из формата BCD в двоичный
// формат
// ----------------------------------
int bcd1bin(char *bcd) 
{
  return( ((*bcd) & 0x0f) +
    10 * (((*bcd) & 0xf0) >> 4) );
}

// ----------------------------------
// Преобразование двухбайтового
// числа из формата BCD в двоичный
// формат
// ----------------------------------
int bcd2bin(char *bcd) 
{
  return( bcd1bin(bcd) + 
    100 * bcd1bin(bcd + 1) );
}

// ----------------------------------
// Преобразование однобайтового
// числа из двоичного формата
// формат BCD
// ----------------------------------
void bin1bcd(int bin, char *bcd) 
{
  int i;
  i = bin / 10;
  *bcd = (i << 4) + (bin - (i * 10));
}

// ----------------------------------
// Программа получает управление
// при срабатывании будильника.
// Ее назначение - выдать звуковой сигнал
// ----------------------------------
void _interrupt _far alarm(void) 
{
  BEEP();
  BEEP();
  BEEP();
  BEEP();
  BEEP();
  BEEP();
  BEEP();
}

/**
*.Name         timer
*.Title        Работа с часами реального времени
*
*.Descr        Эта функция предназначена для обслуживания
*              системных часов реального времени через
*              прерывание INT 1Ah
*
*.Proto        int timer(char fn, SYSTIMER *tm)
*
*.Params       char     fn - выполняемая функция:
*
*              RTC_GET_TIME      - прочитать показания часов;
*              RTC_SET_TIME      - установить часы;
*              RTC_GET_DATE      - прочитать дату;
*              RTC_SET_DATE      - установить дату;
*              RTC_SET_ALARM     - установить будильник;
*              RTC_CLEAR_ALARM   - сбросить будильник.
*
*              SYSTIMER tm - структура, содержащая данные
*                            для установки часов или
*                            показания часов:
*
*.Return       0   - успешное выполнение функции;
*              -1  - часы реального времени отсутствуют
*                    в компьютере;
**/
int timer(char fn, SYSTIMER *tm) 
{
  reg.h.ah = fn;

  switch (fn) 
  {
    case RTC_SET_TIME:
    {
      reg.h.ch = tm->hour;
  reg.h.cl = tm->min;
  reg.h.dh = tm->sec;
  reg.h.dl = tm->daylight_savings;
      break;
    }

case RTC_SET_DATE:
{
      reg.x.cx = tm->year;
  reg.h.dh = tm->month;
  reg.h.dl = tm->day;
      break;
    }

case RTC_SET_ALARM:
{
      reg.h.ch = tm->hour;
  reg.h.cl = tm->min;
  reg.h.dh = tm->sec;
      break;
    }
  }

  int86(0x1a,&reg,&reg);

  if(reg.x.cflag == 1) 
    return(-1);

  switch (fn) 
  {
    case RTC_GET_TIME:
    {
      tm->hour = reg.h.ch;
  tm->min = reg.h.cl;
  tm->sec = reg.h.dh;
      break;
    }

case RTC_GET_DATE:
{
      tm->year = reg.x.cx;
  tm->month = reg.h.dh;
  tm->day = reg.h.dl;
      break;
    }
  }
  return 0;
}