Forum: Mikrocontroller und Digitale Elektronik I²C / TWI spinnt - Master und Slave mit ATmega8


von Paul H. (powl)


Lesenswert?

Hi,

da der letzte Thread wohl zu unübersichtlich wurde führe ich erneut in 
das Thema ein. Ich betreibe testweise zwei ATmega8 mit dem TWI. Einer 
Master, der andere Slave mit der Slaveadresse 127.

Aufbau:
- Master
Ein hardwarenetprellter Taster an PD2 (INT0) gegen Masse um die 
Senderoutine auszulösen. Vier Status-LEDs Rot, Grün, Gelb, Blau an PB 
0-3 über 1kOhm gegen Masse. Eine dicke Rote Error-LED an PB 4, auch über 
1kOhm gegen Masse. An SDA und SCL natürlich die Busleitung, ausserdem 
jeweils ein 4,7kOhm Pull-Up gegen Vcc für den Bus.

- Slave
An PD 0-7 jeweils eine gelbe Ausgabe-LED über 1kOhm gegen Masse. An SDA 
und SCL die Busleitung.


Das Programm im Master hat den Testwert 100 in R17 gespeichert. Dieser 
Wert soll an den Slave gesendet werden. Der Slave soll diesen Wert dann 
an PortD ausgeben. Das klappt schon soweit. Der Master inkrementiert 
währenddessen diesen Wert wodurch er beim nächstenmal eben die 101 
sendet. Leider gibt esnach dem Senden einen Fehler wodurch ich den Bus 
kein zweites Mal benutzen kann. Ich habe schon verschiedene Tests 
durchgeführt.

Die ISR im Slave führt, abhängig vom TWI Status, eine von drei 
verschiedenen Aktionen aus. Entweder für 0x60 (SLA+W wurde empfangen, 
ACK wurde gesendet) oder für 0x80 (Daten wurden empfangen, ACK wurde 
gesendet) oder für 0xA0 (Stop Condition wurde empfangen). Zum Test habe 
ich in jeder Aktion mal eine LED am PortB setzen lassen. Dabei stellte 
sich heraus dass er die Stop-Condition irgendwie nicht erkennt. Beim 
weiteren Testen hat sich ausserdem noch ein Phänomen rausgestellt. Um zu 
prüfen wie viele Interrupts denn im Slave überhaupt ausgeführt werden 
habe ich mal einen Zähler in R17 erstellt. Startwert 0. Dann habe ich 
ihn am ende jeweils mit inc R17 eins hochzählen lassen und den Wert an 
PortD ausgegeben. Sämtliche LEDs leuchteten. Hm, seltsam.. wieso gerade 
255 Interrupts? Dann habe ich den inc-befehl mal auskommentiert und in 
den Teil für den Status 0x60 gesetzt. Ergebnis: ein Interrupt. OK das 
schien zu funktionieren. Dann habe ich ihn in den Teil für 0xA0 
eingesetzt. Ergebnis: kein Interrupt. Wie schon erwartet. Dann das 
ungewöhnliche: Ich setzte den inc Befehl in den Teil für 0x80 ein. Schon 
wieder leuchteten alle LEDs. Aber das geht doch garnicht?! Wenn er hier 
schon bis 255 zählt und im Teil für 0x60 dann müsste es doch einen 
überlauf geben?!. Dann habe ich den inc Befehl auch in den Teil für 0x60 
eingesetzt und immernoch leuchteten alle LEDs, was sehr unlogisch ist, 
da ja der 0x60-Teil einmal ausgeführt wird und es somit einen überlauf 
geben MÜSSTE. Ich habe auch andere Befehle ausprobiert wie subi R17, -1 
oder add R17, R18 über ein Hilfsregister. Überall der gleiche Käse. Hat 
jemand eine Idee?

Ich hoffe jemand ist trotz dem Umfang gewillt mir zu helfen, ich sitze 
schon seit 3 Tagen da dran und verzweifle langsam!! Habe versucht den 
Code für euch so übersichtlich wie möglich zu gestalten. Meine 
ICQ-Nummer für direkte Hilfe: 126342470. Danke im Vorraus!!

Master:
1
.include "m8def.inc"        ; Deklarationen für ATmega8
2
3
.equ  twbrf = 42          ; TWI Bitratenfaktor
4
.equ  twps = 0          ; TWI Prescaler
5
6
.cseg                ; Programm-Flash
7
    rjmp  init        ; Reset-Einsprung
8
9
.org  0x001            ; INT0 Interrupt
10
    rjmp  send
11
12
.org  0x013            ; Interrupteinsprünge übergehen
13
14
15
init:  ldi    R16, LOW(RAMEND)  ; Stapel anlegen
16
    out    SPL, R16
17
    ldi    R16, HIGH(RAMEND)
18
    out    SPH, R16
19
20
21
    ; INT0 Interrupt
22
    ldi    R16, (1 << ISC01)  ; Fallende Flanke an INT0 generiert Interrupt
23
    out    MCUCR, R16
24
25
    ldi    R16, (1 << INT0)  ; INT0 Interrupt aktiviern
26
    out    GICR, R16
27
28
29
    ; Two-Wire-Interface
30
    ldi    R16, twbrf      ; Bitratenfaktor
31
    out    TWBR, R16
32
33
    ldi    R16, twps      ; Prescaler
34
    out    TWSR, R16
35
36
37
    ; Status LED Port
38
    ldi    R16, 0b00011111    ; PortB 0-4 als Ausgang
39
    out    DDRB, R16
40
41
    ldi    R17, 100      ; Test-Wert laden
42
43
    sei              ; Interrupts aktivieren
44
45
loop:  rjmp  loop        ; Endlosschleife
46
47
48
49
send:  cbi    PORTB, 0      ; Alle Status-LEDs aus
50
    cbi    PORTB, 1
51
    cbi    PORTB, 2
52
    cbi    PORTB, 3
53
    cbi    PORTB, 4
54
55
;- Step 1 --------------------------------------------------------------------------------
56
57
    ; Start Condition senden
58
    ldi    R16, (1 << TWINT) | (1 << TWSTA) | (1 << TWEN)
59
    out    TWCR, R16
60
61
    rcall  twi_twint_wait    ; Warten bis ausgeführt
62
63
    sbi    PORTB, 0      ; Rote Status-LED an
64
65
    ; TWI-Status überprüfen
66
    in    R16, TWSR      ; TWI-Status Register laden
67
    andi  R16, 0b11111000    ; Status maskieren
68
    cpi    R16, 0x08      ; Code 0x08 = A START condition has been transmitted
69
    brne  error        ; Springe zu error wenn ungleich
70
71
72
;- Step 2 --------------------------------------------------------------------------------
73
74
    ; 7-Bit Slave Adresse + R/W senden (R=High, W=Low)
75
    ldi    R16, (127 << 1) | (0)
76
    out    TWDR, R16
77
78
    ; Daten senden
79
    ldi    R16, (1 << TWINT) | (1 << TWEN)
80
    out    TWCR, R16
81
82
    rcall  twi_twint_wait    ; Warten bis ausgeführt
83
84
    sbi    PORTB, 1      ; Grüne Status-LED an
85
86
    ; TWI-Status überprüfen
87
    in    R16, TWSR      ; TWI-Status Register laden
88
    andi  R16, 0b11111000    ; Status maskieren
89
    cpi    R16, 0x18      ; Code 0x18 = SLA+W has been transmitted; ACK has been received
90
    brne  error        ; Springe zu error wenn ungleich
91
92
;- Step 3 --------------------------------------------------------------------------------
93
94
    ; Zählerdaten senden
95
    out    TWDR, R17      ; Zählerdaten ins Ausgangsregister laden
96
    inc    R17          ; Zähler inkrementieren
97
98
    ; Daten senden
99
    ldi    R16, (1 << TWINT) | (1 << TWEN)
100
    out    TWCR, R16
101
102
    rcall  twi_twint_wait    ; Warten bis ausgeführt
103
104
    sbi    PORTB, 2      ; Gelbe Status-LED an
105
106
    ; TWI-Status überprüfen
107
    in    R16, TWSR      ; TWI-Status Register laden
108
    andi  R16, 0b11111000    ; Status maskieren
109
    cpi    R16, 0x28      ; Code 0x28 = Data byte has been transmitted; ACK has been received
110
    brne  error        ; Springe zu error wenn ungleich
111
112
;- Step 4 --------------------------------------------------------------------------------
113
114
    ; Stop Condition senden
115
    ldi    R16, (1 << TWINT) | (1 << TWSTO) | (1 << TWEN)
116
    out    TWCR, R16
117
118
    sbi    PORTB, 3      ; Blaue Status-LED an
119
120
;-----------------------------------------------------------------------------------------
121
122
    reti            ; Kehre zurück
123
124
125
126
    ; Fehler aufgetreten, abbruch
127
error:  sbi    PORTB, 4      ; Rote Error-LED an
128
    reti
129
130
131
132
    ; Warten bis TWINT-Bit wieder gesetzt ist
133
twi_twint_wait:
134
    in    R16, TWCR      ; TWI Control Register laden
135
    sbrs  R16, TWINT      ; Schleife wenn TWINT nicht gesetzt
136
    rjmp  twi_twint_wait
137
    ret              ; Kehre zurück

Slave:
1
.include "m8def.inc"        ; Deklarationen für ATmega8
2
3
.equ  adress = 127        ; TWI-Adresse
4
5
.cseg                ; Programm-Flash
6
    rjmp  init        ; Reset-Einsprung
7
8
.org  0x011
9
    rjmp  twis        ; TWI Interrupt
10
11
.org  0x013            ; Interrupteinsprünge übergehen
12
13
14
init:  ldi    R16, LOW(RAMEND)  ; Stapel anlegen
15
    out    SPL, R16
16
    ldi    R16, HIGH(RAMEND)
17
    out    SPH, R16
18
19
20
    ; TWI Slave Adresse + TWGCE
21
    ldi    R16, (adress << 1) | (0 << TWGCE)
22
    out    TWAR, R16
23
24
    ; TWI und Interrupt aktivieren
25
    ldi    R16, (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
26
    out    TWCR, R16
27
  
28
29
    ; Status LEDs Ausgang
30
    ldi    R16, 0b11111111    ; PortD als Ausgang
31
    out    DDRD, R16
32
33
    sei              ; Interrupts aktivieren
34
35
36
loop:  rjmp  loop        ; Endlosschleife
37
38
39
;- 0x60 --------------------------------------------------------------------------------
40
41
twis:  in    R16, TWSR
42
    cpi    R16, 0x60      ; 0x60 = Own SLA+W has been received; ACK has been returned
43
    brne  twis1        ; Springe wenn ungleich
44
45
    ; Data byte will be received and ACK will be returned
46
    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
47
    out    TWCR, R16
48
49
    rjmp  twis3
50
51
;- 0x80 --------------------------------------------------------------------------------
52
53
twis1:  in    R16, TWSR
54
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W; data has been received; ACK has been returned
55
    brne  twis2        ; Springe wenn ungleich
56
57
    in    R16, TWDR      ; Daten an PortD ausgeben
58
    out    PORTD, R16
59
60
    rjmp  twis3
61
62
;- 0xA0 --------------------------------------------------------------------------------
63
64
twis2:  in    R16, TWSR
65
    cpi    R16, 0xA0      ; 0xA0 = A STOP condition or repeated START condition has been received while still addressed as Slave
66
    brne  twis3        ; Springe wenn ungleich
67
68
    ; Switched to the not addressed Slave mode; own SLA will be recognized; GCA will be recognized if TWGCE = 1
69
    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
70
    out    TWCR, R16  
71
72
;- End ---------------------------------------------------------------------------------
73
74
twis3:  reti            ; Kehre zurück

von Jörg X. (Gast)


Lesenswert?

1
;- 0x80 --------------------------------------------------------------------------------
2
3
twis1:  in    R16, TWSR
4
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W
5
                          ; data has been received; ACK has been returned
6
    brne  twis2        ; Springe wenn ungleich
7
;-------------------------------
8
;   IST DAS HIER EIN COPY'N PASTE FEHLER?
9
;   Das TWINT-Bit wird nicht rückgesetzt!
10
;-------------------------------
11
    in    R16, TWDR      ; Daten an PortD ausgeben
12
    out    PORTD, R16
13
14
    rjmp  twis3

Außerdem hättest du ruhig mal irgendwo die Taktfrequenz erwähnen können 
- ".equ  twbrf = 42 " ist zwar ganz witzig, hilft aber wirklich nicht 
weiter.
1
.equ F_CPU = 1000000 ; Der Name ist nicht so wichtig, aber die ZAHL!
2
.equ F_SCL = 100000 ; btw: beim slave MUSS F_CPU 16mal hoeher sein als F_SCL 
3
; - beim Master ist das automatisch so fuer TWBR == 0
4
5
.equ twbrf = (F_CPU /F_SCL -16) / 2 ; diese Formel sollte man dann aber auf Negative Werte pruefen ;)
6
;...

hth. Jörg

von Hauke S. (hauke)


Lesenswert?

Ich habe beide Codes mal mit VMLAB durchsimuliert
Der Mastercode funktioniert.
Der Slavecode bleibt nach der ersten übertragung hängen.

Jörg X. hat recht.
Du vergisst am ende von 0x80 die Flags von TWCR zu setzen.

Mit volgendem Code sollte es funktionieren.
(In VMLAB tut es das zumindest)
1
;- 0x80 --------------------------------------------------------------------------------
2
3
twis1:  in    R16, TWSR
4
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W; data has been received; ACK has been returned
5
    brne  twis2        ; Springe wenn ungleich
6
7
    in    R16, TWDR      ; Daten an PortD ausgeben
8
    out    PORTD, R16
9
10
    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
11
    out    TWCR, R16
12
13
    rjmp  twis3

cu
Hauke

von Paul H. (powl)


Lesenswert?

Dankeschön für die Antwort! Sry, habe das mit der Taktfrequenz 
vergessen. Takt des AVRs ist 1 MHz über den internen Oszi. Der Bustakt 
soll 10 kHz betragen. Die formel liefert mir mit prescaler=0 und 
bitratefaktor=42 einen glatten wert von 10000 Hz :-) Somit ist der 
Systemtakt 100 mal schneller als der Bustakt.

Zum TWINT-Bit. Muss das an dieser Stelle zurückgesetzt werden? Im 
Datenblatt in der Tabelle "Status Codes for Slave Receiver Mode" steht 
bei 0x80 so ein Read data byte or ... . Daher ging ich davon aus dass 
weiteres nicht nötig wäre. Aber nun stehen da zwei optionen:

- Data byte will be received and NOT ACK will be returned
- Data byte will be received and ACK will be returned

Ich erwarte ja an dieser Stelle dass der Master als nächstes das Stop 
sendet. Soll ich einfach TWINT und TWEN neu setzen damit ich weiter 
machen kann? Ich probier das jetzt mal aus aber wär hilfreich wenn ihr 
mir den sinn nochmal erklärt :-)

// Edit: Genial, es geht!! Wirklich ein simpler Fehler aber ich bin echt 
nicht weiter gekommen, war schon total verzweifelt. Danke recht 
herzlich!

mfg PoWl

von Hauke S. (hauke)


Lesenswert?

Das warum?
Hmmmmmm

Das Rücksetsetzen des TWINT Flags signalisiert der TWI Hardware, das du 
das empfangende Byte verarbeitest(ausgelesen) hast, und wieder für 
weitere Übertragungen bereitsteht.
Es gibt ja auch die möglichkeit das ein Slave die Clock Leitung solange 
herunterzieht bis diese Verarbeitung fertig ist.
Der Master müßte dann solange warten bis der Slave fertig ist.

cu
Hauke

von Jörg X. (Gast)


Lesenswert?

Das Datenblatt sagt:
1
 "While the TWINT Flag is set, the SCL low period is stretched.[...]
2
Note that this flag is not automatically cleared by hardware when executing
3
the interrupt routine. Also note that clearing this flag starts the
4
 operation of the TWI[...]".
 Was bedeutet, dass auf dem I²C-Bus gar nichts passiert, solange bei 
irgendeinem beteiligten AVR (mit aktivem TWI-Interfece etc.) das 
TWINT-Bit gesetzt ist und dass man das Bit selbst rücksetzen muss.
Falls du das noch nit getan hast, scahu dir mal die App.-Notes AVR311 
und AVR315 von Atmel zum TWI-Modul an.

hth. Jörg

von Jens (Gast)


Lesenswert?

Stimmt eigentlich die Formel, ich binn ned so gut in Mathe ;)
.equ  TWi = ((F_CPU/8*FSCL)-2)
prescaler lass ich immer auf 0, auch wenn der AVR mit 16mhz rumrotzt.

Ich hab immer gedacht ACK Stop und Start werden immer automatisch 
generiert

von Jörg X. (Gast)


Lesenswert?

ich komme auf ".equ twbrf = (F_CPU /F_SCL -16) / 2" (siehe oben) und 
YACAS(Algebraprogramm) bestätigt mich ;)

hth. Jörg

von Jens (Gast)


Lesenswert?

also hatte ich die ganze zeit ne failure.. ne georgy^^

von Jörg X. (Gast)


Lesenswert?

1
              F_SCL == F_CPU / (16 + TWBR)    | *(16+TWBR)
2
F_SCL * (16 + TWBR) == F_CPU                  | / F_SCL
3
        (16 + TWBR) == F_CPU / F_SCL          | - 16
4
               TWBR == F_CPU / F_SCL - 16
so geht es doch, wenn der prescaler auf 0 steht, oder?
Und es ist klar, dass da kein sinnvoller Wert rauskommt, wenn F_CPU 
kleiner ist als F_SCL * 16.

hth. Jörg

von Paul H. (powl)


Lesenswert?

Wenn es ums Clock Stretching geht, wieso führt der Master dann weiterhin 
sein Programm aus? Oder ist da so eine art Timeout drin?

By the Way: ist das USI der ATtiny familie schwierig zu verwenden?

mfg PoWl

von Jörg X. (Gast)


Lesenswert?

> wieso führt der Master dann weiterhin sein Programm aus?
Woher weißt du das bzw. Wie kommst du darauf?
Der Master setzt die letzte Status LED, wenn er eine STOP-condition 
senden will. Was passiert dann (STO-gesendet löst beim Master keinen 
neuen Interrupt aus)?

Es gibt für TWI und USI Appnotes von Atmel, einfach mal reinschauen. Das 
USI hat keine eigene Taktquelle, das dürfte das komplizierteste daran 
sein.

hth. Jörg

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.