Ich wärme den alten Thread mal wieder auf:

Zitat Zitat von linux_80
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
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