PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : USI interface an tiny 2313



Bluesmash
15.11.2006, 08:12
hallo zusammen!

ich möchte den tiny2313 als i2c slave betreiben, aber so wie ich gesehen haben hat der keinen echten TWI bus sondern das neue USI interface... hat damit schon jemand eine i2c kommunikation aufgebaut? wen ja währe ich sehr froh über ein bisschen hilfe...
mit 2 atmega8 habe ich schon eine i2c kommunikation zustande gebracht... also ein bisschen kenntniss ist vorhanden.... und das ganze sollte mit bascom programmiert werden...

gruss bluesmash

Bluesmash
15.11.2006, 23:15
ich habe jetzt mal mein glück versucht...

code master:


$regfile = "M8def.dat" ' the chip we use
$crystal = 8000000

Config Sda = Portc.4
Config Scl = Portc.5

i2cinit

Dim X As Byte

X = 59


Do

I2csend 10 , X , 1
Waitms 100
Loop

code slave:

$regfile = "ATtiny2313.DAT"
$crystal = 8000000
$baud = 9600

$framesize = 128
$swstack = 128
$hwstack = 128

Usicr = &B11111000

'Config Portb.5 = Input
'Config Portb.7 = Input

Dim X As Byte

On Uci_start Usis
On Uci_ovflw Usio
Enable Interrupts


Config Portd.6 = Output
Print "test"
Print Usidr

Do
Waitms 100
Print X
Loop


Usis:
'Usidr = 0

Usisr.0 = 0
Usisr.1 = 0
Usisr.2 = 0
Usisr.3 = 0
Usisr.7 = 1
Return



Usio:
X = Usidr

Usisr.6 = 1
Usisr.0 = 0
Usisr.1 = 0
Usisr.2 = 0
Usisr.3 = 0
Return


aber ich erhalte bei der ausgabe von x immer nur 59... eigentlich sollte ja zuerst die adresse mit read/write kommen..... kann mir da jemand helfen???

linux_80
15.11.2006, 23:56
Hallo,

ich hätte eigentlich auch schon ein paar T2313 da liegen um genau das zu probieren, aber habs bis jetzt noch nicht gemacht :-(

Hast Du schon diese AN's gelesen:
AVR310 (http://www.atmel.com/dyn/resources/prod_documents/doc2561.pdf): Using the USI module as a I2C-master
AVR312 (http://www.atmel.com/dyn/resources/prod_documents/doc2560.pdf): Using the USI module as a I2C-slave

da gibts auch Beispiel code dazu, zu finden hier:
http://www.atmel.com/dyn/products/app_notes.asp?family_id=607

Bluesmash
16.11.2006, 06:52
ups... das sieht ja noch viel komplizierter aus als ich dachte... ist wohl doch nicht so einfach zu lösen... :(

Bluesmash
16.11.2006, 10:56
ich hab mich jetzt mal in das c prog eingelesen... ich denke es ist machbar.. (ja klar machbar ist alles.... ;))
hab nur noch ein bisschen mühe alles in c zu verstehen...

was bedeuten das folgende statement?



while ( (PIN_USI & (1<<PORT_USI_SCL)) & !(tmpUSISR & (1<<USIPF)) );

definitionen zu den variablen:


#if defined(__AT90Tiny2313__) | defined(__ATtiny2313__)
#define DDR_USI DDRB
#define PORT_USI PORTB
#define PIN_USI PINB
#define PORT_USI_SDA PORTB5
#define PORT_USI_SCL PORTB7
#define PIN_USI_SDA PINB5
#define PIN_USI_SCL PINB7
#define USI_START_COND_INT USISIF
#define USI_START_VECTOR USI_STRT_vect
#define USI_OVERFLOW_VECTOR USI_OVF_vect
#endif

linux_80
16.11.2006, 18:09
Hallo,

die Bedeutung steht ja gleich dahinter:


// Wait for SCL to go low to ensure the "Start Condition" has completed.
// If a Stop condition arises then leave the interrupt to prevent waiting forever.
Es wird gewartet bis die Takleitung SCL low wird, oder evtl. schon wieder ein STOP erkannt wurde.

in Bascom könnte das IMHO so ausschaun:

while (Portb.7 = 1 and not USIS.USIPF = 1)
Die Schleife wird solange wiederholt wie PB7 auf 1 ist und USIS.USIPF nicht auf 1 geht.

Hab das jetz aber nur auf die schnelle zusammengereimt ;-)

Bluesmash
16.11.2006, 23:45
ich glaube langsam das bascom einfach zu langsam ist dafür...
ich habe jetzt den ganzen abend probiert... mit mehr oder weniger gutem erfolg... konnte jedenfals die adresse übertragen und 2 bytes empfangen aber ab dem 3. byte kam nur noch mülll.... keine ahnung wiso....

hat noch niemand das usi als i2c mit bascom geproggt???

linux_80
17.11.2006, 23:29
Hi,
hab mir jetzt was schönes zum basteln vorbereitet:

https://www.roboternetz.de/phpBB2/album_pic.php?pic_id=1140
Damit versuche ich mal die Berge und Täler des USI zu ergründen, und hoffe meinem T2313 etwas TWI-Kommunikation beizubringen.
Wenn's was wird, kann die ein- oder andere Info ins Wiki eingetragen werden.

Da ich (jetzt) keine Lust hatte ein ganzes Board für den T2313 zu erstellen, hab ich nur einen Adpater zusammengeflickt.
Der läuft jetzt mit dem Quarz des RN-Control (16MHz), und kann auch die anderen Features des RN-Control verwenden, soweit überhaupt im T2313 vorhanden.
Es geht Motortreiber, Buzzer, MAX232 und die 8 LEDs, AD-Wandler hat der ja keinen, deshalb bleibt PortA unbenutzt.

Bluesmash
18.11.2006, 17:34
na dann viel glück ;) ich schlage mitlerweilen eine andere rcichtung ein und mache die kommunikation nicht über i2c ;)

gibt js noch einige varianten :)

gruss bluesmash

linux_80
18.11.2006, 19:19
Das Zeug liegt bestimmt schon ein Jahr hier rum, und jetzt hab ich endlich etwas Lust hier weiterzuforschen, hab damals das TWI-Modul untersucht und den Wiki-Artikel gebaut.

Mit dem USI ist das aber so eine Sache, soweit ich das bisher verstanden habe ergibt das mehr ein optimiertes Software-TWI, man muss selber die Richtungsbits setzen usw.
Das einzige was, mehr oder weniger, automatisch geht ist die Erkennung einer Startsequenz, sogar ein ACK oder NACK muss man selber zusammenbauen,
na das wird ja lustig ;-)

Bin grad am Anfang die Beispiele aus dem AN310 in Bascom nachzubauen, denn als Master soll das ohne Timer und Interrupt gehen, aber <language="bairisch (http://de.wikipedia.org/wiki/Bairische_Sprache)">des is a rechts gfrett</language> (deutsch->Zum Zeitpunkt der Planung wurde der Zeitaufwand zur Umsetzung des Projektes aufs gröbste unterschätzt !)
](*,)

Einen Artikel im Wiki hab ich aber schon angefangen, damit ich nicht wieder alles vergesse was ich da alles lese:
USI-Artikel (https://www.roboternetz.de/wissen/index.php/USI_%28Avr%29)

linux_80
19.11.2006, 21:46
Hallo,

der erste Schritt ist getan, der Tiny macht jetzt schon als USI-TWI-Master und sendet einem PCF8574 Daten die ich über UART an den Tiny sende.
Das Programm ist in Bascom gebaut, das Problem ist nun, da der Tiny nur 2KB Flash hat, ist es schon zu 59% belegt. #-o
Mit Schuld ist da auch die ganze UART-String-Print-usw-Umwandlung, aber trotzdem ists relativ eng.
Man kann also nicht mehr viel anderes damit machen, zumindest unter Bascom 8-[


€dit: Ich hab das Beispielprogramm mal ins Wiki gestellt, dann findet es sich leichter:
https://www.roboternetz.de/wissen/index.php?title=Bascom_und_USI-Kommunikation


Das ist im Prinzip eine Übersetzung des C-Beispiels zur AN310 von Atmel:
http://www.atmel.com/dyn/products/app_notes.asp?family_id=607


€dit-PS:
Das Beispiel dürfte auch auf allen anderen AVRsen laufen die USI haben, weil die Bitpositionen der Register immer gleich sind, und die Namen vom Bascom sowieso angepasst übersetzt werden.
Nur ausreichend Speicher ist nötig ;-)

Bluesmash
02.12.2006, 16:50
hallo!

wollte mal nachfragen ob du es geschaft hast den tiny auch als slave zu betreiben?

gruss bluesmash

linux_80
03.12.2006, 02:03
Zum Slave bin ich noch nicht gekommen,
hab die Woche für eine anderen Thread
https://www.roboternetz.de/phpBB2/viewtopic.php?t=25466
schnell eine Platine mit DS1621 gebaut und probiert, erst mit Software-I2C, dann mit USI (siehe Wiki)

Ich hatte in letzter Zeit nicht genug Zeit am Stück, damit ich das lesen der Doku und das testen eines Programms auf einmal geschafft hätte ;-)

Bin aber noch dran, es muss ja noch irgendwie versucht werden, das ganze etwas zu optimieren, damit man ausser dem USI noch was anderes mit dem AVR machen kann ;-)

linux_80
27.12.2006, 02:38
Hallo,

hab nun auch ein paar Tage frei, deshalb darf mein Tiny dran glauben ;-)

Wer sich mal versuchen will, ist die AppNote 312 als Bascom-Version.

Verändert werden sollte erstmal nix, ausser evtl. der Slaveadresse, und dem Soundausgang ;-)
Ansonsten kann für die Funktion nicht garantiert werden ;-)



' USI-I2C Testprogramm
' Slave @ $40
'
' mit Interrupt und ohne Timer

$regfile = "attiny2313.dat"
$crystal = 16000000
$baud = 9600
$hwstack = 40
'$framesize = 16
$swstack = 16

' Unterprogramme für die USI-Kommunikation
Declare Sub Usi_twi_slave_initialise()
Declare Sub Usi_twi_transmit_byte(zeichen As Byte)
Declare Function Usi_twi_receive_byte() As Byte

On Uci_start Usi_start_condition_isr , Nosave
On Uci_ovflw Usi_counter_overflow_isr , Nosave

' einige Aliases anlegen
Pout_usi_scl Alias Portb.7
Pin_usi_scl Alias Pinb.7
Ddr_usi_scl Alias Ddrb.7
Pout_usi_sda Alias Portb.5
Pin_usi_sda Alias Pinb.5
Ddr_usi_sda Alias Ddrb.5

Dim Usi_twi_errorstate As Byte ' eigener Fehlerstatus
' Array der Daten die übertragen werden
Dim Twi_txbuf(16) As Byte
Dim Twi_rxbuf(16) As Byte
' Zeiger auf Buffer
Dim Twi_rxhead As Byte
Dim Twi_rxtail As Byte
Dim Twi_txhead As Byte
Dim Twi_txtail As Byte

Dim Temp_usi As Byte
Dim Temp_usi_isr As Byte ' Byte das in den ISRs verwendet wird
Dim Usi_twi_data_in_receive_buffer As Bit ' Flag ob Buffer Daten enthält
Dim Usi_twi_overflow_state As Byte

Const Twi_slaveaddress = &H40

' Möglichkeiten für Usi_twi_overflow_state
Const Usi_sl_check_address = 0
Const Usi_sl_send_data = 1
Const Usi_sl_req_reply_from_send_data = 2
Const Usi_sl_chk_reply_from_send_data = 3
Const Usi_sl_req_data = 4
Const Usi_sl_get_data_and_send_ack = 5

' Ausgänge für LEDs
Config Portb.6 = Output
Config Portb.2 = Output
Config Portb.1 = Output
Config Portb.0 = Output
Config Portd.6 = Output
Config Portd.4 = Output

Enable Interrupts ' IRQs global erlauben

Call Usi_twi_slave_initialise

Waitms 500 ' Sicherheitspause nach Reset

Sound Portd.5 , 300 , 450 ' BEEP

Print
Print "USI Slavetest"

' Hauptprogramm
Do

Gosub Check_if_data_in_receive_buf
If Usi_twi_data_in_receive_buffer = 1 Then
Temp_usi = Usi_twi_receive_byte()

' Wert an LEDs anzeigen
Portb.6 = Temp_usi.5
Portb.2 = Temp_usi.4
Portb.1 = Temp_usi.3
Portb.0 = Temp_usi.2
Portd.6 = Temp_usi.1
Portd.4 = Temp_usi.0

Temp_usi = Not Temp_usi
Print Temp_usi
'Usi_twi_transmit_byte(Temp_usi)
Else
' Print "nix"
Waitms 100
End If

'Print Twi_rxhead ;
'Print " ";
'Print Twi_rxtail ;
'Print " ";
'Print Twi_txhead ;
'Print " ";
'Print Twi_txtail

Loop

End


' Unterprogramme

' Initialise USI for TWI Slave mode.
Sub Usi_twi_slave_initialise()
Gosub Flush_twi_buffers ' Flushes the TWI buffers

' Direction Out
Ddr_usi_scl = 1
Ddr_usi_sda = 0 ' SDA Input
' Release SCL & SDA
Pout_usi_scl = 1
Pout_usi_sda = 1

' Preload dataregister with "released level" data.
Usidr = &HFF

' Enable Start Condition Interrupt. Disable Overflow Interrupt.
' Set USI in Two-wire mode. No USI Counter overflow prior
' To First Start Condition(potentail Failure)
' Shift Register Clock Source = External , Positive Edge
Usicr = &B10101000

' Clear flags, and reset counter.
Usisr = &B11110000

End Sub

' Puts data in the transmission buffer, Waits if buffer is full.
Sub Usi_twi_transmit_byte(zeichen As Byte)

Local Tmphead As Byte

' Calculate Buffer Index.
Tmphead = Twi_txhead + 1
Tmphead = Tmphead And &H0F

' Wait for free space in buffer.
While Tmphead = Twi_txtail
Wend

Twi_txbuf(tmphead + 1) = Zeichen ' Store Data In Buffer.
Twi_txhead = Tmphead ' Store new index.

End Sub


' Returns a byte from the receive buffer. Waits if buffer is empty
Function Usi_twi_receive_byte() As Byte

Local Tmptail As Byte

' warten bis etwas im Buffer steht
While Twi_rxhead = Twi_rxtail
Wend

' Tmptail =(twi_rxtail + 1 ) & Twi_rx_buffer_mask;
' Calculate buffer index
Tmptail = Twi_rxtail + 1
Tmptail = Tmptail And &H0F
Twi_rxtail = Tmptail ' Store new index

Usi_twi_receive_byte = Twi_rxbuf(tmptail + 1) ' Return data from the buffer.
End Function


' ISR für Startsequenz erkannt
Usi_start_condition_isr:
$asm
PUSH R16
PUSH R20
PUSH R24
PUSH R25
PUSH R26
IN R24,&H3F
PUSH R24
$end Asm
' Code
Usi_twi_overflow_state = Usi_sl_check_address
Ddr_usi_sda = 0 ' Enable SDA as input.

' Wait for SCL to go low.
' If a Stop condition arises then leave the interrupt to prevent waiting forever.
While Pin_usi_scl = 1 And Usisr.5 = 0 ' USIPF
Wend

' Enable Overflow and Start Condition Interrupt. (Keep StartCondInt to detect RESTART)
' Set USI in Two-wire mode.
' Shift Register Clock Source = External, positive edge
Usicr = &B11111000

' Clear flags, and reset counter.
Usisr = &B11110000

$asm
POP R24
Out &H3F , R24
POP R26
POP R25
POP R24
POP R20
POP R16
$end Asm
Return

' ISR für Counteroverflow
Usi_counter_overflow_isr:
$asm
PUSH R10
PUSH R16
PUSH R20
PUSH R24
PUSH R26
IN R24,&H3F
PUSH R24
$end Asm
' Code
'Print Usi_twi_overflow_state
Select Case Usi_twi_overflow_state

' -- Address mode --
' Check address and send ACK (and next USI_SL_SEND_DATA) if OK, else reset USI.
Case Usi_sl_check_address :
Temp_usi_isr = Usidr And &HFE ' 0. Bit ausblenden falls RW gleich 1
If Temp_usi_isr = Twi_slaveaddress Then
' Read oder Write ? 1 - Master will was von uns haben
If Usidr.0 = 1 Then
Usi_twi_overflow_state = Usi_sl_send_data
Else
Usi_twi_overflow_state = Usi_sl_req_data
End If
Gosub Set_usi_to_send_ack
Else
Gosub Set_usi_to_twi_start_cond_mode
'Print "nicht ich"
End If

' -- Master write data mode --
' Check reply and goto USI_SL_SEND_DATA if OK, else reset USI.
Case Usi_sl_chk_reply_from_send_data :
' If NACK, the master does not want more data.
If Usidr = 0 Then
Gosub Set_usi_to_twi_start_cond_mode
Goto Fertig_usi_counter_overflow_isr
Else
' From here we just drop straight into USI_SL_SEND_DATA if the master sent an ACK
If Twi_txhead <> Twi_txtail Then
' Twi_txtail = (twi_txtail + 1 ) And Twi_tx_buffer_mask
Temp_usi_isr = Twi_txtail + 1
Temp_usi_isr = Temp_usi_isr And &H0F
Twi_txtail = Temp_usi_isr

Usidr = Twi_txbuf(temp_usi_isr + 1)

Else
' If the buffer is empty then:
Gosub Set_usi_to_twi_start_cond_mode
Goto Fertig_usi_counter_overflow_isr
End If

Usi_twi_overflow_state = Usi_sl_req_reply_from_send_data
Gosub Set_usi_to_send_data
End If

' Copy data from buffer to USIDR and set USI to shift byte. Next USI_SL_REQ_REPLY_FROM_SEND_DATA
Case Usi_sl_send_data :
If Twi_txhead <> Twi_txtail Then
' Twi_txtail = (twi_txtail + 1 ) And Twi_tx_buffer_mask
Temp_usi_isr = Twi_txtail + 1
Temp_usi_isr = Temp_usi_isr And &H0F
Twi_txtail = Temp_usi_isr

Usidr = Twi_txbuf(temp_usi_isr + 1)

Else
' If the buffer is empty then:
Gosub Set_usi_to_twi_start_cond_mode
Goto Fertig_usi_counter_overflow_isr
End If

Usi_twi_overflow_state = Usi_sl_req_reply_from_send_data
Gosub Set_usi_to_send_data

' Set USI to sample reply from master. Next USI_SL_CHK_REPLY_FROM_SEND_DATA
Case Usi_sl_req_reply_from_send_data :
Usi_twi_overflow_state = Usi_sl_chk_reply_from_send_data
Gosub Set_usi_to_read_ack

' -- Master read data mode --
' Set USI to sample data from master. Next USI_SL_GET_DATA_AND_SEND_ACK.
Case Usi_sl_req_data :
Usi_twi_overflow_state = Usi_sl_get_data_and_send_ack
Gosub Set_usi_to_read_data

' Copy data from USIDR and send ACK. Next USI_SL_REQ_DATA
Case Usi_sl_get_data_and_send_ack :
' Put data into Buffer
' Twi_rxhead = (twi_rxhead + 1 ) & Twi_rx_buffer_mask

Temp_usi_isr = Twi_rxhead + 1
Temp_usi_isr = Temp_usi_isr And &H0F
Twi_rxhead = Temp_usi_isr

Twi_rxbuf(temp_usi_isr + 1) = Usidr

Usi_twi_overflow_state = Usi_sl_req_data
Gosub Set_usi_to_send_ack


End Select

Fertig_usi_counter_overflow_isr:
$asm
POP R24
Out &H3F , R24
POP R26
POP R24
POP R20
POP R16
POP R10
$end Asm
Return

' Einstellungen für Read und Write

'
Set_usi_to_twi_start_cond_mode:
' Enable Start Condition Interrupt. Disable Overflow Interrupt
' Set USI in Two-wire mode. No USI Counter overflow hold.
' Shift Register Clock Source = External, positive edge
Usicr = &B10101000

'Clear all flags, except Start Cond
Usisr = &B01110000

Return

'
Set_usi_to_send_ack:
Usidr = 0 ' Prepare ACK
Ddr_usi_sda = 1 ' Set Sda As Output

' Clear all flags, except Start Cond
' Set Usi Counter To Shift 1 Bit
Usisr = &B01111110

Return

'
Set_usi_to_read_ack:
Ddr_usi_sda = 0 ' Set Sda As Intput

Usidr = 0 ' Prepare ACK

' Clear all flags, except Start Cond
' set USI counter to shift 1 bit
Usisr = &B01111110

Return

'
Set_usi_to_send_data:
Ddr_usi_sda = 1 ' Set Sda As Output

' Clear all flags, except Start Cond
' set USI to shift out 8 bits
Usisr = &B01110000

Return

'
Set_usi_to_read_data:
Ddr_usi_sda = 0 ' Set Sda As Intput

' Clear all flags, except Start Cond
' set USI to shift out 8 bits
Usisr = &B01110000

Return

'
Check_if_data_in_receive_buf:
If Twi_rxtail <> Twi_rxhead Then
Usi_twi_data_in_receive_buffer = 1
Else
Usi_twi_data_in_receive_buffer = 0
End If
Return

' hauwech
Flush_twi_buffers:
Twi_rxtail = 0
Twi_rxhead = 0
Twi_txtail = 0
Twi_txhead = 0
Return

Der Testaufbau schaut so aus:

https://www.roboternetz.de/phpBB2/album_pic.php?pic_id=1188
Die LEDs links auf dem RN-Control, sind von oben die ersten beiden für den I2C-Bus, der Rest ist für die Anzeige der Daten, die vom RN-Mega8 gesendet werden.

Der Mega8 liest die Temperatur vom DS1621, und gibt den Wert an einen PCF8574 weiter, der Tiny2313 spielt eben diesen vor, und gibt den Wert an den 6 LEDs aus.
Das Muster ist dasselbe wie auf dem RN-M8 LEDs unten.

Testprogramm auf dem RN-M8 ist dieses vom Wiki:
https://www.roboternetz.de/wissen/index.php/TWI_Praxis#Transmit_und_Receive


€dit:
Ich hab die If-Abfrage (Zeile 236-237) umgebaut, da der Slave nicht auf ein Master-Read reagiert hat, so geht das jetzt auch. (Falls was im Puffer steht)

Bluesmash
27.12.2006, 07:43
wenn ich jetzt deinen post richtig interpretiere hast du es geschaft den tiny als I2C Hardware slave zu betreiben?? oder ist dies der testaufbau den du gerade testest?

gruss bluesmash

linux_80
27.12.2006, 10:47
Das Programm funktioniert schon so wie es oben steht,
es ist aber natürlich trotzdem ein Testaufbau, denn wirklich brauchen tue ich das ja nicht ;-)

Ich weiss halt jetzt was sich Atmel da gedacht hat bei diesem USI, und wie man einem AVR dazu bringt etwas damit zu machen.

Man könnte das jetzt noch probieren zu optimieren, denn das Progamm ist ziemlich gross, und der Speicher (SRAM) ist bis auf 8 Byte belegt !
Da lässt sich sonst nicht mehr viel machen mit so einem AVR.

Es wäre evtl. noch einen Versuch wert, das ganze ohne ISR hinzubekommen. Auch das hier mit den Puffern ist nicht so ganz Zeitnah, denn wenn ein Master was will, müssen die Daten die zurückgesendet werden erst erzeugt werden, und nicht schon im Puffer stehen !


Ich würde aber sagen, dass das USI besser geeignet ist ein Slave zu sein, als einen Master nachzubilden.



Jetzt könnte ich allerdings noch das mit dem USI-SPI-Modus probieren ... :-k

Bluesmash
27.12.2006, 13:08
ist ja super das du es gschaft hast! ich werde es in den nächsten tagen auch mal testen... ich habe ja auch noch ein bisschen zeit :) aber für einen kleinen rgb led slave controller sollte es ja reichen.. :) da würde ich auch nur die empfansroutine brauchen...

gruss bluesmash

-tomas-
11.01.2009, 15:00
Ich wärme den alten Thread mal wieder auf:


Wer sich mal versuchen will, hier ist die AppNote 312 als Bascom-Version.
Man könnte das jetzt noch probieren zu optimieren, denn das Progamm ist ziemlich gross, und der Speicher (SRAM) ist bis auf 8 Byte belegt !
Da lässt sich sonst nicht mehr viel machen mit so einem AVR.


Der C-Code in der AppNote AVR312 ist eine Grotte, man kommt aber bei Interrupt gesteuertem USI-Slave an der AVR312 nicht vorbei.
Da ich bei einem Projekt einen Attiny25 verwenden möchte, habe ich den Bascom-Code mit ASM-Unterstützung auf 478Byte (23% von 2KByte) zusammengeschossen. Der 128Byte-SRAM ist nur halb voll.
... so bleibt genügend Platz für eigene Erweiterungen.

Erweiterung:
Im Code ist noch eine auskommentierte alternative Main_Loop eingefügt, die I2C-Bus-TimeOuts abfängt, wie "Slave will senden, zieht SDA auf LOW aber Master schickt keinen Takt".
Wenn keine USI-Kommunikation anliegt bzw. nach TimeOut, wird der Attiny in den Power-Down (1µA) Modus versetzt.

Weiterhin habe ich den Ringpuffer aus AVR312 gestrichen, da er für meine Zwecke (Nachbildung PCF85xx) nicht optimal ist.

Nach einer I2C-Startkondition wird:
- das erste empfangene Daten-Byte in Twi_rxbuf(1) geschrieben bzw.
- das erste zu sendende Daten-Byte aus Twi_txbuf(1) entnommen

Die AVR312-Atmel-Kommentare und den zugehörigen C-Code habe ich im Quelltext weitestgehend stehen gelassen

'/************************************************** ***************************
'
' Atmel AppNote : AVR312 - Using the USI module as a TWI slave
'
' Supported devices : All device with USI module can be used.
' The example is written for ATtiny25, ATtiny26, ATtiny2313, ATmega169
'
' Description : Example showing how to use the USI_TWI drivers;
' Loops back received data (incremented).
'
' Changes for BASCOM: I removed for general purposes the AVR312-ringbuffer in Twi_rxbuf() and Twi_txbuf()
' V1.11.9.0 the first new byte after a I2C-Start-Condition is
' saved in Twi_rxbuf(1) or
' read from Twi_txbuf(1)
'
'************************************************* ***************************/

$regfile = "ATtiny25.DAT" 'Controllertyp
$hwstack = 32 'Stack
$framesize = 20 'don't use to much num<>string conversion!!
'if you DECLARE FUNCTION / SUB increase $swstack !!!
$swstack = 0 'in this sample code no LOCAL variable inside a SUB or function!!
$crystal = 8000000 'crystal frequency

'********* parameters set by user (usi port, address and buffer size) ****************************
Const Twi_rx_buffer_size = 8 '1,2,4,8,16,32 bytes are allowed buffer sizes
Const Twi_tx_buffer_size = 8 '1,2,4,8,16,32 bytes are allowed buffer sizes

Const Twi_ownaddress = &H10 'only for compatibility AVR312, the real write adress is 2*Twi_ownaddress !!

Const Ddr_usi = Ddrb
Const Port_usi = Portb
Const Pin_usi = Pinb
Const Pin_sda = 0
Const Pin_scl = 2

Const Mcucr_power_down = &B0011_0000 'alternate main_loop set power-down mode in register MCUCR

'********* Initialise USI ****************************

'USICR => USISIE|USIOIE|USIWM1|USIWM0 USICS1|USICS0|USICLK|USITC (USICR = &H0d)
Const Usicr_start_mode = &B1010_1000 'Set USI in Two-wire mode. No USI Counter overflow prior
'to first Start Condition (potentail failure), Shift Register Clock Source = External, positive edge
Const Usicr_isr_start = &B1111_1000 'like above with Counter Overflow ISR

'USISR => USISIF|USIOIF|USIPF|USIDC USICNT4|USICNT2|USICNT1|USICNT0 (USISR = &H0e)
Const Usisr_isr_start = &B1111_0000 'set USI to shift 8 bits and clear "Start Condition Interrupt Flag"
Const Usisr_send_or_read_data = &B0111_0000 'set USI to shift 8 bits
Const Usisr_send_or_read_ack = &B0111_1110 'set USI to shift 1 bits

Dim Twi_slaveaddress As Byte

Const Twi_rx_buffer_mask = Twi_rx_buffer_size - 1 'filter mask
Dim Twi_rxbuf(twi_rx_buffer_size) As Byte 'read buffer (array)
Dim Twi_rxhead As Byte

Const Twi_tx_buffer_mask = Twi_tx_buffer_size - 1 'filter mask
Dim Twi_txbuf(twi_tx_buffer_size) As Byte 'send buffer (array)
Dim Twi_txhead As Byte

Dim Usi_twi_overflow_state As Byte 'state machine
Const Usi_start_condition_mode = &H00
Const Usi_check_address = &H01
Const Usi_send_data = &H02
Const Usi_request_reply_from_send_data = &H03
Const Usi_check_reply_from_send_data = &H04
Const Usi_request_data = &H05
Const Usi_get_data_and_send_ack = &H06

'********* Initialise ISR ****************************
On Usi_start _isr_usi_start Nosave 'Interrupt NOSAVE_ISR!
Enable Usi_start

On Usi_ovf _isr_usi_ovf Nosave 'Interrupt NOSAVE_ISR!
Enable Usi_ovf

'********* Debug ****************************
Config Portb.3 = Output 'Ein freier Pin wird als LED-Ausgang konfiguriert
' cbi portb,3 'control led off
' SBI portb,3 'control led on

'********* Main Loop Variables ****************************
Dim I As Byte
Dim Timeout As Byte 'for extended main loop, see below
Dim Usi_start_flag As Byte 'for extended main loop, see below

'********* Main Loop ****************************
Gosub Usi_twi_slave_initialise
Enable Interrupts

Do
If Twi_rxhead > 0 Then 'new bytes arrives in the input buffer

'While Usisr.usipf = 0 'wait for USIPF (Stop Condition Flag) then the last byte arrived
Main_01:
SBIS Usisr,usipf
'Wend 'USIPF cleared in next call of _isr_USI_START (SBI Usisr, usipf)
rjmp Main_01

'************* here place your code // attention: Twi_txbuf(0) -> ERROR *****************

'//this is my sample user code, you can remove it

'For I = 1 To Twi_rxhead 'check your constants: Twi_tx_buffer_size > Twi_rx_buffer_size !!
lds r24, {Twi_txhead}
TST r24, r24
BREQ Main_03 'error handler: jmp if Twi_txhead=0
Loadadr Twi_rxbuf(1) , X
Loadadr Twi_txbuf(1) , Y
Main_02:
'Twi_txbuf(i) = Twi_rxbuf(i) + 1
LD r25, X+ 'post increment
inc r25 'r25 = Twi_rxbuf(i) + 1
st Y+, r25 'post increment
'Next
DEC r24
BRNE Main_02
Main_03:

'************* end user code ************************************************** *************
'//now reset the read pointer
Twi_rxhead = 0
End If
Loop


'********* Alternate Main Loop with Power-Down of AVR and I2C-Bus-Reset after TimeOut ****************************

' Gosub Usi_twi_slave_initialise
' Enable Interrupts
' Do
' If Usi_start_flag = 1 Then 'Usi_start_flag set in _isr_USI_START
' Usi_start_flag = 0
' Timeout = 0
' While Usisr.usipf = 0 And Timeout < 200 'wait on USIPF (Stop Condition Flag) with timeout
' Waitus 100 'TiimeOut=100µs*200=20ms
' Incr Timeout
' Wend 'USIPF cleared in next call of _isr_USI_START (SBI Usisr, usipf)

' If Twi_rxhead > 0 Then
' 'Input Daten auswerten, hier nur Input+1 auf Output kopieren
' For I = 1 To Twi_rxhead 'Beachte: Index Twi_rxhead kann auch 0 sein, d.h. Twi_txbuf(0) -> ERROR
' Twi_txbuf(i) = Twi_rxbuf(i) + 1
' Next
' End If
' 'Reset TWI to SET_USI_TO_TWI_START_CONDITION_MODE() and set SDA as Input
' CBI DDR_USI, PIN_SDA 'Set SDA as input
' Usicr = Usicr_start_mode
' Usisr = Usisr_isr_start
' End If

' 'set power-down mode
' Enable Interrupts 'Paranoia
' LDI r18, Mcucr_power_down 'set powerdown
' !Out MCUCR, r18
' sleep
' Loop

'********* Initialise USI for TWI Slave mode ****************************
'---------------------------------------------------------------
'Subroutine: Usi_twi_slave_initialise
'Purpose: Initialise USI for TWI Slave mode
' set I2C Address to TWI_ownAddress
'Result:
'---------------------------------------------------------------
Usi_twi_slave_initialise:
'Flushes the TWI buffers
Twi_rxhead = 0
Twi_txhead = 0
Twi_slaveaddress = Twi_ownaddress

SBI PORT_USI, PIN_SCL 'PORT_USI |= (1<<PORT_USI_SCL) // Set SCL high
SBI PORT_USI, PIN_SDA 'PORT_USI |= (1<<PORT_USI_SDA) // Set SDA high
SBI DDR_USI, PIN_SCL 'DDR_USI |= (1<<PORT_USI_SCL) // Set SCL as output
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input

Usicr = Usicr_start_mode 'USICR = Usicr_start_mode // Enable Start Condition Interrupt. Disable Overflow Interrupt
Usisr = Usisr_isr_start 'USISR = Usisr_isr_start // Clear all flags and reset overflow counter

Usi_twi_overflow_state = Usi_start_condition_mode 'USI_TWI_Overflow_State = Usi_start_condition_mode
Return


'********* ISR USI_START (Interrupt Service Routine )****************************
'---------------------------------------------------------------
'Subroutine: _isr_USI_START
'Purpose: Usi start condition ISR
' Detects the USI_TWI Start Condition and intialises the USI
' for reception of the "TWI Address" packet.
'Note: Start Condition Interrupt Flag will _only_ be cleared by writing
' a logical one to the USISIF bit.
' A start condition interrupt will wakeup the processor from all sleep modes.
' Corrected the STOP CONDITION BUG in AVR312 => while ((PIN_USI & (1<<PORT_USI_SCL)) & !(tmpUSISR & (1<<USIPF)))
'Stack use: 2 byte registers + 2 byte address
'---------------------------------------------------------------
_isr_usi_start:
push r24
in r24, SREG
push r24

LDI r24, 1
sts {Usi_start_flag}, r24 'detect _isr_USI_START in main loop

LDI r24, Usi_check_address '//Set default starting conditions for new TWI package
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = Usi_check_address
CBI Ddr_usi,Pin_sda 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input

_isr_usi_loop: 'while (PIN_USI & (1<<PORT_USI_SCL))
IN R24, Pin_usi '//wait until SCL is LOW, avoid counting the first level change
sbrs r24, Pin_sda ' if (PIN_USI & (1<<PORT_USI_SDA))...
rjmp _isr_usi_no_stop
ldi r24, Usicr_start_mode '//... a Stop condition arises and ...
!out USICR, r24 'Usicr =Usicr_start_mode
rjmp _isr_usi_end '//... then leave the interrupt to prevent waiting forever.
_isr_usi_no_stop:
sbrc r24, Pin_scl
rjmp _isr_usi_loop

ldi r24, Usicr_isr_start 'USICR = Usicr_isr_start
!Out USICR , R24
_isr_usi_end:
ldi r24, Usisr_isr_start 'USISR = Usisr_isr_start
!Out USISR , R24

pop r24
!Out SREG , R24
pop r24
Return 'RETI


'********* ISR USI_OVF (Interrupt Service Routine )****************************
'---------------------------------------------------------------
'Subroutine: _isr_USI_OVF
'Purpose: USI counter overflow ISR
' Handels all the comunication.
' Is disabled only when waiting for new Start Condition.
'Stack use: 5 byte registers + 2 byte address
'---------------------------------------------------------------
_isr_usi_ovf:
push r1
in r1, SREG
push r1
eor r1, r1 'R1 = 0 !
push r24
push r30
push r31

'switch (USI_TWI_Overflow_State)
lds r24, {Usi_twi_overflow_state}

'// ---------- Address mode ----------
'// Check address and send ACK (and next USI_SLAVE_SEND_DATA) if OK, else reset USI.
'case USI_SLAVE_CHECK_ADDRESS:
Isr_ovf_slave_check_address: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_check_address 'case Usi_check_address ?
brNe Isr_ovf_check_rep_from_send_data 'no -> jmp to next case

'if ((USIDR == 0) || (( USIDR>>1 ) == Twi_slaveaddress)) 'check also (TWI-ADDRESS==0)
in r24, USIDR
!and r24, r24
breq Isr_ovf_get_valid_address 'we get a read/write address
in r24, USIDR
lsr r24
lds r30, {Twi_slaveaddress}
cp r24, r30
brne Isr_ovf_set_usi_start_cond_mode 'get a invalid address -> SET_USI_TO_TWI_START_CONDITION_MODE() + BREAK

Isr_ovf_get_valid_address:
'if ( USIDR & &H01 )
sbis USIDR, 0
rjmp Isr_ovf_get_write_address

'//we get a read address
ldi r24, Usi_send_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_SEND_DATA
'//reset pointer
sts {Twi_txhead}, r1 'Twi_txhead=0
rjmp Isr_ovf_set_usi_to_send_ack 'SET_USI_TO_SEND_ACK() + BREAK

'else //we get a read address
Isr_ovf_get_write_address:
ldi r24, Usi_request_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_DATA
'//reset pointer
sts {Twi_rxhead}, r1 'Twi_rxhead=0
RJMP Isr_ovf_set_usi_to_send_ack 'SET_USI_TO_SEND_ACK() + BREAK

'// ----- Master write data mode ------
'// Check reply and goto USI_SLAVE_SEND_DATA if OK, else reset USI.
'case USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA:
Isr_ovf_check_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_check_reply_from_send_data 'case Usi_check_reply_from_send_data ?
brNe Isr_ovf_slave_send_data 'no -> jmp to next case

'if ( USIDR ) // If NACK, the master does not want more data.
in r24, USIDR
!and r24, r24
breq Isr_ovf_req_rep_from_send_data_1 'jmp slave_send_data if Master send a ACK (send next byte)

'SET_USI_TO_TWI_START_CONDITION_MODE()
Isr_ovf_set_usi_start_cond_mode:
ldi r24, Usicr_start_mode
!out USICR, r24 'USICR = Usicr_start_mode
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR = Usisr_send_or_read_data

LDI r24, Usi_start_condition_mode
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = Usi_start_condition_mode

rjmp ISR_OVF_end 'break

'case USI_SLAVE_SEND_DATA
Isr_ovf_slave_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_send_data 'case Usi_send_data ?
brNe Isr_ovf_req_rep_from_send_data 'no -> jmp to next case

Isr_ovf_req_rep_from_send_data_1:
'// Get data from Buffer
lds r24, {Twi_txhead} '// Pointer Twi_txhead
Loadadr Twi_txbuf(1) , Z 'R31:R30
ldi r31, &H00 'paranoia
add r30, r24 'add index
adc r31, r1 'add carry
ld r24, Z
!out USIDR, r24 'USIDR = TWI_TxBuf[Twi_txhead]

'//incr and mask pointer
lds r24, {Twi_txhead}
subi r24, &HFF ' incr Twi_txhead
andi r24, Twi_tx_buffer_mask ' mask pointer
sts {Twi_txhead}, r24 'Twi_txhead = ( Twi_txhead + 1 ) & TWI_TX_BUFFER_MASK

ldi r24, Usi_request_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA

'SET_USI_TO_SEND_DATA()'
SBI DDR_USI, PIN_SDA 'DDR_USI |= (1<<PORT_USI_SDA) // Set SDA as output
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR=Usisr_send_or_read_data
rjmp ISR_OVF_end 'break

'// Set Usi To Sample Reply From Master. Next Usi_slave_check_reply_from_send_data
'case USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA:
Isr_ovf_req_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_request_reply_from_send_data 'case Usi_request_reply_from_send_data ?
brNe Isr_ovf_slave_request_data 'no -> jmp to next case

ldi r24, Usi_check_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'Usi_twi_overflow_state = Usi_slave_check_reply_from_send_data

'SET_USI_TO_READ_ACK()'
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input
!out USIDR, r1 'USIDR = 0
ldi r24, Usisr_send_or_read_ack
!out USISR, r24 'USISR = Usisr_send_or_read_ack
rjmp ISR_OVF_end 'break

'// ----- Master read data mode ------
'// Set USI to sample data from master. Next USI_SLAVE_GET_DATA_AND_SEND_ACK.
'case USI_SLAVE_REQUEST_DATA:
Isr_ovf_slave_request_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_request_data 'case Usi_request_data ?
brNe Isr_ovf_get_data_send_ack 'no -> jmp to next case

ldi r24, Usi_get_data_and_send_ack
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_GET_DATA_AND_SEND_ACK'

'SET_USI_TO_READ_DATA()'
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR=Usisr_send_or_read_data
rjmp ISR_OVF_end 'break

'// Copy data from USIDR and send ACK. Next USI_SLAVE_REQUEST_DATA
'case USI_SLAVE_GET_DATA_AND_SEND_ACK:
Isr_ovf_get_data_send_ack: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_get_data_and_send_ack 'case Usi_get_data_and_send_ack ?
BRNE Isr_ovf_end 'no -> jmp END

'// Put data into Buffer
Loadadr Twi_rxbuf(1) , Z 'R31:R30
ldi r31, &H00 'paranoia
lds r24, {Twi_rxhead}
add r30, r24 'add Twi_rxhead to address Twi_rxbuf(1)
adc r31, r1 'add carry flag -> Z=TWI_RxBuf[Twi_rxhead]
IN r24, USIDR
st Z, r24 'TWI_RxBuf[Twi_rxhead] = USIDR

'//incr and mask pointer
lds r24, {Twi_rxhead}
subi r24, &HFF 'INCR Twi_rxhead
andi r24, Twi_rx_buffer_mask
sts {Twi_rxhead}, r24 'Twi_rxhead = ( Twi_rxhead + 1 ) & TWI_RX_BUFFER_MASK

ldi r24, Usi_request_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_DATA'

'SET_USI_TO_SEND_ACK()
Isr_ovf_set_usi_to_send_ack: 'jmp from case USI_SLAVE_CHECK_ADDRESS
!out USIDR, r1 'USIDR = 0 //Prepare ACK
SBI DDR_USI, PIN_SDA 'DDR_USI |= (1<<PORT_USI_SDA) // Set SDA as output
ldi r24, Usisr_send_or_read_ack
!out USISR, r24 'USISR = Usisr_send_or_read_ack

Isr_ovf_end:
pop r31
pop r30
pop r24
pop r1
!out SREG, r1
pop r1
reti
Return

andreasf
26.09.2009, 07:31
Hallo Thomas
ich habe vor kurzem Deinen Beitrag hier gefunden da ich genau das gesucht hatte , die I2C Verbindung zwischen AVR Kontrollern, war auch das erste Programm was auf Anhieb funktionierte.

Habe aber trotzdem ein Problem damit:
Daten werden in Twi_rxbuf() geschrieben und aus Twi_txbuf() gelesen,
wenn ich aber vorrangig nur Daten lesen will muß ich erstmal ein Byte mit I2Csend (egal was) senden um nacher mit i2creceive ein Byte (welches im Tiny25 aufbereitet wurde und in Twi_txbuf() geschrieben wurde) zu holen.
Die komunikation funktioniert, aber warum muß ich erst was senden?

Da ich mich erst seit kurzem mit AVR Kontrollern beschäftige und die I2C Verbindung zu Slave Mikrokontrollern auch nicht ganz einfach ist habe ich noch Probleme Dein Programm soweit nachzuvollziehen daß ich die Probleme selbst beheben kann.
Ich hoffe Du "hörst" mich noch, der Thread ist ja schon ziemlich alt

linux_80
26.09.2009, 22:13
Hallo,

das Problem bei der Interruptbasierten Versendung ist, dass dies asynchron geschieht. DH. der Slave weiss nicht wann er was Senden soll wenn der Master was anfordert.
Einfach was in den Ausgangspuffer schreiben ist auch nicht unbedingt die Lösung, weil man nicht weiss ob es überhaupt abgeholt wird.

Du kannst aber mal rumprobieren, in der Main-Loop gibts eig. nur eine If-Abfrage, in die nur reingesprungen wird, wenn etwas Empfangen wurde.
Den Ausgangspuffer nur so gross, wie die Anzahl Bytes die auf einmal zum Master übertragen werden sollen. Damit es keine "alten" Daten gibt. Dann Regelmässig diese mit den entsprehenden Daten füllen, aber nur überschreiben, wenn der Master nicht grad am lesen des Puffers ist.

HTH

DaHza
08.08.2013, 18:51
Hallo,
auch ich habe noch eine Frage:



Den Ausgangspuffer nur so gross, wie die Anzahl Bytes die auf einmal zum Master übertragen werden sollen. Damit es keine "alten" Daten gibt. Dann Regelmässig diese mit den entsprehenden Daten füllen, aber nur überschreiben, wenn der Master nicht grad am lesen des Puffers ist.

Aber es wird doch nur das erste Byte aus dem Array gesendet, oder habe ich im Assemblercode (der mir zugegebenermaßen nicht wirklich vertraut ist) irgendwas übersehen?


'// ----- Master write data mode ------
'// Check reply and goto USI_SLAVE_SEND_DATA if OK, else reset USI.
'case USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA:
Isr_ovf_check_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_check_reply_from_send_data 'case Usi_check_reply_from_send_data ?
brNe Isr_ovf_slave_send_data 'no -> jmp to next case

'if ( USIDR ) // If NACK, the master does not want more data.
in r24, USIDR
!and r24, r24
breq Isr_ovf_req_rep_from_send_data_1 'jmp slave_send_data if Master send a ACK (send next byte)

'SET_USI_TO_TWI_START_CONDITION_MODE()
Isr_ovf_set_usi_start_cond_mode:
ldi r24, Usicr_start_mode
!out USICR, r24 'USICR = Usicr_start_mode
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR = Usisr_send_or_read_data

LDI r24, Usi_start_condition_mode
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = Usi_start_condition_mode

rjmp ISR_OVF_end 'break

'case USI_SLAVE_SEND_DATA
Isr_ovf_slave_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_send_data 'case Usi_send_data ?
brNe Isr_ovf_req_rep_from_send_data 'no -> jmp to next case

Isr_ovf_req_rep_from_send_data_1:
'// Get data from Buffer
lds r24, {Twi_txhead} '// Pointer Twi_txhead
Loadadr Twi_txbuf(1) , Z 'R31:R30
ldi r31, &H00 'paranoia
add r30, r24 'add index
adc r31, r1 'add carry
ld r24, Z
!out USIDR, r24 'USIDR = TWI_TxBuf[Twi_txhead]

'//incr and mask pointer
lds r24, {Twi_txhead}
subi r24, &HFF ' incr Twi_txhead
andi r24, Twi_tx_buffer_mask ' mask pointer
sts {Twi_txhead}, r24 'Twi_txhead = ( Twi_txhead + 1 ) & TWI_TX_BUFFER_MASK

ldi r24, Usi_request_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA

'SET_USI_TO_SEND_DATA()'
SBI DDR_USI, PIN_SDA 'DDR_USI |= (1<<PORT_USI_SDA) // Set SDA as output
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR=Usisr_send_or_read_data
rjmp ISR_OVF_end 'break

'// Set Usi To Sample Reply From Master. Next Usi_slave_check_reply_from_send_data
'case USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA:
Isr_ovf_req_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_request_reply_from_send_data 'case Usi_request_reply_from_send_data ?
brNe Isr_ovf_slave_request_data 'no -> jmp to next case

ldi r24, Usi_check_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'Usi_twi_overflow_state = Usi_slave_check_reply_from_send_data

'SET_USI_TO_READ_ACK()'
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input
!out USIDR, r1 'USIDR = 0
ldi r24, Usisr_send_or_read_ack
!out USISR, r24 'USISR = Usisr_send_or_read_ack
rjmp ISR_OVF_end 'break


Loadadr Twi_txbuf(1) , Z
bedeutet doch, dass immer nur das erste Byte gelesen und gesendet wird (wegen TWI_txbuf(1)).
Könnte man da nicht eine Variable einsetzen, die im Array hochzählt, z.B. TWI_txhead ?
TWI_txhead wird doch immer wieder um 1 erhöht. Ich verstehe aber nicht was danach geschieht.
Danach wird doch gesendet, und das ACK oder NACK abgewartet?
Der Slave reagiert aber nicht darauf sondern beendet die ISR, indem er nach ISR_OVF_end springt.

Hab ich das so richtig verstanden und wie sähe es aus, wenn man nach erfolgreichem Abfragen des ACK
wieder in die Senderoutine springt, nur mit erhöhtem Pointer/TWI_txhead?

Leider kenne ich mich nicht so gut mit Assembler aus, es wäre nett wenn mir jemand ein paar Tipps geben könnte.
Paul

DaHza
13.08.2013, 11:36
Keiner eine Idee?

DaHza
18.11.2013, 16:37
Hallo!
ich hab es jetzt geschafft, den Code zu verstehen (yeaah!).
Auch wenn es ein bisschen gedauert hat, der Attiny44 sendet und empfängt jetzt.

Der Assembler-Code ist weitgehend gleich geblieben, belegt also immer noch weniger als 500 Byte (inkl. Testprogramm in Basic 522 Bytes).
Der einzige Unterschied ist, dass es nur noch eine TX/RX-Variable gibt anstatt einem Array und nach jedem erfolgreichen Empfang eine Routine aufgerufen wird, in der die Variable sofort verarbeitet werden muss!

Im wesentlichen ist der Vorteil (für mich), dass man nicht ständig auf empfangene Werte prüfen muss oder ob man das Sendearray gerade füllen darf, sondern empfangene/zu sendende Werte selbst aus/in Arrays usw. holen/füllen kann und dass der Controller sofort benachrichtigt wird, wenn er Werte erhält, was für meine Anwendung als Slave besser ist.

Den Assembler Code habe ich wegen der Übersichtlichkeit aufgeteilt und füge ihn mit "$include" ein.

Haupteil:

'################################################# #####
'Author = Paul B., I just changed the Code from roboternetz.de
'https://www.roboternetz.de/community/threads/22452-USI-interface-an-tiny-2313
'
'Description: I2C-slave with USI-Interface
'Notes: You must set the Usi-port and the pin-number of Sda and Scl
' The code was tested with a frequency of 100kHz and 400kHz
' it works in interrupt mode, so the labels below are called when
' the master wants to transmit or receive Data
'################################################# #####
$regfile = "attiny44.dat"
$crystal = 8000000
$hwstack = 40
$swstack = 32
$framesize = 60


'parameters set by user (usi port, address and buffer size)
'################################################# #########
Const Twi_ownaddress = &H56 'this is the real write-adress, not 2*twi_ownadress as in the previous code
'It must be a number which is divisible by 2 without a leftover

Const Ddr_usi = Ddra
Const Port_usi = Porta
Const Pin_usi = Pina
Const Pin_sda = 6
Const Pin_scl = 4

'I saved the code as an extra file, so i can include it
$include "USI_Slave_neededcodebeforemainloop.bas"


'My Sample Code

Config Porta.0 = Output
Led1 Alias Porta.0
Led1 = 0
Config Porta.1 = Output
Led2 Alias Porta.1
Led2 = 0

Dim I As Byte
I = 0

'############# Main Loop #############
Gosub Usi_twi_slave_initialise
'Interrupts must be enabled as the code works in interrupt mode
Enable Interrupts

Do
Waitms 500
'Usi_start_flag is set when I2Cstart was received
Loop
End

'Routines for USI, must be included after the main loop
$include "USI_Slave_routines.bas"


'If the master wants to receive a byte, you must store it in Twi_tx
I2c_master_needs_byte:
Twi_tx = I
I = I + 1
Toggle Led2
Goto After_master_needs_byte

'If the master transmitted a byte, it will be stored in Twi_rx
I2c_master_has_byte:
If Twi_rx = 16 Then Toggle Led1
Goto After_master_has_byte

Initialisierung und setzten der Konstanten (USI_Slave_neededcodebeforemainloop.bas):

$nocompile
'********* Initialise USI ****************************

'USICR => USISIE|USIOIE|USIWM1|USIWM0 USICS1|USICS0|USICLK|USITC (USICR = &H0d)
Const Usicr_start_mode = &B1010_1000 'Set USI in Two-wire mode. No USI Counter overflow prior
'to first Start Condition (potentail failure), Shift Register Clock Source = External, positive edge
Const Usicr_isr_start = &B1111_1000 'like above with Counter Overflow ISR

'USISR => USISIF|USIOIF|USIPF|USIDC USICNT4|USICNT2|USICNT1|USICNT0 (USISR = &H0e)
Const Usisr_isr_start = &B1111_0000 'set USI to shift 8 bits and clear "Start Condition Interrupt Flag"
Const Usisr_send_or_read_data = &B0111_0000 'set USI to shift 8 bits
Const Usisr_send_or_read_ack = &B0111_1110 'set USI to shift 1 bits

Dim Twi_slaveaddress As Byte

Dim Twi_rx As Byte
Dim Twi_tx As Byte

Dim Usi_twi_overflow_state As Byte 'state machine
Const Usi_start_condition_mode = &H00
Const Usi_check_address = &H01
Const Usi_send_data = &H02
Const Usi_request_reply_from_send_data = &H03
Const Usi_check_reply_from_send_data = &H04
Const Usi_request_data = &H05
Const Usi_get_data_and_send_ack = &H06

'********* Initialise ISR ****************************
On Usi_start _isr_usi_start Nosave 'Interrupt NOSAVE_ISR!
Enable Usi_start

On Usi_ovf _isr_usi_ovf Nosave 'Interrupt NOSAVE_ISR!
Enable Usi_ovf

'********* Main Loop Variables ****************************
Dim Usi_start_flag As Byte

Routinen (USI_Slave_routines.bas):

$nocompile
'********* Initialise USI for TWI Slave mode ****************************
'---------------------------------------------------------------
'Subroutine: Usi_twi_slave_initialise
'Purpose: Initialise USI for TWI Slave mode
' set I2C Address to TWI_ownAddress
'Result:
'---------------------------------------------------------------
Usi_twi_slave_initialise:
Twi_slaveaddress = Twi_ownaddress / 2

SBI PORT_USI, PIN_SCL 'PORT_USI |= (1<<PORT_USI_SCL) // Set SCL high
SBI PORT_USI, PIN_SDA 'PORT_USI |= (1<<PORT_USI_SDA) // Set SDA high
SBI DDR_USI, PIN_SCL 'DDR_USI |= (1<<PORT_USI_SCL) // Set SCL as output
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input

Usicr = Usicr_start_mode 'USICR = Usicr_start_mode // Enable Start Condition Interrupt. Disable Overflow Interrupt
Usisr = Usisr_isr_start 'USISR = Usisr_isr_start // Clear all flags and reset overflow counter

Usi_twi_overflow_state = Usi_start_condition_mode 'USI_TWI_Overflow_State = Usi_start_condition_mode
Return


'********* ISR USI_START (Interrupt Service Routine )****************************
'---------------------------------------------------------------
'Subroutine: _isr_USI_START
'Purpose: Usi start condition ISR
' Detects the USI_TWI Start Condition and intialises the USI
' for reception of the "TWI Address" packet.
'Note: Start Condition Interrupt Flag will _only_ be cleared by writing
' a logical one to the USISIF bit.
' A start condition interrupt will wakeup the processor from all sleep modes.
' Corrected the STOP CONDITION BUG in AVR312 => while ((PIN_USI & (1<<PORT_USI_SCL)) & !(tmpUSISR & (1<<USIPF)))
'Stack use: 2 byte registers + 2 byte address
'---------------------------------------------------------------
_isr_usi_start:
push r24
in r24, SREG
push r24

'Usi_start_flag ist 1 wenn eine start condition empfangen wurde
LDI r24, 1
sts {Usi_start_flag}, r24 'detect _isr_USI_START in main loop

'Die State Maschine wird auf CheckAdress gesetzt damit in der_isr_usi_ovf die Adresse geprüft wird
LDI r24, Usi_check_address '//Set default starting conditions for new TWI package
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = Usi_check_address
CBI Ddr_usi,Pin_sda 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input

_isr_usi_loop: 'while (PIN_USI & (1<<PORT_USI_SCL))
IN R24, Pin_usi '//wait until SCL is LOW, avoid counting the first level change
sbrs r24, Pin_sda ' if (PIN_USI & (1<<PORT_USI_SDA))...
rjmp _isr_usi_no_stop
'wenn ein Stop empfangen wird, USI zurücksetzen und ISR verlassen
ldi r24, Usicr_start_mode '//... a Stop condition arises and ...
!out USICR, r24 'Usicr =Usicr_start_mode
rjmp _isr_usi_end '//... then leave the interrupt to prevent waiting forever.
'ansonsten wird USI auf start gesetzt -> Das empfangene Adress Byte wird in das USIDR geschoben
_isr_usi_no_stop:
sbrc r24, Pin_scl
rjmp _isr_usi_loop

ldi r24, Usicr_isr_start 'USICR = Usicr_isr_start
!Out USICR , R24
_isr_usi_end:
ldi r24, Usisr_isr_start 'USISR = Usisr_isr_start
!Out USISR , R24

pop r24
!Out SREG , R24
pop r24
Return 'RETI


'********* ISR USI_OVF (Interrupt Service Routine )****************************
'---------------------------------------------------------------
'Subroutine: _isr_USI_OVF
'Purpose: USI counter overflow ISR
' Handels all the comunication.
' Is disabled only when waiting for new Start Condition.
'Stack use: 5 byte registers + 2 byte address
'---------------------------------------------------------------
_isr_usi_ovf:
push r1
in r1, SREG
push r1
eor r1, r1 'R1 = 0 !
push r24
push r30
push r31

'switch (USI_TWI_Overflow_State)
lds r24, {Usi_twi_overflow_state}

'// ---------- Address mode ----------
'// Check address and send ACK (and next USI_SLAVE_SEND_DATA) if OK, else reset USI.
'case USI_SLAVE_CHECK_ADDRESS:
'prüfen, ob state machine auf CheckAdress steht
Isr_ovf_slave_check_address: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_check_address 'case Usi_check_address ?
brNe Isr_ovf_check_rep_from_send_data 'ansonsten nächsten status prüfen

'if ((USIDR == 0) || (( USIDR>>1 ) == Twi_slaveaddress)) 'check also (TWI-ADDRESS==0)
in r24, USIDR
!and r24, r24
breq Isr_ovf_get_valid_address 'we get a read/write address
in r24, USIDR 'Die Soll- und ist-Adresse werden in r30 bzw r24 geladen...
lsr r24
lds r30, {Twi_slaveaddress}
cp r24, r30 '...dort verglichen, wenn sie ungleich sind -> reset usi, exit isr ; ansonsten gehts weiter
brne Isr_ovf_set_usi_start_cond_mode 'get a invalid address -> SET_USI_TO_TWI_START_CONDITION_MODE() + BREAK

Isr_ovf_get_valid_address:
'if ( USIDR & &H01 )
sbis USIDR, 0
rjmp Isr_ovf_get_write_address

'//we get a read address
ldi r24, Usi_send_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_SEND_DATA
rjmp Isr_ovf_set_usi_to_send_ack 'SET_USI_TO_SEND_ACK() + BREAK

'//we get a write address
Isr_ovf_get_write_address:
ldi r24, Usi_request_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_DATA
RJMP Isr_ovf_set_usi_to_send_ack 'SET_USI_TO_SEND_ACK() + BREAK





'// ----- Master write data mode ------
'// Check reply and goto USI_SLAVE_SEND_DATA if OK, else reset USI.
'case USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA:
Isr_ovf_check_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_check_reply_from_send_data 'case Usi_check_reply_from_send_data ?
brNe Isr_ovf_slave_send_data 'ansonsten nächsten status prüfen

'if ( USIDR ) // If NACK, the master does not want more data.
in r24, USIDR
!and r24, r24
breq Isr_ovf_req_rep_from_send_data_1 'jmp slave_send_data if Master send a ACK (send next byte)

'SET_USI_TO_TWI_START_CONDITION_MODE()
Isr_ovf_set_usi_start_cond_mode:
ldi r24, Usicr_start_mode
!out USICR, r24 'USICR = Usicr_start_mode
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR = Usisr_send_or_read_data

LDI r24, Usi_start_condition_mode
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = Usi_start_condition_mode

rjmp ISR_OVF_end 'break




'case USI_SLAVE_SEND_DATA
Isr_ovf_slave_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_send_data 'case Usi_send_data ?
brNe Isr_ovf_req_rep_from_send_data 'no -> jmp to next case

Isr_ovf_req_rep_from_send_data_1:

'zu sendendes Datenbyte muss in twi_tx abgelegt werden
push r1
push r24
in r24, SREG
push r24
push r30
push r31
Goto I2c_master_needs_byte
After_master_needs_byte:
pop r31
pop r30
pop r24
!out SREG, r24
pop r24
pop r1

'// Get data from Buffer
'clr r24
Loadadr Twi_tx , Z 'R31:R30
ldi r31, &H00 'paranoia
adc r31, r1 'add carry
ld r24, Z
!out USIDR, r24 'USIDR = TWI_Tx

ldi r24, Usi_request_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA

'SET_USI_TO_SEND_DATA()'
SBI DDR_USI, PIN_SDA 'DDR_USI |= (1<<PORT_USI_SDA) // Set SDA as output
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR=Usisr_send_or_read_data
rjmp ISR_OVF_end 'break





'// Set Usi To Sample Reply From Master. Next Usi_slave_check_reply_from_send_data
'case USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA:
Isr_ovf_req_rep_from_send_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_request_reply_from_send_data 'case Usi_request_reply_from_send_data ?
brNe Isr_ovf_slave_request_data 'no -> jmp to next case

ldi r24, Usi_check_reply_from_send_data
sts {Usi_twi_overflow_state}, r24 'Usi_twi_overflow_state = Usi_slave_check_reply_from_send_data

'SET_USI_TO_READ_ACK()'
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input
!out USIDR, r1 'USIDR = 0
ldi r24, Usisr_send_or_read_ack
!out USISR, r24 'USISR = Usisr_send_or_read_ack
rjmp ISR_OVF_end 'break





'// ----- Master read data mode ------
'// Set USI to sample data from master. Next USI_SLAVE_GET_DATA_AND_SEND_ACK.
'case USI_SLAVE_REQUEST_DATA:
Isr_ovf_slave_request_data: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_request_data 'case Usi_request_data ?
brNe Isr_ovf_get_data_send_ack 'no -> jmp to next case

ldi r24, Usi_get_data_and_send_ack
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_GET_DATA_AND_SEND_ACK'

'SET_USI_TO_READ_DATA()'
CBI DDR_USI, PIN_SDA 'DDR_USI &= ~(1<<PORT_USI_SDA) // Set SDA as input
ldi r24, Usisr_send_or_read_data
!out USISR, r24 'USISR=Usisr_send_or_read_data
rjmp ISR_OVF_end 'break




'// Copy data from USIDR and send ACK. Next USI_SLAVE_REQUEST_DATA
'case USI_SLAVE_GET_DATA_AND_SEND_ACK:
Isr_ovf_get_data_send_ack: 'r24 = Usi_twi_overflow_state
cpi r24, Usi_get_data_and_send_ack 'case Usi_get_data_and_send_ack ?
BRNE Isr_ovf_end 'no -> jmp END

'// Put data into Buffer
Loadadr Twi_rx , Z 'R31:R30
ldi r31, &H00 'paranoia
adc r31, r1 'add carry flag
IN r24, USIDR
st Z, r24 'TWI_Rx = USIDR
'clr r24

ldi r24, Usi_request_data
sts {Usi_twi_overflow_state}, r24 'USI_TWI_Overflow_State = USI_SLAVE_REQUEST_DATA'

'empfangenes Datenbyte liegt in Twi_rx
push r1
push r24
in r24, SREG
push r24
push r30
push r31
Goto I2c_master_has_byte
After_master_has_byte:
pop r31
pop r30
pop r24
!out SREG, r24
pop r24
pop r1

'SET_USI_TO_SEND_ACK()
Isr_ovf_set_usi_to_send_ack: 'jmp from case USI_SLAVE_CHECK_ADDRESS
!out USIDR, r1 'USIDR = 0 //Prepare ACK
SBI DDR_USI, PIN_SDA 'DDR_USI |= (1<<PORT_USI_SDA) // Set SDA as output
ldi r24, Usisr_send_or_read_ack
!out USISR, r24 'USISR = Usisr_send_or_read_ack

Isr_ovf_end:
pop r31
pop r30
pop r24
pop r1
!out SREG, r1
pop r1
reti
Return