BLIT2008-Board mit i2c-tiny-usb-Firmware
Aus BraLUG-Wiki
Statt das Rad neu zu erfinden, kann man sich für I²C auf dem BLIT2008-Board auch die Linux-Kernel-Unterstützung für ein ähnliches Projekt zunutze machen. Die Firmware für das i2c-tiny-usb-Board läuft mit minimalen Änderungen auch und man hat die Treiber- und Software-Unterstützung von Linux als Bonus.
Inhaltsverzeichnis |
Firmware
Da die Firmware schon für den Mega8 vorgesehen ist, war die Portierung auf das Board mehr als einfach. Folgende Schritte sollten funktionieren:
- Beschaffen und Entpacken der Firmware (http://www.harbaum.org/till/i2c_tiny_usb/i2c_tiny_usb-2007-06-07.zip)
- In das Firmware-Verzeichnis wechseln: „
cd i2c_tiny_usb/firmware/
“ - Anpassen der
usbtiny.h
, Ändern des Ports aufPORT_D
und Ändern des D+-Pins auf 2 (Patch) - Bauen mit „
make -f Makefile-usbtiny.mega8
“ - Hochladen auf den Mikrocontroller z.B. mit „
avrusbboot main.hex
“
Kerneltreiber
Der benötigte Kerneltreiber namens i2c-tiny-usb
ist ungefähr seit Version 2.6.22 im Linux-Kernel. Man benötigt folgende Konfigurationsoptionen (sollte im Distributionskernel dabei sein):
CONFIG_I2C CONFIG_I2C_CHARDEV CONFIG_I2C_TINY_USB
Geladen werden müssen i2c_tiny_usb
und i2c_dev
.
i2c-tools
Zum grundlegenden Test, zum Debugging und zum Skripten eignen sich die I2C-Tools, bei Debian auch im Paket
i2c-tools
verfügbar.
Liste der verfügbaren Busse:
# i2cdetect -l i2c-5 i2c i2c-tiny-usb at bus 005 device 004 I2C adapter i2c-0 i2c radeonfb monid I2C adapter i2c-1 i2c radeonfb dvi I2C adapter i2c-2 i2c radeonfb vga I2C adapter i2c-3 i2c radeonfb crt2 I2C adapter i2c-4 smbus SMBus Via Pro adapter at e800 SMBus adapter
- Die Nummer des I²C-Busses ist die 5 in
i2c-5
, nicht die 5 in „at bus 005
“ (USB-Bus-Nummer)
Slave-Chips am Bus finden:
# i2cdetect 5 WARNING! This program can confuse your I2C bus, cause data loss and worse! I will probe file /dev/i2c-5. I will probe address range 0x03-0x77. Continue? [Y/n] 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
-
i2cdetect
verwendet eine etwas andere Darstellung für die Adressen. Im AVR-Code würde man Aread = 2 * n + 1 und Awrite = 2 * n + 0 verwenden,0x51
ist also Adresse0xA2
(schreiben) und0xA3
(lesen). - Die Warnung sollte man durchaus ernst nehmen und das Kommando nicht unreflektiert auf den anderen Bussen ausführen. Ist man sich sicher, den richtigen Bus erwischt zu haben (in diesem Fall
5
), dann kann man auchi2cdetect -y 5
benutzen, um die Nachfrage abzuschalten.
RTC (PCF8583)
RTC auslesen
In Anlehnung an Uwes Beispiele hier ein Script, um die RTC-Zeit auszulesen. Gegebenfalls sind wieder Bus-Nummer und Adresse anzupassen.
get_rtc_time.sh
:
#!/bin/sh BUS=5 ADDR=0x51 get_part () { i2cget -y "$BUS" "$ADDR" $1 | sed -e 's/^0x//' ; } SS=$(get_byte 2) MM=$(get_byte 3) HH=$(get_byte 4) DD=$(get_byte 5) MO=$(get_byte 6) echo "RTC time: $dd.$mo. $hh:$mm:$ss"
# ./get_rtc_time.sh RTC time: 01.01. 03:14:11
RTC beschreiben
Der umgekehrte Weg geht natürlich auch! Mittels des Kommandos i2cset
kann ein I²C-Chip beschrieben werden, wenn er es zulässt. Der PCF8583, welcher auf dem RTC-Board verwendet wird, erlaubt dies. Zum einen kann Datum/Uhrzeit auf einen Wert vorinitialisiert werden, zum anderen gibt es in dem Schaltkreis 240 Byte RAM zur freien Verfügung. Im folgenden Beispiel wird eine solche Speicherzelle beschrieben:
Erst mal schauen, was derzeit in der Speicherzelle (Adresse: 0x11) steht:
# i2cget -y 5 0x51 0x11 0x00
Einen neuen Wert schreiben:
# i2cset -y 5 0x51 0x11 0xbc b Value 0xbc written, readback matched
Nochmals auslesen:
# i2cget -y 5 0x51 0x11 0xbc
RTC auf aktuelles Datum/Uhrzeit setzen
Bevor man Datum/Uhrzeit ausliest, sollte man diese natürlich auf die aktuellen Werte setzen. Hier ein entsprechendes Script dazu:
#!/bin/sh I2C_BUS=5 ADDR_RTC=0x51 set_byte () { i2cset -y "$I2C_BUS" "$ADDR_RTC" $1 $((($2/10<<4)+($2%10))) b ; } set_byte 2 $(date +%S) set_byte 3 $(date +%M) set_byte 4 $(date +%H) set_byte 5 $(date +%d) set_byte 6 $(date +%m)
Kleine Besonderheit des PCF8583: die Datums- und Uhrzeit-Werte sind im BCD-Format anzugeben.
Thermo-Board (LM75) auslesen
Und natürlich das entsprechende Script für das Thermo-Board darf nicht fehlen. Auch hier sind wieder Bus-Nummer und Adresse entsprechend anzupassen (bei mir war es halt der Bus 2...!). Als Shell wird bash vorausgesetzt.
get_temperature.sh
:
#!/bin/bash # # Kodierung der Temperatur im LM75 # -------------------------------- # # High-Byte Low-Byte # 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 # x 1 1 1 1 1 1 1 x x x x x x x x # | | |...........| # | | | # | | +-- Temperatur (Vorkomma): Bit 0...6 # | +---- Vorzeichen: 1 -> Temperatur < 0°C # +---------------------- Nachkommastelle: 0 -> ,0°C; 1 -> ,5°C # I2C_BUS=2 ADDR_LM75=0x49 # Temperatur auslesen val=$((16#$(i2cget -y "$I2C_BUS" "$ADDR_LM75" 0 w | sed -e 's/^0x//'))) lb=$(($val & $((2#11111111)))) hb=$(($val>>8)) # Vorzeichen interpretieren if [ $(($lb & $((2#10000000)))) -eq 0 ] then sign="+" else sign="-" fi # Vorzeichenbit rauskanten lb=$(($lb & $((2#01111111)))) # Nachkomma interpretieren if [ $hb -ge 128 ] then dec=5 else dec=0 fi echo "$sign$lb,$dec°C"
# ./get_temperature.sh +23,5 °C
SAA1064 (7-Segment-Anzeige) beschreiben
Das Beschreiben 7-Segment-Anzeige auf Basis eines I²C-IC SAA1064 funktioniert ebenfalls ohne Probleme.
Initialisieren des SAA1064 (I²C-Bus 0; Adresse 0x38):
i2cset -y 0 0x38 0x00 0x77
Es wird das Register 0 im SAA1064 mit mit der Bitfolge 01110111 gesetzt. Die einzelnen Bits des Register 0 haben in Kurzform folgende Bedeutung (die genaue Bedeutung sollte man Datenblatt nachlesen):
// SAA1064 Control-Register (Adresse 0) // Bit 7 6 5 4 3 2 1 0 // | | | | | | | | // | | | | | | | ---- 0: Digit 1,2; 1: Digit 1...4 // | | | | | | -------- 0/1: Digit 1,3 aus/an // | | | | | ------------ 0/1: Digit 2,4 aus/an // | | | | ---------------- 1: Testmode, alle Segmente an // | | | -------------------- 1: plus 3mA // | | ------------------------ 1: plus 6mA // | ---------------------------- 1: plus 12mA // -------------------------------- nicht belegt
Mit einem
i2cset -y 0 0x38 0x01 0xff
schaltet man alle Segmente des 1.Digit an. Die 0x01 steht für das 1.Digit (für das 4.Digit würde dann z.B. hier eine 0x04 stehen). Das letzte Parameter bildet die anzusteuernden Segmente des Digits ab:
// Bit: 7 6 5 4 3 2 1 0 // Segment: p g f e d c b a // // -a- // |f |b // -g- // |e |c // -d- .p
Eingepackt in ein Script sieht es dann so aus:
saa1064.sh
:
#!/bin/bash I2C_BUS=0 ADDR_7SEGM=0x38 # 0 1 2 3 4 5 6 7 8 9 SEGM=(0x3F 0x06 0x5B 0x4F 0x66 0x6D 0x7D 0x07 0x7F 0x6F) # Wertpruefung VAL=$1 if [ $VAL -gt 9999 ] then echo "Zahl zu gross!" exit fi # Initialisierung SAA1064 i2cset -y "$I2C_BUS" "$ADDR_7SEGM" 0x00 0x77 # Zahl ausgeben i2cset -y "$I2C_BUS" "$ADDR_7SEGM" 0x01 ${SEGM[$((VAL/1000))]} VAL=$(($VAL%1000)) i2cset -y "$I2C_BUS" "$ADDR_7SEGM" 0x02 ${SEGM[$(($VAL/100))]} VAL=$(($VAL%100)) i2cset -y "$I2C_BUS" "$ADDR_7SEGM" 0x03 ${SEGM[$(($VAL/10))]} VAL=$(($VAL%10)) i2cset -y "$I2C_BUS" "$ADDR_7SEGM" 0x04 ${SEGM[$VAL]}
Folgendes Kommando würde also eine 4711 auf dem Display erscheinen lassen:
# ./saa1064.sh 4711
I²C in C
Slaves auf einem I²C-Bus mit nativen C anzusprechen ist auch nicht besonders kompliziert. Hier ein kleines Beispiel:
/* ********************************************* * LM75 auslesen; auf SAA1064 ausgeben * ==================================== * Uwe Berger, 2012 * ********************************************* */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> /* I2C-Definitionen */ #define DEV_I2C "/dev/i2c-2" #define ADDR_LM75 0x49 #define ADDR_7SEGM 0x38 /* Zahl --> 7-Segment-Anzeige */ const unsigned char segm[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; const unsigned char point = 0x80; #define DO_ERROR {printf("Error...!\n"); exit(1);} int file; unsigned char buffer[2], sign, dec; /************************************/ void saa1064_write(int d1, int d2, int d3, int d4) { unsigned char buf[6]; buf[0] = 0x00; /* Basisadresse */ buf[1] = 0x77; /* Init: 21mA; alle 4 Digits... */ buf[2] = d1; /* Digit 1 */ buf[3] = d2; /* Digit 2 */ buf[4] = d3; /* Digit 3 */ buf[5] = d4; /* Digit 4 */ if (ioctl(file, I2C_SLAVE, ADDR_7SEGM)) DO_ERROR; if (write(file, &buf, 6) != 6) DO_ERROR; } /************************************/ /************************************/ /************************************/ int main(void) { /* Device oeffnen */ file = open(DEV_I2C, O_RDWR); if (file < 0) DO_ERROR; /* Temperatur lesen */ if (ioctl(file, I2C_SLAVE, ADDR_LM75)) DO_ERROR; buffer[0] = 0x00; if (write(file, buffer, 1) != 1) DO_ERROR; if (read(file, buffer, 2) != 2) DO_ERROR; /* Temperatur interpretieren/ausgeben */ if (buffer[0] & 0b10000000) sign = 0x40; else sign = 0x00; if (buffer[1] & 0b10000000) dec = segm[5]; else dec = segm[0]; buffer[0]=buffer[0] & 0b01111111; saa1064_write(sign, segm[buffer[0]/10], segm[buffer[0]%10]+point, dec); /* Ende */ close(file); exit(0); }
Es sind ein LM75 (Adresse: 0x49) und ein SAA1064 (Adresse: 0x38) im I²C-Bus (/dev/i2c-2) anzutreffen. Im Programmverlauf wird die Temperatur vom LM75 ausgelesen, interpretiert und entsprechend auf dem Display, welches auf einem SAA1064 basiert, ausgegeben.
Entscheidendes Detail ist der Aufruf der Funktion ioctl()
. Hier wird die Adresse des Slaves eingestellt, auf den die nachfolgenden Schreib- und/oder Lesezugriffe wirken sollen. Weitere ioctl()
-Funktionen auf I²C-Slaves sind in der Kernel-Header-Datei linux/i2c-dev.h
zu finden.
Wahrscheinlich wird man in der Realität für ein solches Thermometer keinen PC belästigen, sondern eine kompaktere Lösung auf Basis eines Mikrocontrollers aufbauen. Aber dieses Beispiel veranschaulicht das Schreiben und Lesen auf einem I²C-Bus recht prägnant. Das Fehlerbehandlung ist natürlich verbesserungswürdig...!
I²C in Tcl
Als bekennender Tcl-Fan wollte ich schon immer mal eine eigene Tcl-Erweiterung schreiben. Hiermit habe ich es einfach mal gemacht und stelle die Sache der Menschheit zur Verfügung:
Bindet man diese Tcl-Erweiterung in sein Script ein, kann schreibend und lesend auf einen I²C-Bus zugegriffen werden. Also ungefähr so:
i2c read $device $adr $reg b i2c write $device $adr $buf
...und jetzt doch mal zugegeben, ein entsprechendes Tcl-Script, welches lesend auf einen LM75 zugreift und die Temperatur auf einer 7-Segment-Anzeige auf Basis eines SAA1064 ausgibt, sieht doch schick aus:
# # Test-Script fuer libiic.so # ========================== # Uwe Berger; 2012 # # load ./libiic.so set device /dev/i2c-2 set adr_saa1064 0x38 set adr_lm75 0x49 set segm [list 0x3F 0x06 0x5B 0x4F 0x66 0x6D 0x7D 0x07 0x7F 0x6F] #************************************** proc do {} { global device adr_saa1064 adr_lm75 segm # LM75 auslesen set temp [i2c read $device $adr_lm75 0x00 w] # Low-/Hight-Byte set lb [expr $temp & 0x00FF] set hb [expr $temp >> 8] # Nachkommastelle if {[expr $hb & 0x80]} {set dec 5} else {set dec 0} # Vorzeichen if {[expr $lb & 0x80]} {set sign "-"} else {set sign "+"} # Vorkommawert set lb [expr $lb & 0x7f] # Ausgabe (Bildschirm) puts "$sign$lb.$dec°C" # Ausgabe auf SAA1064 if {$sign == "+"} {set d1 0x00} else {set d1 0x40} set d2 [lindex $segm [expr $lb / 10]] set d3 [expr [lindex $segm [expr $lb % 10]] + 0x80] set d4 [lindex $segm $dec] i2c write $device $adr_saa1064 [list 0x00 0x77 $d1 $d2 $d3 $d4] # nach einer Sekunde neu starten... after 1000 do } #************************************** #************************************** #************************************** do vwait forever
lm_sensors
Selbstverständlich kann man die ganze Geschichte auch in lm-sensors einbinden, wenn die I²C-Slave-Schaltkreise unterstützt würden. Beim Thermo-Board mit seinem LM75-Chip ist dies der Fall :-)!
Die Installation von lm-sensors auf dem Rechner erfolgt entweder über das Paketsystem der jeweiligen Distribution oder das Übersetzen/Installieren der Originalquellen.
Die Konfiguration von lm-sensors erledigt man sinnvollerweise mit dem mitgelieferten Setup-Script:
# sudo sensors-detect
Sämtliche Fragen können bedenkenlos mit Ja (Yes) beantwortet werden. Die entscheidende Stelle der Ausgaben sollte ungefähr so aussehen:
... Next adapter: i2c-tiny-usb at bus 002 device 007 (i2c-2) Do you want to scan it? (YES/no/selectively): Client found at address 0x49 Probing for `National Semiconductor LM75'... Success! (confidence 6, driver `lm75') Probing for `Dallas Semiconductor DS75'... No Probing for `National Semiconductor LM77'... No Probing for `Dallas Semiconductor DS1621'... No Probing for `National Semiconductor LM92'... No Probing for `National Semiconductor LM76'... No Probing for `Maxim MAX6633/MAX6634/MAX6635'... No Client found at address 0x51 Probing for `Analog Devices ADM1033'... No Probing for `Analog Devices ADM1034'... No Probing for `SPD EEPROM'... No Probing for `EDID EEPROM'... No ...
Nach einem Reboot des Rechners oder manuellem Laden des lm75
-Kernelmoduls sollte lm-sensors auch die Thermo-Zusatzbaugruppe des BLIT-Boards mit eingebunden haben:
# sensors k8temp-pci-00c3 Adapter: PCI adapter Core0 Temp: +26°C Core1 Temp: +31°C lm75-i2c-2-49 Adapter: i2c-tiny-usb at bus 002 device 007 temp: +22.0°C (high = +80.0°C, hyst = +75.0°C)
Zur Visualisierung der gemessenen Temperatur können die verschiedensten Tools verwendet werden. Bei einer Gnome-Oberfläche bietet sich da das Panel-Applet sensors-applet an. Als "desktop-neutrales" Tools wären z.B. xsensors oder GkrellM zu erwähnen.