QNX RTP Logo QNX Realtime Platform: Русский Портал QNX
Thursday, 20 Nov 2008 15:36
Меню

Проект OpenNET - все о Unix
Использование аппаратных часов реального времени в QNX Print E-mail
Эдуард Кромской, ed1k [at] ukr [dot] net

Не возникает ли периодически у вас желание реализовать какую-либо функцию в программе, используя аппаратные ресурсы целевой платформы, что позволило бы уменьшить нагрузку на процессор? Если ответ "нет", то всегда остаётся возможность перейти на процессор большей производительности, и тогда эта статья не для вас. Эта статья также не для вас, если во главу угла ставится платформенная независимость, Но если желание использовать аппаратуру "на все 100%" возникало, то в этой статье я постарался собрать информацию, необходимую для эффективного использования "Часов реального времени" (далее RTC) на x86 платформе под операционной системой (ОС) QNX6. Здесь следует отметить, что QNX6, как и многие другие ОС, использует RTC крайне ограниченно, только лишь в контексте чтения/изменения даты и времени. Так, что все возможности микросхемы RTC, не используются системой и свободны для использования прикладной программой. А работать с аппаратурой в ОС QNX6 достаточно легко и приятно.

Персональные компьютеры АТ класса содержат RTC и энергонезависимую память (CMOS) построенную на чипе Motorolla MC146818 или аналогичных, хотя новые материнские платы могут иметь RTC интегрированный в набор системной логики, например, PIIX4 содержит RTC в 82371 PCI-to-ISA мосте, а более старый набор системной логики 82C480, на котором строились 80386 PC/AT компьютеры, содержит RTC интегрированный в 82C206 (Integrated Peripherals Controller). Главное, что RTC присутсвует и работа с ним одинакова, что на старом 386, что на новеньком Pentium, что на промышленном компьютере, например, PC/104 формата. Здесь, сразу же, хочу отметить, что не все процессорные платы PC/104 формата позволяют использовать все возможности аппаратных часов. Например, всё изложенное далее справедливо для плат AR-B1320/AR-B1422 от Acrosser Technology Co. и плат PCM-SX от WinSystems Inc., а также для плат серии Microspace® MSM386S/MSM486DX/MSM5x86 от Digital-Logic AG, как и для многих других плат. Но есть и исключения - практически все процессорные платы cpuModuleTM от Real Time Devices USA Ltd., например, CMC16686GX/CMD17686GX/CMH6x86DX и другие от этого производителя, используют аппаратуру RTC для своих нужд программой BIOS. Другими словами, разработчики RTD USA Ltd., решили, раз никто не пользуется этим сервисом, будем мы его использовать :-). Результат - все трюки, описанные в этой статье вы можете забыть, зато зайдя в настройки Power Management Setup программы RTD Enhanced Award BIOS, вы можете указать время, когда вашему Geode нужно просыпаться :-) Тоже может быть полезно! Но, многие компьютеры, всё же, поддерживают полную AT совместимость, оставляя нам, программистам, такой аппаратный узел как RTC. Чтобы не принять какие-нибудь новые расширения RTC за стандарт, будем руководствоваться описанием на старенькую микросхему MC146818 или её аналог от Dallas Semiconductor.

Вот и рассмотрим, чем этот чип нам может помочь, естественно мы не будем трогать такие важные вещи как содержимое CMOS, счет времени и т.д. Любопытные, конечно же, могут прочитать документацию от производителя чипов, чтобы иметь представление, как сделать такие злые вещи, как останов RTC, например. Остальным также рекомендуется чтение документации для того, чтобы не сделать вреда по незнанию. Документация в виде файла DS12887.pdf, размером 584Кб доступна на веб сайте Dallas Semiconductor. Что же представляет из себя этот чип, как он подключен в компьютере и, главное, что полезное он может делать? Начнём по порядку.

Чип содержит 64 или более 8 битных регистров, нас будут интересовать первые 13 регистров, а остальные - это и есть энергонезависимая память, в которой BIOS хранит информацию о настройках вашего компьютера. Три регистра (0,2 и 4) содержат текущее время - секунды, минуты и часы. Хотя RTC могут хранить время как в двоичном, так и в двоично-десятичном (BCD) формате, как правило, BIOS настраивает их работу в двоично-десятичном формате (байт разбивается на две тетрады, каждая тетрада представляет собой десятичную цифру). Также регистр, хранящий часы, обычно настроен на 24 часовой режим, хотя микросхема умеет поддерживать 12 часовой режим - старший бит тогда означает "до полудня" (бит равен нулю) или "после полудня" (бит равен единице).

Ещё одна тройка регистров (1, 3 и 5) содержат тоже секунды, минуты и часы, но уже будильника. Вот первая находка - мы имеем будильник в системе :-) Как часто мы пользуемся будильником в реальной жизни? Некоторые скажут - довольно часто. Как часто мы пользуемся вот этим готовым аппаратным будильником? Уверен, многие и не подозревают, что его можно использовать. А микросхема умеет генерировать прерывание будильника. Далее нас интересуют четыре регистра статуса/управления: регистр A, регистр B, регистр C и регистр D, расположенные соответственно по адресам 0x0a, 0x0b, 0x0c и 0x0d. Остальные регистры из первых 13 - день недели и дата нас не будут интересовать, оставим их операционной системе или программе BIOS.

Кроме того, мы находим, что чип умеет генерировать периодическую последовательность с периодом от 500 мс до 122 мкс, правда установить частоту можно из дискретного ряда частот: 2, 4, 8, 16, ... 4096, 8192 Гц. Куда уходит его "square wave out" мы интересоваться не будем и, на всякий случай, разрешать этот выход тоже не будем (в моих примерах нет функций для работы с этим выходом), главное, что мы можем получать периодически вызываемые аппаратные прерывания - а это иногда очень полезно. Для любопытных, все же, сообщу. Как правило, выход "square wave out" RTC микросхемы никуда не подключен. Так было в старых PC/AT, так есть в большинстве новых AT совместимых компьютерах. Даже более того, RTC интегрированный в микросхему 82371AB набора системной логики PIIX4, не имеет этого выхода вообще. Соответствующий бит в регистре управления оставлен для совместимости. В процессорной плате PC/104 формата AR-B1320 этот выход есть и используется, он подключен к светодиоду. Установив низкую частоту генерации и разрешив этот выход вы можете информировать об ужасной аварии, например. Ужасной в том смысле, конечно же, что обычный ввод/вывод может быть уже и не работает, и вы пошли на крайний метод индикации проблем в системе :-).

Также мы обнаруживаем ещё третье прерывание, так называемое "update-ended". Смысл этого прерывания следующий - регистры статуса/управления мы можем читать всегда, а вот регистры собственно времени - не всегда, так как микросхема раз в секунду обновляет их показания. Строго говоря, в новых реализациях микросхем RTC, мы можем читать все регистры в любой момент, так как временные регистры имеют двойную буферизацию. Вопрос только в том, насколько корректные данные мы прочтем. В конце этого внутреннего цикла обновления микросхема может генерировать прерывание - "цикл закончился, можно прочитать новое время". То есть, мы имеем прерывание раз в секунду - хорошая вещь, если нам надо отображать текущее время, не так ли? А довольно часто заказчик хочет видеть текущее время на экране какой-нибудь системы АСУТП. Забегая немного вперед, скажу, вот эту "академическую" задачу - отображение текущего времени на консоли - я и выбрал для примеров использования всех трёх прерываний RTC (clock.tgz).

Следует отметить, что все три прерывания абсолютно независимы и автономны, программа может использовать как одно любое, так и несколько. Мой пример использует все три прерывания и отображает на консоль соответственно трое часов. Кстати, не обязательно только время обновлять на экране раз в секунду. Обновляться на экране, и не только, с такой частотой может все, что угодно - это зависит только от вашей конкретной задачи и полета вашей фантазии. Использование периодических прерываний RTC для создания таких вот часов в консоли, по большому счету - это извращение, пошел я на это, чтобы на однотипном примере показать использование всех трёх прерываний RTC.

Периодические прерывания RTC использовать крайне просто. Записываете в регистр A требуемую частоту, затем устанавливаете бит разрешения периодических прерываний в регистре B и обслуживаете прерывания. Временная точность этих аппаратных прерываний достаточно высока и определяется точностью кварцевого резонатора RTC. Если требуется именно высокая временная точность для каких-либо критических, но не продолжительных действий, то лучше всего эти действия выполнять непосредственно в обработчике прерывания и использовать функцию InterruptAttach(). Если же крайне высокая точность не требуется, то можно использовать функцию InterruptAttachEvent() и обрабатывать событие прерывания в отдельном потоке. Именно второй вариант используется в моем примере для всех трёх часов.

Вполне вероятно, что вы решите использовать периодические прерывания для чего-нибудь другого и более критичного ко временной точности: например, чтения какого-либо порта АЦП, или ещё чего-нибудь полезного. Разница в том, что при использовании InterruptAttachEvent(), при возникновении прерывания система переводит ваш поток из блокированного состояния ожидания прерывания (INTR) в состояние готовности к выполнению (READY), то есть, другими словами, добавляется временной интервал, который требуется ОС, чтобы запустить ваш поток, стоящий в очереди и готовый выполняться. Этот интервал зависит от приоритета вашего потока и наличия других потоков в очереди и их приоритетов. Но хочу особо отметить, что действия в обработчике прерывания, подключенного вызовом InterruptAttach(), должны быть именно не продолжительными. Например, требуется читать данные, упаковывать их со "штампом времени" и записывать на диск. Так вот, в обработчике прерывания, я бы только читал данные и счетчик, а в другом потоке уже переводил показания счетчика во время, упаковывал данные и писал на диск. А может быть, и не добавлял бы свой обработчик прерывания вызовом InterruptAttach(), а просто поднял бы приоритет моего потока ждущего прерывание. Все зависит от конкретных требований в конкретной задаче.

Написанию обработчиков прерываний посвящен целый раздел документации ОС QNX6, прочтите его обязательно очень вдумчиво и внимательно, если ещё не прочитали. Ещё один вопрос, который часто возникает и я хочу затронуть здесь. Часто спрашивают, а зачем самому бороть всё это, прерывания, обработчики? Разве не то же самое предоставляет ОС посредством таймеров? Ну может во временной точности какой-то выигрыш, уже рассмотрели... А что ещё? Ответ следующий: ОС предоставляет более мощные и удобные средства, чем RTC. Второе, часто путают RTC и какой-то там аппаратный таймер, который использует система. Это разные вещи. Таймер, которым ОС измеряет время, подключен к IRQ0 и это абсолютно другой кварцованный таймер. Это сердце ОС, которое синхронизирует всю работу ядра. Как ОС QNX6 работает с таймерами, прекрасно написано, с иллюстрациями, в книге Роберта Кёртена "Введение в QNX/Neutrino 2" (заказать эту книгу на русском языке можно здесь). Скажу лишь, что каждый таймер, предоставляемый ОС - это дополнительная работа ядру ОС. Если мы используем прерывания микросхемы RTC, то ядру ОС никакой дополнительной работы нет, кроме "шедулирования" нашего процесса, разумеется. Если нам требуются временные события не частые, то выигрыш может быть существенным. Но о "не частых" прерываниях далее в статье. Использование периодических прерываний RTC обсуждается во многих источниках и интернет-конференциях, и я предчувствую, что больше всего вопросов с прерыванием будильника.

Многие скажут, ну и зачем мне прерывание будильника? В моих примерах на нём построены часы без секунд. Идея простая - как только случилось прерывание от будильника мы "заводим" будильник на минуту вперёд. Таким образом, мы имеем прерывания раз в минуту, то есть, всего лишь раз в минуту процессор отвлекается на задачу отображения времени. Может быть и красиво, но, на первый взгляд, как-то не очень полезно. Кроме того, это опять же "академический" пример, как будет сказано далее, прерывание будильника гораздо мощней и может существенно упростить решение такой задачи. Ну что ж, как это прерывание использовать, решать вам.

Но вот более живой пример: система приема информации со спутников серии NOAA, да или любых других полярно-орбитальных, которые не висят неподвижно где-то над экватором, а летают вокруг земного шара, передавая на землю информацию дистанционного зондирования. Есть в вашей системе процесс, который занимается баллистическими рассчетами и вы имеете таблицу, когда и где над горизонтом появиться очередной спутник. Вот и пришли мы к расписанию и будильнику. Самые упрямые скажут - есть в системе cron, почему бы его не использовать. Я на это могу ответить, что cron - это действительно то, что нужно, если вы не хотите возиться с прерываниями, хотите портабельности... Кажется, я с этого начал статью? Но, приведу отвлеченный пример. Скажем, система, упомянутая выше, не автоматическая, а обслуживаемая оператором. Теперь представим оператора новичка. Отработав сеанс он будет периодически поглядывать на часы, если ещё к тому же он склеротик, то будет заглядывать в расписание, дабы не пропустить следующий сеанс. К утру он будет как варёная сарделька. Теперь представим старого матёрого оператора. Отработав сеанс он заведёт будильник на время следующего. Не уверен, что по регламенту ему разрешается спать, но уверен, что к утру он будет гораздо лучше новичка выглядеть. Причем здесь это? Это простой пример того как работает cron и как можно организовать работу. Процессор у нас же "каменный"? Ну да, точно так, и от нас зависит, как он будет греться и сможет ли вообще решить задачу (точнее тот букет задач, которым мы его нагрузили, мы же ведь в мультизадачной ОС :)). Кстати, написать наш cron, который бы "сидел" на аппаратном будильнике, тоже в наших силах - много нареканий слышал на штатный cron в QNX. Может эта информация и поможет кому-то с этой задачей справиться, значит я не зря сейчас трачу время. Но всё же, хрустальная мечта, что всё "железо" должно отображаться под /dev. То есть, менеджер ресурса крайне желателен и для этой штуки. Даже не совсем понимаю, почему такого драйвера нет в QNX. Тогда бы и альтернативный cron мог бы спокойно себе работать с /dev/rtc/alarm.

Дополнительным расширением прерывания будильника является следующее: мы имеем возможность запрограммировать это прерывание на срабатывание раз в секунду, раз в минуту или раз в час. Осуществляется это путем установки, так называемого, "don't care" значения в регистры будильника. Как раз такое решение напрашивается для моего примера, а не перепрограммирование будильника каждый раз на минуту вперед, но... предлагаю проделать это в качестве самостоятельной проработки материала. Теория без практики мертва, писал классик. Необходимые функции включены в файл rtc.c архива примеров.

Современная аппаратура может иметь и некоторые дополнительные возможности. Например, в 82371AB, шесть младших бит регистра D отведены для хранения дня месяца будильника. Таким образом, прерывание будильника может происходить от раза в секунду, как при стандартном Motorolla MC146818 чипе, до один раз в месяц. Но все эти дополнительные возможности современного оборудования использовать нужно, для этого нужно читать документацию на конкретную аппаратуру, но использовать нужно аккуратно, с учетом целевой платформы, так как промышленный компьютер может и не иметь всех расширений десктоп системы. Возможен и обратный вариант, например процессорная плата AR-B1422 имеет два будильника, редкая десктоп система имеет такое расширение :-)

Использование прерываний будильника также не должно вызвать затруднений. Записываете в регистры будильника время срабатывания или "don't care" значения, после этого разрешаете прерывание будильника путем установки соответствующего бита в регистре B. В заданное время обработчик прерывания получит управление.

Самое простое в использовании, третье, "update-ended" прерывание. Использовать его имеет смысл только в том случае, если мы действительно работаем со временем, хранимым в RTC, устанавливаем, корректируем его и т.д. Ну или, как в примере, это оказалось полезное и незадействованное прерывание. Например, с использованием периодических прерываний мы выводим данные в порт с заданной скоростью, "шедулер" использует прерывания будильника, а на этом прерывании "висит" процесс общего мониторинга: считает время бездействия каких-то процессов и "снимает" их по тайм-ауту. Да, чтобы использовать "update-ended" прерывание, нужно установить бит, разрешающий это прерывание в регистре B, и обслуживать его. Никаких дополнительных настроек не предусмотрено.

Доступ к регистрам RTC осуществляется через два порта ввода-вывода, порт с адресом 0x70 является адресным, а порт 0x71 собственно представляет доступ к данным. То есть, другими словами, если вы хотите прочитать регистр, скажем 0x0c - хотите знать кто вызвал прерывание, вы записываете 0x0c в порт 0x70, а затем, читаете из порта 0x71 собственно значение этого регистра - смотрите возбужденные биты и решаете как обрабатывать прерывание. Микросхема подключена к аппаратной линии IRQ8 контроллера прерываний. Вот, собственно, и вся специфика микросхемы RTC. Более детально эта информация расписана в файле rtc.h примера. Я умышленно не расписываю в статье биты регистров, этому посвящены тонны документаций. В заголовочном файле это всё также есть. Моя задача дать общее представление о возможностях микросхемы, которая присутствует практически в любом компьютере. Единственное, на что следует обратить внимание - все три типа прерываний вызывают одно аппаратное прерывание IRQ8 и чтобы разобраться, которое прерывание случилось, нужно читать регистр статуса C. Более того, чтение этого регистра надо делать обязательно в обработчике прерываний до размаскирования аппаратного прерывания, так как чтение регистра статуса С заодно и обнуляет его - как правило, именно это событие приводит к снятию сигнала с аппаратной линии прерывания.

Теперь несколько слов о примере. В файле rtc.h также обьявлена трехбайтная структура cmos_time, в которой храниться время, прямо в BCD формате, а также прототипы функций, некоторые из которых используют указатель на эту структуру, как параметр. Сами функции описаны в файле rtc.c, назначение их, я думаю, понятно из названий функций и комментариев в коде. Тестовый пример, который реализовывает трое часов в консоли, файл clock_qnx.c. Если, все же, возникли вопросы, я отвечу на конкретные по электронной почте или в форуме.

Внимание! Неправильная работа с аппаратурой может привести к выходу её из строя, а также другим плачевным последствиям. Я не несу никакой ответственности за возможную порчу Вашего оборудования, потерю данных или другой урон, включая случаи со смертельным исходом, не зависимо, нанесен он прямо или косвенно из-за неточностей в этой статье, неверного толкования и прочих причин, даже, если будет доказано в судебном порядке, что ущерб произошел по причине неточностей в этой статье. Примеры, находящиеся в файле clock.tgz, являются неотьемлемой частью этой статьи. Удачного программирования!

[Вернуться к списку]
©   2000-2003 Команда проекта QNX.ORG.RU // QNX.ORG.RU Team
Авторы проекта: Дмитрий Алексеев [dmi] и Дмитрий Васильев. Техническое сопровождение проекта: Игорь Сорокин [isorokin]. Информационное сопровождение: Дмитрий Алексеев [dmi]
QNX - зарегистрированная торговая марка QNX Software Systems, Ltd., Canada. Остальные упоминаемые на сайте торговые марки и логотипы являются исключительно собственностью их уважаемых владельцев. Ничьи права не затронуты. Материалы сайта не могут быть скопированы и где-либо использованы в той или иной форме без письменного разрешения разработчиков сайта.
Powered by Mambo Open Source