;********************I²C********************************** ;** Include-Datei für AT90S4433, ATmega8 und ähnliche: ** ;** I²C-Bus-Master im Ping-Pong-Verfahren inklusive ** ;** slave-seitiger Takt-Drosselung. ** ;** Als notwendige Pull-Up-Widerstände werden die ** ;** im Mikrocontroller vorhandenen benutzt. ** ;********************I²C********************************** ;=== Festlegungen === .equ pi2c=PORTB ;Festlegen des I2C Ports .equ di2c=DDRB ;Richtungsregister des Ports .equ scl=2 ;Pin (Bit-Nummer) für SCL .equ sda=0 ;Pin (Bit-Nummer) für SDA .equ CPUCLK=8000000 ;CPU-Taktfrequenz in Hz: 8 Mhz .equ I2CCLK=400000 ;maximale I²C-Busfrequenz: 400 kHz .equ I2CTMR=TCNT0 ;Leseregister Timer0 (zur Zeitmessung) .equ I2CTMRV=1024 ;aktuell gesetzter Vorteiler für Timer ;=== Benötigte statische Register === .def i2ct=r4 ;letzter Stand der "Uhr" ;=== Makros === .macro TOLOW ;Makro für scl,sda low cbi PORTB,@0 ;hochohmig sbi DDRB,@0 ;aktiv low .endm .macro TOWEAK ;Makro für scl,sda high sbi PORTB,@0 ;aktiv high cbi DDRB,@0 ;schwaches high .endm .macro DJNZ dec @0 brne @1 .endm ;=== Routinen (Bit- und Byte-Ebene) === i2c_wait: ;(Interne Wartefunktion) ;Wartet ab, bis seit dem letzten Aufruf von i2c_wait eine halbe ;I²C-Taktperiode vergangen ist, bei 100 kHz Bustakt also 5 µs usw. ;Dringende Voraussetzung: Timer I2CTMR muss laufen! ;VR: R18,R5,i2ct in r18,I2CTMR ;Timerstand laden mov r5,r18 ;Timerstand in r5 kopieren sub r18,i2ct ;vergangene Zeit seit letztem Stopp cpi r18,CPUCLK/I2CCLK/2/I2CTMRV ;halbe Perioden! brlo i2c_wait mov i2ct,r5 ret i2c_init: ;I²C (und Timer) initialisieren ;ldi r18,$01 ;out tccr0,r18 ;timer starten in i2ct,I2CTMR ;Startwert der "Uhr" laden toweak scl ;scl auf High setzen i2c_stop: ;Stoppsequenz senden ;VR: R18,R5 clt ;T-Flag rücksetzen rcall i2c_bit ;T übertragen rcall i2c_wait TOWEAK sda ;sda auf High ret i2c_start: ;Startsequenz senden ;VR: R18,R5 rcall i2c_wait TOLOW sda ;sda auf Low setzen ret i2c_send: ;I²C-Byte senden ;PE: R16 = zu sendendes Byte ;PA: T-Flag = Quittungsbit ;VR: R16,R17,R18,R5 ldi r17,8 i2c_s: bst r16,7 ;kopiert Bit 7 von R16 auf T rcall i2c_bit ;Aufruf Bit senden rol r16 ;r16 links rotieren DJNZ r17,i2c_s ;Schleife i2c_bit1: set ;setzt T-Flag i2c_bit: ;Senden und Empfangen: gibt T aus und liefert T rcall i2c_wait TOLOW scl brtc tis0 TOWEAK sda rjmp tis1 tis0: TOLOW sda tis1: rcall i2c_wait TOWEAK scl tis2: sbis pinb,scl ;springe wenn pinb,scl gesetzt rjmp tis2 set sbis pinb,sda ;springe wenn pinb,sda gesetzt clt ret i2c_recv: ;Empfang eines Bytes ;PE: R16 Bit 0 = zu sendendes Quittungsbit ;PA: R16 = gelesenes Byte ;VR: R16,R17,R18,R5 ldi r17,8 i2crr: rcall i2c_bit1 add r16,r16 ;linksschieben bld r16,0 ;T-Flag in R16 einsetzen DJNZ r17,i2crr rol r17 bst r17,0 rjmp i2c_bit ;=== Routinen (ganze Telegramme) === i2c_sendstr_follow: ;sendet nachfolgende (festen) Pascal-String ;Diese Routine ;VR: R16,R17,R18,Z(R30,R31),R5 pop zh pop zl ;dies ist die WORTadresse add zl,zl ;oder shl zl adc zh,zh ;oder rol zh, jetzt BYTEadresse rcall i2c_sendstr ;dann zeigt z hinter die Null adiw zl,1 ;aufrunden lsr zh ror zl ;WORTadresse draus machen ijmp ;Rückkehr zum Aufrufer i2c_sendstr: ;Sendet konstanten String inklusive Adresse ;PE: Z = Pascal-String-Adresse (mit Längenbyte) ;PA: Z vorgerückt hinter das String-Ende ;VR: R16,R17,R18,Z(R30,R31),R0,R1,R5 lpm adiw zl,1 mov r1,r0 rcall i2c_start i2c_send1: lpm adiw zl,1 mov r16,r0 rcall i2c_send brts i2c_stop DJNZ r1,i2c_send1 rjmp i2c_stop i2c_readstr: ;PE: R16 = I²C-Adresse, R19 = Anzahl der zu lesenden Bytes X = Puffer-Zeiger ;PA: X vorgerückt hinter letztes gelesenes Byte (ggf. weniger als R16) ;VR: R16,R17,R18,R19,X(R26,R27),R5 rcall i2c_start rcall i2c_send ;in r16 übergeben brts i2c_stop i2c_read1: ldi r16,1 cpse r16,r19 dec r16 ;=0 rcall i2c_recv st x+,r16 DJNZ r19,i2c_read1 rjmp i2c_stop