I²C mit USI realisieren

Dieses Thema im Forum "Hardware" wurde erstellt von LotadaC, 4. Dezember 2018.

Schlagworte:
  1. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    In diesem Thread soll es um die Realisierung eines I²C-Busses durch das USI, welches es in einigen ATtinies (und wohl auch wenigen ATmegas) gibt, gehen.

    Vorweg erstmal nötige Fakten über I²C selbst. (Wobei ich nur an der Oberfläche bleiben werde - für den tieferen Einstieg wird eine Suche im Internet sicher Resultate erbringen. Hervorheben möchte ich zumindest die I²C-Spezifikation von NXP).

    • zwei Signale (Leitungen) - SCL=Serial Clock, SDA=Serial Data
    • beide Signale sind dominant-rezessiv (im freigegebenen (idle) Zustand ziehen externe Pull-Widerstände die Signale auf high (rezessiv)
    • jeder Teilnehmer kann(!) sie auf Gnd legen (dominant)
    • den freien Bus darf(!) ein Teilnehmer belegen, indem er eine Startcondition generiert. Dadurch wird er zum Master (bis er selbst den Bus durch eine Stop Condition wieder freigibt, oder die Arbitrierung an einen anderen Master verliert).
    • Nur der (ein) Master darf die SCL low ziehen - jeder Teilnehmer darf SCL dann weiter low halten (Clock-Stretching).
    Code:
        _                 _
    SDA  \               /
          ¯¯¯¯¯¯'''¯¯¯¯¯¯
        _____         _____
    SCL      \       /  
              ¯¯'''¯¯  
         ^   ^       ^   ^
         |   |       |   |
        (1) (2)     (3) (4)
    
    • (1) Teilnehmer zieht SDA low, während SCL (und SDA) high sind, belegt dadurch den Bus, wird zum Master (->Start Condition)
    • (2) Master zieht SCL low.
    • (3) Master gibt SCL frei (geht über Pullup high).
    • (4) Master gibt SDA frei, während SCL high ist (-> Stop Condition)


    • Ein leeres Telegramm (nur Start und Stop) ist nicht erlaubt.
    • Nach einer (Repeated) Start Condition ist immer der Master Transmitter, alle anderen Teilnehmer Receiver.
    • Der Master generiert neun Taktimpulse auf SCL, während der ersten acht wird ein Byte (beginnend mit dem MSB) vom Transmitter an den Receiver übertragen. (D.h. der Transmitter legt vor der steigenden SCL-Flanke (bzw direkt nach der vorherigen fallenden Flanke) den entsprechenden "Pegel" auf SDA, nach der steigenden SCL-Flanke liest der Receiver das Bit ein.
      Beim neunten Takt überträgt der Receiver ein ACK(=low) / NACK(=high) zum Transmitter. (D.h. nach der fallenden SCL-Flanke des achten Bits gibt der Transmitter SDA frei, und der Receiver legt den entsprechenden Pegel auf SDA.) Nach der fallenden Flanke des neunten Bits gibt der Receiver SDA dann wieder frei.
    • Ggf. werden weitere Bytes (genauso) übertragen.
    • Der Master beendet das Telegramm mit einer Stop Condition
    Code:
        _       _____ _____ _____ _____ _____ _____ _____ _____ _____      _
    SDA  \     / MSB X  7  X  6  X  5  X  4  X  3  X  2  X LSB X ACK \    /
          ¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯
        ____     __    __    __    __    __    __    __    __    __     ____
    SCL     \   /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \   /       
             ¯¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯¯
       (Start)^  ^                                       ^  ^  ^  ^  ^  (Stop)         
              |  |                                       |  |  |  |  |           
             (1)(2)                    . . .            (3)(4)(5)(6)(7) 
    
    • (1) Fallende SCL-Flanke -> Transmitter (derzeit der Master) legt sein MSB auf SDA.
    • (2) Steigende SCL-Flanke -> alle Receiver lesen SDA ein.
    . . .
    • (3) Fallende SCL-Flanke -> Transmitter legt sein LSB auf SDA.
    • (4) Steigende SCL-Flanke -> Receiver lesen SDA ein.
    • (5) Fallende SCL-Flanke -> Transmitter gibt SDA frei, (adressierter) Receiver legt ACK/NACK auf SDA.
    • (6) Steigende SCL-Flanke -> Transmitter liest SDA (ACK/NACK) ein.
    • (7) Fallende SCL-Flanke -> Receiver gibt SDA frei.
    Im dargestellten Schema zieht der Master nach (7) SDA low, um die folgende Stop-Condition vorzubereiten (steigende SDA während SCL high ist, also muß erstmal SDA low gezogen werden,während SCL low ist). (*) Stattdessen könnten aber auch weitere Bytes übertragen, oder eine Repeated-Start-Condition erzeugt werden.

    Grundsätzlich ist nach einer (Repeated-) Start-Condition der Master Transmitter. Das erste übertragene Byte besteht aus der Receiver-Adresse (7bit, auf 10bit-Adressierung gehe ich hier nicht ein), das LSB bestimmt die Datenrichtung für den Rest des Telegrammes (also bis zum nächsten Stop oder Start). Low=Master bleibt Transmitter, High=Slave wird Transmitter.
    Code:
        _       _____ _____ _____ _____ _____ _____ _____       _____      _
    SDA  \     /ADDR7XADDR6XADDR5XADDR4XADDR3XADDR2XADDR1\ R/W / ACK \    /
          ¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        ____     __    __    __    __    __    __    __    __    __     ____
    SCL     \   /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \   /       
             ¯¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯¯
    
    (*) Wann kann der Master überhaupt ein Repeated Start oder ein Stop generieren? Theoretisch immer dann, wenn der Slave SDA vor der steigenden SCL-Flanke nicht Low hält.
    Insbesondere also als Transmitter (ausserhalb des ACK/NACK-Slots).
    Als Receiver, wenn der Slave 'n High vorbereitet hat, oder innerhalb seines ACK/NACK-Slots.

    Aber wann darf er ein Repeated Start/Stop generieren?

    Ok, soweit erstmal der Überblick über den eigentlichen I²C-Bus.
    Im nächsten Beitrag würde ich dann erstmal nur auf die USI-Hardware eingehen wollen.

    Also bitte hier vorerst keine Kommentare anhängen - etwaige Hinweise und Korrekturen etc... bitte als persönliche Nachricht;)
     
    #1 LotadaC, 4. Dezember 2018
    Zuletzt bearbeitet: 4. Dezember 2018
    TommyB, Hero_123 und Dirk gefällt das.
  2. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Wie bereits angedeutet, soll's in diesem Beitrag um die Hardware, das eigentliche USI-Modul gehen. Repräsentativ hier mal das entsprechende Diagramm aus dem Datenblatt des ATtiny261A/462A/861A (die mir bekannten Tinies sind weitgehend identisch).
    USI.png
    Wie immer kann die Software mit der Hardware über so genannte I/O-Register kommunizieren - hier sind das vier:
    • USIDR - das USI Data Register, also das eigentliche Schieberegister.
    • USIBR - das USI Buffer Register, ein komplett ins USIDR eingetaktetes Byte (stimmt so nicht, geh ich noch genauer drauf ein) wird in diesen Puffer kopiert, und kann "später" ausgelesen, während das USIDR bereits weitergeschoben wurde.
    • USISR - das USI Status Register, es enthält einen 4-bit Counter (unteres Nibble) sowie vier Status-Flags (oberes Nibble)
    • USICR - das USI Control Register, hier wird die Hardware quasi eingestellt/kontrolliert.
    Das USIDR ist also ein Schieberegister - sein Eingang (LSB) ist immer mit dem DI/SDA-Pin verbunden. Der Ausgang (MSB) hingegen kann nach einem transparenten Latch wahlweise auf DO (entspräche dem 3-wire-Mode) oder DI/SDA (=2-wire-Mode) geschaltet werden.( Der "Schalter" wird übrigens über das USIWM1-Bit (USI Wire Mode) im USICR angesteuert - ist das Bit gesetzt wird auf SDA geschaltet).
    Hier geht's um den 2-wire-Mode...

    Am Takt-Eingang des Schieberegisters befindet sich ein Clock-Source-Multiplexer (der die Quelle der Schiebe-Impulse festlegt). Dieser Multiplexer wird über die beiden USICS-Bits (USI Clock Source Select Bits) im USICR eingestellt.
    Die vier möglichen Quellen wären:
    • das USICLK-Bit in USICR (das Bit wirkt als Clock-Strobe, schreibt man 'ne "1" rein, wird einmal geschoben)
    • ein Compare-Match von Timer0 (wobei unklar ist, welches bzw beide)
    • eine steigende Flanke am USCK/SCL-Pin
    • eine fallende Flanke am USCK/SCL-Pin
    Ein weitgehend identischer Multiplexer speist den 4-bit-Counter, der verfügt zusätzlich über einen Flankendetektor - bei USICS[1..0]=1x bin (also gesetztem USICS1) inkrementiert also jede USCK-Flanke den Counter.

    Auf den USCK/SCL wirkt sich außerdem das Clock-Hold-Unit aus, und zwar nur im 2-wire-Mode. Also wenn USIWM1 in USICR gesetzt wird. Nach jeder, erkannten Startcondition (fallende SDA-Flanke während SCL high) wird nach der nächsten fallenden SCL-Flanke SCL weiterhin auf Low gehalten -> Start Condition SCL Hold. Ist außerdem noch USIWM0 gesetzt, wird auch nach einem Überlauf des 4-bit-Counters gestrecht (bzw nach der darauf folgenden fallenden SCL-Flanke) ->Counter Overflow SCL Hold.

    Ok, was fehlt noch?
    Im USISR gab es (bereits angedeutet, im oberen Nibble) vier Flags:
    • USISIF - USI Start Condition Interrupt Flag - im 2-wire-Mode setzt eine erkannte Start Detection dieses Flag. Dabei wird gleichzeitig die oben erwähnte Start Condition SCL Hold ausgelöst, und zwar bis das Bit manuell wieder gelöscht wird. USISIF kann einen IRQ triggern.
    • USIOIF - USI Counter Overflow Interrupt Flag - wird bei einem Überlauf des 4-bit-Counters gesetzt. Löst im 2-wire-Mode das oben erwähnte Counter Overflow SCL Hold aus bis USIOIF gelöscht wird. Kann einen IRQ triggern.
    • USIPF - USI Stop Condition Flag - wird (im 2-wire-mode) bei einer erkannten Stop-Condition gesetzt (hier gibt's keinen IRQ)
    • USIDC - USI Data Output Collision - ist (im 2-wire-mode) gesetzt, genau dann wenn MSB des Schieberegisters und tatsächlicher Zustand des SDA-Pins unterschiedlich sind.
    Für USISIF und USIOIF gibt's dann im USISR entsprechende Interrupt Enable Bits (USISIE und USIOIE).

    Zum USIDB schrieb ich anfangs, daß das USIDR "nach einem komplett eingetakteten Byte" ins USIDB kopiert wird, und daß das nicht ganz korrekt wäre. Um das "komplett eingetaktet" muß man sich nämlich selbst kümmern - die Kopie wird erzeugt, wenn der 4-Bit-Counter überläuft. Der muß also so eingestellt werden, daß er nach dem LSB überläuft - dort muß aber eh interagiert werden... Ein Transmitter muß hier SDA freigeben damit der Receiver ACK/NACK drauflegen kann, und der Receiver muß genau das tun...

    Im nächsten Teil werde ich darauf eingehen, wie die normalen Portfunktionen durch USI, genauer den Two Wire Mode, außer Kraft gesetzt werden.

    P.S. auch hier noch:
     
  3. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Ok, weiter geht's...

    soweit mir bekannt ist, gibt es das USI nur in den "klassischen SPI-ISP"-Controllern … also jenen, bei denen die Beine und Pullups über das Datenrichtungs- und das PORT-Register eingestellt werden.
    Den meisten wird bekannt sein, daß man die "Datenrichtung" mittels DDRegister festlegt, und dann mit dem PORT Register den Pegel (bei einem Ausgang) bzw Tristate oder den aufgeschalteten Pullup (bei einem Eingang).

    Ich finde die "Datenrichtung" allerdings etwas … irreführend - das Bein ist (abgesehen vom Sleep) immer Eingang, und kann zusätzlich Ausgang sein. Hierzu das entsprechende Bild (auch wieder aus dem Datenblatt des Tiny261A/461A/861A:
    Port1.png
    Links ist das eigentliche Bein, rechts der Datenbus. Dazwischen sind (in drei D-Q-Latches) die drei Register-Bits - DDR, PORT und PIN des Beines dargestellt.

    In der Mitte ist das PORT-Bit erkennbar, welches auf dem Eingang des Ausgangstreibers liegt, es legt also den Pegel des Beines fest.
    Allerdings nur, wenn der Treiber aktiviert ist, und das geschieht durch das DDR-Bit.
    DDR legt also fest, ob das Bein Ausgang ist. Eingang ist es immer.
    PORT (direkt) und DDR (negiert) liegen oben außerdem zusammen mit dem globalen Pullup Disable Bit (auch negiert) auf einem dreifach-AND-Gatter, welches den internen Pullup zuschaltet (also genau dann wenn PORT gesetzt ist UND PUD gelöscht ist UND DDR gelöscht ist.

    Ganz unten dann hinter Schmitt-Trigger und Synchronizer das PIN-Register, welches quasi immer ausgelesen werden kann - nur im Sleep wird es vom Pin abgekoppelt, und intern auf Gnd geschaltet.

    Das ist die vereinfachte Darstellung, alternative Hardwarefunktionen setzen diese normalen PORT-Funktionen außer Kraft/übersteuern sie; über entsprechende Override-Multiplexer:
    Port2.png
    Zwischen Pullup und AND-Gatter gibt es einen Multiplexer der durch ein Pullup-Override-Enable-Signal ein Pullup-Override-Value-Signal auf den Pullup legt, den Ausgang des AND-Gatters übersteuert.

    Für den Ausgangstreiber gibt's ein Data-Direction-Override-Enable-Signal, welches dann das DDR durch ein Data-Direction-Override-Value übersteuern läßt.

    Analog dazu PORT-Value-Override-Enable-Signal und Port-Value-Override-Value.

    Und unten das Digital-Input-Enable-Override-Enable-Signal und Digital-Input-Enable-Override-Value (damit kann das Bein quasi vom Sleep ausgenommen werden - um ein Wakeup zu ermöglichen).

    So, nun zum USI bzw konkret zum 2-wire-Mode...
    • SDA
      • PUOE = 0 → die Zuschaltung des Pullups wird weiterhin durch die Bits in PORT und DDR bestimmt
      • DDOE = USI_TWO_WIRE • USIPOS → SDA kann hier durch USIPOS remapped werden; USI_TWO_WIRE ist quasi das USIWM1-Bit. Sobald also der 2-wire (auf diesem Bein) gewählt ist, wird die Datenrichtung übersteuert...
      • DDOV = (/(SDA) + /(PORTB0)) • DDRB0 • USIPOS → USIPOS ist klar, über das DDR-Bit kann das Bein weiterhin auf Eingang geschaltet werden. Mit DDR=1 wird es aber nur dann Ausgang, wenn SDA (=das MSB des Schieberegisters) oder das PORT-Bit "0" sind.
      • PVOE = USI_TWO_WIRE • DDRB0 • USIPOS → wie beim DDOE, nur daß hier auch das DDR-Bit gesetzt sein muß, für den Override
      • PVOV = 0 → wenn durch das DDR auf Ausgang geschaltet wurde wird das PORT immer mit "0" übersteuert.
    Was heißt das?
    USIPOS und USI_TWO_WIRE sind gesetzt, klar...
    Mit dem DDR-Bit kann man entweder auf Eingang schalten (zB wenn der Controller Receiver sein soll), oder als Ausgang. Wenn DDR gelöscht ist, ist der Treiber (wegen DDOV) deaktiviert. Allerdings würde ein gesetztes PORT-Bit den Pullup zuschalten.
    Wenn DDR gesetzt ist, liefert der PORT-Override immer 'ne "0" - die landet aber nur dann auf dem Bein, wenn DDOV den Ausgangstreiber aktiviert, also wenn SDA oder PORT 'ne "0" enthalten, sonst ist das Bein Tristate (abgesehen vom Pullup) - was letztlich dem I²C entspricht.

    Für einen Receiver bzw einen nicht adressierten Slave ist also das DDR-Bit (und auch das PORT-Bit) zu löschen → Tristate.
    Als Transmitter (bzw zum Senden eines ACK) ist das DDR-Bit zu setzen, jetzt bestimmen SDA bzw PORT-Bit ob das Bein hart auf Gnd geht, oder Tristate bleibt. Soll das Schieberegister das festlegen, ist vorher das PORT-Bit zu setzen, soll das PORT-Bit bestimmen, muß das MSB des Schieberegisters 'ne "1" sein.
    • SCL
      • PUOE = 0 → die Zuschaltung des Pullups wird weiterhin durch die Bits in PORT und DDR bestimmt
      • DDOE = USI_TWO_WIRE • USIPOS → SCL kann hier durch USIPOS remapped werden; USI_TWO_WIRE ist quasi das USIWM1-Bit. Sobald also der 2-wire (auf diesem Bein) gewählt ist, wird die Datenrichtung übersteuert...
      • DDOV = (USI_SCL_HOLD + /(PORTB2)) • DDB2 • USIPOS → USIPOS wie oben, klar. Das DDR-Bit kann auch hier einen Eingang erzwingen. Ist es hingegen gesetzt, führt ein gelöschtes PORT-Bit oder das SCL_HOLD vom Clock-Hold-Unit zum Ausgang.
      • PVOE = USI_TWO_WIRE • DDRB2 • USIPOS → wie beim DDOE, nur daß hier auch das DDR-Bit gesetzt sein muß, für den Override
      • PVOV = 0 → wenn durch das DDR auf Ausgang geschaltet wurde wird das PORT immer mit "0" übersteuert.
    Was heißt das?
    USIPOS und USI_TWO_WIRE sind gesetzt, wie oben...
    Das DDR-Bit kann auf Eingang schalten (Slave) oder auf Ausgang (Master) - der Ausgangstreiber wird aber nur aktiviert, wenn das PORT-Bit 'ne "0" enthält, oder das Clock-Hold-Unit die Leitung auf Gnd halten will - sonst bleibt der Treiber aus, und das Bein (abgesehen vom Pullup) tristate (was beim I²C einer rezessiven "1" entspricht).
    Wenn DDR gesetzt ist, liefert der PORT-Override auch hier immer 'ne "0", die wegen DDOV nur dann am Bein anliegt, wenn das SCL-Hold-Unit greift, oder der im PORT-Bit selbst 'ne "0" steht - entspricht der dominanten I²C-0.

    Als Slave ist also das DDR-Bit zu löschen, und wegen dem Pullup auch das PORT-Bit.
    Als Master ist das DDR-Bit zu setzen, über das PORT-Bit kann jetzt zwischen Gnd und Tristate gewechselt werden; das SCL-Hold-Signal kann außerdem die Clock stretchen.

    Das PORT-Bit kann sogar getoggelt werden, indem man 'ne "1" ins USITC-Bit schreibt.
     
    #3 LotadaC, 18. Dezember 2018
    Zuletzt bearbeitet: 18. Dezember 2018
    Dirk gefällt das.
  4. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Im letzten Beitrag hatte ich festgestellt, wie durch Aktivierung des Two-Wire-Modes die normalen Port-Funktionen der Beine übersteuert werden. Die mir bisher bekannten Controller sind diesbezüglich weitgehend identisch - bis auf die internen Pullups (einige deaktivieren die):
    1. Pullups werden gar nicht übersteuert - ATtiny24/44/84/24A/44A/84A, ATtiny261/461/861/261A/461A/861A, ATtiny87/167, ATtiny43u
    2. Nur der SCL-Pullup (nicht der SDA-Pullup) wird übersteuert - ATtiny2313/2313A/4313
    3. Beide Pullups werden übersteuert - ATtiny25/45/85/25V/45V/85V, ATtiny26/26L
    Der ATtiny1634 gehört theoretisch auch in Gruppe 2, allerdings werden bei ihm die Pullups eh nicht über PORT- und DDR-Register manipuliert - es gibt ein zusätzliches PUEB-Register, in dem für jedes Bein der Pullup unabhängig (von DDR/PORT) aktiviert werden kann. (Die beiden Bilder oben sehen beim 1634 anders aus, der ist ähnlich dem 441/841 ein … ähm... Hybrid, irgendwo zwischen den konventionellen SPI-ISP und den TPI-ISP-Controllern).

    Abgesehen von den Pullups kümmert sich also die USI-Hardware um die "I²C-konformen Pegel" - Eingänge sind tristate, Ausgänge wechseln zwischen Gnd=low und tristate=high. Bei SCL gilt hier das Port-Bit, bei SDA zusätzlich das MSB der USIDR-Schieberegisters.
    Wie ist nun ein Slave zu realisieren?
    Zur Verfügung stehen uns zwei Interrupt-Quellen: die erkannte Startbedingung, und der Überlauf des Counters. Nochmal das Bild aus dem ersten Beitrag:
    Code:
        _       _____ _____ _____ _____ _____ _____ _____ _____ _____      _
    SDA  \     / MSB X  7  X  6  X  5  X  4  X  3  X  2  X LSB X ACK \    /
          ¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯¯ ¯¯¯¯
        ____     __    __    __    __    __    __    __    __    __     ____
    SCL     \   /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \  /  \   /        
             ¯¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯    ¯¯¯
         ^  ^                                                ^     ^   ^  ^          
         |  |                                                |     |   |  |              
        (1)(2)                                              (3)   (4) (5)(6)  
    
    Bei (1)=fallende SDA-Flanke würde der Start-Detector auslösen. Aber wann zieht der Master auch SCL runter (2) ?
    Im Standard Mode frühestens nach 4µs, im Fast Mode nach frühestens 600ns, im Fast Mode Plus nach frühestens 260ns.
    260ns entsprächen bei 20MHz gut fünf Takten, also schneller als der Controller braucht, um überhaupt in der ISR anzukommen, aber die 4µs wären bei 80 Takte.
    Der Counter kann nur bis 16 Flanken zählen (bzw läuft mit der sechzehnten über) - es muß also sichergestellt werden, daß (2) bereits eingetreten ist, wenn man den Counter auf &B0000 setzt, und den dazugehörenden IRQ scharfmacht.
    Der Counter würde also nach weiteren sechzehn SCL-Flanken (also acht Bits) auslösen - bei (3).
    Als Receiver müßte hier das empfangene Byte behandelt werden, insbesondere die Adresse ausgewertet, und dann ggf das ACK vorbereitet werden. Als Transmitter müßte hier SDA freigegeben werden, damit der Master ein N/ACK generieren kann.
    Der Counter wäre jetzt mit 14 zu beladen, damit er wieder bei (4) auslösen würde.
    Dort müßten das ggf empfangene N/ACK ausgewertet werden bzw ein gesendetes ACK wieder freigegeben werden. Grundsätzlich entspricht Punkt (4) Punkt (2), falls weitere Bytes kommen.
    Nach einem NACK wäre der Slave theoretisch fertig (bis zum nächsten Start), die eigentliche StopBedingung läßt sich per Interrupt nicht erkennen. Lediglich (5) könnte über den Counter erkannt werden.

    Hier mal die Grundidee des nötigen Zustandsautomaten:
    USI_State_Machine.png
    Das Init stellt zuerst einmal das ganze ein, klar.
    Der Start-Detector (IRQ) löst bei jeder Startbedingung aus, setzt die StateMachine in den Grundzustand zurück, und macht den Counter-Überlauf-IRQ scharf.
    Die ISR des Counter-Überlaufs beinhaltet dann die eigentliche State-Machine, es gibt fünf zu unterscheidende Zustände. Die Namen beziehen sich auf den Zustand bei Eintritt des Interruptes, die Start-Detector-ISR zB setzt den Zustand also auf 0=Adress received; tritt der Counterüberlauf ein, steht der Status auf Adress received, die Adresse (und R/W) wird ausgewertet, und der entsprechende neue Zustand vorbereitet.
     
  5. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Kleiner Praxis-Exkurs - erstes Experiment.

    Im STK500 befindet sich gerade ein Mega88 - in diesen wurde folgendes Meisterwerk hochintellektueller Programmierkunst geflasht:


    CodeBox BascomAVR
    $regfile = "m88adef.dat"
    $crystal = 8000000
    ' C5 ist mit SCL des Tiny2313A verbunden,
    ' C4 ist mit SDA des Tiny2313A verbunden,
    ' Taster an C2 "toggelt" SCL im Sinne vom TWI,
    ' Taster an C3 "toggelt" SDA im Sinne vom TWI
    
    ' Alle Signale sind Tristate...
    Portc = &B00111100
    ' ...mit aktivierten Pullups (entspricht TWI-idle)
    Do
       'C2 toggelt SCL
       If Pinc.2 = 0 Then
          If Portc.5 = 1 Then
             Portc.5 = 0
             nop
             Ddrc.5 = 1
          Else
             Ddrc.5 = 0
             nop
             Portc.5 = 1
          End If
          Waitms 500                                            'Warteschleifen-Entprellung
       End If
       'C3 toggelt SDA
       If Pinc.3 = 0 Then
          If Portc.4 = 1 Then
             Portc.4 = 0
             nop
             Ddrc.4 = 1
          Else
             Ddrc.4 = 0
             nop
             Portc.4 = 1
          End If
          Waitms 500                                            'Warteschleifen-Entprellung
       End If
    Loop

    C4 und C5 (vom PortC-Header) flugs mit LED7 und LED6 (vom LED-Header) verbunden;
    C2 und C3 ebenso mit Switch6 und Switch7 (vom Switches-Header).
    Test: Nach dem Start sind beide LEDs aus, C4 und C5 high (Pullup). Mit den beiden Tastern kann man getrennt die beiden Signale "toggeln" (wobei 'ne leuchtende LED 'nem aktiv-Gnd = logisch"0", und 'ne nicht leuchtende LED 'nem Pullup-high = logisch"1" entspricht.

    Ok, auf 'nem Breadboard 'nen Tiny2313A aufgesteckt (mit Stromversorgungs- und Programmiergedöns), C5 vom STK500 (Expand0-Header) auf SCL des Tinies, C4 auf SDA des Tinies.
    'N USB-TTL-Wandler an TXD.
    Folgendes Programm:

    CodeBox BascomAVR
    $regfile = "attiny2313a.dat"
    $crystal = 8000000
    $baud = 9600
    
    'SCL-IO-Register-Bits aliased
    Scl_port Alias Portb.7
    Scl_pin Alias Pinb.7
    Scl_ddr Alias Ddrb.7
    
    'SDA-IO-Register_Bits aliased
    Sda_port Alias Portb.5
    Sda_pin Alias Pinb.5
    Sda_ddr Alias Ddrb.5
    
    'SDA und SCL sind default Eingang, Low also Tristate
    
    'USI aktivieren, WM=2 erstmal ohne IRQs, nur Start-Clock-Stretching
    '         +-StartCondition-IRQ disabled
    '         |
    '         |+-Overflow-IRQ disabled
    '         ||
    '         ||++-WM=2wire without clock-stretching at TOV??
    '         ||||
    '         ||||+++-shifting at rising edge (external)
    '         |||||||
    Usicr = &B00101000
    'SCL-Overrides
    'DDOV=(USI_SCL_HOLD+!(SCL_PORT))*SCL_DDR
    'SCL ist tristate, wegen SCL_DDR=0
    Scl_port = 1
    Scl_ddr = 1
    'SCL ist tristate, solange USI_SCL_HOLD=0
    'PVOV=0 wirkt nur bei Ausgang (DDOV ist "Stärker")
    'Fazit: Clock-Stretching enabled
    
    'SDA-Overrides
    'DDOV=(!(SDA)+!(SDA_PORT))*SDA_DDR
    'SDA ist tristate wegen SDA_DDR=0
    Sda_port = 1
    'damit bestimmt nur noch SDA den Pegel - bei einer "0" wird der Pin zum Ausgang,
    'und PVOV=0 greift, sonst ist der Pin Eingang Tristate
    Usidr = &HF0                                                'zum testen
    
    '         ++++-clear all flags
    '         ||||
    '         ||||++++-reset counter
    '         ||||||||
    Usisr = &B11110000
    
    Print "ready"
    Print "USIDR: " ; Bin(usidr) ; " USIBR: " ; Bin(usibr) ; " USISR:" ; Bin(usisr)
    
    'PCINT (nur Flag-Polling) für SDA und SCL freigeben
    Pcmsk0 = &B10100000
    
    Do
       If Gifr.5 = 1 Then
          Gifr.5 = 1                                            'PCINT-Flag zurücksetzen
          'USI-Registerinhalte ausgeben
          Print "USIDR: " ; Bin(usidr) ; " USIBR: " ; Bin(usibr) ; " USISR:" ; Bin(usisr)
          'Start-Condition-Flag zurücksetzen (wegen Clock-Stretching)
          Usisr.7 = 1
       End If
    Loop
    End                                                         'end program

    Hmm… zwei Fehler:
    "Numeric Parameter expected [USIBR]" und "Assignment error [PCMSK0: 0 &B10100000: 112]"
    Äh...
    Ein Blick in das eingebundene Regfile zeigt, daß die beiden Register dort tatsächlich nicht angelegt/bekannt gemacht wurden. Copy&Paste aus dem File des Tiny2313, ohne Anpassung an das "A"...
    Im Regfile will ich nicht rumwurschteln...
    Aber ich(!) weiß ja, wo sich die Register befindet, also gleich am Anfang folgendes eingefügt:


    CodeBox BascomAVR
    Dim Usibr As Byte At &H20 Overlay
    Dim Pcmsk0 As Byte At &H40 Overlay

    (Zugriff wie auf SRAM)
    Hmm… jetzt sind's "Address out of Bounds" Errors - ob mit oder ohne Overlay spielt keine Rolle. Bascom selbst(!) will erst hinter den I/O-Registern den Zugriff erlauben... schade...

    Also von hinten durch die Brust ins Auge...


    CodeBox BascomAVR
    $regfile = "attiny2313a.dat"
    
    $crystal = 8000000
    $baud = 9600
    
    Dim Usibr As Byte
    
    'SCL-IO-Register-Bits aliased
    Scl_port Alias Portb.7
    Scl_pin Alias Pinb.7
    Scl_ddr Alias Ddrb.7
    
    'SDA-IO-Register_Bits aliased
    Sda_port Alias Portb.5
    Sda_pin Alias Pinb.5
    Sda_ddr Alias Ddrb.5
    
    'SDA und SCL sind default Eingang, Low also Tristate
    
    'USI aktivieren, WM=2 erstmal ohne IRQs, nur Start-Clock-Stretching
    '         +-StartCondition-IRQ disabled
    '         |
    '         |+-Overflow-IRQ disabled
    '         ||
    '         ||++-WM=2wire without clock-stretching at TOV??
    '         ||||
    '         ||||+++-shifting at rising edge (external)
    '         |||||||
    Usicr = &B00101000
    'SCL-Overrides
    'DDOV=(USI_SCL_HOLD+!(SCL_PORT))*SCL_DDR
    'SCL ist tristate, wegen SCL_DDR=0
    Scl_port = 1
    Scl_ddr = 1
    'SCL ist tristate, solange USI_SCL_HOLD=0
    'PVOV=0 wirkt nur bei Ausgang (DDOV ist "Stärker")
    'Fazit: Clock-Stretching enabled
    
    'SDA-Overrides
    'DDOV=(!(SDA)+!(SDA_PORT))*SDA_DDR
    'SDA ist tristate wegen SDA_DDR=0
    Sda_port = 1
    'damit bestimmt nur noch SDA den Pegel - bei einer "0" wird der Pin zum Ausgang,
    'und PVOV=0 greift, sonst ist der Pin Eingang Tristate
    Usidr = &HF0                                                'zum testen
    
    '         ++++-clear all flags
    '         ||||
    '         ||||++++-reset counter
    '         ||||||||
    Usisr = &B11110000
    
    Print "ready"
    'BASCOM kennt das USIBR nicht, also von hinten durch die Brust ins Auge
    $asm
       in r16, 0
       sts {usibr}, r16
    $end Asm
    
    Print "USIDR: " ; Bin(usidr) ; " USIBR: " ; Bin(usibr) ; " USISR:" ; Bin(usisr)
    'BASCOM kennt PCMSK0 nicht...
    $asm
       ldi r16, 160                                             'SDA und SCL PCINT
       Out &H20 , R16                                           'in PCMSK0 freigeben
    $end Asm
    Do
       If Gifr.5 = 1 Then
          Gifr.5 = 1
          'BASCOM kennt das USIBR nicht, also von hinten durch die Brust ins Auge
          $asm
             in r16, 0
             sts {usibr}, r16
          $end Asm
    
          Print "USIDR: " ; Bin(usidr) ; " USIBR: " ; Bin(usibr) ; " USISR:" ; Bin(usisr)
          Usisr.7 = 1
       End If
    Loop
    End                                                         'end program
    

    Das schluckt Bascom - nach dem Reset kann man mit einem Terminalprogramm folgendes empfangen:
    Code:
    ready
    USIDR: 11110000 USIBR: 00000000 USISR:00010000
    Das Datenregister wurde in Zeile 45 so beschrieben, der Buffer ist "leer", der Counter steht auf Null, und lediglich das Data-Collision-Flag ist gesetzt. Warum ist mir nicht ganz klar. USIDR.7=1 und die Leitung ist high...
    Als erstes zieht SDA auf Gnd:
    Code:
    USIDR: 11110000 USIBR: 00000000 USISR:10000000
    USIDR und -BR unverändert, klar - im -SR kommt jetzt das Start-Flag (inklusive Clock-Stretching, aber das behebt ja die letzte Anweisung im Then-Block). Eigenartigerweise jetzt keine Data-Collision mehr (Leitung ist "0", USIDR.7="1" - das muß irgendwas mit dem transparenten Latch zu tun haben).
    Jetzt auch SCL auf Gnd:
    Code:
    USIDR: 11110000 USIBR: 00000000 USISR:00010001
    Der Counter inkrementiert, klar; die DC ist jetzt auch korrekt.
    Mal ein paar (3) nullen eingetaktet (also SCL sechsmal getoggelt):
    Code:
    USIDR: 11100000 USIBR: 00000000 USISR:00010010
    USIDR: 11100000 USIBR: 00000000 USISR:00010011
    USIDR: 11000000 USIBR: 00000000 USISR:00010100
    USIDR: 11000000 USIBR: 00000000 USISR:00010101
    USIDR: 10000000 USIBR: 00000000 USISR:00010110
    USIDR: 10000000 USIBR: 00000000 USISR:00010111
    Der Counter zählt jede Flanke, USIDR schiebt jede steigende Flanke. Paßt. Bevor da nur nullen stehen, setzen wir mal SDA auf high (SCL ist ja gerade 0).
    Code:
    USIDR: 10000000 USIBR: 00000000 USISR:00000111
    Der Conter bleibt, da ja nicht SCL getoggelt wird - SDA ist jetzt gleich USIDR.7 Aber nicht mehr lange, weil jetzt vier einsen eingetaktet werden (achtmal SCL toggeln):
    Code:
    USIDR: 00000001 USIBR: 00000000 USISR:00001000
    USIDR: 00000001 USIBR: 00000000 USISR:00011001
    USIDR: 00000011 USIBR: 00000000 USISR:00011010
    USIDR: 00000011 USIBR: 00000000 USISR:00011011
    USIDR: 00000111 USIBR: 00000000 USISR:00011100
    USIDR: 00000111 USIBR: 00000000 USISR:00011101
    USIDR: 00001111 USIBR: 00000000 USISR:00011110
    USIDR: 00001111 USIBR: 00000000 USISR:00011111
    Ok, der Counter würde bei der nächsten SCL-Flanke überlaufen, vorher nochmal SDA auf Gnd:
    Code:
    USIDR: 00001111 USIBR: 00000000 USISR:00001111
    Löst die DC auf, kein Shift, kein Increment - klar.
    Also jetzt: SCL highsetzen; sollte 'ne Null eintakten, außerdem läuft der Timer über - was die Kopie ins BR und das USIOIF zur Folge haben sollte...
    Code:
    USIDR: 00011110 USIBR: 00011110 USISR:00000000
    Hmm… kein USIOIF?? Am nicht gesetzten USIOIE sollte es ja eigentlich nicht liegen
    Hmm… geht das nur bei gesetztem USIWM0-Bit?

    Die Kopie scheint zu passen - braucht man wegen Clock-Stretching aber eh nicht.
    Da SCL gerade high ist, und SDA low, könnte man jetzt noch SDA high setzen - was einer Stop-Condition entspräche:
    Code:
    USIDR: 00011110 USIBR: 00011110 USISR:00110000
    Paßt, die DC natürlich auch. SDA ist high, wir takten einfach ein paar davon ein (die erste SCL-Flanke fällt, schiebt also nicht):
    Code:
    USIDR: 00011110 USIBR: 00011110 USISR:00110001
    USIDR: 00111101 USIBR: 00011110 USISR:00110010
    USIDR: 00111101 USIBR: 00011110 USISR:00110011
    USIDR: 01111011 USIBR: 00011110 USISR:00110100
    USIDR: 01111011 USIBR: 00011110 USISR:00110101
    USIDR: 11110111 USIBR: 00011110 USISR:00110110
    USIDR: 11110111 USIBR: 00011110 USISR:00100111
    USIDR: 11101111 USIBR: 00011110 USISR:00101000
    USIDR: 11101111 USIBR: 00011110 USISR:00101001
    USIDR: 11011111 USIBR: 00011110 USISR:00101010
    USIDR: 11011111 USIBR: 00011110 USISR:00101011
    USIDR: 10111111 USIBR: 00011110 USISR:00101100
    USIDR: 10111111 USIBR: 00011110 USISR:00101101
    USIDR: 01111111 USIBR: 00011110 USISR:00101110
    USIDR: 01111111 USIBR: 00011110 USISR:00111111
    USIDR: 11111111 USIBR: 11111111 USISR:00110000
    In der letzten Zeile dann ein weiterer Überlauf.
     
    #5 LotadaC, 7. Januar 2019
    Zuletzt bearbeitet: 8. Januar 2019
  6. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    NEIN! Es liegt am auslesen des USIBR!
    Wenn ich das auslesen und ausgeben des USIBR vor und in der Schleife rausnehme, die Schleife durch:

    CodeBox BascomAVR
    Do
       If Gifr.5 = 1 Then
          Gifr.5 = 1
          Print "USIDR: " ; Bin(usidr) ; " USISR: " ; Bin(usisr)
          If Usisr.6 = 1 Then
             Print "USIOIF detected"
             'BASCOM kennt das USIBR nicht, also von hinten durch die Brust ins Auge
             $asm
                in r16, 0
                sts {usibr}, r16
             $end Asm
             Print "USIBR: " ; Bin(usibr) ; " USISR: " ; Bin(usisr)
          End If
    
          Usisr.7 = 1
       End If
    Loop
    ersetze, ergibt sich folgendes Log (einfach nur SCL getoggelt, also einsen eingetaktet...):
    Code:
    ready
    USIDR: 11110000 USISR: 00010000
    USIDR: 11110000 USISR: 00000001
    USIDR: 11100001 USISR: 00000010
    USIDR: 11100001 USISR: 00000011
    USIDR: 11000011 USISR: 00000100
    USIDR: 11000011 USISR: 00000101
    USIDR: 10000111 USISR: 00000110
    USIDR: 10000111 USISR: 00000111
    USIDR: 00001111 USISR: 00001000
    USIDR: 00001111 USISR: 00011001
    USIDR: 00011111 USISR: 00011010
    USIDR: 00011111 USISR: 00011011
    USIDR: 00111111 USISR: 00011100
    USIDR: 00111111 USISR: 00011101
    USIDR: 01111111 USISR: 00011110
    USIDR: 01111111 USISR: 00011111
    USIDR: 11111111 USISR: 01010000
    USIOIF detected
    USIBR: 11111111 USISR: 00010000
    USIDR: 11111111 USISR: 00000001
    
    Vor "USIOIF detected" (genau genommen in diesem Moment) ist das Flag gesetzt, und wird auch ausgegeben. Danach lasse ich USIBR lesen und ausgeben, und nochmal USISR - hier ist das USIOIF bereits gelöscht. Zum USIBR steht im Datenblatt:
     
  7. LotadaC

    LotadaC Sehr aktives Mitglied

    Registriert seit:
    22. Januar 2009
    Beiträge:
    3.047
    Zustimmungen:
    54
    Punkte für Erfolge:
    48
    Sprachen:
    BascomAVR, Assembler
    Einen hab ich noch:
    Zur Erinnerung: Wir schieben bei einer steigenden SCL-Flanke. Gestartet wurde hier mit High-Pegeln auf beiden Leitungen und Counter=0. Der Counter wurde (bisher) durch das Programm nie zurückgesetzt, er läuft also bei mit einer steigenden Flanke von 0xF auf 0x0 über. Die steigende Flanke schiebt SDA in das USIDR ein, Das USIOIF triggert (bis man USIBR liest). Und USIDR scheint ins USIBR kopiert zu werden.
    Läßt man den Counter allerdings nicht in einer Schiebe-Flanke überlaufen (also hier einfach mit einer fallenden Flanke starten -> überlaufen lassen), ergibt sich zB folgendes Log (SDA=high, SCL=low und Reset des Tinies, und ein paar Bits eingetaktet):
    Code:
    ready
    USIDR: 11110000 USISR: 00000000
    USIDR: 11100001 USISR: 00000001
    USIDR: 11100001 USISR: 00000010
    USIDR: 11000011 USISR: 00000011
    USIDR: 11000011 USISR: 00000100
    USIDR: 10000111 USISR: 00000101
    USIDR: 10000111 USISR: 00000110
    USIDR: 00001111 USISR: 00000111
    USIDR: 00001111 USISR: 00011000
    USIDR: 00011111 USISR: 00011001
    USIDR: 00011111 USISR: 00011010
    USIDR: 00111111 USISR: 00011011
    USIDR: 00111111 USISR: 10001011
    USIDR: 00111111 USISR: 00001100
    USIDR: 01111110 USISR: 00001101
    USIDR: 01111110 USISR: 00001110
    USIDR: 11111100 USISR: 00001111
    USIDR: 11111100 USISR: 01010000
    USIOIF detected
    USIBR: 11111000 USISR: 00010000
    USIDR: 11111100 USISR: 00000000
    Beim Triggern von USIOIF hatte USIDR &B11111100 als Inhalt, es erfolgte (wie gesagt) kein Shift. Aus USIBR wird aber &B11111000 zurückgelesen, als wenn ein Shift erfolgt wäre. Für die letzte Zeile hab ich einfach nochmal SDA setzen lassen (kein Shift), USIDR ist weiterhin &B11111100.
    Wenn ich diese "1" mit einer weiteren SCL-Flanke eintakte:
    Code:
    USIDR: 11111001 USISR: 00000001
    , würde man aus USIDR also &B11111001 lesen, um USIBR stünde immer noch &B11111000.

    Die Kopie wurde also bereits vor dem Schieben erstellt, USIBR ist aber bereits geschoben. Meiner Meinung nach ist nur folgendes plausibel:
    Die Kopie wird um eins nach links verschoben erstellt, und zwar vor dem eventuellen Schieben des USIDR, das LSB wird mit dem derzeitigen Pinzustand übernommen. Also:
    Code:
              ___ ___ ___ ___ ___ ___ ___ ___
    USIDR:   |MSB| 6 | 5 | 4 | 3 | 2 | 1 |LSB|
              ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯
                   ↓   ↓   ↓   ↓   ↓   ↓   ↓ 
                  ___ ___ ___ ___ ___ ___ ___ ___
    USIBR:       |MSB| 6 | 5 | 4 | 3 | 2 | 1 |LSB|
                  ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯ ¯¯¯
                                               ↑
                                           _________
                                          |SDA-Input|
                                           ¯¯¯¯¯¯¯¯¯
     
  • Über uns

    Unsere immer weiter wachsende Community beschäftigt sich mit Themenbereichen rund um Mikrocontroller- und Kleinstrechnersysteme. Neben den Themen Design von Schaltungen, Layout und Software, beschäftigen wir uns auch mit der herkömmlichen Elektrotechnik.

    Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  • Coffee Time

    Unser makerconnect-Team arbeitet hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und unser eigener makerconnekt-Server regelmäßig gewartet wird. Wir nehmen das Thema Datensicherung und Datenschutz sehr ernst und sind hier sehr aktiv, auch sorgen wir uns darum, dass alles Drumherum stimmt!

    Dir gefällt das Forum und die Arbeit unseres Teams und du möchtest es unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft, unser Team freut sich auch über eine Spende für die Kaffeekasse :-)
    Vielen Dank!
    Dein makerconnect-Team

    Spende uns! (Paypal)
  1. Diese Seite verwendet Cookies, um Inhalte zu personalisieren und die Seite optimal für dich anzupassen. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies.
    Information ausblenden