Anpassung mit Interruptroutine
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:
Code:
'######################################################
'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):
Code:
$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):
Code:
$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