Emulation des I2C-Busses per Software

am Beispiel des PIC16F628


zurück zu Input/Output , PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage

Allgemeines zum I2C-Bus findet man auf dieser Seite.

Nicht alle PICs verfügen über ein SSP (MSSP)-Modul, das als I2C-Interface verwendet werden kann. Aber auch ohne unterstützende Hardware lässt sich ein PIC als z.B. I2C-Master benutzen. Ich möchte das am Beispiel des PIC16F628 zeigen.

zu Beginn eine allgemeine I2C-Beschreibung
Allgemeines (auf einer anderen Seite)

ab hier wird es spezifisch für 16F628
Verdrahtung
Initialisierung
Daten auf I2C-Bus schreiben
Daten vom I2C-Bus lesen
 


Verdrahtung

Dieser Artikel bedarf einer Überarbeitung. Die nötigen zwei open-drain-Ausgänge lassen sich einfacher mit nur zwei Pins erzeugen, indem man anstelle der PORTA-Bits die TRISA-Bits schaltet.
 
Zur Ansteuerung des I2C-Busses benötigt man 2 open-drain- oder open-kollektor-Ausgänge, die auch als Eingänge funktionieren. Das Pin RA4 ist das einzige Pin, das diese Voraussetzung erfüllt. Allerdings ist es aufgrund der RA4-Falle, nicht ganz einfach zu nutzen. 
Ich bastle mir lieber aus jeweils 2 Pins des Ports A einen bidirektionalen open-drain-Leitungstreiber. Folglich sind die Pins RA0..RA3 für den I2C-Bus reserviert.
  • RA0    SDA-Ausgang
  • RA1    SCL-Eingang
  • RA2    SDA-Eingang
  • RA3    SCL-Ausgang
Die nebenstehende Grafik zeigt das an einem praktischen Beispiel.
Link zu einem Lernbeispiel

Ich lege per   #define-Befehl eingängige Namen für diese 4 Pins fest:
 
        list p=16f628
;**************************************************************
;*      Pinbelegung
;*      ---------------------------------- 
;*      PORTA:  0 SDA out
;*              1 CLK in
;*              2 SDA in
;*              3 CLK out
;*              4 -
;**************************************************************
;
;sprut (zero) Bredendiek 12/2002 
; I2C-Bus am 16F628
; Prozessor 16F628 
; Prozessor-Takt 10 MHz
;
; I2C am PortA
;
;**********************************************************
; Includedatei für den 16F628 einbinden
        #include <P16f628.INC>

; Configuration festlegen:
; Power on Timer, kein Watchdog, HS-Oscillator, kein Brown out, kein LV-programming
        __CONFIG        _PWRTE_ON & _WDT_OFF & _HS_OSC & _BODEN_OFF & _LVP_OFF

; für I2C
#define SDAo    PORTA,0         ;Daten output
#define SDAi    PORTA,2         ;Daten input
#define SCL     PORTA,3         ;Takt
#define SCLo    PORTA,3         ;Takt
#define SCLi    PORTA,1         ;Takt input

nach oben


Initialisierung
Um die Pins RA0..RA3 wie oben beschrieben nutzen zu können, müssen RA0 und RA3 als Output definiert werden. Außerdem müssen die Komparatoreingänge deaktiviert werden, damit die Pins überhaupt als digitale I/O-Pins nutzbar sind.
 
; Das Programm beginnt mit der Initialisierung
Init    bsf     STATUS, RP0     ; Bank 1
        movlw   B'11100110'     ; PortA alle input außer RA0,3,4
        movwf   TRISA
        bcf     STATUS, RP0     ; Bank 0
        clrf    PORTA 

; 16F628 alle Comparatoreingänge auf Digital umschalten
        BSF     CMCON, CM0
        BSF     CMCON, CM1
        BSF     CMCON, CM2

        call    i2c_reset

nach oben


Einstellung der Busgeschwindigkeit

Da das gesamte Busprotokoll per Software erzeugt wird, hängt die Busgeschwindigkeit von der Kompaktheit der Softwareroutinen und dem Takt des PICs ab. Die hier vorgestellten Routinen erreichen bei 10 MHz-PIC-Takt einen I2C-Bus-Takt von 210 kHz. Sie können für low-speed-I2C-Slaves verwendet werden, wenn der PIC mit 4 MHz getaktet wird:
 
PIC-Takt
I2C-Takt
Standart-Mode
(100 kHz)
Fast-Mode
(400 kHz)
4 MHz
85 kHz
o.k.
o.k.
8 MHz
170 kHz
-
o.k.
10 MHz
210 kHz
-
o.k.
12 MHz
250 kHz
-
o.k.
16 MHz
340 kHz
-
o.k.
20 MHz
420 kHz
-
-

Natürlich kann der I2C-Takt auch durch das Einfügen einiger NOP-Befehle in die Routinen verringert werden. Eine Beschleunigung ist aber nur möglich, wenn man nicht alle Feinheiten des I2C-Busses nutzen will. Meine Routinen berücksichtigen die Möglichkeit, dass ein Slave den I2C-Takt aktiv verlängert. Das macht aber in Wirklichkeit kaum ein I2C-Schaltkreis. Wer sich sicher ist, dass er dieses Feature nicht braucht, kann die I2C-Routinen verschlanken, und einen schnelleren I2C-Takt erreichen.

nach oben


Daten auf den I2C-Bus schreiben

Um Daten auf den I2C-Bus zu schreiben, wird der Bus mit i2c_on übernommen. Danach wird das zu schreibende Byte nach 'w' geladen, und danach i2c_txt aufgerufen.
Ist der Datentransfer beendet, gibt man mit i2c_off den Bus wieder frei.

Nachfolgendes Beispiel steuert einenTDA8444 an. Dieser Chip enthält acht 6-Bit-Digital-Analog-Wandler (DAC). Die ersten 6 DACs des TDA8444 werden auf 6 unterschiedliche Spannungen eingestellt:
 
; Achtung PIC-Takt maximal 4 MHz

        call    i2c_on          ; Bus übernehmen

        movlw   H'40'           ; Adresse des TDA8444 (A0..A2=0)
        call    i2c_tx

        movlw   H'00'           ; kanal 0 increment adress
        call    i2c_tx

        movlw   H'00'           ; DAC0: 0V
        call    i2c_tx

        movlw   H'01'           ; DAC1: 1/64 Vcc
        call    i2c_tx

        movlw   H'02'           ; DAC2: 1/32 Vcc
        call    i2c_tx

        movlw   H'04'           ; DAC3: 1/16 Vcc
        call    i2c_tx

        movlw   H'08'           ; DAC4: 1/8 Vcc
        call    i2c_tx

        movlw   H'10'           ; DAC5: 1/4 Vcc
        call    i2c_tx

        movlw   H'20'           ; DAC6: 1/2 Vcc
        call    i2c_tx

        call    i2c_off         ; Bus freigeben

Da der TDA8444 ein Standard-Mode-Chip (max. 100 kHz) ist, darf der PIC dabei mit nur 4 MHz getaktet werden. Der TDA benötigt offiziell eine Betriebsspannung (Vcc) von mindestens 4,5V. Meiner Erfahrung nach läuft er aber erst ab 6V. Diese Betriebsspannung darf natürlich nicht mit dem Vcc-Pin des PIC verbunden werden. Der braucht seine eigenen 5 V.

nach oben


Daten vom I2C-Bus lesen

Um Daten vom den I2C-Bus zu lesen, wird der Bus mit i2c_on übernommen. Danach wird die Adresse des auszulesenden I2C-Bausteins mit gesetztem Bit0 in 'w' geladen, und dieser Baustein dann mit i2c_tx adressiert. Danach wird i2c_rx oder i2c_rxack aufgerufen. Diese Routine liest ein Byte vom I2C-Bus, und schreibt es nach 'w'. i2c_rxack erzeugt zusätzlich ein ACK-Signal für den gelesenen I2C-Baustein. Das ist nötig, wenn noch weitere Bytes gelsesen werden sollen..
Ist der Datentransfer beendet, gibt man mit i2c_off den Bus wieder frei.

Nachfolgendes Beispiel steuert einen LM75-Temperatursensor an, und liest die von ihm gemessene Temperatur aus (2 Byte) :
 
        call    i2c_on          ; Bus aktiv 

        movlw   H'91'           ; 1001 0001 , LM75 (A0..A2=0)
        call    i2c_tx          ; LM75 zum Lesen adressieren 

        call    i2c_rxack       ; lesen mit ACK
        movwf   Temp_h          ; 1. Byte in Speicherzelle Datenpuffer retten

        call    i2c_rx          ; lesen ohne ACK - letztes Byte
        movwf   Temp_l          ; 2. Byte in Speicherzelle Datenpuffer retten 

        call    i2c_off         ; Bus freigeben

nach oben


Hilfsroutinen

Folgende Routinen werden benötigt:

Es wird ein Register 'buf' benötigt, das mit einem EQU-Befehl vorab definiert werden muss.
 
;*****************************************************************
; Routinen für I2C
;       Bus zurücksetzen       i2c_reset
;       Bus übernehmen         i2c_on
;       W senden               i2c_tx
;       Byte empfangen         i2c_rx    (nach w und buf)
;       Byte empfangen & ACK   i2c_rxack (nach w und buf)
;       Bus freigeben          i2c_off
;*****************************************************************
i2c_reset
        bsf     SDAo
        bsf     SCLo
        nop
        movlw   9
        movwf   buf
i2c_reset1
        nop
        bcf     SCLo
        nop
        nop
        nop
        nop
        nop
        bsf     SCLo
        nop
        decfsz  buf, f
        goto    i2c_reset1
        nop
        call    i2c_on
        nop
        bsf     SCLo
        nop
        nop
        bcf     SCLo
        nop
        call    i2c_off
        return

i2c_on
        ; wenn SDA und SCL beide High, dann SDA auf Low ziehen
        bsf     SCL             ; failsave
        bsf     SDAo            ; failsave
        ;testen, ob der Bus frei ist
        btfss   SCLi
        goto    i2c_on          ; Taktleitung frei?
        btfss   SDAi
        goto    i2c_on          ; Datenleitung frei?
        bcf     SDAo
        nop
        bcf     SCL
        return

i2c_tx
        ; w über i2c senden
        ; takt ist unten
        ; daten sind unten
        call    WrI2cW          ; 8 Bit aus W nach I2C
        ; ACK muss nun empfangen werden
        ; Takt ist low
        bsf     SDAo            ;Datenleitung loslassen
        bsf     SCL             ; ACK Takt high
i2c_tx2
        btfss   SCLi
        goto    i2c_tx2
        nop
;i2c_tx1
;       btfsc   SDAi            ; ACK empfangen?
;       goto    i2c_tx1         ; nein SDA ist high
        bcf     SCL             ; ja , Takt beenden
        bcf     SDAo
        return 

i2c_rxack
        ; takt ist unten
        ; daten sind unten
        call    RdI2cW          ; 8 von I2C nach W
        ; Takt ist unten
        ; ACK muss nun gesendet werden
        bcf     SDAo
        nop
        nop
        nop
        nop
        bsf     SCL
i2c_rxack1
        btfss   SCLi
        goto    i2c_rxack1
        nop
        bcf     SCL
        bcf     SDAo
        return

i2c_rx
        ; takt ist unten
        ; daten sind unten
        call    RdI2cW          ; 8 von I2C nach W
        ; Takt ist unten
        ; kein ACK
        nop
        nop
        bsf     SDAo
        nop
        bsf     SCL
i2c_rx1
        btfss   SCLi
        goto    i2c_rx1
        nop
        bcf     SCL
        bcf     SDAo
        return

i2c_off
        ; SCL ist Low und SDA ist Low
        nop
        nop
        bsf     SCL
        nop
        bsf     SDAo
        return

;*****************************************************
; I2C-Peride ist 2,5 µs
; PIC-Zyklus ist 4/10MHz = 0,4µs
; -> Takt muss für 3 Zyklen H und für 3 Zyklen L sein
;     + 1 Zyklus Reserve

;schiebt das Byte aus W in den I2C
; MSB zuerst
; 78 Takte
WrI2cW
        ; Takt unten, Daten unten
        ; Datenbyte in w
        movwf   buf
        movlw   8
        movwf   count           ; 8 Bits
WrI2cW1
        ; Datenleitung setzen
        bcf     SDAo
        rlf     buf,f
        btfsc   STATUS,C        ; 0?
        bsf     SDAo            ; nein, 1
        nop
        bsf     SCL             ; Taht high
WrI2cW2
        btfss   SCLi
        goto    WrI2cW2
        bcf     SCL             ; Takt low
        decfsz  count,f         ; 8 Bits raus?
        goto    WrI2cW1         ; nein
        return                  ; ja

;liest das Byte aus I2C nach W
        ; takt ist unten
        ; daten sind unten
RdI2cW
        clrf    buf
        movlw   8
        movwf   count
        bsf     SDAo            ;failsave
RdI2cW1
        nop
        clrc
        btfsc   SDAi
        setc
        rlf     buf,f
        bsf     SCL             ; Takt high
RdI2cW2
        btfss   SCLi
        goto    RdI2cW2
        bcf     SCL             ; Takt low
        decfsz  count,f         ; 8 Bits drinn?
        goto    RdI2cW1         ; nein
        movfw   buf             ; ja fertig
        return

nach oben

zurück zu Input/Output , PIC-Grundlagen , PIC-Prozessoren , Elektronik , Homepage
Autor: sprut
erstellt: 19.11.2002
letzte Änderung: 20.11.2003