I²C mit USI realisieren

LotadaC

Sehr aktives Mitglied
22. Jan. 2009
3.547
70
48
Marwitz
Sprachen
  1. BascomAVR
  2. 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;)
 
Zuletzt bearbeitet:
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:
Also bitte hier vorerst keine Kommentare anhängen - etwaige Hinweise und Korrekturen etc... bitte als persönliche Nachricht;)
 
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.
bitte hier vorerst keine Kommentare anhängen - etwaige Hinweise und Korrekturen etc... bitte als persönliche Nachricht;)
 
Zuletzt bearbeitet:
  • Like
Reaktionen: Dirk
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.
Also bitte hier vorerst keine Kommentare anhängen - etwaige Hinweise und Korrekturen etc... bitte als persönliche Nachricht;)
 
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
USIOIF: Counter Overflow Interrupt Flag
This flag is set (one) when the 4-bit counter overflows (i.e., at the transition from 15 to 0). If the USIOIE bit in USICR and the Global Interrupt Enable Flag are set an interrupt will also be generated when the flag is set. The flag will only be cleared if a one is written to the USIOIF bit.
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.
 
Zuletzt bearbeitet:
Hmm… kein USIOIF?? Am nicht gesetzten USIOIE sollte es ja eigentlich nicht liegen
Hmm… geht das nur bei gesetztem USIWM0-Bit?
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:
Instead of reading data from the USI Data Register the USI Buffer Register can be used. This makes controlling the USI less time critical and gives the CPU more time to handle other program tasks. USI flags as set similarly as when reading the USIDR register. The content of the USI Data Register is loaded to the USI Buffer Register when the transfer has been completed.
 
Einen hab ich noch:
Die Kopie scheint zu passen
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|
                                       ¯¯¯¯¯¯¯¯¯
 
Oh, hatte ich ganz vergessen...
Weiter geht's, verändert wird der Code aus #5...


CodeBox BascomAVR
$regfile = "attiny2313a.dat"

$crystal = 8000000
$baud = 9600

Dim Usibr As Byte

Die drei Direktiven bleiben, das Buffer-Register hingegen benötige ich (wegen Clock-Stretching) nicht - Zeile 6 also weg.

Die Initialisierung des USI wird (der Übersichtlichkeit halber) in eine eigene Subroutine verlagert, diese muß (sollte) in Bascom erstmal deklariert werden:

CodeBox BascomAVR
Declare Sub Usitwi_init                                     'Init-Subroutine deklarieren


Bei einer erkannten StartCondition und einem Überlauf des Flankenzählers soll der jeweilige Interrupt genutzt werden; dazu muß der Sprung in die jeweilige Interrupt Service Routine (ISR) in der Interrupt Vektor Tabelle (IVT) eingetragen werden. C macht das möglicherweise mehr oder weniger automatisch, bei Bascom geschieht dies mit "On Interrupt":

CodeBox BascomAVR
On Usi_start Usitwi_scdetected                              'Start Condition detected
On Usi_ovf Usitwi_tov                                       'Flankenzählerüberlauf


Für den später zu implementierenden Zustandsautomaten (Bild in #4) wird eine Variable angelegt, für die möglichen diskreten Zustände außerdem Namen für Konstanten vergeben. Quasi sowas wie 'ne Enumeration, die kenne ich nämlich in BASCOM nicht. Die Zustandsnamen beziehen sich dabei immer auf den Moment, in dem der Flankenzähler übergelaufen ist, also beim Eintritt der (nächsten) ISR.

CodeBox BascomAVR
Dim Usitwi_state As Byte                                    'Statemachine-Variable
Const Adress_received = 0                                   'falls wer 'ne
Const Ack_transmitted = 4                                   'Enumeration in BASCOM kennt
Const N_ack_received = 5
Const Data_received = 2
Const Data_transmitted = 3


Der Zustand des USI selbst wird ja vorgegeben/eingestellt/verändert, indem die Bits im USICR beschrieben werden, also irgendwas diesem Register zugewiesen wird. Bisher war das nur einmal beim Init der Fall (Zeile 29), aber jetzt wird das häufiger vorkommen. Also werden hier auch gleich ordentliche Namen für Konstanten festgelegt:

CodeBox BascomAVR
'StartCondition-IRQ, 2wireMode mit ClockStretching@ SC, Eintakten bei steigender Flanke
Const Usitwi_idle = 2 ^ Usisie + 2 ^ Usiwm1 + 2 ^ Usics1
'zusätzlich Flankenzähler-IRQ und ClockStretching bei Überlauf
Const Usitwi_active = Usitwi_idle + 2 ^ Usioie + 2 ^ Usiwm0
Die erste setzt nur die drei Bits USISIE, USIWM1 und USICS1, damit reagiert nur der Start Condition Interrupt und ein Timerüberlauf stretcht nicht SCL. Die zweite setzt zusätzlich USIOIE (Überlaufs-IRQ) und USIWM0 (Clock Stretching bei Überlauf).

Um die drei Flags im USISR zurückzusetzen (USIDC ist readonly) der Vollständigkeit halber auch eine Konstante:

CodeBox BascomAVR
'Flags zurücksetzen
Const Resetflags = 2 ^ Usisif + 2 ^ Usioif + 2 ^ Usipf


Die Benamsung der Registerbit-Namen der Beine SDA/SCL bleibt wie gehabt:

CodeBox BascomAVR
'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


Ok, bisher würden nur die beiden Eintragungen in der IVT Code erzeugen, nämlich die Sprünge in die jeweilige (noch zu implementierende) ISR.

In #5 kam als nächstes die Initialisierung des USI, das sollte ja jetzt in einer Subroutine geschehen. Also rufe ich sie jetzt auf:

CodeBox BascomAVR
Call Usitwi_init


Zum testen wird wie bisher 0xF0 ins USI-Datenregister geladen (damit man beim Schieben was sieht), ausgeben lasse ich jetzt aber nur noch bei jeder SCL-Flanke was (also nur noch das Bit für das SCL-Bein im PCMSK setzen):

CodeBox BascomAVR
Usidr = &HF0                                                'zum testen

Print "USIDR: " ; Bin(usidr) ; " USISR: " ; Bin(usisr)
'BASCOM kennt PCMSK0 nicht...
$asm
   ldi r16, 128                                             'SCL PCINT
   Out &H20 , R16                                           'in PCMSK0 freigeben
$end Asm

Da Interrupts verwendet werden sollen, müssen diese global freigegeben werden, kurz und knackig mit:


CodeBox BascomAVR
sei                                                         'IRQs enabled

In der Endlosschleife wird weiterhin der PCINT gepollt, ausgegeben wird aber in Anlehnung an den Code aus #6 weniger:

CodeBox BascomAVR
Do
   If Gifr.5 = 1 Then
      Gifr.5 = 1
      Print "USIDR: " ; Bin(usidr) ; " USISR: " ; Bin(usisr)
   End If
Loop
End      


So, fehlen nur noch die drei Subroutinen...


CodeBox BascomAVR
'   ***init***
Sub Usitwi_init
   Usicr = Usitwi_idle                                      '2wire awaiting SC
   Scl_port = 1                                             'SCL released
   Scl_ddr = 1                                              'ClockStretching enabled
   Sda_port = 1                                             'SDA released, input
   sbi usisr, usisif                                        'reset USISIF
   Print "USI-TWI-Init done"
End Sub

USICR die definierte Konstante für Idle zuweisen (nur der StartCondition-IRQ scharf, und auch nur dort die Clock stretchen).
SCL und SDA über die PORT-Register im Sinne von TWI freigeben (also den rezessiven High-Zustand setzen), SCL wird außerdem Ausgang, wodurch der Start Condition Detector die Clock stretchen kann. Anschließend sicherheitshalber(!) das USISIF zurückgesetzt.



CodeBox BascomAVR
'   ***Start Condition detected***
Usitwi_scdetected:
   Print "Start Condition detected, waiting for SCL-low..."
   While Scl_pin = 1                                        'warten bis SCL = 0
   Wend
   Print "Ok"
   Usicr = Usitwi_active                                    'Overflow-IRQ scharfmachen, clock-stretching at TOV enablen
   Usitwi_state = Adress_received                           '<-bei nächstem USI_TOV
   Usisr = Resetflags                                       'Flankenzähler zurücksetzen, Flags löschen
   Print "SC-ISR done"
Return

Start Condition ist die fallende SDA-Flanke während SCL high ist. Unklar ist, wie schnell der Master SCL runterzieht - also insbesondere, ob das bereits beim Eintritt in die ISR der Fall geschehen ist oder nicht (hängt ja auch vom Slave-Takt ab, und was der nebenbei so macht). Also wird gewartet, bis SCL low ist. Anschließend wird im USICR zusätzlich der Überlaufs-IRQ und dessen Clock-Streching aktiviert (durch laden der definierten Konstante).
Der Zustandsautomat wird auf "adress-received" gesetzt, das ist ja der Zustand, bei dem die Flankenzähler-ISR das nächste mal zuschlägt.
Zuletzt wird der Flankenzähler auf "0" gesetzt, und gleichzeitig die drei Flags gelöscht (da könnte aus übersichtlichkeitsgründen auch "USISR=Resetflags+0" stehen). Durch das (zurück)setzen von USISIF wird hier insbesondere das Clock-Stretching aufgehoben.



CodeBox BascomAVR
'   ***EdgeCounterOverrun detected***
Usitwi_tov:
   Print "Edgecounter Overrun detected..."
   'Zustandsautomat einfügen
   Print "nothing implemented yet"
   Print Bin(usidr) ; " empfangen."
   sbi usisr, usioif                                        'reset USIOIF
   Print "Overrun-ISR done"
Return

Hier wird erstmal nur das empfangene Byte (in binärer Darstellung) ausgegeben, und anschließend das Clock-stretching aufgehoben (indem das USIOIF zurückgesetzt wird).
Insbesondere der Zustandsautomat ist noch zu implementieren. Trotzdem kann das ganze Konstrukt schon mal getestet werden.
Das Programm nochmal als ganzes:

CodeBox BascomAVR
$regfile = "attiny2313a.dat"
$crystal = 8000000
$baud = 9600

Declare Sub Usitwi_init                                     'Init-Subroutine deklarieren

On Usi_start Usitwi_scdetected                              'Start Condition detected
On Usi_ovf Usitwi_tov                                       'Flankenzählerüberlauf

Dim Usitwi_state As Byte                                    'Statemachine-Variable
Const Adress_received = 0                                   'falls wer 'ne
Const Ack_transmitted = 4                                   'Enumeration in BASCOM kennt
Const N_ack_received = 5
Const Data_received = 2
Const Data_transmitted = 3

'StartCondition-IRQ, 2wireMode mit ClockStretching@ SC, Eintakten bei steigender Flanke
Const Usitwi_idle = 2 ^ Usisie + 2 ^ Usiwm1 + 2 ^ Usics1
'zusätzlich Flankenzähler-IRQ und ClockStretching bei Überlauf
Const Usitwi_active = Usitwi_idle + 2 ^ Usioie + 2 ^ Usiwm0
'Flags zurücksetzen
Const Resetflags = 2 ^ Usisif + 2 ^ Usioif + 2 ^ Usipf

'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

'************************************************
Call Usitwi_init

Usidr = &HF0                                                'zum testen

Print "USIDR: " ; Bin(usidr) ; " USISR: " ; Bin(usisr)
'BASCOM kennt PCMSK0 nicht...
$asm
   ldi r16, 128                                             'SCL PCINT
   Out &H20 , R16                                           'in PCMSK0 freigeben
$end Asm

sei                                                         'IRQs enabled

Do
   If Gifr.5 = 1 Then
      Gifr.5 = 1
      Print "USIDR: " ; Bin(usidr) ; " USISR: " ; Bin(usisr)
   End If
Loop
End                                                         'end program
'*****************  Subroutines *****************

'**************** USI-Subroutines ***************
'   ***init***
Sub Usitwi_init
   Usicr = Usitwi_idle                                      '2wire awaiting SC
   Scl_port = 1                                             'SCL released
   Scl_ddr = 1                                              'ClockStretching enabled
   Sda_port = 1                                             'SDA released, input
   sbi usisr, usisif                                        'reset USISIF
   Print "USI-TWI-Init done"
End Sub

'   ***Start Condition detected***
Usitwi_scdetected:
   Print "Start Condition detected, waiting for SCL-low..."
   While Scl_pin = 1                                        'warten bis SCL = 0
   Wend
   Print "Ok"
   Usicr = Usitwi_active                                    'Overflow-IRQ scharfmachen, clock-stretching at TOV enablen
   Usitwi_state = Adress_received                           '<-bei nächstem USI_TOV
   Usisr = Resetflags                                       'Flankenzähler zurücksetzen, Flags löschen
   Print "SC-ISR done"
Return

'   ***EdgeCounterOverrun detected***
Usitwi_tov:
   Print "Edgecounter Overrun detected..."
   'Zustandsautomat einfügen
   Print "nothing implemented yet"
   Print Bin(usidr) ; " empfangen."
   sbi usisr, usioif                                        'reset USIOIF
   Print "Overrun-ISR done"
Return


Nach dem Start kommt:
Code:
USI-TWI-Init done
USIDR: 11110000 USISR: 00010000
Die letzte Zeile kommt nicht aus dem Init, sondern kurz vor der Endlosschleife. Nach runterziehen von SDA:
Code:
Start Condition detected, waiting for SCL-low...
Wenn dann auch SCL runtergezogen wird:
Code:
Ok
SC-ISR done
USIDR: 11110000 USISR: 00010000
Danach fix ein paar Bits eingetaktet:
Code:
USIDR: 11100000 USISR: 00010001
USIDR: 11100000 USISR: 00010010
USIDR: 11000000 USISR: 00010011
USIDR: 11000000 USISR: 00010100
USIDR: 10000000 USISR: 00010101
USIDR: 10000000 USISR: 00010110
USIDR: 00000000 USISR: 00010111
USIDR: 00000000 USISR: 00001000
USIDR: 00000001 USISR: 00011001
USIDR: 00000001 USISR: 00011010
USIDR: 00000010 USISR: 00001011
USIDR: 00000010 USISR: 00001100
USIDR: 00000101 USISR: 00011101
USIDR: 00000101 USISR: 00011110
USIDR: 00001010 USISR: 00001111
Nach der nächsten SCL-Flanke schlägt der TOV-IRQ zu:
Code:
Edgecounter Overrun detected...
nothing implemented yet
00001010 empfangen.
Overrun-ISR done
und unterbricht dabei das Polling des PCINT - deswegen kommt diese Zeile erst danach:
Code:
USIDR: 00001010 USISR: 00000000

Und gleich ein zweites Byte hinterher:
Code:
USIDR: 00010101 USISR: 00010001
USIDR: 00010101 USISR: 00010010
USIDR: 00101011 USISR: 00010011
USIDR: 00101011 USISR: 00010100
USIDR: 01010111 USISR: 00010101
USIDR: 01010111 USISR: 00010110
USIDR: 10101111 USISR: 00010111
USIDR: 10101111 USISR: 00001000
USIDR: 01011111 USISR: 00001001
USIDR: 01011111 USISR: 00011010
USIDR: 10111111 USISR: 00011011
USIDR: 10111111 USISR: 00001100
USIDR: 01111110 USISR: 00011101
USIDR: 01111110 USISR: 00001110
USIDR: 11111100 USISR: 00001111
Edgecounter Overrun detected...
nothing implemented yet
11111100 empfangen.
Overrun-ISR done
USIDR: 11111100 USISR: 00010000
Und zum Abschluß mal 'ne Stop-Condition generiert:
Code:
USIDR: 11111000 USISR: 00010001
USIDR: 11111000 USISR: 00100010
Das Flag kommt - leider gibt's dafür keinen IRQ...
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)