Electronic – Setting up an ATtiny45 as I2C master – SCL toggles low, but refuses to toggle high

attinyci2ctroubleshooting

Another pin, another question: However closely related to Setting up an ATtiny45 as I2C master – SDA stuck low this is different question.

I'm trying to control a DAC from an ATtiny45, but somehow the SCL line gets stuck after toggled once. To find the problem in the code I did the following:

  • I simplified the circuit to only the controller with the pull up resistors, there is no I2C slave attached;
  • I simplified the C code base to the bare minimum to reproduce the problem.

The following is the result so far:

  • When I remove the toggle'ing from the main loop, the SCL line is high;
  • When I include the SCL toggle-code: After first toggle, the SCL line is low;
  • The heartbeat pin proves that the main loop is active.

What happens:

  • After the first time I write to USITC-bit in USICR, SCL is toggled low, after that it is stuck low.

USITC Universal Serial Interface Toggle Clock Port Pin

USICR Universal Serial Interface Control Register

SCL I2C Serial Clock pin

What I expect:

  • Every time I write to USITC-bit in USICR, I expect SCL to toggle up or down (in the same rythm as the heartbeat pin).

Writing a one to this bit location toggles the USCK/SCL value either
from 0 to 1, or from 1 to 0. The toggling is independent of the
setting in the Data Direction Register, but if the PORT value is to be
shown on the pin the corresponding DDR pin must be set as output (to
one). This feature allows easy clock generation when implementing
master devices.

My question is: What am I doing wrong, how can I make SCL follow USITC toggle'ing?

Chapter 15 of the datasheet is about the Universal Serial Interface and I'm trying to set up I2C/TWI as described in 15.3.4 and further.

Here is the minimized program code:

#include <avr/io.h>
#include <util/delay.h>

#define _BS(bit) ( 1 << ( bit ) )
#define _BC(bit) ( 0 << ( bit ) )

const uint8_t heartbeatPin      = PB3;
const uint8_t sdaPin            = PB0;
const uint8_t sclPin            = PB2;

const uint8_t maskUSICR =                                       // Universal Serial Interface Control Register
                                _BC( USISIE ) |                 // (rw) disable USI Start Condition Interrupt Enable
                                _BC( USIOIE ) |                 // (rw) disable USI Counter Overflow Interrupt Enable
                                _BS( USIWM1 ) | _BC( USIWM0 ) | // (rw) Two wire mode, not using counter overflow
                                _BC( USICS1 ) | _BC( USICS0 ) | // (rw) Clock select => software clock strobe (USICLK)
                                _BC( USICLK ) |                 // (-w) Clock strobe
                                _BC( USITC );                   // (-w) Toggle Port Pin

/*
        ATtiny45

        pin     1       2       3       4       5       6       7       8
        name    PB5     PB3     PB4     GND     PB0     PB1     PB2     VCC
        func    !RESET  h/beat          GND     SDA             SCL     VCC
*/

int main( void ) {
        //
        // Setup
        //

        // Setting up output drivers
        DDRB =                                                  // Port B Data Direction Register.
                _BS( heartbeatPin ) |                           // PB3 output driver enable
                _BS( sdaPin ) |                                 // SDA output driver enable
                _BS( sclPin );                                  // SCL output driver enable

        // Enable SCL & SDA
        PORTB |=                                                // PORT B output register
                _BS( sclPin ) |                                 // Set SCL pin high
                _BS( sdaPin );                                  // Set SDA pin high

        // Set up I2C / TWI
        USICR =                                                 // Universal Serial Interface Control Register
                maskUSICR;                                      // I2C, no interrupts, software clock

        //
        // Endless loop
        //
        while ( 1 ) {
                // Toggle SCL high
                USICR = maskUSICR |                                     // Universal Serial Interface Control Register
                        _BS( USITC );                                   // Release SCL, Toggle Clock Port Pin

                // Toggle hearbeat Pin
                PINB = _BS( heartbeatPin );                             // Toggle heartbeat pin
        }
}

Best Answer

The idiomatically correct way to set a bit in AVR-GCC is: value |= _BV(bit_number). Likewise to clear a bit it's: value &= ~_BV(bit_number)...

Have you taken a look at Atmel App Note AVR310: Using the USI module as a I2C master? Here's a link to accompanying software.