Grundsätzlich ist ja im Artikel TWI Praxis alles dargestellt, war der Mensch braucht, um die Hardware TWI eines AVR zu verwenden.
- Wenn "Multimaster" lediglich darin besteht, daß sich mehrere Master die I2C-Perpherie gewissermaßen teilen, reicht das auch völlig, da das TWI-Modul des Avr immer automatisch wartet, bis der Bus frei ist.
- Im Multimasterbetrieb mit gegenseitigem Zugriff treten allerdings Situationen auf, die die Sache dann leicht ins Stocken bringen
Inhaltsverzeichnis
Konflikte
Bus Lost
Die TWI-Hardware verfolgt ja immer den Bus, und wenn ein Master bei einem gerade besetzten Bus ein START absetzen will, wird einfach gewartet. Innerhalb eines kurzen Zeit-Fensters wird das allerdings zu spät bemerkt, da tritt dann die "Arbitrierung" in Kraft (ganz genau steht das im Datenblatt). Irgendwann merkt ein Master, daß ein anderer auch gerade Daten sendet und bricht dann seine eigene Übertragung ab. Status: Irgendeine Variante mit "Bus Lost".
Das einzig Sinnvolle ist es dann zu warten, bis der andere seine Übertragung beendet hat, und es dann ganz einfach wieder zu versuchen. Genauso ist es, wenn andere Störungen aufgetreten sind (Es reicht völlig, wenn ein anderer µC mit den Bascom-I2C Soft-Funktionen einfach irgendwann zu senden beginnt).
Deadlock
Wenn so ein Multimaster gleichzeitig auch als Slave fungiert, kann aber auch ein Deadlock entstehen. Wenn wir einfach eine Sendeschleife definieren, bis wir mit unserer (Master) Sendung durchgekommen sind, und zur selben Zeit will ausgerechnet der Master, den wir gerade adressieren, mit der gleichen Strategie auf UNS zugreifen, finden beide Prozessoren kein Ende in ihrer Schleife. Jeder wartet, bis der andere endlich zuhört.
In diesem Fall müssen wir den Sendeversuch eigentlich abbrechen und erstmal schauen, ob wir nicht selbst adressiert worden sind. Wenn ja, müssen wir erstmal das verarbeiten, was wir gekriegt haben und erst dann können wir nochmal einen Sendeversuch starten.
MyTWI.LIB
Diese Bascom-Library versucht nun, diese Konflikte soweit abzuhandeln, daß der User nicht allzuviel damit konfrontiert ist.
MyTWI.BAS
Zu dieser Library gehört eine Include-Datei MyTWI.BAS, die die Library, die Externals und einige Felder und Konstanten definiert. Die vollständigen Originale können runtergeladen werden ->Links.
$lib "MyTWI.LIB" $external Twi_sense $external Mst_start $external Mst_stop $external Twi_mst_slv_isr Const Twi_m_sent = 1 Const Twi_m_recv = 2 Const Twi_timeout = 3 Const Twi_m_busy = 4 Const Twi_buserr = 7
Felder für Master Transmitter/Receiver
'------------------------------------- ' twi-STRUCTURE MASTER '------------------------------------- Dim Twi_mst_flag As Byte '0 Kontroll-flags Dim Twi_mst_addr As Byte '1 SLA + R/W Dim Twi_mt_curr As Byte '0 resv Dim Twi_mt_data As Word '1/2 Data address Dim Twi_mt_cntr As Byte '3 anzahl bytes Dim Twi_mr_curr As Byte '0 resv Dim Twi_mr_data As Word '1/2 Data address Dim Twi_mr_cntr As Byte '3 anzahl bytes
Felder für Slave Receiver/Transmitter
'------------------------------------- ' twi-STRUCTURE SLAVE RECV / TRANS '------------------------------------- Dim Twi_slv_flag As Byte '0 Kontroll-flags Dim Twi_slv_addr As Byte '1 slave adresse Dim Twi_slv_stat As Byte '2 twi-state (SLA) Dim Twi_sr_cntr As Byte '0 anzahl bytes Dim Twi_sr_data As Word '1/2 Data-address Dim Twi_st_cntr As Byte '0 anzahl bytes Dim Twi_st_data As Word '1/2 Data-address
Wofür die Felder dienen, ist im Demoprogramm am besten zu verstehen
M32Slave.BAS
Das ist das "Haupt" Programm. Der Timer0 hat mit den TWI-Funktionen nichts zu tun. Er soll nur jede Sekunde eine TWI-Master-Sendung auslösen.
Definitionen
$regfile = "m32def.dat" 'das muß man für sich anpassen, logo $crystal = 8000000 'detto $include "MyTWI.bas" $baud = 9600 'natürlich beliebig $hwstack = 64 'Wegen der Interrupts + Reserve Const Tmr_c_prescale = 64 Const Tmr_c_preload = 131 Const Tmr_c_divis = 250 '------------------------------------------------- Config Timer0 = Timer , Prescale = Tmr_c_prescale 'Timer 1mS On Timer0 Interrupt_ticker ' Timer for Timer Queue Dim Timeout As Byte Dim Timediv As Word '------------------------------------------------- Declare Sub Twi_show_state(byref State As Byte) 'siehe weiter unten
Adressen
Die verwendeten I2C-Adressen müß man natürlich auch auswählen
'------------------------------------------------- ' Die eigene I2C Adresse (als Slave) '------------------------------------------------- Const Mn1_adr = &H6A '------------------------------------------------- ' fremde I2C Adresse (als Master) '------------------------------------------------- Const Mn2_adr = &H6E
Setup als Slave
Dim Twi_slv_buff(24) As Byte 'Buffer für Slavefunktionen Twi_slv_addr = Mn1_adr 'lokale I2C-Adresse Twi_sr_data = Varptr(twi_slv_buff(1)) 'datenbuffer empfang Twi_st_data = Varptr(twi_slv_buff(1)) 'datenbuffer senden Twar = Twi_slv_addr + 1 'I2C Adress Mega32 + GCA Config Twi = 400000 'I2C Speed wahlweise On Twi Twi_mst_slv_isr , Nosave 'ISR f. TWI Gosub Twi_sense 'Aktivieren Slave-Funktion Enable Timer0 'Timer Enable Interrupts 'Generell
- Anmerkung: Wenn man nur
Twar = Twi_slv_addr
schreibt, wird ein GCA NICHT erkannt.
Hauptschleife
Ob man als Slave angesprochen wurde, muß man natürlich periodisch abfragen und quittieren. Lange "WAIT" sind also nicht günstig
Hauptschleife Beginn
Do
Wer hätte das gedacht ?
Hauptschleife Slave-Flag abfragen
If Twi_slv_flag <> 0 Then '----------------------------------------------------------------- ' Vorsicht, der Bus ist solange blockiert '----------------------------------------------------------------- Select Case Twi_slv_flag Case &H60 : Print Spc(30) ; "SLAVE recv:" ; 'es ist was empfangen worden Print Hex(twi_slv_addr) ; " "; For Temp = 1 To Twi_sr_cntr Print Hex(twi_slv_buff(temp)); 'print der Daten Next Case &HA8 : Print Spc(30) ; "SLAVE tran:" ; 'es ist was abgeholt worden Print Hex(twi_slv_addr) ; " "; For Temp = 1 To Twi_st_cntr Print Hex(twi_slv_buff(temp)); 'print der Daten Next Case &H70 : Print Spc(30) ; "SLAVE GCA :" ; 'ein General Call ist gekommen Print Hex(twi_slv_addr) ; " "; For Temp = 1 To Twi_sr_cntr Print Hex(twi_slv_buff(temp)); 'print der Daten Next Case Else: Print Chr(7) ; 'Irgendein Fehler Call Twi_show_state(twi_slv_flag) 'Print status-text End Select Print Twi_slv_flag = 0 'quittieren Twi_slv_stat = 0 ' Gosub Twi_sense 'Slave wieder scharfmachen 'und Bus freigeben End If
Der Kern: Slave auswerten
- Twi_slv_flag =
- 0 Keine Slave Aktion hat stattgefunden
- &H60 Daten wurden empfangen. Sie stehen im Buffer mit der Länge Twi_sr_cntr
- &HA8 Daten wurden gesendet. Sie stehen im Buffer mit der Länge Twi_st_cntr
- &H70 GCA-Daten wurden empfangen.
- else Irgendein Fehler ist gewesen. Irgendeine Reaktion ist aber nicht notwendig.
Der Kern: Slave quittieren und wieder enablen
Twi_slv_flag = 0 Twi_slv_stat = 0 Gosub Twi_sense
- Anmerkung: Die Demo geht natürlich bei "Slave Transmitter" ein wenig an der Realität vorbei. Normalerweiser wird ja erst in "Slave Receiver" festgelegt, welche Daten überhaupt zu senden sind. Daher ist eine nachträgliche Auswertung ja sinnlos. Bestenfalls als Bestätigung, daß sie der fremde Master auch tatsächlich geholt hat.
Hauptschleife wechseln auf Master
Das ist jetzt reine Demo
'----------------------------------------------------------------- ' Jede Sekunde was senden & empfangen '----------------------------------------------------------------- If Timeout = 1 Then Gosub Master_transmit 'Senden in einer SUB If Twi_mst_flag = 0 Then 'Hats geklappt ? Timeout = 0 'ja ' else (sonst probieren wir's gleich nochmal) End If End If '-----------------------------------------------------------------
Hauptschleife Ende
Loop End
Sende-SUB
'---------------------------------------- ' Beispiel 8 byte senden, ' dann mit Rep.Start von gleicher adresse 3 Byte empfangen '---------------------------------------- Master_transmit: ' sende buffer (mit testdaten) füllen For Temp = 1 To 24 Twi_mst_buff(temp) = Slv_byte Next Twi_mst_addr = Mn2_adr 'I2C adresse ZIEL Twi_mt_cntr = 8 ' 8 Byte senden Twi_mt_data = Varptr(twi_mst_buff(1)) ' Daten Twi_mr_cntr = 3 ' dann 3 Byte empfangen Twi_mr_data = Varptr(twi_mst_buff(1)) ' empfangsbuffer Gosub Run_wait_master ' noch eine SUB Return
- Anmerkung:
Nur senden
Twi_mst_addr = Mn2_adr 'I2C adresse ZIEL Twi_mt_cntr = 8 ' 8 Byte senden Twi_mt_data = Varptr(twi_mst_buff(1)) ' Daten Twi_mr_cntr = 0
Nur abholen
Twi_mst_addr = Mn2_adr + 1 'I2C adresse ZIEL (+R) Twi_mr_cntr = nn ' wieviele Byte Twi_mr_data = Varptr(twi_mst_buff(1)) ' empfangsbuffer
Durchführen der Master-Funktion
Run_wait_master: Gosub Mst_start 'aufruf LIBRARY Select Case Twi_mst_flag 'ergebnis ? Case Twi_m_sent: 'gesendet Print "<<<<<<MASTER sent:" ; Print Hex(twi_mst_addr) ; " "; For Temp = 1 To Twi_mt_cntr Print Hex(twi_mst_buff(temp)); Next Print Twi_mst_flag = 0 Case Twi_m_recv: 'geholt Print ">>>>>>MASTER read:" ; Print Hex(twi_mst_addr) ; " "; For Temp = 1 To Twi_mr_cntr Print Hex(twi_mst_buff(temp)); Next Print Twi_mst_flag = 0 Case Else: 'irgendein Problem Print Chr(7) ; Hex(twi_mst_addr) ; " "; Call Twi_show_state(twi_mst_flag) End Select Return
- Anmerkung: Auch das ist nur DEMO. Auswerten sollte man eventuell die Probleme, denn die bedeuten, daß das Ganze fehlgeschlagen ist, entweder, weil man gerade selbst adressiert wurde, oder weil eben irgendwas auf dem Bus passiert ist, was die Library-Funktion nicht abhandeln konnte.
TWI_Show_State.BAS
Das ist ein unabhängiger Programmteil, der einfach den TWI Status mit Text über "PRINT" ausgibt. Für den Betrieb ist das nicht notwendig, er verbraucht wegen der Texte doch einigen Platz.
'---------------------------------------- ' '---------------------------------------- Sub Twi_show_state(byref State As Byte) Print Hex(state); Select Case State Case &H08 : Print " Start " Case &H10 : Print " Start Rep " Case &HA0 : Print " Stop / Rep Start " Case &HA8 : Print " Sla R Received Acked " Case &H60 : Print " Sla W Received Acked " Case &H70 : Print " Gca Received Acked " Case &H80 : Print " SR Sla Data Received Acked " Case &H90 : Print " SR Gca Data Received Acked " Case &H88 : Print " SR Sla Data Received Not Acked " Case &H98 : Print " SR Gca Data Received Not Acked " Case &HB8 : Print " ST Data Transmitted Acked " Case &HC0 : Print " ST Data Transmitted Not Acked " Case &HC8 : Print " ST Last Data Transm Acked " Case &H18 : Print " MT Sla W Transmitted Acked " Case &H40 : Print " MT Sla R Transmitted Acked " Case &H20 : Print " MT Sla W Transmitted Not Acked" Case &H48 : Print " MT Sla R Transmitted Not Acked " Case &H38 : Print " MT Lost Or Sla R / W Not Acked " Case &H68 : Print " MT Lost / Sla W Received Acked " Case &H78 : Print " MT Lost / Gca Received Acked " Case &HB0 : Print " MT Lost / Sla R Received Acked " Case &H28 : Print " MT Data Transmitted Acked " Case &H30 : Print " MT Data Transmitted No Acked " Case &H50 : Print " MT Data Received Acked " Case &H58 : Print " MT Data Received Not Acked " Case Twi_timeout : Print " M Timeout" Case Twi_m_busy : Print " TWI Busy" Case Twi_buserr : Print " Bus Error" Case Else : Print " ??" End Select End Sub
Demo-Programm
- m32slave.bas demo/testprogramm
- twi_show_state.bas Twi-Status printen
- mytwi.bas includefile für die library
- mytwi.lib Bascom library, gehört natürlich ins Bascom LIB directory
Funktionen der Demo
- Das Demo definiert sich als Slave und sendet oder empfängt auf diese Weise Daten.
- Jede Sekunde aber sendet er selbst als Master Daten an einen anderen I2C-Slave und holt dann gleich auch Daten von dort ab
Test
Getestet hab ich das im Kreuz-und Quer-Dauerbetrieb mit 3 Master/Slaves (2 x Atmega32, 1x AT90S2313 und den drei PCFs von der RNBFRA-Karte. Die Busgeschwindigkeit ist mit 400 kHz recht günstig, da dadurch der Bus jweils nicht solange besetzt ist. Definitionsgemäß ist es nicht notwendig, daß alle Teilnehmer die gleiche Speed haben.
Vorsicht beim 2313: Wenn der mit den normalen Bascom I2C Funktionen sendet, benimmt er sich wie ein absoluter Terrorist, da es sich um nix kümmert.