Emulation des I2C-Busses per Software (16F628)

Fonte: SPRUR.de

Link zu einem Lernbeispiel

Verdrahtung

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.

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

; 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

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



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.


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.


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


Hilfsroutinen

Folgende Routinen werden benötigt:

  • i2c_reset
    bringt den I2C-Bus in einen definierten Ausgangszustand
  • i2c_on
    übernimmt den I2C-Bus, sobald er frei ist
  • i2c_off
    gibt den I2C-Bus wieder frei
  • i2c_tx
    sendet das Byte aus 'w' über den I2C-Bus
  • i2c_rx
  • empfängt ein Byte aus dem I2C-Bus, und legt es in 'w', buf ab
  • i2c_rxack
    empfängt ein Byte aus dem I2C-Bus, und legt es in 'w', buf ab
    anschließend wird ein ACK-Signal erzeugt
Es wird ein Register 'buf' benötigt, das mit einem EQU-Befehl vorab definiert werden muß.

;*****************************************************************
; 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 muß 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 muß 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 muß 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