AVR-Kopplung über I2C

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.

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, hier zufällig auf die dafür auch vorgesehenen Ports PC.4 (ADC4, SDA, PCINT12) und PC.5 (ADC5, SCL, PCINT13), da sie für das Platinenlayout günstig lagen. Das Einbinden der i2c_twi.lib wäre bei Verwendung des TWI Hardwaremoduls eines AVR erforderlich, wobei dann aber tatsächlich die jeweiligen SDA/SCL-Ports zu benutzen wären. Bei dieser einfachen Ein-Master - ggf. mehrere Slaves - Konfiguration bringt die Hardwareimplementierung keinen wirklichen Performance-Vorteil.

'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 = 1                                        '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

Einordung: