AVR-Kopplung über I2C

AVR-Kopplung über I2C DL6GL Mo., 27.01.2020 - 16:17
LCD Display

Der im September 2016 veröffentlichte gleichnamige Artikel zeigte einen ersten Ansatz auf, zwei AVR zu koppeln. Die Kopplung beschränkte sich darauf, vom I2C-Master Daten an einen Slave zu schicken. Nun soll auch der bislang stumme Slave ertüchtigt werden, mit dem Master ins Gespräch zu kommen und auf Anforderung Daten an den Master zu schicken. Die Interrupt-Steuerung übernimmt im Slave wie auch im Master für den Anwender unsichtbar den kompletten Ablauf.

Der erste Abschnitt gibt den ursprünglichen Artikel von 2016 wieder. In den Folgeabschnitten wird gezeigt, wie auch im Master eine Interrupt-Serviceroutine eingesetzt werden kann, die die von BASCOM gewohnten I2C-Befehle wie I2CStart, I2CStop, I2CWByte und I2CRByte durch lediglich zwei Befehle, TWI_MasterWrite und TWI_MasterRead, ersetzt. Alle weiteren I2C/TWI-spezifischen Aktionen sind dort für den Anwender unsichtbar untergebracht. Der Anwender muss sich nur noch um den Satz Eingabedaten für das Schreiben vom Master in den Slave und die Ausgabedaten vom Lesen aus dem Slave zurück zum Master kümmern.

Das Ziel ist also:

  • Den Anwender von allen I2C/TWI-spezifischen Funktionen abzuschirmen. Die Arbeit verrichten Interrupt-Serviceroutinen im Hintergrund. Im Master-Programm sind dazu nur zwei Aufrufe, TWI_MasterWrite und TWI_MasterRead, zu sehen.
     
  • Der Anwender lässt den Master eine Anzahl Bytes an den Slave schicken. Inhalt und Anzahl legt er fest - mehr nicht. Wenn er auch Daten vom Slave zurück haben will, kann er das in diesem Datensatz auf geeignete Weise festlegen. Der Master meldet die Erledigung.
     
  • Wenn im Slave festgelegt (programmiert) ist, wie auf bestimmte Master-Daten zu reagieren ist, kann dieser die vom Master gewünschten Daten zurückschicken. Der Anwender muss nur die Anzahl der erwarteten Daten festlegen - mehr nicht. Aus dem für die jeweilige Anwendung vorgesehenen Inhalt der Daten lässt sich dann eine Auswertung vornehmen, etwa Byte 4 bis 7 ergeben die Frequenz in Hertz als 4 Byte DWORD-Variable. So was in der Art.
     

Wohlgemerkt, der Slave ist wie der Master ein Mikrocontroller. Bei passiven I2C-Bausteinen wie z.B. EEPROM's oder ADC sind Art und Adressierung der Daten in der Bausteinspezifikation festgelegt. Der Slave muss erst entsprechend programmiert werden, damit er ein erwartetes Verhalten zeigt. Im Testprogramm in Abschnitt 3 addiert er eine 1 zu den empfangenen Daten und schickt sie wieder zurück, zu sehen im Aufmacherbild oben.

Master und Slave sind Mikrocontroller vom Typ ATmega, die über ein TWI-Interface verfügen. Bei ATtiny-Controllern müsste die USI-Schnittstelle bemüht werden. Das ist hier nicht vorgesehen.

Ein- und Ausgabedaten besitzen die gleiche Struktur als Byte-Array mit
Byte 1: Slave-Adresse, Bytes 2 … n: Nutzdaten, insgesamt n + 1 Bytes.
Die Slave-Adresse ist in einem Übertragungsprotokoll immer das erste Byte.

Die im Master und im Slave festgelegte Slave-Adresse, in den Programmbeispielen 0x78, modifizieren die o.g. TWI_MasterWrite und TWI_MasterRead passend zur Datenrichtung (Write, "W" bzw. Read ("R") entsprechend der I2C-Spezifikation:

  • TWI_MasterWrite, Master schreibt: Das R/W-Bit ist 0 ("W"),
  • TWI_MasterRead, Master liest: Das R/W-Bit ist 1 ("R").

I2C-Adressen sind maximal 7 Bit lang. Das R/W-Bit ergänzt das Adress-Byte als Bit 0 (LSB, Last significant Bit)). Wenn, wie hier, die Slave-Adresse gerade ist, bringt eine Schreib-(W-)Adresse 0x78 = B0111_1000 ein LSB = 0 schon mit. Die daraus abgeleitete Lese-(R-)Adresse mit LSB = 1 ist dann B0111_1001 = 0x79. Man könnte auch die Slave-Adresse im Adress-Byte um eine Position nach links schieben oder mit 2 multiplizieren, was aus's Gleiche hinausläuft, um das LSB frei zu bekommen. Sparen wir uns.

Mit dieser Schaltung wurden alle nachfolgenden Anwendungen realisiert.

Master-Slave schematics

Referenzen
[1]  https://www.mikrocontroller.net/topic/249639
[2]  https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en591794
[3]  https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en591792

 

1 Master mit BASCOM an stummen Slave

1 Master mit BASCOM an stummen Slave DL6GL Di., 28.01.2020 - 09:13

Dies ist der ursprüngliche Artikel von September 2016. Im Slave wurde ein erster bescheidener Ansatz mit TWI-Registern in einer Interrupt-Routine versucht. Der Master verwendet die von BASCOM bekannten I2C-Befehle.

Um zwei AVR-Controller über I2C/TWI zu koppeln, kann man innerhalb einer Do...Loop empfangene Zeichen pollen, wie bei der GLCD-Kopplung beschrieben, oder den TWI-Interrupt verwenden. Der Empfang mit dem TWI-Interrupt soll hier beschrieben werden. Da ein Interrupt unmittelbar mit dem Eintreten des auslösenden Ereignisses und nur dann bearbeitet wird, hier mit dem Empfang eines jeden Zeichens, ist dies die zweckmäßigere Methode.

Es geht hier um eine Master-Slave-Konfiguration mit einem Master, der Daten sendet und den Zugriff auf den I2C-Bus kontrolliert, und um einen oder auch mehrere Slaves, die die Master-Daten empfangen. Ein Slave-Controller kann im hier gezeigten Beispiel nicht eigenständig dem Master Daten übermitteln. Das würde eine Multi-Master-Software erfordern, die den Verkehr dann eigenständig agierender Master-Controller auf dem Bus regelt und auftretende Konflikte auflöst. Eine komplizierte Sache...

Nachtrag vom 20.03.2019: Ein kompletter Satz Master-Slave ist dem Download zugefügt. Die Pin-Belegungen für SDA/SCL und Test-Displays sind im Quellcode beschrieben.

Hier einige Code-Snippets dazu, zunächst der Erklärungsteil im TWI-Slave.

$regfile = "m48def.dat"                           'Controller ATmega48
$crystal = 14745600                               '14.7456 MHz xtal
$hwstack = 40
$swstack = 40
$framesize = 40
Config SDA = PortC.4                              'TWI SDA
Config SCL = PortC.5                              'TWI SCL

Bei der standardmäßigen Softwareemulation von I2C/TWI mit BASCOM können SDA und SCL auf wählbare Ports gelegt werden. Das ist im Masterprogramm (s.u.) möglich. Da hier aber auf die TWI-Register zugegriffen wird, müssen die für I2C/TWI vorgesehenen Ports des ATmega48, PC.4 (ADC4, SDA, PCINT12) und PC.5 (ADC5, SCL, PCINT13), verwendet werden. Bei anderen Slave-Controllern das Datenblatt konsultieren. Das sonst bei der Verwendung des TWI Hardwaremoduls notwendige Einbinden der i2c_twi.lbx entfällt hier. Wir befummeln die TWI-Register auf direktem Wege. Die gleichen TWI-Portzuordnungen haben z.B. ATmega88, 168 und 328, etwa auch die Arduino NANO und UNO.

'Variables TWI-Communication  -------------------------------------------------
Const bytTWIAdr = &H78                            'TWI Slave address
Dim bytTWICount As Byte                           'TWI receive buffer byte counter
Dim bytTWIcontrol As Byte                         'TWI Control register data
Dim bytTWIstatus As Byte                          'TWI status
'Subs & Functions -------------------------------------------------------------
Declare Sub TWISlaveInit

"bytTWIAdr" legt die Adresse des Slave-Controllers fest, hier 78hex. Mit dieser Adresse spricht der Master den Slave an.
Die restlichen Variablen werden in der Interruptroutine (s.u.) verwendet.

Da die Übertragung byteweise erfolgt, ist es zweckmäßig, Byte-Overlays zu der vom Master gesendeten und hier vom Slave auszulesenden Meldung zu verwenden. Im Fall einer 2 Byte langen Word-Variablen:

Const bytTWILen = 2                               'Length of TRX frequency message
Dim wrdFreq As Word                               'TRX frequency (kHz)
Dim bytFreq(2) As Byte At wrdFreq Overlay         'Hi / lo byte of wrdFreq

Mit voller Frequenzauflösung in Hz bräuchte man eine 4 Byte lange Long- oder DWord-Variable, "2" also jeweils durch "4" ersetzen.
Soll ein Textstring, z.B. der Länge 12 übertragen werden, sähe das so aus:

Const bytTWILen = 12                              'Length of Text message
Dim strMsg As String*12                           'Text message
Dim bytMsg(12) As Byte At strMsg Overlay          'strMsg as single bytes

strMsg belegt zwar intern einschließlich eines Null-Bytes am Ende 13 Bytes, das Null-Byte wird aber nicht übertragen, also reichen 12 Bytes für bytMsg.

Nun müssen wir den TWI-Interrupt definieren:

'Enable TWI interrupt service routine -----------------------------------------
On TWI TWI_isr                                    'Go to TWI_isr if character is received
Call TWISlaveInit                                 'Initialize TWI slave
Enable TWI                                        'Enable TWI receive interrupt
SREG.7 =                                          'Enable all interrupts

Die Initialisierung als I2C-Slave erfolgt in der Sub TWISlaveInit. Die TWI-Registerbezeichnungen und die weiter unten in der "TWI_isr" verwendeten Statuscodes findet man im jeweiligen AVR Data sheet.

Sub TWISlaveInit
'Initialize TWI slave function
'Input:
'bytTWIAdr  TWI slave address

   TWSR = 0                                       'Clear TWI status register
   TWBR = 0                                       'Clear baud rate register
   TWDR = &B11111111                              'TWI data register initial value
   TWAR = bytTWIAdr                               'TWI slave address
   TWCR = &B01000100                              'TWI control register
   '                                              'Bit 2: TWEN, enable TWI
   '                                              'Bit 6: TWEA, enable Ack
End Sub

Mit der hier gesetzten Slave-Adresse bytTWIAdr hebt dieser Slave nur dann den Hörer ab, wenn der Master diese am Anfang des Übertragungsprotokolls auf den Bus schickt, siehe unten.

Fehlt noch die Interrupt Serviceroutine "TWI_isr":

TWI_isr:
'TWI receive interrupt service routine
'For TWI registers and status codes see ATmega data sheet
'Output:
'wrdFreq TRX frequency in kHz, overlayed with bytFreq(1...2)

      bytTWIstatus = TWSR And &B11111000          'Status register, mask TWI status=bits 7...3
      If bytTWIstatus = &HA0 Then                 'Stop or repeated start condition
         bytTWICount = 0                          'Initialize byte counter
      ElseIf bytTWIstatus = &H60 Or bytTWIstatus = &H68 Then       'Control data byte received
         bytTWICount = 0                          'Initialize byte counter
      Elseif bytTWIstatus = &H80 Or bytTWIstatus = &H88 Then       'Data received
         Incr bytTWICount                         'Increment byte counter
         bytFreq(bytTWICount) = TWDR              'Read byte from TWI data register
      End If
      TWCR = &B11000101                           'Set TWI Control register
                                                  'Bit 0: TWIE , enable TWI interrupt
                                                  'Bit 2: TTWEN, TWI enable
                                                  'Bit 6: TWAE , TWI enable acknowledge bit
                                                  'Bit 7: TWINT, TWI interrupt flag
Return

Für die o.a. Text-Variante müsste in der blau markierten Zeile statt
bytFreq(bytTWICount) = TWDR → bytMsg(bytTWICount) = TWDR
geschrieben werden.

Die "Nutzlast", also die zu übertragenden Nettoinhalte ohne die Steuerzeichen werden nur dann aus dem Data Register TWDR übernommen, wenn der Status 80 oder 88hex ist.

Damit ist die zeitkritische Übernahme der vom TWI-Master gesendeten Daten erledigt. Die Auswertung kann nun in einer Do...Loop vorgenommen werden, etwa so

Do
   If bytTWICount = bytTWILen Then                'Message received complete
      'Do someting usefull with the received data
      Lowerline
      LCD wrdFreq
      'or
      Upperline
      LCD strMsg
      bytTWICount = 0
   End If
Loop

Nun die Masterseite. Die ist regelrecht langweilig, z.B.

Config SDA = PortB.1
Config SCL = PortB.2
Const bytSLVAdr = &H78                            'TWI Slave address

Const bytTWILen = 2                               'Length of TRX TWI frequency message
Dim wrdFreq As Word                               'TRX frequency (kHz)
Dim bytFreq(2) As Byte At wrdFreq Overlay         'Hi / lo byte of wrdFreq

wrdFreq = 3520                                    '3520 kHz = 3.520 MHz

I2CStart
   I2CWByte bytSLVAdr                             'Slave address
   I2CWByte bytFreq(1)                            'Send Hi frequency byte
   I2CWByte bytFreq(2)                            'Send Lo frequency byte
I2CStop

oder so:

Config SDA = PortB.1
Config SCL = PortB.2
Const bytSLVAdr = &H78                            'TWI Slave address

Const bytTWILen = 12                              'Length of Text message
Dim strMsg As String*12                           'Text message
Dim bytMsg(12) As Byte At strMsg Overlay          'strMsg as single bytes
Dim bytTmp0 As Byte

strMsg = "Hello world!"

I2CStart
   I2CWByte bytSLVAdr                             'Slave address
   For bytTmp0 = 1 To bytTWILen
      I2CWByte bytMsg(bytTmp0)                    'Send byte after byte
   Next bytTmp0
I2CStop

Ein Schnelltest am Anfang des Master-Programms könnte noch anzeigen, ob der Slave überhaupt ansprechbar ist.

Function CheckTWI(byVal bytAdr As Byte) As Byte
'Check TWI chip @ slave address bytAdr
'CheckTWI = 1: OK, 0: else

   I2CStart
      I2CwByte bytAdr                             'Try to write I2C address
   I2CStop
   If Err = 1 Then                                'Error raised
      CheckTWI = 0                                'Not OK
   Else
      CheckTWI = 1                                'Is OK
   End If
End Function

Alternativ könnte man das TWI-Statusregister auswerten, etwa so:

Function CheckI2C(byVal bytAddr As Byte) As Byte
'Check I2C chip @ address bytAddr
'CheckI2C = 1: OK, 0 or 2: else
'Success is checked with TWI status register TWSR
'Status code is stored in the 5 upper bits, masked out with "And &B11111000"
'Write status code:
'&H08:   Start command was sent
'&H10:   Repeated start command was sent
'&H18:   Slave address (send condition) was sent, ACK received
'&H20:   Slave address (send condition) was sent, NACK received

   I2cstart
      bytTWI_Status = TWSR And &B11111000         'Start status code
      If bytTWI_Status = &H08 Or bytTWI_Status = &H10 Then       'Start condition is set
         I2cwbyte bytAddr                         'Send Slave address
         bytTWI_Status = TWSR And &B11111000      'Slave address write status code
         If bytTWI_Status = &H18 Then             'Slave answered with ACK
            CheckI2C = 1                          'Slave found
         Else
            CheckI2C = 0                          'Slave not found
         End If
      Else
         CheckI2C = 2                             'Start error
      End If
   I2cstop

End Function

Hier werden mögliche Fehler bei der Ausführung von I2Cstart und I2Cwbyte (Ansprechen des Slave) unterschieden.

2 Master mit TWI an stummen Slave

2 Master mit TWI an stummen Slave DL6GL Di., 28.01.2020 - 09:16

Das Verhältnis zwischen Master und Slave im ersten Abschnitt ist ja nun recht einseitig. Nur der Master hat ein Mitteilungsbedürfnis. Der Slave reagiert nur mit verschämten ACK's oder NACK's, indem er mal eben kurz an der SDA-Leitung zupft oder auch nicht. Das erledigt die TWI-Hardware. Aus der Entwicklung der Software zur Steuerung eines Grafik-TFT-Shield war seinerzeit noch ein Wunsch übrig geblieben, Touch-Ereignisse vom TFT-Slave zurück an den Master zu geben.

Das Rad muss ja nicht ständig neu erfunden werden, daher wurde das Web befragt mit der Bedingung "BASCOM". Die BASCOM-Fraktion zeigte sich allerdings, gemessen an veröffentlichten Lösungen, arg bedeckt. Realisierungen in C waren aber reichlich zu finden, z.B. [1], [2], [3], auf denen die hier beschriebene Lösung basiert. Wohl oder übel musste ich mich mal in C reinknien. Für einen Physiker aus der 68'er-Generation, der aus der FORTRAN- und MS Visual Basic-Ecke kommt, war ein etwas dickeres Brett zu bohren. Nachdem insbesondere die anfangs schwer zu deutenden C-Operatoren einigermaßen verstanden waren, ließen sich die C-Quelltexte doch ziemlich flüssig lesen. Zugegeben, die C-Syntax ist elegant, für meinen Geschmack aber dann doch reichlich verdichtet, ja kryptisch. Egal wie, so nahm die BASCOM-Variante von [1], Version "TWI_USI_master_slave_c_120303.zip" (auf der Seite unten), so langsam Gestalt an.

Doch der Reihe nach: Zunächst sollte ein Äquivalent zu der Lösung in Abschnitt 1, also stummer Slave, gefunden werden, d.h. zentrale Aufgaben:

  • Ersetzen der BASCOM-Befehle wie I2CStart, I2CWByte, I2CStop im Master durch ein simples TWI_MasterWrite,
  • Bearbeitung der kompletten I2C-Kommunikation innerhalb einer Interrupt-Serviceroutine sowohl im Master als auch im Slave.

2.1       TWI-Register

Mit Verzicht auf die BASCOM-Standards müssen wir uns eingehender mit den TWI-Registern befassen. Die müssen wir nun - näher an der Hardware - hantieren. Neben TWAR (TWI Slave Address Register) und TWBR (TWI Bit Rate Register) sind die in den Interrupt-Serviceroutinen benutzten Register

  • TWCR (TWI Control Register)
Bit 7 6 5 4 3 2 1 0
Name TWINT TWEA TWSTA TWSTO TWWC TWEN - TWIE
Default 0 0 0 0 0 0 0 0
Zugriff R/W R/W R/W R/W R/W R/W R R/W
TWINT TWI Interrupt flag, Bit 7
1 Wird von der Hardware auf 1 gesetzt, wenn eine TWI-Operation beendet wurde.
Um eine neue Operation zu starten, muss TWINT mit TWINT=1 (ja 1) per Software gelöscht werden.
Wenn globale Interrupts mit SREG.7=1 aktiviert sind und TWIE=1 gesetzt ist, wird ein TWI-Interrupt ausgelöst, der in der Interrupt-Serviceroutine bearbeitet werden kann. Hier kann die Software entsprechend reagieren.
0 Eine TWI-Operation wurde gestartet, ist aber noch nicht beendet.
TWEA TWI Enable Acknoledge Bit, Bit 6
1 Die Hardware sendet ein ACK (acknoledged) nachdem Daten angekommen sind.
0 Die Hardware sendet ein NACK (not acknoledged) nachdem Daten angekommen sind.
Daten können in beiden Fällen die Slave-Adresse oder Nutzdaten sein.
TWSTA TWI START Condition Bit, Bit 5
1 Der Master reserviert sich den Bus, ggf. wartet er, bis der Bus durch ein STOP
freigegeben wurde. Danach wird erneut ein START abgesetzt.
0 Wenn das START-Bit abgeschickt wurde, muss es per Software wieder zurückgesetzt werden.
TWSTO TWI STOP Condition Bit, Bit 4
1 Der Master gibt den Bus frei (SDA und SCL gehen auf high (+VCC).
0 Die Hardware setzt TWSTO nach Senden einer STOP-Bedingung automatisch zurück.
TWWC TWI Write Collision Flag, Bit 3
1 Die Hardware erkennt eine Kollision auf dem Bus, wenn versucht wird, in das Datenregister TWDR Daten zu schreiben und TWINT=0 ist (noch offene Operation).
0 Die Hardware löscht TWWC automatisch, wenn in das Datenregister TWDR Daten geschrieben werden und TWINT=1 ist (keine noch offene Operation).
TWEN TWI Enable Bit, Bit 2
1 TWI ist aktiviert. Damit werden die Mehrfachfunktionen zweier spezifizierter I/O-Pins, z.B. beim ATmega16 PC.0 und PC.1, auf I2C/TWI umgeschaltet, d.h. diese Pins werden mit dem AVR-internen  TWI-Interface verbunden.
0 TWI ist deaktiviert.
Bit 1 Reserviert, nur Lesezugriff. Wird grundsätzlich auf 0 gesetzt.
TWIE TWI Interrupt enable Bit, Bit 0
1 TWI-Interrupt ist aktiviert. Wenn Interrupts global aktiviert (SREG.7=1) und TWINT = 1 sind, wird ein Interrupt Request ausgelöst. Damit kann eine Interrupt-Serviceroutine (TWI_isr) angesprungen werden.
0 Kein Interrupt.
  •  TWSR (TWI Status Register)
  Das 8 Bit breite TWI Status Register hat drei Abschnitte:
1 Bits 7 bis 3, TWS7…TWS3 als eigentliche Status-Bits.
Hieraus werden die Status-Codes einer vorangegangenen Operation ermittelt.
Mit Ausmaskieren dieser 5 Bits erhält man den Status-Code in Hex. Diese sind in den Datenblättern angegeben für Master Transmit, Master Receive, Slave Transmit und Slave Receive. In den Interrupt-Serviceroutinen TWI_isr in den Beispielen werden die verwendeten Hex-Codes gezeigt.
2 Bit 2 ist reserviert. Wird immer 0 gesetzt.
3 Bits 1 und 0: TWPS (TWI Prescaler Bits), Vorteiler für die Busgeschwindigkeit, siehe Datenblatt. In den Programmen hier sind TWPS1 = TWPS0 = 0, also Vorteiler 1.
  • TWDR (TWI Data Register)
  Enthält das zu sendende oder empfangene Datenbyte.
  Schreiben: TWDR = DataByte
  Lesen: DataByte = TWDR

 2.2  Das Master-Programm

Ein "Config SDA" und "…SCL" in BASCOM ist schnell hingeschrieben. Die direkte Benutzung der TWI-Register zur Initialisierung ist aber auch nicht aufwendiger. Es müssen die jeweils vorgesehenen SDA- und SCL-Pins verwendet werden, beim ATmega 16 im Master also Port C.1 (SDA ) und Port C.0 (SCL).

Sub TWI_MasterInit
'Configure TWI on dedicated controller SCL/SDA hardware ports -----------------
'For ATmega16: SCL = PortC.0 and SDA = PortC.1, no "Config" necessary

   TWBR = 72                                      'Bit rate register, 100kHz SCL clock
   'TWBR = 32                                      'Bit rate register, 200kHz SCL clock
   'TWBR = 12                                      'Bit rate register, 400kHz SCL clock
   TWDR = &HFF                                    'Default data
   TWSR = &B0000_0000                             'Clear status register, prescaler 1
   'Enable TWI, nothing else
   TWCR = &B0000_0100                             'Enable TWI, TWEN is set

End Sub

Die Formel zur Berechnung des TWBR ist im Datenblatt zu finden. Hier sind die Prescaler TWPS=0. Zunächst wird nur das TWI mit TWCR.TWIE (Bit 2) aktiviert.

Im Hauptprogramm werden mit …

'Enable TWI interrupt service routine -----------------------------------------
On TWI TWI_isr                                    'Go to TWI_isr on TWI events
Call TWI_MasterInit                               'Initialize TWI master
SREG.7 = 1                                        'Enable all interrupts

… die Interrupts und die Interrupt-Serviceroutine komplett aktiviert.

Für unseren Test werden fortlaufend jeweils 3 Datenbytes an den Slave geschickt. Das entspricht dann der ersten Zeile im Aufmacherbild oben. Die erste Zahl dort ist die Slave-Adresse in hex.

Do
   For bytRun = 1 To 5
      'Set up user defined message buffer for test ----------------------------
      'bytMsg_Buf(1) = slave address, set in TWI_MasterWrite
      bytMsg_Buf(2) = bytRun
      bytMsg_Buf(3) = bytRun + 1
      bytMsg_Buf(4) = bytRun + 2
      Call TWI_MasterWrite(4 , bytSLV_Addr)       'Send data to slave
      Locate 1 , 1
      LCD "Out " ; Hex(bytMsg_Buf(1)) ; " " ; bytMsg_Buf(2) ; " " ; bytMsg_Buf(3) ; " " ; bytMsg_Buf(4)
      Wait 3
   Next bytRun
Loop

Die Sub TWI_MasterWrite nimmt uns alle Arbeit ab. Wir müssen nur das zu sendende Byte-Array bytMsg_Buf wie oben mit Daten besetzen.

Sub TWI_MasterWrite(byVal bytMsg_Size As Byte , byVal bytSLA As Byte)
'Write a message aray bytMsg_Buf to slave, address bytSLA, via TWI_isr.
'Input:  bytMsg_Buf     Message array to send
'        bytMsg_Size    Number of message bytes incl. slave address (bytMsg_Buf(1))
'        bytSLA         Slave Address
'Output: bytTWI_Success =FALSE before beeing transmitted via TWI_isr
'        bytTWI_Buf     Data buffer, copy of bytMsg_Buf to be processed in TWI_isr

   Call TWI_Ready                                 'Wait while TWI is busy
   bytSLV_AddrW = bytSLA And &B1111_1110          'slave address + Write bit 0=0
   bytMsg_Buf(1) = bytSLV_AddrW                   'Slave write address
   bytTWI_BufLen = bytMsg_Size                    'Handled in TWI_isr
   For bytMsg_Cnt = 1 To bytMsg_Size              'Loop data bytes...
      bytTWI_Buf(bytMsg_Cnt) = bytMsg_Buf(bytMsg_Cnt)       '...into Transmit buffer
      bytTWI_Success = False                      'To be set True in TWI_isr

      'Enable TWI & TWI interrupt, set START condition
      TWCR = &B1010_0101                          'TWINT, TWSTA, TWEN, TWIE are set
      'Now TWI_isr starts runing, transmit data to slave...
   Next bytMsg_Cnt

End Sub

Nachdem die Slave-Write-Adresse in bytMsg_Buf(1) geschrieben ist und das Datenarray bytMsg_Buf in das Array bytTWI_Buf zur Bearbeitung in der Interrupt-Serviceroutine umgespeichert ist, rennt diese mit dem Befehl TWCR = &B1010_0101 los. TWINT=1 (Bit 7) löst das Ereignis aus; Mit TWSTA=1 (Bit 5) wird ein  START abgesetzt, TWEN=1 (Bit 2) aktiviert TWI, TWIE=1 (Bit 0) aktiviert den TWI-Interrupt.

Unzweckmäßig ist es hier jedoch, den TWI-Befehl "TWCR = &B1010_0101" in der For...Next-Schleife anzuordnen. Nach der Schleife, die zunächst den Puffer bytTWI_Buf für die TWI_isr füllt, wäre er besser aufgehoben so wie in der Sub TWI_MasterWrite in Abschnitt 3.

Ein Ausschnitt aus der Interrupt-Serviceroutine TWI_isr zeigt, was diese damit anfängt.

   bytTWI_Status = TWSR And &B1111_1000           'Status, TWSR status=bits 2...0

   Select Case bytTWI_Status

      'START (0x08) has been transmitted
      Case &H08                                   'Start condition
         GoTo ISR1                                'I don't like GoTo's

      'Repeated START (0x10) has been transmitted
      Case &H10                                   'Repeated Start condition
ISR1:                                             'From here &H08 or &H10
         bytTWI_BufCnt = 1                        'Buffer counter for slave address
         GoTo ISR2

      'Master transmitter mode ------------------------------------------------
      'TWINT is cleared in any case (TWINT=1) to continue operation.

      'SLA+W has been transmitted; ACK has been received     (0x18)
      'Byte to be sent is slave address SLA+W (bytTWI_BufCnt=1)or data byte
      Case &H18                                   'SLA+W has been transmitted
ISR2:
         GoTo ISR3

      'Data byte has been transmitted; ACK has been received (0x28)
      Case &H28                                   'Data byte has been transmitted
ISR3:                                             'From here &H18 or &H28
         If bytTWI_BufCnt <= bytTWI_BufLen Then   'More bytes to be sent
            TWDR = bytTWI_Buf(bytTWI_BufCnt)      'Put it into TWDR register to send
            Incr bytTWI_BufCnt                    'Next byte to send
            'Enable TWI and TWI interrupt, clear TWINT to continue
            TWCR = &B1000_0101                    'TWINT, TWEN, TWIE are set
         Else                                     'Was the last byte, send STOP
            bytTWI_Success = True                 'Message completed
            'Enable TWI, disable TWI interrupt, set STOP condition, clear TWINT
            TWCR = &B1001_0100                    'TWINT, TWSTO, TWEN, are set
         End If
...

   End Select

Es zeigte sich eine richtig nette Eigenart von C. Dort werden im "Switch" verschiedene Einstiegspunkte "Case xx" hintereinander abgearbeitet solange, bis ein "break" einen Ausstieg veranlasst. "Switch" entspricht dem BASCOM "Select Case" mit dem Unterschied, dass immer nur ein "Case xx" bearbeitet wird. Hier mussten ein paar unschöne GoTo's herhalten, um die Case-Bedingungen ohne "break" nachzubilden. Einfacher und übersichtlicher erscheint mir dagegen das Setzen der TWCR-Bits wie gezeigt statt des Bit-Schiebens und bitweisen Verknüpfens in C.

  • In "Case &H10" (Repeated START) wird der Zählindex bytTWI_BufCnt mit 1 initialisiert.
     
  • In "Case &H18" (SLA+W has been transmitted) oder "Case &H28" (Data byte has been transmitted) wird mit "TWDR = bytTWI_Buf(bytTWI_BufCnt)" das aktuelle Datenbyte auf das Datenregister TWDR zum Absenden geschrieben. Die Anzahl zu sendender Datenbytes ist in "bytTWI_BufLen" definiert. Wenn die erreicht ist, wird mit "bytTWI_Success = True" der Vollzug gemeldet und anschließend mit "TWCR = &B1001_0100" ein STOP (Bit 4) veranlasst. Damit ist der Vorgang abgeschlossen und der Bus wieder frei. TWINT (Bit 7) wird in allen Fällen gesetzt, um den jeweiligen Vorgang auszulösen.
     
  • Zusammengefasst: Der Master schickt alle mit "bytTWI_BufLen" festgelegten Daten-Bytes einschließlich der Slave-Adresse aus dem Array "bytTWI_Buf" nacheinander an den Slave und beendet den Vorgang mit einem STOP, alles erledigt, und gibt damit den Bus wieder frei.

2.3       Das Slave-Programm

Das Slave-Programm hat gegenüber dem alten in Abschnitt 1 nicht viel Neues zu bieten bis auf das Gegenstück zum obigen TWI_MasterWrite, einer universellen Leseroutine TWI_SlaveRead, und eine erweiterte Interrupt-Serviceroutine.

Das Hauptprogramm ist schon etwas ärmlich:

Do
   Call TWI_SlaveRead                             'Collect data from master
Loop

Hier die Leseroutine:

Sub TWI_SlaveRead
'Copy TWI buffer data bytTWI_Buf_RX received in TWI_isr to message buffer bytMsg_Buf
'Input:  bytTWI_BufCnt_RX  Current receive buffer counter in TWI_isr (plus 1)
'                          TWI_isr increments counter for the next transfer
'                          if some data have been received
'        bytTWI_Buf_RX     Receive buffer counter
'Output: bytMsg_Buf        Copied message buffer

   While TWI_Busy() = True                        'Wait while TWI is busy
   Wend

   If bytTWI_Success = True Then                  'Last transmission complete
      bytTWI_BufLen = bytTWI_BufCnt_RX - 1        'Current received data number
      If bytTWI_BufLen > 0 Then
         For bytMsg_Cnt = 1 To bytTWI_BufLen      'Copy data from TWI buffer
            bytMsg_Buf(bytMsg_Cnt) = bytTWI_Buf_RX(bytMsg_Cnt)
         Next bytMsg_Cnt
      End If
   End If

   'TWI buffer data are now saved.
   'If you want to show received data make it here before
   'restarting TWI_isr with the subsequent statements.
   Call ShowReceivedData

   'Continue reception via TWI_isr
   bytTWI_BufCnt_RX = 1                           'Initialize buffer counter
   'Enable TWI and TWI interrupt and Acknoledge , clear TWINT to continue
   TWCR = &B1100_0101                             'TWINT, TWEA, TWEN, TWIE are set

End Sub

Der Aufbau ist vergleichbar mit der o.a. Sub TWI_MasterWrite. Zunächst muss mit "While TWI_Busy() = True" solange gewartet werden, bis der Master mit Schreiben fertig ist. Das erfolgt hier, indem geprüft wird, ob TWIE (Bit 0) im TWCR nun 0 geworden ist:

Function TWI_Busy() As Byte
'Check if TWI Bus is busy (TWI.isr is activated), i.e. TWCR.TWIE = 1
'So check TWCR.TWIE bit
'Input:  TWCR  TWI control register
'Output: TWI_Busy =1=True: Is busy, =0=False: Is not busy

   TWI_Busy = TWCR AND &B0000_0001                '=0 if TWIE=0, =1 if TWIE=1

End Function

Weiter mit TWI_SlaveRead: Mit "bytTWI_Success = True" meldet die TWI_isr, dass ein Datenpaket vom Master vollständig gelesen wurde. Nur wenn außer der Slave-Adresse noch Daten-Bytes gelesen wurden, wird der TWI-Lesepuffer auf den Output-Puffer bytMsg_Buf umgespeichert.

Bis hierher ist Ruhe auf dem Bus, so dass nun die empfangenen Daten mit der Routine ShowReceivedData auf LCD ausgegeben werden können. Das braucht ja einen Augenblick. Erst danach wird der Bus mit einem neuen TWCR wieder aktiviert.

Der Vollständigkeit halber noch der Ausschnitt aus der TWI_isr:

   bytTWI_Status = TWSR And &B1111_1000           'Status, TWSR status=bits 7...3

   'Slave reveive mode -----------------------------------------------------
   'Own SLA+W has been received or data byte has been transmitted

   Select Case bytTWI_Status

      'Own address SLA + W has been received, ACK returned (0x60)
      Case &H60
         bytTWI_BufCnt_RX = 1                     'Initialize receive byte counter
         bytTWI_Buf_RX(bytTWI_BufCnt_RX) = TWDR   'Slave address
         Incr bytTWI_BufCnt_RX                    'Next buffer position for data
         'Enable TWI, TWI interrupt and Acknoledge, clear TWINT
         TWCR = &B1100_0101                       'TWINT, TWEA, TWEN, TWIE are set

      'Previously addressed with own SLA+W, data received, ACK returned (0x80)
      Case &H80
         'Max. bytTWI_BufSize bytes will be received
         If bytTWI_BufCnt_RX < bytTWI_BufSize Then       'Below max buffer size
            bytTWI_Buf_RX(bytTWI_BufCnt_RX) = TWDR       'Received byte into input buffer
            Incr bytTWI_BufCnt_RX                 'Increment byte counter
         End If
         'Enable TWI, TWI interrupt and Acknoledge, clear TWINT
         TWCR = &B1100_0101                       'TWINT, TWEA, TWEN, TWIE are set

      'STOP or Repeated START condition has been received (0xA0)
      Case &HA0
         'Enable TWI, disable TWI interrupt and Acknoledge, clear TWINT
         bytTWI_Success = True                    'Operation successful
         TWCR = &B1000_0100                       'TWINT, TWEN are set
. . .
   End Select

Die Logik ist vergleichbar mit der Master TWI_isr, nur dass hier die Inhalte des Datenregisters TWDR gelesen und auf den Empfangspuffer bytTWI_Buf_RX geschrieben werden.

  • In "Case &H0" wird Lesevorgang eines Datenpakets vom Master dann als beendet erkannt, wenn dieser ein STOP gesetzt hat (siehe 2.2). Das wird mit "bytTWI_Success = True" quittiert, worauf TWI_SlaveRead reagiert.
     
  • Der Empfang wird daraufhin beendet, in dem in TWCR TWIE (Bit 0) auf 0 gesetzt wird (Interrupt aus).
     
  •  Zusammengefasst: Der Slave liest solange, bis der Master mit einem STOP dem ein Ende macht und den Bus wieder frei gibt.
     

Beide TWI_isr enthalten auch schon die Teile Master Receive bzw. Slave Transmit. Die werden im Folgeabschnitt behandelt.

3 Master mit TWI an gesprächigen Slave

3 Master mit TWI an gesprächigen Slave DL6GL Di., 28.01.2020 - 09:17

Nun das eigentliche Anliegen, Daten vom Slave abzuholen. Der Slave bleibt dabei immer noch unter der Fuchtel des Masters, er reagiert also nur auf eine Leseanforderung des Masters. Eine Multi-Master-Konfiguration, bei der ein oder mehrere Slaves auch frei über den Bus verfügen und sich als Master aufspielen können, ist das nicht. Um das Gerangel um die Bus-Hoheit aller Teilnehmer zu regeln, müsste man noch eine Schippe nachlegen.

Dieses Programm beherrscht beide Optionen: Nur Daten an den Slave wie in Abschnitt 2 senden als auch bei Bedarf Daten vom Slave anfordern.

Nicht ausprobiert, aber die hier vorgestellte Master-Software sollte auch mit passiven I2C-Bausteinen wie EEPROM, ADC und Port-Expander umgehen können.

Nicht verschwiegen sei, dass das in [1] beschriebene Verhalten beim Lesen von Daten aus dem Slave nicht ganz reproduziert werden konnte. Der vom Slave an den Master zu schickende Datensatz musste um das erste Byte, die Slave-Adresse, verkürzt werden, da der Master das erste Byte selbst schreibt. Einzelheiten in Abschnitt 3.2.

3.1       Das Master-Programm

Die Erweiterung des Master-Programms aus Abschnitt 2.2 beschränkt sich auf eine zusätzliche Routine TWI_MasterRead und entsprechende Funktionen in der TWI_isr.

Hier das Hauptprogramm:

Do
   For bytRun = 1 To 5
      'Set up user defined message buffer for test ----------------------------
      'bytMsg_Buf(1) = slave address, set in TWI_MasterWrite
      bytMsg_Buf(2) = bytRun
      bytMsg_Buf(3) = bytRun + 1
      bytMsg_Buf(4) = bytRun + 2
      Call TWI_MasterWrite(4 , bytSLV_Addr)       'Send data to slave
      Locate 1 , 1
      LCD "Out " ; Hex(bytMsg_Buf(1)) ; " " ; bytMsg_Buf(2) ; " " ; bytMsg_Buf(3) ; " " ; bytMsg_Buf(4)

      'Read data from slave ---------------------------------------------------
      Call TWI_MasterRead(4 , bytSLV_Addr)        'Receive data from slave
      Locate 2 , 1
      LCD "In  " ; Hex(bytMsg_Buf(1)) ; " " ; bytMsg_Buf(2) ; " " ; bytMsg_Buf(3) ; " " ; bytMsg_Buf(4)
      Wait 3
   Next bytRun
Loop

Neu gegenüber Abschnitt 2.2 ist nur der untere Teil mit dem TWI_MasterRead. Das sieht so aus:

Sub TWI_MasterRead(byVal bytMsg_Size As Byte , byVal bytSLA As Byte)
'Read a message aray bytMsg_Buf from slave, address bytSLA, via TWI_isr.
'First the slave read address is sent to slave.
'Then the slave transmits the requested data to master via TWI.
'TWI_isr puts these data into buffer bytTWI_Buf.
'bytTWI_Buf buffer data are sorted in this sub into output array bytMsg_Buf.
'Input:  bytTWI_Buf     Data buffer from TWI_isr
'        bytMsg_Size    Number of message bytes incl. slave address (bytMsg_Buf(1))
'        bytSLA         Slave Address
'Output: bytMsg_Buf     Message array. bytMsg_Buf(1) is slave address.
'        bytTWI_Success =FALSE before beeing transmitted via TWI_isr.

   Call TWI_Ready                                 'Wait while TWI is busy
   bytSLV_AddrR = bytSLA OR &B0000_0001           'slave address + Read bit 0=1
   bytTWI_Buf(1) = bytSLV_AddrR                   'Slave read address
   bytTWI_BufLen = bytMsg_Size                    'Handled in TWI_isr
   bytTWI_Success = False                         'To be set True in TWI_isr
   'Enable TWI & TWI interrupt, set START condition, clear TWINT
   TWCR = &B1010_0101                             'TWINT, TWSTA, TWEN, TWIE are set

   'Now TWI_isr starts runing, receive data from slave...
   'When receive operation is finished, TWIE is cleared, then TWSTO is set.
   'So TWI Stop is generated. If done, TWSTO is cleared as well.
   Waitms 10
   Call TWI_Ready                                 'Wait while TWI is busy
   If bytTWI_Success = True Then                  'Job finished, get data
      'Copy data from transceiver buffer bytTWI_Buf to message buffer bytMsg_Buf
      For bytMsg_Cnt = 1 To bytMsg_Size           'Loop data bytes...
         bytMsg_Buf(bytMsg_Cnt) = bytTWI_Buf(bytMsg_Cnt)       '...from Transmit buffer
      Next bytMsg_Cnt
   End If

End Sub

Gesetzt werden die Slave-Read-Adresse mit Bit 0 = 1 und die Anzahl bytTWI_BufLen der vom Slave zu sendenden Daten-Bytes. Bei sinnvolleren Programmen als dieses Testprogramm wird der Master die gewünschte Anzahl Daten und ggf. eine Auswahl von Daten, die der Slave bereitstellen kann, im Anforderungsprotokoll schicken.

Dann wird mit gesetzten Bits für START, TWEN, TWIE und wie immer TWINT die TWI_isr beauftragt, den Auftrag zu erledigen.

Mit "Call TWI_Ready" wird auf die Erledigung gewartet.

Sub TWI_Ready
'Wait until TWI is ready for next transmission
'TWI is ready (not busy) if TWIE (bit 0) and TWSTO (bit 4) are 0
'i.e. TWI has been stopped and no TWI interrupt is active
'Input:  TWCR  TWI control register
'Output: bytTWI_Busy

   bytTWI_Busy = TWCR AND &B0001_0001             'Check TWIE & TWSTO in TWCR
                                                  'Gives 0 if TWIE & TWSTO = 0
                                                  '>0 if TWIE or TWSTO = 1
   While bytTWI_Busy > 0                          'Wait while TWI is busy
   Wend

End Sub

Der Bus wird beobachtet. Er ist aktiv, solange TWIE oder TWSTO oder beide auf 1 gesetzt sind, aber wieder frei, wenn TWIE und TWSTO Null geworden sind, also der TWI Interrupt nicht mehr aktiv ist und ein STOP gesetzt worden ist (dann hat die Hardware TWSTO wieder auf Null gesetzt).

Wenn dann auch die TWI_isr den vollständigen Empfang eines Datenpakets vom Slave mit "bytTWI_Success = True" vermeldet, werden die im Empfangspuffer bytTWI_Buf gesammelten Bytes in den Puffer bytMsg_Buf umgespeichert. Genauso wie in Abschnitt 2.2.

In der Do…Loop des Masterprogramms wird dann bytMsg_Buf auf dem LCD angezeigt, im Aufmacherbild oben in der zweiten Zeile. Der Slave hat zu den vom Master erhaltenen Bytes jeweils eine 1 addiert, bevor er sie zurück geschickt hat.

Hier noch der Ausschnitt aus der TWI_isr, der den Datenempfang von Slave regelt.

   bytTWI_Status = TWSR And &B1111_1000           'Status, TWSR status=bits 2...0
   Select Case bytTWI_Status
. . .
      'Master receiver mode ---------------------------------------------------
      'Master receives specified number of bytes bytTWI_BufLen.
      'Last byte is returned with NACK, then STOP.
      'TWINT is cleared in any case (TWINT=1) to continue operation.

      'Data byte has been received,  ACK has been returned (0x50)
      Case &H50
         Incr bytTWI_BufCnt                       'Next byte to receive
         bytTWI_Buf(bytTWI_BufCnt) = TWDR         'Read it from TWDR
         GoTo ISR4

      'SLA+R has been transmitted; ACK has been received   (0x40)
      Case &H40
ISR4:                                             'From here &H40 Or &H50
         If bytTWI_BufCnt < bytTWI_BufLen Then    'Is not the last byte, next one
            'Enable TWI and TWI interrupt, clear TWINT to continue
            TWCR = &B1100_0101                    'TWINT, TWEA, TWEN, TWIE are set
         Else                                     'Last byte, send NACK after reception
            'Enable TWI and TWI interrupt, disable TWEA (Not ACK), clear TWINT
            TWCR = &B1000_0101                    'TWINT, TWEN, TWIE are set
         End If

      'Data byte has been received, NACK has been returned (0x58)
      Case &H58
         Incr bytTWI_BufCnt                       'Last byte to receive
         bytTWI_Buf(bytTWI_BufCnt) = TWDR
         bytTWI_Success = True                    'Message completed
         'Enable TWI, disable TWI interrupt, set STOP condition, clear TWINT
         TWCR = &B1001_0100                       'TWINT, TWSTO, TWIE are set

      'All other cases: disable TWI interrupt, send Stop condition
      Case Else
         'Enable TWI, disable TWI interrupt, set STOP condition, clear TWINT
         TWCR = &B1001_0100                       'TWINT, TWSTO, TWEN are set

   End Select
  • Die Prozedur zum Lesen von Bytes ist ähnlich wie beim Slave in Abschnitt 2.3 bis auf die jeweils anderen Status-Codes für den Master Receive-Mode.
     
  • In "Case &H50" werden unentwegt Daten-Bytes eingelesen, wenn sie mit ACK vom Slave gekommen sind.
     
  • In "Case &H40" wird geprüft, ob es das letzte erwartete Byte war. Wenn noch nicht, wird mit ACK geantwortet. Wenn es das letzte Byte ist, signalisiert der Master das Ende der Übertragung des Datenpakets mit einem NACK zum letzten Datenbyte.
     

  • "Case &H58" setzt der Master darauf mit dem letzten Byte mit NACK vom Slave ein STOP, womit dieser Vorgang abgeschlossen ist.
     

  • Zusammengefasst: Der Master liest solange Daten vom Slave rein, bis er mit dem letzten erwarteten Byte dem Slave mit einem NACK zu verstehen gibt, Schluss zu machen. Nach dessen Bestätigung von Slave beendet er die Sache. Mit einem STOP gibt es den Bus wieder frei.

3.2       Das Slave-Programm

Das Hauptprogramm ist gegenüber Abschnitt 2.3 etwas fülliger:

Do

   bytTWI_DataRecv = TWI_BytesRecv()              'Bytes received
   If bytTWI_DataRecv > 0 Then
      'Save buffer bytTWI_Buf_RX received from Master to buffer bytMsg_Buf
      Call TWI_SlaveRead
      Select Case bytTWI_DataRecv
         Case 1                                   'Only SLA+R received

         Case Else                                'SLA+R and data received
            'Sending back data with SLA at the first position (0 in Ref. 1, 1 here)
            'and data at the subsequent positions could not be reproduced here.
            'Master received record is SLA+R from Master Read START condition @ 1,
            'SLA+R from Slave transmit @ 2 (which is bullshit)
            'and subsequent data from Slave transmit @ 3...
            'So data to be transmitted by Slave are shifted one position back
            'eliminating above SLA+R from Slave transmit @ 2
            'Data sent back start from address 1 (not 2!)
            'Change received data (add 1 for test)...
            For bytMsg_Cnt = 2 To bytTWI_DataRecv
               bytTmp0 = bytMsg_Cnt - 1
               bytMsg_Buf(bytTmp0) = bytMsg_Buf(bytMsg_Cnt) + 1
            Next bytMsg_Cnt

            'This version taken from Ref. 1 does not work
            'For bytMsg_Cnt = 1 To bytTWI_DataRecv
            '   bytMsg_Buf(bytMsg_Cnt) = bytMsg_Buf(bytMsg_Cnt) + 1
            'Next bytMsg_Cnt

            '... and send them back to master
            'Put changed data back into TWI TX buffer
            Call TWI_SlaveWrite(bytTWI_DataRecv)
      End Select

   End If

Loop

Die Funktion TWI_BytesRecv() stellt fest, ob Daten vom Master und wie viele empfangen wurden. Nachdem in TWI_SlaveRead ein Datenpaket vom Master aufgenommen wurde, wird geprüft, ob neben der Slave-Adresse auch noch Nutzdaten empfangen wurden. Wenn ja, werden diese zur Unterscheidung jeweils um 1 erhöht und ab Position 1 (!) zurück in den Datenpuffer bytMsg_Buf geschrieben. Dieser unschöne Symmetriebruch - Arrayposition 1 war der Slave-Adresse vorbehalten - passt nicht zur Beschreibung in [1]. Das Problem liegt allerdings in der Master -TWI_isr. Hier wird mit der Start-Bedingung zum Lesen vom Slave die Slave-Adresse schon auf Position 1 gespeichert. Eine weitere Slave-Adresse im Übertragungsprotokoll vom Slave ist dann eine zu viel. Daher sendet der Slave nur die Nutzdaten ab Arrayposition 1. Eine Möglichkeit, diese Unkonsistenz zu beseitigen, habe ich (noch) nicht gefunden.

Die Routine TWI_SlaveWrite schreibt die Daten zurück in den Puffer bytTWI_Buf, den die TWI_isr nun zurück an den Master schickt. Der Ausschnitt aus der TWI_isr zeigt, wie diese das macht:

   bytTWI_Status = TWSR And &B1111_1000           'Status, TWSR status=bits 7...3
   Select Case bytTWI_Status
. . .
      'Slave transmit mode ----------------------------------------------------
      'Data are transmitted until master answers with a NACK

      'I admit, the C language "switch" statement with or without "break" is elegant,
      'so you can enter a "case" and continue with some more "case" up to the next "break"
      'The BASCOM "Select case" jumps out after any "case", i.e. makes a "break".
      'So, though I don't like, some GoTo's must be used to realize the "no break".

      'Own address SLA + R has been transmitted, ACK returned (0xA8)
      Case &HA8
         bytTWI_BufCnt_TX = 0                     'Initialize byte counter
         GoTo ISR1                                'I don't like GoTo's

      'Data byte in TWDR has been transmitted, ACK returned (0xB8)
      Case &HB8
ISR1:
         'Data are sent until master stops with NACK
         'Take care in master software not to extend buffer size!
         If bytTWI_BufCnt_TX < bytTWI_BufSize Then       'Below max buffer size
            Incr bytTWI_BufCnt_TX                 'Increment byte counter
            TWDR = bytTWI_Buf_TX(bytTWI_BufCnt_TX)       'Send 1 byte from output buffer
         End If
         'Enable TWI, TWI interrupt and Acknoledge, clear TWINT
         TWCR = &B1100_0101                       'TWINT, TWEA, TWEN, TWIE are set

      'Data byte has been transmitted, NACK returned (0xC0), end of transmission
      Case &HC0
         'Enable TWI, TWI interrupt and Acknoledge, clear TWINT
         TWCR = &B1100_0101                       'TWINT, TWEA, TWEN, TWIE are set

      'All other cases: disable TWI interrupt and Acknoledge, clear TWINT
      Case Else
         'Enable TWI, disable TWI interrupt and Acknoledge, clear TWINT
         TWCR = &B1000_0100                       'TWINT, TWEN are set

   End Select
  • Die Funktionen des Slave Transmit Mode sind bis auf unterschiedliche Statuscodes vergleichbar mit denen des Master Transmit Mode in Abschnitt 2.2.
     
  • In "CaseB8" werden unentwegt Daten-Bytes geschrieben, solange der Master mit ACK antwortet.
     
  • Die Übertragung läuft solange, bis in "Case &HC0" das NACK vom Master registriert wird (siehe 3.1). Das wird mit ACK bestätigt.
     
  • Zusammengefasst: der Slave sendet solange Daten, bis der Master mit dem letzten erwarteten Byte ein NACK erhält. Der Master beendet daraufhin mit einem STOP die Sache. Der Bus ist wieder frei.
     
  • Das Ergebnis ist dann in der zweiten Zeile des Aufmacherbildes oben zu sehen.

Dem Autor Michael S., der volle Name wurde in [1] nicht angegeben, danke ich für die nach einigen Mühen auch für mich verständliche Adaption der Microchip/Atmel-Programme in [2] und [3]. Nun auch als C-Jünger bekehrt hat er mich nicht Ich bleibe bei BASCOM. Die C-Programmierer kochen auch nur mit Wasser, obschon elegant so in Zeichensprache verdichtet, dass aufmerksames Lesen unerlässlich ist. Für einen Compiler kein Problem, für meine in die Jahre gekommenen grauen Zellen aber schon ein wenig...

4 Downloads

4 Downloads DL6GL Di., 28.01.2020 - 09:18

Hier sind noch die oben beschriebenen Testprogramme:

  • I2C_Master_Slave.zip: Software von 2016 zu Abschnitt 1.
     
  • TWI_Master_Slave_102.zip: Software zu Abschnitten 2 und 3.
    Beigefügt sind noch die TWI Master- und Slave-Status-Codes zur Information.
    Das in [1] beschriebene Verhalten beim Master Read ließ sich nicht reproduzieren. In der vorläufig letzten Version 1.02 blieb es dann dabei, dass der Datensatz vom Slave an den Master um das erste Byte verkürzt werden muss. Die Nutzdaten werden also ab Byteposition 1 an den Master geschickt.

Sie sind als Werkzeugkasten für eigene Anwendungen zu verstehen.