mikrocontroller.net

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


Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht 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:
.include "m8def.inc"        ; Deklarationen für ATmega8

.equ  twbrf = 42          ; TWI Bitratenfaktor
.equ  twps = 0          ; TWI Prescaler

.cseg                ; Programm-Flash
    rjmp  init        ; Reset-Einsprung

.org  0x001            ; INT0 Interrupt
    rjmp  send

.org  0x013            ; Interrupteinsprünge übergehen


init:  ldi    R16, LOW(RAMEND)  ; Stapel anlegen
    out    SPL, R16
    ldi    R16, HIGH(RAMEND)
    out    SPH, R16


    ; INT0 Interrupt
    ldi    R16, (1 << ISC01)  ; Fallende Flanke an INT0 generiert Interrupt
    out    MCUCR, R16

    ldi    R16, (1 << INT0)  ; INT0 Interrupt aktiviern
    out    GICR, R16


    ; Two-Wire-Interface
    ldi    R16, twbrf      ; Bitratenfaktor
    out    TWBR, R16

    ldi    R16, twps      ; Prescaler
    out    TWSR, R16


    ; Status LED Port
    ldi    R16, 0b00011111    ; PortB 0-4 als Ausgang
    out    DDRB, R16

    ldi    R17, 100      ; Test-Wert laden

    sei              ; Interrupts aktivieren

loop:  rjmp  loop        ; Endlosschleife



send:  cbi    PORTB, 0      ; Alle Status-LEDs aus
    cbi    PORTB, 1
    cbi    PORTB, 2
    cbi    PORTB, 3
    cbi    PORTB, 4

;- Step 1 --------------------------------------------------------------------------------

    ; Start Condition senden
    ldi    R16, (1 << TWINT) | (1 << TWSTA) | (1 << TWEN)
    out    TWCR, R16

    rcall  twi_twint_wait    ; Warten bis ausgeführt

    sbi    PORTB, 0      ; Rote Status-LED an

    ; TWI-Status überprüfen
    in    R16, TWSR      ; TWI-Status Register laden
    andi  R16, 0b11111000    ; Status maskieren
    cpi    R16, 0x08      ; Code 0x08 = A START condition has been transmitted
    brne  error        ; Springe zu error wenn ungleich


;- Step 2 --------------------------------------------------------------------------------

    ; 7-Bit Slave Adresse + R/W senden (R=High, W=Low)
    ldi    R16, (127 << 1) | (0)
    out    TWDR, R16

    ; Daten senden
    ldi    R16, (1 << TWINT) | (1 << TWEN)
    out    TWCR, R16

    rcall  twi_twint_wait    ; Warten bis ausgeführt

    sbi    PORTB, 1      ; Grüne Status-LED an

    ; TWI-Status überprüfen
    in    R16, TWSR      ; TWI-Status Register laden
    andi  R16, 0b11111000    ; Status maskieren
    cpi    R16, 0x18      ; Code 0x18 = SLA+W has been transmitted; ACK has been received
    brne  error        ; Springe zu error wenn ungleich

;- Step 3 --------------------------------------------------------------------------------

    ; Zählerdaten senden
    out    TWDR, R17      ; Zählerdaten ins Ausgangsregister laden
    inc    R17          ; Zähler inkrementieren

    ; Daten senden
    ldi    R16, (1 << TWINT) | (1 << TWEN)
    out    TWCR, R16

    rcall  twi_twint_wait    ; Warten bis ausgeführt

    sbi    PORTB, 2      ; Gelbe Status-LED an

    ; TWI-Status überprüfen
    in    R16, TWSR      ; TWI-Status Register laden
    andi  R16, 0b11111000    ; Status maskieren
    cpi    R16, 0x28      ; Code 0x28 = Data byte has been transmitted; ACK has been received
    brne  error        ; Springe zu error wenn ungleich

;- Step 4 --------------------------------------------------------------------------------

    ; Stop Condition senden
    ldi    R16, (1 << TWINT) | (1 << TWSTO) | (1 << TWEN)
    out    TWCR, R16

    sbi    PORTB, 3      ; Blaue Status-LED an

;-----------------------------------------------------------------------------------------

    reti            ; Kehre zurück



    ; Fehler aufgetreten, abbruch
error:  sbi    PORTB, 4      ; Rote Error-LED an
    reti



    ; Warten bis TWINT-Bit wieder gesetzt ist
twi_twint_wait:
    in    R16, TWCR      ; TWI Control Register laden
    sbrs  R16, TWINT      ; Schleife wenn TWINT nicht gesetzt
    rjmp  twi_twint_wait
    ret              ; Kehre zurück

Slave:
.include "m8def.inc"        ; Deklarationen für ATmega8

.equ  adress = 127        ; TWI-Adresse

.cseg                ; Programm-Flash
    rjmp  init        ; Reset-Einsprung

.org  0x011
    rjmp  twis        ; TWI Interrupt

.org  0x013            ; Interrupteinsprünge übergehen


init:  ldi    R16, LOW(RAMEND)  ; Stapel anlegen
    out    SPL, R16
    ldi    R16, HIGH(RAMEND)
    out    SPH, R16


    ; TWI Slave Adresse + TWGCE
    ldi    R16, (adress << 1) | (0 << TWGCE)
    out    TWAR, R16

    ; TWI und Interrupt aktivieren
    ldi    R16, (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
    out    TWCR, R16
  

    ; Status LEDs Ausgang
    ldi    R16, 0b11111111    ; PortD als Ausgang
    out    DDRD, R16

    sei              ; Interrupts aktivieren


loop:  rjmp  loop        ; Endlosschleife


;- 0x60 --------------------------------------------------------------------------------

twis:  in    R16, TWSR
    cpi    R16, 0x60      ; 0x60 = Own SLA+W has been received; ACK has been returned
    brne  twis1        ; Springe wenn ungleich

    ; Data byte will be received and ACK will be returned
    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
    out    TWCR, R16

    rjmp  twis3

;- 0x80 --------------------------------------------------------------------------------

twis1:  in    R16, TWSR
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W; data has been received; ACK has been returned
    brne  twis2        ; Springe wenn ungleich

    in    R16, TWDR      ; Daten an PortD ausgeben
    out    PORTD, R16

    rjmp  twis3

;- 0xA0 --------------------------------------------------------------------------------

twis2:  in    R16, TWSR
    cpi    R16, 0xA0      ; 0xA0 = A STOP condition or repeated START condition has been received while still addressed as Slave
    brne  twis3        ; Springe wenn ungleich

    ; Switched to the not addressed Slave mode; own SLA will be recognized; GCA will be recognized if TWGCE = 1
    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
    out    TWCR, R16  

;- End ---------------------------------------------------------------------------------

twis3:  reti            ; Kehre zurück

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
;- 0x80 --------------------------------------------------------------------------------

twis1:  in    R16, TWSR
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W
                          ; data has been received; ACK has been returned
    brne  twis2        ; Springe wenn ungleich
;-------------------------------
;   IST DAS HIER EIN COPY'N PASTE FEHLER?
;   Das TWINT-Bit wird nicht rückgesetzt!
;-------------------------------
    in    R16, TWDR      ; Daten an PortD ausgeben
    out    PORTD, R16

    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.
.equ F_CPU = 1000000 ; Der Name ist nicht so wichtig, aber die ZAHL!
.equ F_SCL = 100000 ; btw: beim slave MUSS F_CPU 16mal hoeher sein als F_SCL 
; - beim Master ist das automatisch so fuer TWBR == 0

.equ twbrf = (F_CPU /F_SCL -16) / 2 ; diese Formel sollte man dann aber auf Negative Werte pruefen ;)
;...

hth. Jörg

Autor: Hauke Sattler (hauke)
Datum:

Bewertung
0 lesenswert
nicht 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)
;- 0x80 --------------------------------------------------------------------------------

twis1:  in    R16, TWSR
    cpi    R16, 0x80      ; 0x80 = Previously addressed with own SLA+W; data has been received; ACK has been returned
    brne  twis2        ; Springe wenn ungleich

    in    R16, TWDR      ; Daten an PortD ausgeben
    out    PORTD, R16

    ldi    R16, (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE)
    out    TWCR, R16

    rjmp  twis3

cu
Hauke

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Hauke Sattler (hauke)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Datenblatt sagt:
 "While the TWINT Flag is set, the SCL low period is stretched.[...]
Note that this flag is not automatically cleared by hardware when executing
the interrupt routine. Also note that clearing this flag starts the
 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

Autor: Jens (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg X. (Gast)
Datum:

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

hth. Jörg

Autor: Jens (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
also hatte ich die ganze zeit ne failure.. ne georgy^^

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
              F_SCL == F_CPU / (16 + TWBR)    | *(16+TWBR)
F_SCL * (16 + TWBR) == F_CPU                  | / F_SCL
        (16 + TWBR) == F_CPU / F_SCL          | - 16
               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

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg X. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.