0
\$\begingroup\$

I have the following I2C configuration with an ATmega168 (1 MHz) as the master and an Arduino Uno (16 MHz) as the slave.

enter image description here

The command for the LED connected to the slave comes from the master and vice versa. I can't get the I2C to work. The LEDs are at whatever value I initially set ledValue to be, and it is not getting it from the slave/master.

The code running on the master.

#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t slaveAddress = 5;
volatile uint8_t ledValue = 0;
volatile uint8_t refValue = 100;
volatile uint8_t rw;

void send_start(){ TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWSTA) | (1 << TWEN) | (1 << TWIE); }

void send_stop(){ TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWSTO) | (1 << TWEN) | (1 << TWIE); }

void send_data(){ TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE); }

ISR(TWI_vect)
{
    volatile uint8_t status = TWSR & 0xF8;
    if(status == 0x08) // start send successfully - now send SLA+R/W
    {
        TWDR = (slaveAddress << 1) + rw;
        send_data();
        return;
    }
    if(status == 0x50 || status == 0x58) //receive mode - data received - read it - send stop
    {
        ledValue = TWDR;
        send_stop();
        return;
    }
    if(status == 0x18 || status == 0x20) //transmit mode - SLA+W successfully sent - now send data
    {
        TWDR = refValue; //data to be transmitted
        send_data();
        return;
    }
    if(status == 0x28 || status == 0x30) //transmit mode - data successfully sent - now send stop
    {
        send_stop();
        return;
    }
    //anything else - clear TWINT
    TWCR |= (1 << TWINT);
}

void initialize_pwm()
{
    DDRD |= (1 << DDD3);
    TCCR2A |= (1 << COM2B1) | (1 << WGM20);
    TCCR2B |= (1 << CS20);
}

int main(void)
{
    initialize_pwm();
    TWBR = 0;
    //TWSR &= ~((1 << TWPS1) | (1 << TWPS0)); // prescaler 1
    // SCL = 1 MHz/(16 + 2*0*1) = 1/16 MHz
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWIE);
    sei();

    while(1)
    {
        rw = 1; //read
        send_start();
        _delay_ms(1000);
        OCR2B = ledValue;
        rw = 0; //write
        send_start();
    }
    return 0;

}

The code running on the slave.

#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t slaveAddress = 5;
volatile uint8_t ledValue = 0;
volatile uint8_t refValue = 100;

void send_data(){ TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE); }

ISR(TWI_vect)
{
    volatile uint8_t status = TWSR & 0xF8;
    if(status == 0xA8) // trasmit mode - master sent SLA+R - now send data back
    {
        TWDR = refValue; //data to be transmitted
        send_data();
        return;
    }
    if(status == 0x80 || status == 0x88) // receive mode - master sent data after SLA+W - read it
    {
        ledValue = TWDR;
        TWCR |= (1 << TWINT);
        return;
    }
    //anything else - clear TWINT
    TWCR |= (1 << TWINT);
}

void initialize_pwm()
{
    DDRD |= (1 << DDD3);
    TCCR2A |= (1 << COM2B1) | (1 << WGM20);
    TCCR2B |= (1 << CS20);
}

int main(void)
{
    initialize_pwm();
    TWAR = (slaveAddress << 1);
    TWCR = (1 << TWINT) | (1 << TWEN) | ( 1 << TWIE);
    sei();

    while(1)
    {
        _delay_ms(1000);
        OCR2B = ledValue;
    }

}
\$\endgroup\$
9
  • \$\begingroup\$ What steps have you taken to debug the problem? \$\endgroup\$ Commented Jun 25, 2015 at 15:00
  • 1
    \$\begingroup\$ How did you programm the master? \$\endgroup\$
    – MathieuL
    Commented Jun 25, 2015 at 15:16
  • \$\begingroup\$ It looks like you are missing a ground connection between the top and bottom busses on the breadboard. You should also have a bypass capacitor between Vcc and Gnd on the chip on the breadboard. You don't show a Vcc connection to the breadboard. \$\endgroup\$ Commented Jun 25, 2015 at 15:27
  • \$\begingroup\$ @MathieuL, I used the usbasp. \$\endgroup\$ Commented Jun 25, 2015 at 15:39
  • 1
    \$\begingroup\$ Well, you could start by adding debugging statements that output to the UART - or even just toggle IO pins - to let you determine what code is running, when. \$\endgroup\$ Commented Jun 25, 2015 at 15:49

2 Answers 2

1
\$\begingroup\$

I've been able to arrive at an answer to my own question.

Master code.

#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdbool.h>

volatile uint8_t ledValue = 0;
volatile uint8_t refValue = 0;
volatile uint8_t status;
volatile uint8_t pstatus = 0;
volatile uint8_t rw;
volatile bool receiveQ = true;
volatile bool transmitQ = false;

void send_start(){ TWCR &= ~(1 << TWSTO); TWCR |= (1 << TWINT) | (1 << TWSTA); }

void send_stop(){ TWCR &= ~(1 << TWSTA);  TWCR |= (1 << TWINT) | (1 << TWSTO); }

void send_data(){ TWCR &= ~((1 << TWSTA) | (1 << TWSTO)); TWCR |= (1 << TWINT); }

void toggle(){PORTB ^= (1 << PORTB0);}

ISR(TWI_vect)
{
    status = TWSR & 0xF8;
    if(status == 0x08 || status == 0x10)
    {
        pstatus = 1;
        TWDR = rw;
        send_data();
        return;
    }
    if(status == 0x38)
    {
        pstatus = 2;
        send_start();
        return;
    }
    if(status == 0x40)
    {
        pstatus = 3;
        TWCR &= ~(1 << TWEA);
        send_data();
        return;
    }
    if(status == 0x48)
    {
        pstatus = 4;
        send_start();
        return;
    }
    if(status == 0x50)
    {
        pstatus = 5;
        ledValue = TWDR;
        TWCR &= ~(1 << TWEA);
        send_data();
        return;
    }
    if(status == 0x58)
    {
        pstatus = 6;
        ledValue = TWDR;
        receiveQ = false;
        transmitQ = true;
        send_stop();
        return;
    }
    if(status == 0x18 || status == 0x20)
    {
        pstatus = 7;
        TWDR = refValue; //data to be transmitted
        send_data();
        return;
    }
    if(status == 0x28 || status == 0x30)
    {
        pstatus = 8;
        receiveQ = true;
        transmitQ = false;
        send_stop();
        return;
    }
    if((pstatus == 8 || pstatus == 6)){toggle();}
    pstatus = 0;
    send_stop();
}

void initialize_pwm()
{
    DDRD |= (1 << DDD3);
    TCCR2A |= (1 << COM2B1) | (1 << WGM20);
    TCCR2B |= (1 << CS20);
}

int main(void)
{
    DDRB |= (1 << DDB0);
    //uint8_t slaveAddress = 5;
    initialize_pwm();
    TWBR = 10;
    TWCR = (1 << TWEN) | (1 << TWIE);
    sei();

    while(1)
    {
        if(receiveQ)
        {
            rw = 11; //BitShiftLeft[5, 1] + 1, read
            refValue += 5;
            if(refValue > 255){refValue = 0;}
            send_start();
        }
        _delay_ms(100);
        OCR2B = ledValue;
        if(transmitQ)
        {
            rw = 10; //BitShiftLeft[5, 1], write
            send_start();
        }
    }
    return 0;

}

Slave code.

#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t ledValue = 0;
volatile uint8_t refValue = 0;
volatile uint8_t status;

void send_ack(){ TWCR |= (1 << TWINT) | (1 << TWEA);}

void send_n_ack(){ TWCR &= ~(1 << TWEA); TWCR |= (1 << TWINT);}

void toggle(){PORTB ^= (1 << PORTB0);}

ISR(TWI_vect)
{
    status = TWSR & 0xF8;
    if(status == 0xA8 || status == 0xB0)
    {
        TWDR = refValue;
        send_n_ack();
        return;
    }
    if(status == 0xB8)
    {
        TWDR = refValue;
        send_n_ack();
        return;
    }
    if(status == 0xC0 || status == 0xC8)
    {
        send_ack();
        return;
    }
    if(status == 0x60 || status == 0x68 || status == 0x70|| status == 0x78)
    {
        send_ack();
        return;
    }
    if(status == 0x80 || status == 0x90)
    {
        ledValue = TWDR;
        send_n_ack();
        return;
    }
    if(status == 0x88 || status == 0x98 || status == 0xA0)
    {
        send_ack(); 
        return;
    }
    toggle();
    send_ack();
}

void initialize_pwm()
{
    DDRD |= (1 << DDD3);
    TCCR2A |= (1 << COM2B1) | (1 << WGM20);
    TCCR2B |= (1 << CS20);
}

int main(void)
{
    DDRB |= (1 << DDB0);
    initialize_pwm();
    TWAR = 10; //BitShiftLeft[5, 1];
    TWCR = (1 << TWEN) | ( 1 << TWIE) | (1 << TWEA);
    sei();

    while(1)
    {
        refValue += 5;
        if(refValue > 255){refValue = 0;}
        _delay_ms(100);
        OCR2B = ledValue;
    }

}
\$\endgroup\$
1
  • \$\begingroup\$ Everything works fine, but I have a follow-up question which I have not been able to figure out. Right after sending a stop, the master goes into status 0x00 ("bus error due to illegal START or STOP condition"). I recover from it by sending another stop. But why is it happening in the first place, and can it be avoided. \$\endgroup\$ Commented Jul 1, 2015 at 15:26
0
\$\begingroup\$

As far as I remember, I2C communication will not work inside an ISR. A lot of other things also don't work (properly) inside an ISR, digitalWrite for instance. Some time ago I was trying to use an MPU6050 sensor (I2C) with function calls inside an ISR, but that didn't work so I had to change to an analog sensor. Just search "function call from ISR" to confirm.

\$\endgroup\$
1
  • \$\begingroup\$ It was a tempting argument, (and part of me was hoping you were right), but I decided to persevere a tad bit more. :) \$\endgroup\$ Commented Jul 1, 2015 at 15:28

Not the answer you're looking for? Browse other questions tagged or ask your own question.