Raspberry Pi
37) Vlastní ovladač pro RTC obvod
BQ32000 v Pythonu
V článku č. 14 jsem zprovozňoval modul hodin reálného času RTC s
obvodem PCF8563. Tehdy jsem ještě neměl z programováním moc zkušeností,
tak jsem využíval už hotové ovladače. Nyní už se v komunikaci I2C
vyznám trochu lépe, a tak jsem si napsal vlastní ovladač v Pythonu pro
RTC obvod BQ32000 od Texas
Instruments. Tento obvod jsem si zvolil hlavně z
toho důvodu, že je možné jeho přesnost naladit softwérově. Navíc
jsem ho sehnal zdarma jako vzorek od výše uvedené firmy - Thank you
T.I.
Jeho katalogový
list je tady: http://www.ti.com/lit/gpn/bq32000
Schéma zapojení:
Celý ovládací program je tady:
Dal by se napsat i úsporněji, ale já jsem se snažil spíše o to,
aby byl lépe pochopitelný.
Když program trochu upravíte, měl by být použitelný i pro jiné
RTC obvody, které jsou řízeny přes I2C.
Některé části tu vysvětlím podrobněji:
1) Předávání parametrů přes příkazovou řádku.
Program má v sobě několik funkcí, které lze vybírat pomocí
parametrů předávaných přes příkazovou řádku.
Konkrétní příklad vypadá třeba takto:
sudo python /home/pi/bq32000.py -pi2rtc |
Parametr je v tomto případě řetězec "-pi2rtc" (je to příkaz
pro uložení systémového času z RasPi do obvodu RTC).
Jiný příklad je tady:
sudo python /home/pi/bq32000.py -f1 |
Tady je parametrem řetězec "-f1" (tímto parametrem se na
výstupní pin obvodu RTC pošle obdélníkový signál s frekvencí 1Hz).
Speciálním případem pro předávání parametrů je nastavování
kalibrační konstanty pro přesné seřízení času. V tom případě se
předávají parametry dva:
sudo python /home/pi/bq32000.py -k 12 |
Parametr č.1 je řetězec "-k" a parametr č.2 je také řetězec
"12".
Pomocí pythonu je možné tyto předávané parametry ukládat do proměnných
a dále s nimi pracovat.
Pokud je parametr očekávaný, ale nebyl předaný, skončil by program s
chybovým hlášením. Proto je vhodné se na tento případ připravit a
tuto případnou chybu odchytit. K tomu se používá konstrukce příkazů
"try:" a "except:"
Část programu, která se stará o předávání a vyhodnocování parametrů pak vypadá
takto - ke každému parametru (akci) je přiřazen nějaký podprogram:
.
.
.
import sys
.
.
.
.
# vyhodnoceni predavaneho parametru pri spousteni programu
try: # odchytavani chyby, ktera by vznikla pri chybejicim parametru
akce = sys.argv[1] # parametr spousteneho programu do promenne "akce"
except: # kdyz parametr chybi, je nahrazen prazdnym retezcem
akce=""
akce = akce.lower() # prevod parametru na mala pismena
if (akce == "-i"): # rozeskoky pri ruznych akcich
rtc_info()
elif (akce == "-pi2rtc"):
rtc_zapis()
elif (akce == "-rtc2pi"):
rtc_info(True)
elif (akce == "-ntp2pi"):
ntp()
elif (akce == "-reg"):
rtc_registry()
elif (akce == "-f1"):
frekvence(1)
elif (akce == "-f512"):
frekvence(512)
elif (akce == "-out1"):
vystup(True)
elif (akce == "-out0"):
vystup(False)
elif (akce == "-stop"):
zastavit(1)
elif (akce == "-start"):
zastavit(0)
elif (akce == "-k"):
try: # odchytavani chyby, ktera by vznikla pri chybejicim parametru
konstanta = int(sys.argv[2]) # hodnota zadane kalibracni konstanty do promenne "konstanta"
except: # kdyz hodnota chybi, nebo je to text, nastaví se konstanta na 0
konstanta = 0
naladit(konstanta)
else: # pri jakemkoli jinem parametru se zobrazi napoveda
print "Pripustne parametry:"
print "... -i INFO - Jen zobrazi cas v RasPi a v RTC"
print "... -pi2rtc Zapise cas z RasPi do RTC"
print "... -rtc2pi Zapise cas z RTC do RasPi"
print "... -ntp2pi Restartuje NTP - seridi cas v RasPi podle internetu"
print "... -reg Vypise aktualni stav vsech registru"
print "... -f1 Nastavi IRQ generator na 1Hz"
print "... -f512 Nastavi IRQ generator na 512Hz"
print "... -out0 Nastavi IRQ na '0'"
print "... -out1 Nastavi IRQ na '1'"
print "... -stop Zastavi hodiny"
print "... -start Spusti zastavene hodiny"
print "... -k xnn Nastavi kalibracni konstantu na hodnotu 'xnn' (-31 az +31)"
print " x je znamenko ('-' zrychluje RTC; '+' zpomaluje RTC)"
print "-------------"
print "Priklady:"
print " sudo python /home/pi/bq32000 -k -15 .......... zmena konstanty na -15"
print " sudo python /home/pi/bq32000 -k +3 ............ zmena konstanty na 3"
print " sudo python /home/pi/bq32000 -f512 ....... vystup IRQ rozkmita 512Hz"
|
2) Čtení a vyhodnocování registrů v RTC obvodu
Další část programu převádí přečtené číslo z jednoho z
registrů na srozumitelný údaj o čase.
Jako příklad je tady zjištění sekund z nultého registru.
Podle katalogového listu obsahuje nultý registr 3 údaje:
- V nejvyšším (7.) bitu je informace o tom, jestli RTC obvod běží,
nebo jestli je zastavený.
- V bitech 6, 5 a 4 je údaj o desítkách sekund.
- V nejnižších 4 bitech je údaj o jednotkách sekund.
Část programu, která tento registr vyhodnocuje, je tady:
.
.
.
data = bus.read_i2c_block_data(adresa,0x00) # okamzite nacteni vsech registru z obvodu
sek1 = data[0] & 0b00001111 # spodni 4 bity v nultem registru jsou jednotky sekund
sek10 = data[0] & 0b01110000 # horni 3 bity v nultem registru jsou desitky sekund
sek10 = sek10 >> 4 # posun desitek sekund o 4 pozice vpravo
sekundy = (sek10 * 10) + sek1
if (data[0] > 0b01111111): # kdyz je nejvyssi bit nastaven na "1", RTC je zastavene
print "RTC obvod je v rezimu STOP!"
.
.
.
|
Při těchto operacích se využívá funkce logického bitového součinu
dvou čísel.
Jedno z těch čísel je načtená hodnota z registru, a druhé číslo je
bitová maska. Pro větší přehlednost jsem tu masku zapisoval ve
dvojkové soustavě.
Platí, že bity, které jsou v masce nulové, budou nulové i ve výsledku.
Naproti tomu bity, které jsou v masce jedničkové, se z registru překopírují
do výsledku. Tímto způsobem je možné pracovat jen z několika bity z
registru a ostatní bity ignorovat.
Pro příklad:
Když je třeba získat údaj o jednotkách sekund, který se nachází na
nejnižších 4 bitech nultého registru, nastaví se maska na 0b00001111.
Když pak registr bude obsahovat například binární hodnotu 0b01010110
(to je 86 desítkově),
bude výsledkem číslo 0b00000110.
Což znamená šestka na místě jednotek sekund.
|
Stop
|
10_sek |
1_sek |
|
Registr |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
= 86 desítkově |
Maska |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
|
Bitový součin |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
= 6 desítkově |
Ze stejného čísla z registru je třeba získat i údaj o desítkách
sekund.
V tom případě se maska nastaví na 0b01110000.
Po jejím logickém
součinu s hodnotou v registru 0b01010110 je výsledkem číslo: 0b01010000.
Toto číslo je ještě třeba posunout o 4 místa vpravo, aby se nejnižší
pozice dostala na nultý bit.
Takže výsledkem je 5 desítek sekund.
|
Stop |
10_sek |
1_sek |
|
Registr |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
= 86 desítkově |
Maska |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
|
Bitový součin |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
|
Po posunu o 4 místa >> |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
= 5 desítkově |
Nakonec se k počtu desítek sekund přičte ještě hodnota jednotek
sekund. Tento součet pak udává celkový počet sekund v RTC obvodu - v
příkladu je to 56 sekund.
Poslední informace z nultého registru se nachází v nejvyšším
bitu. I tady je možné použít stejný princip s maskou, jako při zjišťování
počtu sekund. V tomto případě je ale jednodušší testovat údaj z
registru na to, jestli je větší, než 127 (binárně je to 0b01111111).
Když je totiž nejvyšší bit v "1", je číslo v registru
minimálně 128.
V předchozím přikladu je číslo v registru 86. To je menší, než
128. To tedy znamená, že "STOP_bit" je v nule - RTC tedy běží.
3) Ukládání hodnot do RTC
Opačným případem je ukládání aktuálního času do registru
v RTC obvodu.
.
.
.
datcas = datetime.datetime.utcnow() # datcas bude obsahovat aktualni datum a cas v UTC (GMT)
sek10 = int(datcas.second / 10) # cele desitky sekund
sek1 = datcas.second - (10 * sek10) # jednotky sekund
stopbit = 0 # stopbit nastavit na normalni beh (pri 128 by se RTC zastavil)
bus.write_byte_data(adresa , 0 ,stopbit + (sek10 << 4) + sek1) # ulozit do registru 0
.
.
.
|
To je trochu jednodušší. Nejdříve se zjistí aktuální čas.
Je zvykem do RTC obvodu ukládat čas převedený do UTC - Univerzální
(světový) čas. Proto je použita funkce utcnow().
Z času se zjistí aktuální počet sekund - například 56 (jako v
minulém příkladu).
Tento údaj se rozdělí na desítky sekund a sekundy: 5 a 6
Protože je nevhodné, aby po seřízení času v RTC byl tento obvod v
zastaveném stavu, musí se ještě vynulovat "STOP_bit".
(Ta proměnná "stopbit" je tam sice zbytečná, ale myslím, že
pro pochopení skládání výsledného čísla je to názornější.)
Údaj v desítkách sekund je třeba bitově posunout ještě o 4 místa vlevo.
To se provede buď operátorem << 4, nebo jednoduchým vynásobením
číslem 16.
Nakonec se takto upravený údaj s desítkami sekund sečte s údajem o
jednotkách sekund, případně se k tomu všemu ještě přičte
STOP_bit.
Výsledný součet se pak zapíše do příslušného registru v RTC
obvodu.
4) Příkaz pro nastavení času v RasPi
Další důležitou operací je nastavení systémového času.
Nepodařilo se mi zjistit, jestli je možné přímo z Pythonu měnit čas
v RasPi. Pomohl jsem si tedy oklikou.
Python dokáže spouštět příkazy BASHe, tak se stačilo podívat, jak
se v BASHi nastavuje čas a tento příkaz potom v Pythonu vykonat:
.
.
import os
.
.
.
prikaz="sudo date -u " + (str(mesice).rjust(2,"0") +
str(dny).rjust(2,"0") +
str(hodiny).rjust(2,"0") +
str(minuty).rjust(2,"0") +
str(roky) + "." +
str(sekundy).rjust(2,"0"))
os.system (prikaz)
.
.
.
|
Protože je v RTC uložený datum a čas v UTC, musí se i při
nastavování času v RasPi použít za příkazem date parametr -u
pro univerzální čas.
V tomto příkladu je navíc ukázáno rozdělení dlouhé a nepřehledné
řádky na několik jednodušších řádek.
Funkce rjust doplní před řetězec nuly tak, aby výsledek měl
požadovanou délku (v tomto případě 2 znaky).
5) SFR - Registr pro speciální funkce
Jednou ze zvláštností obvodu BQ32000 je ovládání výstupního pinu
IRQ.
Ve většině ostatních RTC obvodů stačí jednoduše do nějakého registru
uložit číslo a výstup se přepne do "1", "0", nebo začne kmitat nějakou
frekvencí .
Tento obvod je však neobvyklý tím, že pokud chcete výstup
rozkmitat, musíte si nejdřív "odemknout" speciální registr
(SFR).
K odemknutí jsou třeba dva klíče (kódy), které se postupně zapisují
do registrů 0x20 a 0x21. Hned potom se získá přístup do SFR v
registru 0x22. Zapsáním "1" nebo "0" do tohoto SFR
registru se nastavuje výstupní frekvence pinu IRQ buď 512Hz, nebo 1Hz.
Oba klíče jsou uvedeny v katalogovém listu.
Do registru se 0x20 se
musí uložit kód 0x5E a do registru 0x21 se vkládá kód 0xC7.
Protože mezi ukládáním klíčů do registru nesmí dojít k přerušení
datového toku v komunikaci I2C, je třeba tyto registry plnit trochu jiným
způsobem (sekvenčně):
.
.
.
if (freq == 1):
klice=[0x5E,0xC7,0x01] # frekvenci generatoru nastavit na 1Hz
bus.write_i2c_block_data(adresa,0x20,klice)
print "vystup IRQ kmita frekvenci 1Hz"
else:
klice=[0x5E,0xC7,0x00] # frekvenci generatoru nastavit na 512Hz
bus.write_i2c_block_data(adresa,0x20,klice)
print "vystup IRQ kmita frekvenci 512Hz"
.
.
.
|
Sice to nikde není uvedeno, ale domnívám se, že
existují i jiné, tajné klíče, které otevírají přístup k dalším
funkcím.
Proč by se jinak tak složitě nastavoval jeden parametr, který je u jiných
RTC obvodů běžně přístupný?
Ty "tajné" funkce mohou být třeba přístup do volného paměťového
prostoru, nebo nastavování jiných frekvencí na IRQ výstupu.
To jsou ale jen moje spekulace. Třeba tam opravdu žádné další tajné
funkce nejsou.
6) Kalibrace krystalu
Další funkce, kterou u jiných RTC obvodů většinou nenajdete, je
možnost softwérově dokalibrovat frekvenci oscilátoru.
Pomocí čísla zapsaného do registru 0x07 se každou minutu může přidat
nebo ubrat několik impulzů oscilátoru podle tabulky v katalogovém
listu.
Zápis a čtení konstanty je podobné, jako zápis a čtení údaje o
sekundách, které jsem popisoval výše. Číselná konstanta je uložena
v bitech 0 až 5, takže může nabývat desítkové hodnoty 0 až 31. K
tomu je ještě v bitu č.6 uloženo znaménko, které udává zrychlování,
nebo zpomalování RTC obvodu.
7) Výpis registrů
Do programu jsem přidal ještě funkci rtc_registry(), která přehledným
způsobem vypíše aktuální stav všech registrů v obvodu s jejich rozdělením
na jednotlivé bloky.
Tento výpis byl získán v neděli 2.6.2013 v 17:43:19 SELČ (...to je
15:43:19 UT).
LEDka na výstupu IRQ blikala frekvencí 1Hz.
Kalibrační časová konstanta byla nastavena na číslo +3 (to je
zpomalení o 6 pulzů oscilátoru za minutu).
8) Seřízení času podle internetu.
Při pokusech se mi několikrát stalo, že jsem si
rozladil čas v RasPi a pak jsem ho musel složitě přes příkaz date
v terminálu nastavovat zpátky.
RasPi si sice nastavuje čas automaticky podle internetu při
spouštění, ale restart trval ještě déle, než ruční vypsání všech
parametrů v tom příkazu date.
Pak jsem ale zjistil, že se dá čas seřídit podle
internetu i bez restartu celého RasPi. Stačí v terminálu zadat:
sudo /etc/init.d/ntp restart |
Předpokládá to samozřejmě, že je RasPi k internetu připojeno.
|