Amateurfunk verbindet die Welt

1 Master mit BASCOM an stummen Slave

Erstellt: DL6GL, 26.09.2016, letzte Änderung 27.01.2020

« AVR-Kopplung über I2C
TOP » 2 Master mit TWI an stummen Slave

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.

« AVR-Kopplung über I2C
TOP » 2 Master mit TWI an stummen Slave