Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

 
(Siehe auch)
 
(12 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
=Bascom Software I2C Routinen=
+
Im Zusammenhang für das Projekt "RN-Netzwerk", wo es ja darum geht, PC und Avr zu verbinden, ist ja natürlich auch eine Integration des I2C-Bus unbedingt erforderlich. Da ich mit einer RNBFRA 1.2 Karte gesegnet bin, sollte natürlich gerade der AT90S2313 (der Co-Prozessor) auch zum Mitspieler werden. Das bisherige "durchrouten" über die RS232 ist ja doch nicht sehr befriedigend.
  
 +
Über die Therorie und Praxis des [[I2C]] bzw. [[TWI]] -Bus gibt es hervorragende Artikel in RN-Wissen. Bitte dort nachzulesen, wenn irgendetwas unklar ist. 
  
 +
Und nochwas: Ich betrachte das als "Open-Source" Projekt. Wenn also jemand Hand anlegen möchte oder sonstwelche Ideen dazu hat, bitte um Rückmeldungen. Aber immer auch so, dass alle was davon haben. Also eventuell mit "Change-History" und Kommentaren.
  
==Beispiel 2313.bas==
+
=Bascom Software I2C Routinen=
  
<pre>
+
Das Ziel ist:
'----------------------------------------------
+
*Kommunikation auf der PC-Plattform via IP (Stream-Sockets),
'  2313 oder was anderes als I2C Soft-Slave
+
*vom PC zum Atmega32 über COMx und AVR-UART,
'----------------------------------------------
+
*und auf der Mikrocontroller-Ebene dann über I2C, (später ev. auch andere Busse)    
'   PicNick was here  www.roboternetz.de
+
*ausserdem soll das Ganze natürlich bei einer ansprechenden Bus-Geschwindigkeit funktionieren. Getestet hab ich die Soft-Routinen mit 400 kHz, was bei Multimaster-Betrieb auch recht günstig ist, da ja dann der Bus für einen Message weniger lange belegt ist (ergo weniger Konflikte).
'----------------------------------------------
+
'  Das ist ein einfacher Sende- und Empfangs Slave
+
'----------------------------------------------
+
$crystal = 4000000                                ' Quarzfrequenz
+
$baud = 9600                                      'Baudrate für RS232 (Gewohnheit)
+
  
$hwstack = 48
+
==I2C auf AT90S2313==
$swstack = 16
+
In diesem Prozessor (und auch auf dem Tiny2313) gibt's keine Hardwarunterstützung. Und in einer Multimaster-Umgebung war auch mit der I2C.LIB von Bascom nichts zu machen. Denn gerade bei I2CSTART kümmert sich Bascom in keiner Weise darum, ob auf dem Bus gerade irgendwas abläuft. Und dann war ja immer noch das Problem, daß es keine SLAVE-Funktionen gibt (Nur um Geld).
$framesize = 16
+
  
Const Tmr_c_prescale = 64
+
==Step 1: Multimasterfähige I2c-Funktionen==
Const Tmr_c_preload = 191
+
Es genügte fürs Erste,
 +
*die Start-Routine durch eine multimasterfähige Funktion zu ersetzen,
 +
$external I2cwaitbus                              'wait for free bus & reserve it
 +
*und das Adressen- oder Bytesenden durch eine Funktion, die auch die Arbitrierung kontrolliert. 
 +
$external I2cwmaster                              'send byte & watch arbitration
  
Const Tmr_c_divis = 1000
+
*I2crbyte u. I2cstop kann man eigentlich im Moment lassen, da sie nichts böses tun.
  
Config Timer0 = Timer , Prescale = Tmr_c_prescale 'Timer 1mS
+
==Step 2: I2c-Slave-Funktionen==
On Timer0 Interrupt_ticker                        ' Timer for Timer Queue
+
Da gibt es eigentlich nur drei Funktionen:
 +
*warten auf eine Startbedingung und lesen der I2C-Adresse
 +
$external I2c_get_addr                            'wait for start-cond & read in I2C address
 +
Und, je nachdem, ob Read oder Write
 +
*Bytes vom Master empfangen
 +
$external I2c_slave_rx_data                      'ACK & receive master data
 +
*Bytes zum Master senden
 +
$external I2c_slave_tx_byte                      'ACK & send data to  master
  
Dim Timeout As Byte
 
Dim Timediv As Word
 
  
'-------------------------------------
+
===I2c_get_addr===
' Pin-definition  I2C
+
Ich habe aus mehreren Gründen darauf verzichtet, die Adress-Prüfung zu automatisieren, wie das ja sonst üblich ist:
'-------------------------------------
+
*Ich möchte in weiterer Folge die Funktionen auch als eine Art I2C-Monitor/Logger verwenden
Config I2cdelay = 5
+
*Ich seh' nicht ein, warum ein Slave immer sowohl Read als auch Write zulassen soll
Config Scl = Portd.3                              'Ports fuer IIC-Bus
+
*Und ich wollt mir auch die Parametriesierung ersparen, ob auch General-Calls oder nicht akzeptiert werden sollen. Irgendeine Art von "IF" muss der User ja sowieso machen, also soll er auch gleich selbst entscheiden, was er "acked" oder nicht.  
Config Sda = Portd.2
+
  
'--------------------------Library und definitionen ------------------
+
d.h. also, wenn die Funktion zurückkommt, ist ein I2C-Start erfolgt und eine Adresse ist gesendet worden. Die steht dann vollständig in Feld "I2C_ADDR"
$include "MyI2c.BAS"
+
*NULL ist eine General Call Adresse
 +
*SLA+W ist eine "Slave-Write" Adresse
 +
*SLA+R ist eine "Slave-Read" Adresse
  
'-------------------------------------
+
Welche SLA-Adresse seine eigene ist, kann der User nun selbst entscheiden.
'  Address-definition RNBFRA
+
'-------------------------------------
+
  
Const Pwr_adr = &H74                              'I2C Adress Powerport
+
===I2c_slave_rx_data===
Const Out_adr = &H72                              'I2C Adress Out PCF
+
wird das aufgerufen, wird ein ACK ausgesendet und es werden Daten vom Master empfangen und in den Buffer geschrieben, der im Feld "I2c_write" angegeben ist. Wieviel das sind, entscheidet der Master durch "STOP" oder "Repeated Start". Einen Begrenzung seitens der Funktion ist nicht vorgesehen, da sich ja die Applikationen im Master und im Slave sowieso irgendwie vereinbart haben müssen, wieviele Bytes wann zu senden sind.
Const Exp_adr = &H7E                              'I2C Adress PCF Expansion
+
  
Const Co1_adr = &H68                              'I2C Adress 2313    I
+
Ist die Routine fertig, steht die Anzahl der empfangenen Bytes im Feld "I2c_cntr"
Const Co1_adrr = &H69                            'I2C Adress 2313    I
+
  
Const Mn1_adr = &H6A                              'I2C Adress Atmega  I
 
Const Mn1_adrr = &H6B                            'I2C Adress Atmega  I
 
  
Const Co2_adr = &H6C                              'I2C Adress 2313  II
+
===I2c_slave_tx_byte===
Const Co2_adrr = &H6D                            'I2C Adress 2313  II
+
wird das aufgerufen, wird ein ACK ausgesendet und es werden die Daten zum Master gesendet, die im Feld "I2c_read" angegeben sind. Beendet wird das vom Master duch ein "NACK". Einen Begrenzung der Anzahl durch die Funktion ist auch hier nicht vorgesehen.
  
Const Mn2_adr = &H6E                              'I2C Adress Atmega II
+
Ist die Routine fertig, steht die Anzahl der gesendeten Bytes im Feld "I2c_cntr", falls das den User interessieren sollte. 
Const Mn2_adrr = &H6F                            'I2C Adress Atmega II
+
  
Dim Sent_flag As Byte
 
  
Dim M32_byte(24) As Byte                          'I2C Buffer Senden & empfangen
+
==DEMO MYI2C.ZIP==
 +
Das Demo-Set
 +
[[Bascom_Soft-I2c_Library#Web_Links]]
 +
kann heruntergeladen werden, besteht aus drei Teilen
 +
*2313.bas Demo Programm
 +
*myi2c.bas Include File
 +
*myi2c.lib eine Bascom library, die gehört zu den anderen Bascom Libraries, irgendwo im Installations-directory.
  
  Enable Timer0
+
==Beispiel 2313.bas==
 +
Das Demo-Programm macht eigentlich nix ausser Bytes zu empfangen oder zu senden. Wurde was empfangen und es ist auch gerade eine Sekunde 'rum, sendet er selbst an das RNBFRA Out-Port (PCF) das erste Byte, das im Empfangsbuffer steht. Damit sind die I2C Routinen prinzipiell gut zu testen.
  
  Enable Interrupts
+
Das vollständige Programm ist in der Zip-File. Das Wesentliche ist folgendes:
  
'---------------------------------------------
+
<pre>
'
+
$crystal = 4000000                                ' Quarzfrequenz
'---------------------------------------------
+
$hwstack = 48
   I2cinit                                        ' Bascom soll auch was tun
+
 
 +
   I2cinit                                        ' normale Bascom Funktion
  
 
   I2c_write = Varptr(m32_byte(1));              ' Data receive-Buffer
 
   I2c_write = Varptr(m32_byte(1));              ' Data receive-Buffer
Zeile 79: Zeile 85:
 
   Do
 
   Do
 
         Loadadr I2c_flag , Z
 
         Loadadr I2c_flag , Z
         Gosub I2c_get_addr                      ' I2C Adresse read
+
         Gosub I2c_get_addr                      ' I2C Address read
 +
 
 
         Select Case I2c_addr
 
         Select Case I2c_addr
 +
 
         Case Co1_adr:
 
         Case Co1_adr:
 
             Loadadr I2c_flag , Z
 
             Loadadr I2c_flag , Z
 
             Gosub I2c_slave_rx_data              ' daten empfangen bis Stop/Rep
 
             Gosub I2c_slave_rx_data              ' daten empfangen bis Stop/Rep
 
             Sent_flag = 1
 
             Sent_flag = 1
 +
 
             '------------------------------------
 
             '------------------------------------
 
             ' WORKOUT Message
 
             ' WORKOUT Message
Zeile 94: Zeile 103:
  
 
         Case Else
 
         Case Else
             I2cinit
+
 
 +
             I2cinit                               'Bus freigeben
 +
 
 
         End Select
 
         End Select
  
Zeile 100: Zeile 111:
 
             Sent_flag = 0
 
             Sent_flag = 0
 
             Timeout = 0
 
             Timeout = 0
             Gosub Out_transmit
+
             Gosub Out_transmit                   'senden an PCF
 
         End If
 
         End If
 +
  Loop
  
 
  Loop
 
 
End
 
End
'-----------------------------------------------------
 
Interrupt_ticker:
 
  Timer0 = Tmr_c_preload
 
  Incr Timediv
 
  If Timediv >= Tmr_c_divis Then
 
      Timeout = 1
 
      Timediv = 0
 
  End If
 
  Return
 
 
'-----------------------------------------------------
 
'-----------------------------------------------------
 
'
 
'
Zeile 121: Zeile 122:
 
   Do
 
   Do
 
       Gosub I2cwaitbus                            ' wait for Bus
 
       Gosub I2cwaitbus                            ' wait for Bus
 +
 
       $asm
 
       $asm
 
       ldi  r17, out_adr
 
       ldi  r17, out_adr
 
       !Call I2cwmaster                            ' send SLA+W  & arbitr.Check
 
       !Call I2cwmaster                            ' send SLA+W  & arbitr.Check
 
       $end Asm
 
       $end Asm
 +
 
       If Err = 0 Then
 
       If Err = 0 Then
 
             Loadadr M32_byte(1) , X
 
             Loadadr M32_byte(1) , X
Zeile 138: Zeile 141:
 
   I2cinit
 
   I2cinit
 
   Return
 
   Return
 
 
 
</pre>
 
</pre>
  
 
+
'''Anmerkung:'''
 +
Das Senden muß eventuell mehrmals wiederholt werden. Auf einem Multimaster-Bus ist es ja nicht gesagt, daß nicht schon mal der Bus verloren gehen kann, weil ein anderer dazwischenfunkt.
  
 
==Include-File MyI2c.bas==
 
==Include-File MyI2c.bas==
Zeile 196: Zeile 198:
 
==Bascom Library Myi2c.lib ==
 
==Bascom Library Myi2c.lib ==
  
<pre>
+
===Slave Receiver===
copyright = R.Toegel
+
Bei einer Busgeschwindigkeit von 400 kHz dauert ein Takt (1 Bit) gerade mal 2.5 µS, d.h. um SCL High oder Low zu erkennen, stehen 1.25 µS zur Verfügung. Da macht der AT90S2313 bei 4 MHZ gerade mal 5 Zyklen. Das geht sich nur aus, wenn man den Master durch "Clock-Stretching" synchronisiert. Da bei 400 kHz das Übertragen von 8 Bit nur 20 µS dauert, werden auch in dieser Zeit alle Interrupts disabled. 
www      = http://www.oldformation.at
+
email    = roberttoegel@aon.at
+
comment  = BASCOM-AVR I2C Slave / Multimaster library add to I2c.LIB
+
libversion  = 1.0.0.1
+
date        = 26 Jun 2006
+
statement    = Based on ATMEL application note
+
history      = This lib is an addon to i2c.lib
+
  
;-----------------------------------------------------
+
Zum Byte-Lesen gibt es zwei Varianten:
; I2c_slave_tx_ack Send ACK
+
*'''I2C Adresse lesen''' (8-Bit) und in das Feld I2C_addr schreiben. Vor dem Return wird der Bus angehalten. 
; I2c_slave_tx_nak Send NACK
+
Das Hauptprogramm kann nun die Adresse prüfen auf
; I2c_release Release Lines
+
# Eigene Adresse und WRITE 
; I2c_wait_start Wait for Start Condition
+
# Eigene Adresse und READ
; I2c_get_addr Get Message Address
+
# GCA  (Adresse=0)
; I2c_slave_tx_byte Slave Transmitter
+
# Weder noch.
; I2c_slave_rx_data Slave Receiver
+
Wenn das Hauptprogramm den Aufruf nicht annehmen kann oder will, wird einfach durch "I2CINIT" der Bus wieder freigegeben.
; I2c_read_byte Internal
+
 
;-----------------------------------------------------
+
*'''Datenbytes lesen'''. Dabei wird aber auch auf STOP oder REPEATED-START geprüft. Das ist gleichzeitig das Zeichen, dass keine Bytes mehr zu empfangen sind. Sonst wird jedes Byte "acked" und in den Buffer geschrieben.
; I2cwaitbus
+
 
; I2cwmaster
+
 
;-----------------------------------------------------
+
<pre>
 
[I2c_slave_tx_ack]
 
[I2c_slave_tx_ack]
 
I2c_slave_tx_ack:
 
I2c_slave_tx_ack:
Zeile 286: Zeile 281:
 
   std  Z + 0, r22    ;clear I2c_flag
 
   std  Z + 0, r22    ;clear I2c_flag
 
   ldi r24 ,1        ;set 'end'-Bit
 
   ldi r24 ,1        ;set 'end'-Bit
* in r1, SREG ; save SREG
+
* in r1, SREG ;save SREG
cli ; disable interrupts
+
cli ;disable interrupts
 
   clc ;clear carry
 
   clc ;clear carry
 
Wta_scl_hi:
 
Wta_scl_hi:
 
* cbi _sclDDR,_scl ;SCL Release
 
* cbi _sclDDR,_scl ;SCL Release
 +
Wta_scl_hi1:
 
* sbis _sclPIN,_scl    ;SCL
 
* sbis _sclPIN,_scl    ;SCL
   rjmp    wta_scl_hi    ;wait SCL hi
+
   rjmp    wta_scl_hi1    ;wait SCL hi
 
* sbic _sdaPIN,_sda    ;SDA = 0 ?
 
* sbic _sdaPIN,_sda    ;SDA = 0 ?
 
   sec                    ;SDA = 1 -> set carry
 
   sec                    ;SDA = 1 -> set carry
Zeile 369: Zeile 365:
 
   ret ; byte done
 
   ret ; byte done
 
[end]
 
[end]
 +
</pre>
  
 +
===Slave Transmitter===
 +
Hier erwartet der Slave, daß der Master beim letzten Byte ein "NACK" zurücksendet, denn eine STOP/REPEATED START-Bedingung kann da nicht vom Master kommen, da ja der Slave auf der SDA-Line "sitzt".
 +
Wie auch immer, die Funktion prüft trotzdem, ob jedes von ihr gesetzte HIGH auf der SDA Line auch wirklich kommt. Wenn nicht, wird abgebrochen.
 +
 +
<pre>
 
;-----------------------------------------------------
 
;-----------------------------------------------------
 
[I2c_slave_tx_byte]
 
[I2c_slave_tx_byte]
Zeile 438: Zeile 440:
  
 
[end]
 
[end]
 +
</pre>
  
 +
===Master===
 +
 +
====Wait Bus & Start====
 +
Die Funktion prüft, ob SCL und SDA High sind. Wenn das wenigstens 15 mal hintereinander gelingt, ist ziemlich sicher der Bus gerade frei. Dann wird die Start-Bedingung gesetzt. Nun sollte keiner am Bus mehr dazwischenfunken. Trotzdem kann das passieren, deswegen dann beim Senden die Arbitrierungs-Prüfung.
 +
 +
Die Pause-Funktion "_i2c_hp_delay" ist übrigens die, die Bascom aus "Config I2cdelay=" generiert.
 +
 +
<pre>
 
;---------------------------------------------------------
 
;---------------------------------------------------------
 
; MASTER Functions
 
; MASTER Functions
Zeile 462: Zeile 473:
 
Rjmp _i2c_hp_delay  ; half period delay
 
Rjmp _i2c_hp_delay  ; half period delay
 
[end]
 
[end]
 +
</pre>
 +
 +
====Send Byte & Arbitration Check====
 +
Auch hier werden die Interrupts disabled, damit nicht ev. auf dem Bus ein Stillstand eintritt, den ein anderer als "Bus-ist-frei" interpretieren kann. Bei jedem gesetzen HIGH auf der SDA-Line wird geprüft, ob nicht jemand anders irgendwie doch ein LOW erzwingt. Ist das so, gilt der Bus als "LOST" und ein Fehlerzeichen "ERR=1" wird gesetzt. Das Hauptprogramm kann (muß) seinen Sendeversuch dann wiederholen. Das gilt natürlich auch, wenn kein "ACK" kommt.
 +
 +
 +
Die etwas "eckige" Datenübergabe an die Funktion mit "Register 17" rührt daher, daß ich eigentlich die Funktionen in die Bascom "I2C.LIB" integrieren möchte, und da ist das eben die Konvention. Jetzt muß ich aber erstmal sehen, ob das auch so klappt, wie ich mir das vorstelle. Denn ich kann nicht einfach die "I2c.LIB" umschreiben und ersetzen, die wäre ja beim nächsten Update vielleicht wieder weg. Und dem Mark Albers von Bascom möcht ich ja auch nicht in die Quere kommen. 
 +
 +
<pre>
 
;---------------------------------------------------------
 
;---------------------------------------------------------
 
; Send one Byte R17 & check Arbitration
 
; Send one Byte R17 & check Arbitration
Zeile 517: Zeile 537:
 
       RJMP  Tx_1_ok ;continue
 
       RJMP  Tx_1_ok ;continue
 
[end]
 
[end]
 +
</pre>
  
 +
==Bemerkungen==
 +
Bei Soft-Slave-Funktionen ist das Problem das Erkennen einer Startbedingung und der nachfolgenden Adresse. Die Dauer einer Startbedingung richtet sich leider nach der Bus-Geschwindigkeit, und daher ist der Prozessor in dieser Situation für andere Dinge blockiert und kann auch irgendwelche anderen Interrupts nicht gut brauchen. 
  
 +
Natürlich funktioniert die Sache am Besten in einer Multimaster-Umgebung, denn da kann sich ein Master sowieso nicht drauf verlassen, daß der Bus jederzeit zur Verfügung steht. Also wird er, wenn er Pech hat, ohnehin mehrere Sendeversuche machen müssen, bis ein Slave reagiert.
  
 +
Man muß also nur darauf achten, daß ein Slave mit den obigen Funktionen möglichst oft bereit ist, am Bus zu lesen. Dann klappt das ganz gut.
  
 +
Gut ist es auch, wenn an sich auf dem I2C-Bus ein reges Leben herrscht, denn immerhin, bei jeder Message, die unseren Slave nicht betrifft, kann er die "WAIT START" Routine verlassen und hat etwas Zeit, auch was anderes zu tun.
  
  
  
 
 
</pre>
 
 
==Autor==
 
==Autor==
 
[[Benutzer:PicNick]]
 
[[Benutzer:PicNick]]
Zeile 537: Zeile 560:
 
* [[Bascom Inside-Code]]
 
* [[Bascom Inside-Code]]
 
* [[Bascom Libraries]]
 
* [[Bascom Libraries]]
 +
* [[TWI Praxis Multimaster]]
  
[[Kategorie:Microcontroller]]
+
[[Kategorie:Projekte]]
 
[[Kategorie:Grundlagen]]
 
[[Kategorie:Grundlagen]]
 
[[Kategorie:Software]]
 
[[Kategorie:Software]]
 
[[Kategorie:Quellcode Bascom]]
 
[[Kategorie:Quellcode Bascom]]
[[Kategorie:Quellcode Assembler AVR]]
 

Aktuelle Version vom 30. August 2006, 15:46 Uhr

Im Zusammenhang für das Projekt "RN-Netzwerk", wo es ja darum geht, PC und Avr zu verbinden, ist ja natürlich auch eine Integration des I2C-Bus unbedingt erforderlich. Da ich mit einer RNBFRA 1.2 Karte gesegnet bin, sollte natürlich gerade der AT90S2313 (der Co-Prozessor) auch zum Mitspieler werden. Das bisherige "durchrouten" über die RS232 ist ja doch nicht sehr befriedigend.

Über die Therorie und Praxis des I2C bzw. TWI -Bus gibt es hervorragende Artikel in RN-Wissen. Bitte dort nachzulesen, wenn irgendetwas unklar ist.

Und nochwas: Ich betrachte das als "Open-Source" Projekt. Wenn also jemand Hand anlegen möchte oder sonstwelche Ideen dazu hat, bitte um Rückmeldungen. Aber immer auch so, dass alle was davon haben. Also eventuell mit "Change-History" und Kommentaren.

Bascom Software I2C Routinen

Das Ziel ist:

  • Kommunikation auf der PC-Plattform via IP (Stream-Sockets),
  • vom PC zum Atmega32 über COMx und AVR-UART,
  • und auf der Mikrocontroller-Ebene dann über I2C, (später ev. auch andere Busse)
  • ausserdem soll das Ganze natürlich bei einer ansprechenden Bus-Geschwindigkeit funktionieren. Getestet hab ich die Soft-Routinen mit 400 kHz, was bei Multimaster-Betrieb auch recht günstig ist, da ja dann der Bus für einen Message weniger lange belegt ist (ergo weniger Konflikte).

I2C auf AT90S2313

In diesem Prozessor (und auch auf dem Tiny2313) gibt's keine Hardwarunterstützung. Und in einer Multimaster-Umgebung war auch mit der I2C.LIB von Bascom nichts zu machen. Denn gerade bei I2CSTART kümmert sich Bascom in keiner Weise darum, ob auf dem Bus gerade irgendwas abläuft. Und dann war ja immer noch das Problem, daß es keine SLAVE-Funktionen gibt (Nur um Geld).

Step 1: Multimasterfähige I2c-Funktionen

Es genügte fürs Erste,

  • die Start-Routine durch eine multimasterfähige Funktion zu ersetzen,
$external I2cwaitbus                              'wait for free bus & reserve it
  • und das Adressen- oder Bytesenden durch eine Funktion, die auch die Arbitrierung kontrolliert.
$external I2cwmaster                              'send byte & watch arbitration
  • I2crbyte u. I2cstop kann man eigentlich im Moment lassen, da sie nichts böses tun.

Step 2: I2c-Slave-Funktionen

Da gibt es eigentlich nur drei Funktionen:

  • warten auf eine Startbedingung und lesen der I2C-Adresse
$external I2c_get_addr                            'wait for start-cond & read in I2C address

Und, je nachdem, ob Read oder Write

  • Bytes vom Master empfangen
$external I2c_slave_rx_data                       'ACK & receive master data
  • Bytes zum Master senden
$external I2c_slave_tx_byte                       'ACK & send data to  master


I2c_get_addr

Ich habe aus mehreren Gründen darauf verzichtet, die Adress-Prüfung zu automatisieren, wie das ja sonst üblich ist:

  • Ich möchte in weiterer Folge die Funktionen auch als eine Art I2C-Monitor/Logger verwenden
  • Ich seh' nicht ein, warum ein Slave immer sowohl Read als auch Write zulassen soll
  • Und ich wollt mir auch die Parametriesierung ersparen, ob auch General-Calls oder nicht akzeptiert werden sollen. Irgendeine Art von "IF" muss der User ja sowieso machen, also soll er auch gleich selbst entscheiden, was er "acked" oder nicht.

d.h. also, wenn die Funktion zurückkommt, ist ein I2C-Start erfolgt und eine Adresse ist gesendet worden. Die steht dann vollständig in Feld "I2C_ADDR"

  • NULL ist eine General Call Adresse
  • SLA+W ist eine "Slave-Write" Adresse
  • SLA+R ist eine "Slave-Read" Adresse

Welche SLA-Adresse seine eigene ist, kann der User nun selbst entscheiden.

I2c_slave_rx_data

wird das aufgerufen, wird ein ACK ausgesendet und es werden Daten vom Master empfangen und in den Buffer geschrieben, der im Feld "I2c_write" angegeben ist. Wieviel das sind, entscheidet der Master durch "STOP" oder "Repeated Start". Einen Begrenzung seitens der Funktion ist nicht vorgesehen, da sich ja die Applikationen im Master und im Slave sowieso irgendwie vereinbart haben müssen, wieviele Bytes wann zu senden sind.

Ist die Routine fertig, steht die Anzahl der empfangenen Bytes im Feld "I2c_cntr"


I2c_slave_tx_byte

wird das aufgerufen, wird ein ACK ausgesendet und es werden die Daten zum Master gesendet, die im Feld "I2c_read" angegeben sind. Beendet wird das vom Master duch ein "NACK". Einen Begrenzung der Anzahl durch die Funktion ist auch hier nicht vorgesehen.

Ist die Routine fertig, steht die Anzahl der gesendeten Bytes im Feld "I2c_cntr", falls das den User interessieren sollte.


DEMO MYI2C.ZIP

Das Demo-Set Bascom_Soft-I2c_Library#Web_Links kann heruntergeladen werden, besteht aus drei Teilen

  • 2313.bas Demo Programm
  • myi2c.bas Include File
  • myi2c.lib eine Bascom library, die gehört zu den anderen Bascom Libraries, irgendwo im Installations-directory.

Beispiel 2313.bas

Das Demo-Programm macht eigentlich nix ausser Bytes zu empfangen oder zu senden. Wurde was empfangen und es ist auch gerade eine Sekunde 'rum, sendet er selbst an das RNBFRA Out-Port (PCF) das erste Byte, das im Empfangsbuffer steht. Damit sind die I2C Routinen prinzipiell gut zu testen.

Das vollständige Programm ist in der Zip-File. Das Wesentliche ist folgendes:

$crystal = 4000000                                ' Quarzfrequenz
$hwstack = 48

   I2cinit                                        ' normale Bascom Funktion

   I2c_write = Varptr(m32_byte(1));               ' Data receive-Buffer
   I2c_read = Varptr(m32_byte(1));                ' Data send-Buffer

   Do
         Loadadr I2c_flag , Z
         Gosub I2c_get_addr                       ' I2C Address read

         Select Case I2c_addr

         Case Co1_adr:
            Loadadr I2c_flag , Z
            Gosub I2c_slave_rx_data               ' daten empfangen bis Stop/Rep
            Sent_flag = 1

            '------------------------------------
            ' WORKOUT Message
            '------------------------------------

         Case Co1_adrr:
            Loadadr I2c_flag , Z
            Gosub I2c_slave_tx_byte               ' daten senden bis NAK

         Case Else

            I2cinit                               'Bus freigeben

         End Select

         If Sent_flag = 1  And Timeout = 1 Then
            Sent_flag = 0
            Timeout = 0
            Gosub Out_transmit                    'senden an PCF
         End If
   Loop

End
'-----------------------------------------------------
'
'-----------------------------------------------------
Out_transmit:
   Do
      Gosub I2cwaitbus                            ' wait for Bus

      $asm
      ldi   r17, out_adr
      !Call I2cwmaster                            ' send SLA+W  & arbitr.Check
      $end Asm

      If Err = 0 Then
            Loadadr M32_byte(1) , X
            $asm
            ld    r17, x+
            !Call I2cwmaster                      ' send data  & arbitr.Check
            $end Asm
      End If
      If Err = 0 Then
         I2cstop
      End If
   Loop Until Err = 0
   I2cinit
   Return

Anmerkung: Das Senden muß eventuell mehrmals wiederholt werden. Auf einem Multimaster-Bus ist es ja nicht gesagt, daß nicht schon mal der Bus verloren gehen kann, weil ein anderer dazwischenfunkt.

Include-File MyI2c.bas

'----------------------------------------------
' MyI2C.BAS
'    I2c Soft-functions
'----------------------------------------------
'   PicNick was here  www.roboternetz.de
'----------------------------------------------

'---------------------------  Library und definitionen ------------------
$lib "MyI2c.LIB"


'--------------------------- Slave receive & Send ---------------------
Const I2c_m_start = 1

$external I2c_get_addr                            'wait for start-cond & read in I2C address
Declare Sub I2c_get_addr

$external I2c_slave_rx_data                       'ACK & receive master data
Declare Sub I2c_slave_rx_data

$external I2c_slave_tx_byte                       'ACK & send data to  master
Declare Sub I2c_slave_tx_byte

'--------------------------- Multimaster sending ---------------------
$external I2cwaitbus                              'wait for free bus & reserve it
Declare Sub I2cwaitbus

$external I2cwmaster                              'send byte & watch arbitration
Declare Sub I2cwmaster

'-------------------------------------
'  I2C-STRUCTURE
'-------------------------------------
Dim I2c_flag As Byte                              '0   Kontroll-flags
Dim I2c_addr As Byte                              '1   slave adresse
Dim I2c_cntr As Byte                              '2   bei empfang anzahl bytes
Dim I2c_write As Word                             '3/4 adresse empfangsbuffer
Dim I2c_read As Word                              '5/6 adresse sendebuffer

'---------------------------------------------
'
'---------------------------------------------
   I2cinit


Bascom Library Myi2c.lib

Slave Receiver

Bei einer Busgeschwindigkeit von 400 kHz dauert ein Takt (1 Bit) gerade mal 2.5 µS, d.h. um SCL High oder Low zu erkennen, stehen 1.25 µS zur Verfügung. Da macht der AT90S2313 bei 4 MHZ gerade mal 5 Zyklen. Das geht sich nur aus, wenn man den Master durch "Clock-Stretching" synchronisiert. Da bei 400 kHz das Übertragen von 8 Bit nur 20 µS dauert, werden auch in dieser Zeit alle Interrupts disabled.

Zum Byte-Lesen gibt es zwei Varianten:

  • I2C Adresse lesen (8-Bit) und in das Feld I2C_addr schreiben. Vor dem Return wird der Bus angehalten.

Das Hauptprogramm kann nun die Adresse prüfen auf

  1. Eigene Adresse und WRITE
  2. Eigene Adresse und READ
  3. GCA (Adresse=0)
  4. Weder noch.

Wenn das Hauptprogramm den Aufruf nicht annehmen kann oder will, wird einfach durch "I2CINIT" der Bus wieder freigegeben.

  • Datenbytes lesen. Dabei wird aber auch auf STOP oder REPEATED-START geprüft. Das ist gleichzeitig das Zeichen, dass keine Bytes mehr zu empfangen sind. Sonst wird jedes Byte "acked" und in den Buffer geschrieben.


[I2c_slave_tx_ack]
I2c_slave_tx_ack:
* 	sbi 	_sdaDDR,_sda    ;Hold SDA LO (ACK)
Wt_ack_scl:
*	in	r1, SREG	; save SREG
	cli			; disable interrupts
* 	cbi 	_sclDDR,_scl	;SCL Release
Wt_scl_hi2:
* 	sbis 	_sclPIN,_scl    ;SCL
  	rjmp    wt_scl_hi2     	;wait SCL hi
Wt_scl_lo3:
* 	sbic 	_sclPIN,_scl    ;SCL
   	rjmp     wt_scl_lo3     ;wait SCL lo
* 	sbi 	_sclDDR,_scl	;SCL Stretch
* 	cbi 	_sdaDDR,_sda    ;release SDA
*	out	SREG, R1
   	ret
[end]
;-----------------------------------------------------
[I2c_slave_tx_nak]
I2c_slave_tx_nak:
$external I2c_slave_tx_ack
* 	cbi 	_sdaDDR,_sda    ;Release SDA  
	rjmp 	Wt_ack_scl
[end]
;-----------------------------------------------------
[I2c_release]
I2c_release:
* 	cbi 	_sdaDDR,_sda    ;SDA Release
* 	cbi 	_sclDDR,_scl	;SCL Release
   	clr      r22
   	std      Z + 0, r22     ;clear flag
	ret
[end]
;-----------------------------------------------------
[I2c_wait_start]
I2c_wait_start:
	ldi	r22, 0
   	std  	Z + 0, r22     	; clear I2c_flag
Wt_hi_hi:
* 	sbis 	_sclPIN,_scl    ; SCL ?
   	rjmp     wt_hi_hi       ; wait SCL hi
* 	sbis 	_sdaPIN,_sda    ; SDA ?
   	rjmp     wt_hi_hi       ; wait SDA hi
Wt_sda_lo0:
* 	sbic 	_sdaPIN,_sda    ; SDA
	rjmp     wt_sda_lo0     ; wait SDA lo
* 	sbis 	_sclPIN,_scl    ; SCL start condition ?
   	rjmp     wt_hi_hi       ; no, repeat the whole sequence
Wt_scl_lo0:
* 	sbic 	_sclPIN,_scl    ; SCL
   	rjmp     wt_scl_lo0     ; wait SCL lo	
* 	sbi 	_sclDDR,_scl	; SCL Stretch
   	ret
[end]
;-----------------------------------------------------
[I2c_get_addr]
I2c_get_addr:
$external I2c_wait_start
   	ldd	r22, Z + 0 	; i2c_flag
*	cpi	r22, I2c_m_Start
        breq    rept_s		; start-cond. already set
	rcall	I2c_wait_start	; wait for Bus-start
rept_s:
*	clr 	r22
   	std  	Z + 0, r22     	;clear I2c_flag
   	ldi	r24 ,1         	;set 'end'-Bit
*	in	r1, SREG	;save SREG
	cli			;disable interrupts
   	clc			;clear carry
Wta_scl_hi:
* 	cbi 	_sclDDR,_scl	;SCL Release
Wta_scl_hi1:
* 	sbis 	_sclPIN,_scl    ;SCL
   	rjmp     wta_scl_hi1    ;wait SCL hi
* 	sbic 	_sdaPIN,_sda    ;SDA = 0 ?
   	sec                     ;SDA = 1 -> set carry
Wta_scl_lo:
* 	sbic 	_sclPIN,_scl    ;SCL
   	rjmp     wta_scl_lo     ;wait SCL lo
* 	sbi 	_sclDDR,_scl	;SCL Stretch
   	rol      r24            ;roll carry in/out
   	brcc     Wta_scl_hi     ;Bit loop 
   	std      Z + 1, r24     ;Store i2c address
*	out	SREG, r1
   	ret
[end]
;-----------------------------------------------------
[I2c_slave_rx_data]
I2c_slave_rx_data:
   	ldd      xl, Z + 3           	; Load Buffer Address
   	ldd      xh, Z + 4           	;
   	clr      r23
   	std      Z + 2, r23          	; clear counter
I2c_read_loop:
$external I2c_slave_tx_ack
   	rcall	I2c_slave_tx_ack	; Send ACK 

*	in	r1, SREG	; save SREG
	cli			; disable interrupts
   	clt                          	; clear T-Bit
   	rcall   I2c_read_byte		; read 8 Bit
   	brtc    Rd_sto_ack		; T-Bit clear ? o.k.--> 
*	out	SREG, r1	
   	std     Z + 2, r23    		; store counter
   	std     z + 0, r22     		; set start/stop/none flag
   	ret
Rd_sto_ack:
*	out	SREG, r1	
   	st      x+, r24			; store character
	inc	r23			; data counter++
   	rjmp   I2c_read_loop		; no, cont'd next byte

'-------------------------------------------
I2c_read_byte:
   	ldi      r24 ,1                 ;set end-bit
   	clc                             ;clear carry
Rd_loop:
* 	cbi 	_sclDDR,_scl		;SCL Release
Wt_scl_hi:
* 	sbis 	_sclPIN,_scl    	; SCL
   	rjmp     wt_scl_hi              ; wait SCL hi
* 	sbis 	_sdaPIN,_sda    	; SDA ?
   	rjmp     Wt_chk_0		; SDA Low-Check
   	sec              		; SDA Hi-Check
Wt_chk_1:
* 	sbis 	_sclPIN,_scl    	; SCL
   	rjmp     wt_ok_x                ; scl low---> ok, next bit
* 	sbic 	_sdaPIN,_sda    	; SCL Hi & SDA lo --> repeated Start
   	rjmp     wt_chk_1               ; SCL & SDA Hi --> cont'd wait
Rep_start_cond:
* 	sbic 	_sclPIN,_scl    	; SCL			
   	rjmp     Rep_start_cond		; wait SCL lo
* 	sbi 	_sclDDR,_scl		; SCL STRETCH
   	SET				; set t-Bit
*	ldi 	r22, I2c_m_Start	; signal REP-Start
   	ret                                     
Wt_chk_0:				; SDA is Low
* 	sbis 	_sclPIN,_scl    	; SCL ?
   	rjmp     wt_ok_x             	; scl low---> ok, next bit
* 	sbis 	_sdaPIN,_sda    	; SDA ?
   	rjmp     wt_chk_0		; SCL Hi & SDA lo --> cont'd wait
Stop_cond:				: stop-condition
   	SET				; set t-bit
   	ret				; xit
Wt_ok_x:
* 	sbi 	_sclDDR,_scl		; STRETCH
   	rol      r24                    ; roll carry in/out
   	brcc     rd_loop		; next bit
   	ret				; byte done
[end]

Slave Transmitter

Hier erwartet der Slave, daß der Master beim letzten Byte ein "NACK" zurücksendet, denn eine STOP/REPEATED START-Bedingung kann da nicht vom Master kommen, da ja der Slave auf der SDA-Line "sitzt". Wie auch immer, die Funktion prüft trotzdem, ob jedes von ihr gesetzte HIGH auf der SDA Line auch wirklich kommt. Wenn nicht, wird abgebrochen.

;-----------------------------------------------------
[I2c_slave_tx_byte]
I2c_slave_tx_byte:
$external I2c_slave_tx_ack
   	rcall    I2c_slave_tx_ack	; Send ACK
   	ldd      xl, Z + 5 		;Load Buffer Address
   	ldd      xh, Z + 6      	;
*	in	r1, SREG	; save SREG
	cli			; disable interrupts
	rcall	Tx_loop
*	out	SREG, r1
	ret
;--------------------------------------------------
Tx_loop:
   	ld       r24, x+		; next character
   	ldi      r25, 8			; 8 Bit
Tx_c_loop:
   	Sbrc     R24 , 7        	;Data Bit ?
   	rjmp     Tx_Bit_1		;set SDA High
;--------------------------------------------------
;---------------- send NULL------------------------
* 	sbi 	_sdaDDR,_sda    	;set SDA Low
* 	cbi 	_sclDDR,_scl		;SCL Release--------------------
Tx0_scl_hi_0:
* 	sbis 	_sclPIN,_scl    	;SCL
   	rjmp     Tx0_scl_hi_0		;wait scl hi
	nop
	nop
Tx0_scl_lo_0:
* 	sbic 	_sclPIN,_scl    	;SCL
   	rjmp     Tx0_scl_lo_0		;wait scl lo

* 	sbi 	_sclDDR,_scl		;SCL Stretch
	rjmp	Tx_step
;--------------------------------------------------
;---------------- send HIGH------------------------
Tx_bit_1:
* 	cbi 	_sdaDDR,_sda    	;SDA Release High
* 	cbi 	_sclDDR,_scl		;SCL Release
Tx1_scl_hi_0:
* 	sbis 	_sclPIN,_scl    	;SCL
   	rjmp     Tx1_scl_hi_0		;wait scl hi
* 	sbis 	_sdaPIN,_sda    	;SDA HI  ?
	ret				;Bus Lost , anyhow
Tx1_scl_lo_0:
* 	sbic 	_sclPIN,_scl    	;SCL
   	rjmp     Tx1_scl_lo_0		;wait scl lo
* 	sbi 	_sclDDR,_scl		;SCL Stretch
Tx_step:
   	rol      r24            	;roll carry in/out
   	dec      r25			;count bits
   	brne     Tx_c_loop		;next bit
;--------------------------------------------------
;---------------- get ACK/NAK ---------------------
* 	cbi 	_sdaDDR,_sda    	;SDA Release
* 	cbi 	_sclDDR,_scl		;SCL Release
Tx_scl_hi_1:				;get ack bit
* 	sbis 	_sclPIN,_scl    	;SCL
   	rjmp     Tx_scl_hi_1		;wait scl hi
* 	sbic 	_sdaPIN,_sda    	;SDA (ACK) ?
   	ret             		;no-ack --> return
Tx_scl_lo_1:
* 	sbic 	_sclPIN,_scl    	;SCL
   	rjmp     Tx_scl_lo_1		;wait scl lo
* 	sbi 	_sclDDR,_scl		;SCL Stretch
   	rjmp    tx_loop			; next

[end]

Master

Wait Bus & Start

Die Funktion prüft, ob SCL und SDA High sind. Wenn das wenigstens 15 mal hintereinander gelingt, ist ziemlich sicher der Bus gerade frei. Dann wird die Start-Bedingung gesetzt. Nun sollte keiner am Bus mehr dazwischenfunken. Trotzdem kann das passieren, deswegen dann beim Senden die Arbitrierungs-Prüfung.

Die Pause-Funktion "_i2c_hp_delay" ist übrigens die, die Bascom aus "Config I2cdelay=" generiert.

;---------------------------------------------------------
;	MASTER Functions
;---------------------------------------------------------
;	Wait for free Bus
;---------------------------------------------------------
[I2cwaitbus]
I2cwaitbus:
$EXTERNAL _I2C
*	in	r1, SREG	; save SREG
	cli			; disable interrupts
Ck_1_x:
      	LDI   	r23,0x0f  	; 15 times
Ck_1_y:
* 	sbis	_sclPIN,_scl    ; SCL 
	RJMP	Ck_1_x          ; wait SCL Hi
* 	sbis 	_sdaPIN,_sda    ; SDA HI  ?
      	RJMP  	Ck_1_x          ; wait SDA Hi
      	DEC   	r23
      	BRNE  	Ck_1_y          ; (8) hang on
* 	sbi 	_sdaDDR,_sda    ; SDA down for Start
*	out	SREG, r1
	Rjmp 	_i2c_hp_delay   ; half period delay
[end]

Send Byte & Arbitration Check

Auch hier werden die Interrupts disabled, damit nicht ev. auf dem Bus ein Stillstand eintritt, den ein anderer als "Bus-ist-frei" interpretieren kann. Bei jedem gesetzen HIGH auf der SDA-Line wird geprüft, ob nicht jemand anders irgendwie doch ein LOW erzwingt. Ist das so, gilt der Bus als "LOST" und ein Fehlerzeichen "ERR=1" wird gesetzt. Das Hauptprogramm kann (muß) seinen Sendeversuch dann wiederholen. Das gilt natürlich auch, wenn kein "ACK" kommt.


Die etwas "eckige" Datenübergabe an die Funktion mit "Register 17" rührt daher, daß ich eigentlich die Funktionen in die Bascom "I2C.LIB" integrieren möchte, und da ist das eben die Konvention. Jetzt muß ich aber erstmal sehen, ob das auch so klappt, wie ich mir das vorstelle. Denn ich kann nicht einfach die "I2c.LIB" umschreiben und ersetzen, die wäre ja beim nächsten Update vielleicht wieder weg. Und dem Mark Albers von Bascom möcht ich ja auch nicht in die Quere kommen.

;---------------------------------------------------------
;	Send one Byte R17 & check Arbitration
;---------------------------------------------------------
[I2cwmaster]
I2cwmaster:
$EXTERNAL _I2C
*	in	r1, SREG	; save SREG
	cli			; disable interrupts
      	Sec                                     ; set carry flag
      	Rol   	r17                             ; shift in carry and out bit one
	rcall	I2cwmaster_loop
*	out	SREG, r1
	ret
I2cwmaster_loop:
* 	sbi 	_sclDDR,_scl	                ;SCL DOWN
      	BRCC  	I2cwmaster_low                  ;carry clear --> send Low
; send High ------------------------------------------
      	NOP                                     ; filler
* 	cbi 	_sdaDDR,_sda      		; release SDA
      	RCALL 	_i2c_hp_delay                   ; wait
* 	cbi 	_sclDDR,_scl			; SCL Release
Tx_1_ck:
* 	sbis	_sclPIN,_scl    		; SCL 
      	RJMP  	Tx_1_ck                         ; wait SCL Hi
* 	sbis 	_sdaPIN,_sda    		; SDA still HI  ?
      	RJMP  	Tx_lost                         ; No, ---> arbitration lost
Tx_1_ok:
      	RCALL 	_i2c_hp_delay      		;wait
      	LSL   	r17                             ;2^^7 -->Cy
      	brne  	I2cwmaster_loop			;more bits ? 
;---------------------------------------------------
I2cwmaster_loop_x:                           	; Get ACK / NACK
* 	sbi 	_sclDDR,_scl	                ; SCL DOWN
* 	cbi 	_sdaDDR,_sda      		; release SDA
      	RCALL 	_i2c_hp_delay                   ; wait
* 	cbi 	_sclDDR,_scl			; SCL Release
Tx_x_ck:
* 	sbis	_sclPIN,_scl    		; SCL 
      	RJMP  	Tx_x_ck                         ; wait Hi
      	CLT                                     ; clear t-bit
* 	sbic 	_sdaPIN,_sda    		; SDA = ACK/NAK ?
Tx_lost:
      	SET                                     ; set t-bit
      	BLD 	r6,2              	    	; t-Bit -> to Bascom "ERR"
      	RJMP 	_i2c_hp_delay                   ; wait & return
' send Low ------------------------------------------
I2cwmaster_low:
* 	sbi 	_sdaDDR,_sda      		;down SDA
      	RCALL 	_i2c_hp_delay       		;wait
* 	cbi 	_sclDDR,_scl			;SCL Release
Tx_0_ck:
* 	sbis	_sclPIN,_scl    		;SCL 
      	RJMP  	Tx_0_ck                         ;wait SCL Hi
      	RJMP  	Tx_1_ok				;continue
[end]

Bemerkungen

Bei Soft-Slave-Funktionen ist das Problem das Erkennen einer Startbedingung und der nachfolgenden Adresse. Die Dauer einer Startbedingung richtet sich leider nach der Bus-Geschwindigkeit, und daher ist der Prozessor in dieser Situation für andere Dinge blockiert und kann auch irgendwelche anderen Interrupts nicht gut brauchen.

Natürlich funktioniert die Sache am Besten in einer Multimaster-Umgebung, denn da kann sich ein Master sowieso nicht drauf verlassen, daß der Bus jederzeit zur Verfügung steht. Also wird er, wenn er Pech hat, ohnehin mehrere Sendeversuche machen müssen, bis ein Slave reagiert.

Man muß also nur darauf achten, daß ein Slave mit den obigen Funktionen möglichst oft bereit ist, am Bus zu lesen. Dann klappt das ganz gut.

Gut ist es auch, wenn an sich auf dem I2C-Bus ein reges Leben herrscht, denn immerhin, bei jeder Message, die unseren Slave nicht betrifft, kann er die "WAIT START" Routine verlassen und hat etwas Zeit, auch was anderes zu tun.


Autor

Benutzer:PicNick

Web Links

http://www.oldformation.at/electronic/download/down.htm

Siehe auch


LiFePO4 Speicher Test