Code:
'/*****************************************************************************
'
' 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
Lesezeichen