1

I am attempting to write an I2C to SPI bridge using an attiny84. The problem that I am running into is that the I2C seems to start and stop working intermittently. Sometimes my interrupt functions will be triggered and sometimes they just won't.

I have quadruple checked my connections, tried different chips, and different master devices and it keeps happening. Every time I think it has started working it breaks again for no reason.

I am using the USI TWI slave library that TinyWireS is based on. Any help is greatly appreciated.

#define F_CPU 8000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <avr/interrupt.h>

#include "SoftSPI.h"

extern "C" {
#include "USI_TWI_Slave.h"
}


#define I2C_ADDR 0x28

#define MODE_COMMAND 0
#define MODE_TRANSMIT 1
#define MODE_CONFIGURE_SPI 2

#define MOSI PA0
#define MISO PA1
#define SCK  PA2

#define CS_DDR DDRA
#define CS_PORT PORTA

#define BUFFER_SIZE 32

SoftSPI SPI(MOSI, MISO, SCK);

char buffer_in[BUFFER_SIZE];
char buffer_out[BUFFER_SIZE];
uint8_t buffer_in_index = 0;
uint8_t buffer_out_index = 0;
uint8_t buffer_out_length = 0;
uint8_t CS = 0x00;

uint8_t mode = MODE_COMMAND;

uint8_t received = 0;

void I2C_received(uint8_t bytes_recieved)
{
    received = usiTwiReceiveByte();
    for(int i = 0; i < bytes_recieved; i++)
    {
        uint8_t received_data = usiTwiReceiveByte();
        switch(mode)
        {
            case MODE_COMMAND:
                if(received_data >= 0x01 && received_data <= 0x0f)
                {
                    CS = received_data;
                    mode = MODE_TRANSMIT;
                }else if(received_data == 0xf0)
                {
                    mode = MODE_CONFIGURE_SPI;
                }
                break;
            case MODE_TRANSMIT:
                if(buffer_in_index < BUFFER_SIZE)
                {
                    buffer_in[buffer_in_index] = received_data;
                    buffer_in_index++;
                }
                break;
            case MODE_CONFIGURE_SPI:
                //TODO
                break;
        }
    }
    mode = MODE_COMMAND;
}
void I2C_requested()
{
    usiTwiTransmitByte(received);
    if(buffer_out_index <= buffer_out_length)
    {
        usiTwiTransmitByte(buffer_out[buffer_out_index]);
    }
    else if(buffer_out_index <= BUFFER_SIZE)
    {
        usiTwiTransmitByte(0);
    }
    else if(buffer_out_index > BUFFER_SIZE)
    {
        buffer_out_index = 0;
        usiTwiTransmitByte(buffer_out[buffer_out_index]);
    }
    buffer_out_index++;
}

int main ()
{   
    // init I2C
    usiTwiSlaveInit(I2C_ADDR);

    // set received/requested callbacks
    usi_onRequestPtr = I2C_requested;
    usi_onReceiverPtr = I2C_received;

    // init CS pins
    CS_DDR |= 0x0F;
    CS_PORT |= 0x0F;

    // init software SPI
    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV2);

    // enable interrupts
    sei();

    //clear buffer
    for(int i = 0; i < BUFFER_SIZE; i++)
    {
        buffer_out[i] = 0;
    }
    while(true)
    {
        //cli();
        for(uint8_t i = 0; i < buffer_in_index; i++)
        {
            buffer_out[i] = SPI.transfer(buffer_in[i]);
            buffer_in[i] = 0;
            buffer_out_length = i;
        }
        buffer_in_index = 0;
        _delay_ms(50);
        //sei();
    }
}
/*==============================================================================================================*

    @file     USI_TWI_Slave.h
    @license  MIT (c) 2016 Nadav Matalon

    ATtiny I2C Slave Library

    Ver. 1.0.0 - First release (1.12.16)
 
 *===============================================================================================================*
    LICENSE
 *===============================================================================================================*
 
    The MIT License (MIT)
    Copyright (c) 2016 Nadav Matalon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
    documentation files (the "Software"), to deal in the Software without restriction, including without
    limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial
    portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
    LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 *==============================================================================================================*/


#ifndef USI_TWI_Slave_h
#define USI_TWI_Slave_h

#include <stdbool.h>

void    usiTwiSlaveInit( uint8_t );
void    usiTwiTransmitByte( uint8_t );
uint8_t usiTwiReceiveByte( void );
bool    usiTwiDataInReceiveBuffer( void );
void    (*_onTwiDataRequest)(void);
bool    usiTwiDataInTransmitBuffer(void);
uint8_t usiTwiAmountDataInReceiveBuffer(void);
void    (*usi_onRequestPtr)(void);
void    (*usi_onReceiverPtr)(uint8_t);

#ifndef TWI_RX_BUFFER_SIZE
#define TWI_RX_BUFFER_SIZE (32)
#endif

#define TWI_RX_BUFFER_MASK (TWI_RX_BUFFER_SIZE - 1)

#if (TWI_RX_BUFFER_SIZE & TWI_RX_BUFFER_MASK)
#error TWI RX buffer size is not a power of 2
#endif

#ifndef TWI_TX_BUFFER_SIZE
#define TWI_TX_BUFFER_SIZE (32)
#endif

#define TWI_TX_BUFFER_MASK (TWI_TX_BUFFER_SIZE - 1)

#if (TWI_TX_BUFFER_SIZE & TWI_TX_BUFFER_MASK)
#error TWI TX buffer size is not a power of 2
#endif

#endif

/*==============================================================================================================*
 
    @file     USI_TWI_Slave.c
    @license  MIT (c) 2016 Nadav Matalon

    ATtiny I2C Slave Library

    Ver. 1.0.0 - First release (1.12.16)
 
 *===============================================================================================================*
    LICENSE
 *===============================================================================================================*
 
    The MIT License (MIT)
    Copyright (c) 2016 Nadav Matalon

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
    documentation files (the "Software"), to deal in the Software without restriction, including without
    limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
    the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial
    portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
    LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 *==============================================================================================================*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include "USI_TWI_Slave.h"

#if defined(__AVR_ATtiny2313__)
#  define DDR_USI             DDRB
#  define PORT_USI            PORTB
#  define PIN_USI             PINB
#  define PORT_USI_SDA        PB5
#  define PORT_USI_SCL        PB7
#  define PIN_USI_SDA         PINB5
#  define PIN_USI_SCL         PINB7
#  define USI_START_COND_INT  USISIF
#  define USI_START_VECTOR    USI_START_vect
#  define USI_OVERFLOW_VECTOR USI_OVERFLOW_vect
#endif

#if defined(__AVR_ATtiny84__) |                 \
    defined(__AVR_ATtiny44__)
#  define DDR_USI             DDRA
#  define PORT_USI            PORTA
#  define PIN_USI             PINA
#  define PORT_USI_SDA        PORTA6
#  define PORT_USI_SCL        PORTA4
#  define PIN_USI_SDA         PINA6
#  define PIN_USI_SCL         PINA4
#  define USI_START_COND_INT  USISIF
#  define USI_START_VECTOR    USI_START_vect
#  define USI_OVERFLOW_VECTOR USI_OVF_vect
#endif

#if defined(__AVR_ATtiny25__) |                 \
    defined(__AVR_ATtiny45__) |                 \
    defined(__AVR_ATtiny85__)
#  define DDR_USI             DDRB
#  define PORT_USI            PORTB
#  define PIN_USI             PINB
#  define PORT_USI_SDA        PB0
#  define PORT_USI_SCL        PB2
#  define PIN_USI_SDA         PINB0
#  define PIN_USI_SCL         PINB2
#  define USI_START_COND_INT  USISIF
#  define USI_START_VECTOR    USI_START_vect
#  define USI_OVERFLOW_VECTOR USI_OVF_vect
#endif

#if defined(__AVR_ATtiny261__) |                \
    defined(__AVR_ATtiny461__) |                \
    defined(__AVR_ATtiny861__)
#  define DDR_USI             DDRB
#  define PORT_USI            PORTB
#  define PIN_USI             PINB
#  define PORT_USI_SDA        PB0
#  define PORT_USI_SCL        PB2
#  define PIN_USI_SDA         PINB0
#  define PIN_USI_SCL         PINB2
#  define USI_START_COND_INT  USISIF
#  define USI_START_VECTOR    USI_START_vect
#  define USI_OVERFLOW_VECTOR USI_OVF_vect
#endif

#define SET_USI_TO_SEND_ACK() {                                 \
  USIDR    =  0;                                                \
  DDR_USI |= (1 << PORT_USI_SDA);                               \
  USISR    = (0 << USI_START_COND_INT)  | (1 << USIOIF) |       \
             (1 << USIPF) | (1 << USIDC)| (0x0E << USICNT0);    \
}

#define SET_USI_TO_READ_ACK() {                                     \
    DDR_USI &= ~(1 << PORT_USI_SDA);                                \
    USIDR    =   0;                                                 \
    USISR    =  (0 << USI_START_COND_INT)   | (1 << USIOIF) |       \
                (1 << USIPF) | (1 << USIDC) | (0x0E << USICNT0);    \
}

#define SET_USI_TO_TWI_START_CONDITION_MODE() {                                   \
    USICR = (1 << USISIE) | (0 << USIOIE) | (1 << USIWM1) | (0 << USIWM0) |       \
            (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | (0 << USITC);         \
    USISR = (0 << USI_START_COND_INT) | (1 << USIOIF) |                           \
            (1 << USIPF)  | (1 << USIDC)  | (0x0 << USICNT0);                     \
}

#define SET_USI_TO_SEND_DATA() {                                              \
    DDR_USI |= (1 << PORT_USI_SDA);                                           \
    USISR    = (0 << USI_START_COND_INT)    | (1 << USIOIF) |                 \
               (1 << USIPF) | ( 1 << USIDC) | ( 0x0 << USICNT0 );             \
}

#define SET_USI_TO_READ_DATA() { \
    DDR_USI &= ~(1 << PORT_USI_SDA);                                          \
    USISR    =  (0 << USI_START_COND_INT)   | (1 << USIOIF) |                 \
                (1 << USIPF) | (1 << USIDC) | (0x0 << USICNT0);               \
}

#define USI_RECEIVE_CALLBACK() {                                              \
    if (usi_onReceiverPtr) {                                                  \
        if (usiTwiAmountDataInReceiveBuffer()) {                              \
            usi_onReceiverPtr(usiTwiAmountDataInReceiveBuffer());             \
        }                                                                     \
    }                                                                         \
}

#define ONSTOP_USI_RECEIVE_CALLBACK() {                                       \
    if (USISR & (1 << USIPF)) {                                               \
        USI_RECEIVE_CALLBACK();                                               \
    }                                                                         \
}

#define USI_REQUEST_CALLBACK() {                                              \
    USI_RECEIVE_CALLBACK();                                                   \
    if(usi_onRequestPtr) usi_onRequestPtr();                                  \
}

typedef enum {
    USI_SLAVE_CHECK_ADDRESS                = 0x00,
    USI_SLAVE_SEND_DATA                    = 0x01,
    USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA = 0x02,
    USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA   = 0x03,
    USI_SLAVE_REQUEST_DATA                 = 0x04,
    USI_SLAVE_GET_DATA_AND_SEND_ACK        = 0x05
} overflowState_t;

static uint8_t slaveAddress;
static volatile overflowState_t overflowState;

static uint8_t rxBuf[ TWI_RX_BUFFER_SIZE ];
static volatile uint8_t rxHead;
static volatile uint8_t rxTail;
static volatile uint8_t rxCount;

static uint8_t txBuf[ TWI_TX_BUFFER_SIZE ];
static volatile uint8_t txHead;
static volatile uint8_t txTail;
static volatile uint8_t txCount;

static void flushTwiBuffers(void) {
    rxTail  = 0;
    rxHead  = 0;
    rxCount = 0;
    txTail  = 0;
    txHead  = 0;
    txCount = 0;
}

void usiTwiSlaveInit(uint8_t ownAddress) {
    flushTwiBuffers();
    slaveAddress = ownAddress;
    DDR_USI  |=  (1 << PORT_USI_SCL) | (1 << PORT_USI_SDA);
    PORT_USI |=  (1 << PORT_USI_SCL);
    PORT_USI |=  (1 << PORT_USI_SDA);
    DDR_USI  &= ~(1 << PORT_USI_SDA);
    USICR     =  (1 << USISIE) | (0 << USIOIE) | (1 << USIWM1) | (0 << USIWM0) |
                 (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | (0 << USITC);
    USISR     =  (1 << USI_START_COND_INT) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC);
}


bool usiTwiDataInTransmitBuffer(void) {
    return txCount;
}

void usiTwiTransmitByte(uint8_t data) {
    uint8_t tmphead;
    while (txCount == TWI_TX_BUFFER_SIZE);
    txBuf[txHead] = data;
    txHead = (txHead + 1) & TWI_TX_BUFFER_MASK;
    txCount++;
}

uint8_t usiTwiReceiveByte(void) {
    uint8_t rtn_byte;
    while (!rxCount);
    rtn_byte = rxBuf [rxTail];
    rxTail = (rxTail + 1) & TWI_RX_BUFFER_MASK;
    rxCount--;
    return rtn_byte;
}

uint8_t usiTwiAmountDataInReceiveBuffer(void) {
    return rxCount;
}
 
ISR(USI_START_VECTOR) {
    overflowState = USI_SLAVE_CHECK_ADDRESS;
    DDR_USI &= ~(1 << PORT_USI_SDA);
    while ((PIN_USI & (1 << PIN_USI_SCL)) && !((PIN_USI & (1 << PIN_USI_SDA))));
    if (!(PIN_USI & (1 << PIN_USI_SDA))) {
        USICR = (1 << USISIE) | (1 << USIOIE) | (1 << USIWM1) | (1 << USIWM0) |
                (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | (0 << USITC);
    } else {
        USICR = (1 << USISIE) | (0 << USIOIE) | (1 << USIWM1 ) | (0 << USIWM0) |
                (1 << USICS1) | (0 << USICS0) | (0 << USICLK ) | (0 << USITC);
    }
    USISR = (1 << USI_START_COND_INT)   | (1 << USIOIF) |
            (1 << USIPF) | (1 << USIDC) | (0x0 << USICNT0);
}

ISR(USI_OVERFLOW_VECTOR) {
    switch (overflowState) {
        case (USI_SLAVE_CHECK_ADDRESS):
            if ((USIDR == 0) || ((USIDR >> 1) == slaveAddress)) {
                if (USIDR & 0x01) {
                    USI_REQUEST_CALLBACK();
                    overflowState = USI_SLAVE_SEND_DATA;
                } else overflowState = USI_SLAVE_REQUEST_DATA;
                SET_USI_TO_SEND_ACK();
            } else SET_USI_TO_TWI_START_CONDITION_MODE();
            break;
        case (USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA):
            if (USIDR) {
                SET_USI_TO_TWI_START_CONDITION_MODE();
                return;
            }
        case (USI_SLAVE_SEND_DATA):
            if (txCount) {
                USIDR = txBuf[txTail];
                txTail = (txTail + 1) & TWI_TX_BUFFER_MASK;
                txCount--;
            } else {
                SET_USI_TO_READ_ACK();
                SET_USI_TO_TWI_START_CONDITION_MODE();
                return;
            }
            overflowState = USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA;
            SET_USI_TO_SEND_DATA();
            break;
        case (USI_SLAVE_REQUEST_REPLY_FROM_SEND_DATA):
            overflowState = USI_SLAVE_CHECK_REPLY_FROM_SEND_DATA;
            SET_USI_TO_READ_ACK();
            break;
        case (USI_SLAVE_REQUEST_DATA):
            overflowState = USI_SLAVE_GET_DATA_AND_SEND_ACK;
            SET_USI_TO_READ_DATA();
            break;
        case (USI_SLAVE_GET_DATA_AND_SEND_ACK):
            if (rxCount < TWI_RX_BUFFER_SIZE) {
                    rxBuf[rxHead] = USIDR;
                    rxHead = (rxHead + 1) & TWI_RX_BUFFER_MASK;
                    rxCount++;
            }
            overflowState = USI_SLAVE_REQUEST_DATA;
            SET_USI_TO_SEND_ACK();
            break;
    }

}

4
  • The first line in the for-loop received = usiTwiReceiveByte(); together wit the readings in the for loop has the effct that bytes_recieved + 1 bytes are read. I don't know if this is the problem, but you read one byte more that there is in the buffer. That might work as long as further bytes are send over the i2c connection. But if the buffer is empty, it might crash. It is just a guess. – Peter Paul Kiefer Dec 3 '20 at 9:43
  • Good catch. I forgot I still had that in there. I have tested without it though and it doesn't change anything :\ – Zachary Schroeder Dec 3 '20 at 9:53
  • I saw another possible point of failure. You use buffer_in_index in the for loop in the main method. This variable is modified in an interrupt routine. Are you sure this doesn't induce any race conditions? Actually I don't have the time to look at the code. But that could be worth to think over. If you (or another member) did not find a solution, until this evening, I'll have a closer look at it. – Peter Paul Kiefer Dec 3 '20 at 10:05
  • That's a good point. I did try removing almost everything that wasn't strictly necessary for I2C communication and the problem persisted so I don't think that's the issue, but I will definitely make sure to find a different solution for the final code. – Zachary Schroeder Dec 3 '20 at 18:39

Browse other questions tagged or ask your own question.