Записки Вовика

Модуль часов DS3231 и Fedora 18

Просмотров: 3337Комментарии: 0
ЗаметкиСubieboardRaspberry Pi*nix

Как известно, Rasperry Pi не имеет встроенных часов, которые сохраняют системное время при отключении питания. Т.е. при загрузке системное время установлено в какое-то фантастическое значение на несколько лет назад.

Для решения этой проблемы был куплен модуль DS3231 и тут началось.

В интернете полно описаний как быстро и легко настраивается данный модуль. Однако эти описания как правило для ОС Raspbian. Аналогично легко настраивается данный модуль на плате Cubieboard с ОС Cubian. Т.е. во всех ОС семейства Debian/Ubuntu есть модуль ядра rtc-ds1307 с драйвером под эту микросхему, ну или есть варианты нарыть этот модуль и добавить в свое ядро. Но вот Федора начиная с версии 18 решила что этот модуль не нужен и удалили его из ядра, вроде как из-за того что устройство несколько самопальное и нисколько не стандартизированное. У меня же на Raspberry установлена Fedora 18 и на Cubieboard2 работает Fedora 19.

Чем интересно использование модуля ядра rtc-ds1307. В этом варианте в системе появляется новое устройство /dev/rtc0, и для работы с ним имеется масса стандартных программ типа hwclock. Но без наличия устройства /dev/rtc0 с часами работать оказалось тоже весьма легко. В общем, два дня у меня ушло на разборки с Федорой и модулем ядра. После этого я бросил идею решить задачу на уровне ядра и за один день настроил скрипты для чтения и синхронизации времени без драйверов устройства. А значит задачка не очень сложная, особенно учитывая, что это мой самый первый опыт работы с Python.

Доступ к DS3231 очень просто выполняется через шину I2C, а уж тут средств для реализации своего творческого потенциала более чем достаточно. Работать с DS3231 через I2C можно используя C/C++, Python или даже bash.

Т.е. имеем основные задачи:

  • прочитать время из DS3231
  • установить время в DS3231
  • сверить и изменить системное время
  • автоматизировать процесс

Мой выбор пал на Python. Использование его для доступа к I2C и GPIO показалось мне наиболее простым и приятным. Ну и с чего-то же нужно было начинать осваивать этот язык.

Чтение и запись данных в DS3231

Для работы с DS3231 через шину I2C устанавливаем i2c-tools. Тут есть варианты и выбор, но выбрал i2c-tools как легковесный и достаточный инструментарий. Основная альтернатива это Arduino, но для моих задач это как из пушки по воробьям.

i2c-tools можно ставить из репозитория, но сборка выполняется очень просто и быстро. После установки будут доступны такие программы как i2cdetect, i2cget, i2cset, i2cdump. Т.е. уже можно из командной строки или shell-скрипта делать с DS3231 уже почти все что угодно.

Примеры (раскрыть...)

Вызов команд пакета i2c-tools нужно выполнять от рута, иначе программа не сможет получить доступ к устройству как на запись, так и на чтение:

$ i2cget -y 1 0x68 0
Error: Could not open file `/dev/i2c-1': Permission denied

Также i2c-tools содержит библиотеку Python для работы с I2C. Чтобы она была собрана при компиляции нужно указать следующее:

$ make EXTRA="py-smbus"

После компиляции необходимо выполнить её установку:

$ cd py-smbus
$ sudo python setup.py install

Теперь в Python библиотека доступна для использования:

import smbus

Далее становится вопрос какие данные и функции используются для работы с DS3231. Все доступные регистры описаны выше - ничего сложного в общем-то, но с регистрами работать постоянно не совсем удобно. Напрашивается создание библиотеки для того чтобы реализовать простой и красивый интерфейс работы с устройством. В результате было выбрано готовое решение RTC_SDL_DS3231. Эту библиотеку из 200 строк можно взять за основу, изучать как работать в DS3231 и точить самому если что-то не устраивает. Я исправил только название файла и класса - удалил SDL_ из SDL_DS3231, слишком длинным показался.

Там есть демонстрационная программа testSDL_DS3231.py, которая записывает текущее системное время в DS3231 и всячески демонстрирует возможности работы с микросхемой - показывает время и считывает данные датчика температуры.

Для считывания времени из DS3231 используется скрипт ds3231time.py:

#!/usr/bin/env python
# prepares data string
# from DS3231 for system clock
import sys
import DS3231
ds3231 = DS3231.DS3231(1, 0x68)
sys.stdout.write("%s" % ds3231.read_datetime())

Пример вызова:

# ./ds3231time.py 
2014-08-22 18:18:14

Используется вызов sys.stdout.write вместо print чтобы в конце не вставлялся перевод строки.

Для записи текущего системного времени в DS3231 вызывается функция:

ds3231.write_now()

Для записи текущего системного времени в DS3231 скрипт еще не готов, так как тут есть несколько нерешенных моментов. Дело в том, что DS3231 хранит время с точностью до 1 секунды. Т.е. чтобы избежать расхождений запись в DS3231 нужно выполнить в момент перехода между секундами. Особой надобности выверять точность нет, но хотелось бы попасть на границу хоть приблизительно. Системное время корректируется при наличии соединения с интернетом. После выполнения корректировки хочется выполнить автоматически корректировку времени в DS3231. Так что этот скрипт пока еще в процессе создания.

Корректировка системного времени

Для этого я воспользовался вызовом date из GNU coreutils. Текстовую строку со значением формирует скрипт ds3231time.py.

date --set="`ds3231time.py`" +"%Y-%m-%d %H:%M:%S"

Обращаю внимание, что ds3231time.py должен быть исполняемым файлом, или же нужно писать python ds3231time.py .

Также я написал скрипт check.py для проверки разницы времени между системными часами и DS3231. Допустимой разницей времени установил 10 минут, и если разница меньше, то скрипт вернет ноль, иначе единицу.

Код check.py:

#!/usr/bin/env python
#
# Check time difference between
# system clock and DS3231
#
# Exit values
# 0 - time is ok
# 1 - correction needed
import sys
import datetime
import DS3231
# allowed difference 10 min
delta_allowed = datetime.timedelta(minutes=10)
ds3231 = DS3231.DS3231(1, 0x68)
currenttime = datetime.datetime.now()
rtctime = ds3231.read_datetime()
if rtctime > currenttime :
    deltatime = rtctime - currenttime
else:
    deltatime = currenttime - rtctime
#print "system",  str(currenttime)
#print "ds3231", str(rtctime)
#print "delta ", str(deltatime)
if deltatime > delta_allowed :
    sys.exit(1)
else:
    sys.exit(0)
    print "System time is ok"

Ничего необычного, только один немного странный оператор if.

Дело в том, что мне нужно вычислить разницу (расстояние) между двумя моментами времени. Оператор if обеспечивает вычитание меньшего значения из большего. Если вычитать из меньшей даты большую получим "интересный" результат.

Вот пример обоих вариантов вычитания:

system 2014-08-22 20:52:57.883845
ds3231 2014-08-22 20:52:58
delta  -1 day, 23:59:59.883845
delta  0:00:00.538300

Получается, если значение "-1 day, 23:59:59.883845" сравнить с интервалом "10 минут", то потребуется коррекция времени, хотя на самом деле расстояние меньше секунды. Какое значение времени будет больше, system или ds3231, зависит от момента считывания времени и величины рассинхронизации времени, т.е. носит случайный характер.

Автоматизация работы

В Fedora18 используется systemd. Будем использовать для считывания времени из DS3231 запуск сервиса chronyd, который обеспечивает функционал NTP протокола и синхронизацию времени через интернет. Для корректировки сделан shell-скрипт hw_clock, который вызывается перед запуском chronyd. Он выполняет проверку разницы времени скриптом check.py, и если нужно выполняется корректировка системного времени.

Запуск hw_clock прописывается в файле /usr/lib/systemd/system/chronyd.service в параметре ExecStartPre.

Код hw_clock:

#!/bin/bash
#******************************************
# chronyd before start script
# for setting RPi time from DS3231
# 
# put this script at ExecStartPre
# (/usr/lib/systemd/system/chronyd.service)
# of systemctl start chronyd.service
#******************************************
RETVAL=0
#
# check system and DS3231 time difference
echo "System time checking"
/etc/ds3231/check.py  2>&1
RETVAL=$?
[ $RETVAL = 0 ] && exit 0
#
# system time is wrong and needs corretion
# read time from DS3231 and set system time
echo "System time correcting"
date --set="`/etc/ds3231/ds3231time.py`" +"%Y-%m-%d %H:%M:%S" 2>&1
#
# checking correction results
/etc/ds3231/check.py > /dev/null 2>&1
RETVAL=$?
[ $RETVAL != 0 ] && echo "Correction failed"
exit 0

Результат и лог корректировки времени можно увидеть командой:

# systemctl status chronyd
chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled)
   Active: active (running) since Fri 2014-08-22 17:41:01 EEST; 2h 17min ago
  Process: 359 ExecStartPost=/usr/libexec/chrony-helper add-dhclient-servers (code=exited, status=0/SUCCESS)
  Process: 355 ExecStart=/usr/sbin/chronyd -u chrony $OPTIONS (code=exited, status=0/SUCCESS)
  Process: 346 ExecStartPre=/etc/chronyd_pid_fix (code=exited, status=0/SUCCESS)
  Process: 153 ExecStartPre=/etc/ds3231/hw_clock (code=exited, status=0/SUCCESS)
 Main PID: 357 (chronyd)
   CGroup: name=systemd:/system/chronyd.service
           └─357 /usr/sbin/chronyd -u chrony
Mar 07 22:04:21 rpi_fedora.home hw_clock[153]: System time correcting
Aug 22 17:40:59 rpi_fedora.home hw_clock[153]: 2014-08-22 17:40:59
Aug 22 17:41:01 rpi_fedora.home chronyd[357]: chronyd version 1.29 starting
Aug 22 17:41:01 rpi_fedora.home chronyd[357]: Linux kernel major=3 minor=6 patch=11
Aug 22 17:41:01 rpi_fedora.home chronyd[357]: hz=100 shift_hz=7 freq_scale=1.00000000 nominal_tick=10000 slew_delta_tick=833 max_tick_bias=1000 shift_pll=2
Aug 22 17:41:01 rpi_fedora.home chronyd[357]: Frequency 2.453 +/- 19.823 ppm read from /var/lib/chrony/drift
Aug 22 17:41:01 rpi_fedora.home systemd[1]: Started NTP client/server.
Aug 22 17:42:31 rpi_fedora.home chronyd[357]: Selected source 91.236.251.29
Aug 22 17:44:44 rpi_fedora.home chronyd[357]: Selected source 79.142.192.4
Aug 22 17:46:55 rpi_fedora.home chronyd[357]: Selected source 91.218.89.74

Итог

Скрипты разместил в папке /etc/ds3231:

-rwxrw-r-- 1 root root  779 Aug 22 20:13 check.py
-rw-rw-r-- 1 root root 6633 Aug 21 20:49 DS3231.py
-rwxrw-r-- 1 root root  201 Aug 22 18:18 ds3231time.py
-rwxrw-r-- 1 root root  742 Aug 22 20:15 hw_clock

Скачать скрипты

Оставьте комментарий!

grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question


Комментарий будет опубликован после проверки

     

  

(обязательно)