2
\$\begingroup\$

I'm trying to control a DAC from an ATtiny45, but somehow the SDA line is stuck low. 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:

  • The SDA line is stuck low;
  • When I remove the controller the pull up resistor pulls the line high as expected;
  • I tested PB0 with a different program and its hardware appears OK;
  • I checked the disassembly and that code apparently does exactly what I intended. So think I am misreading how the controller mechanism works, rather than having made a programming error.

What I would expect:

  • As I program USIDR (Universal Serial Interface Data Register) with 0x00 and 0xFF the SDA line should toggle with it.

The output pin (DO or SDA, depending on the wire mode) is connected via the output latch to the most significant bit (bit 7) of the USI Data Register. The output latch ensures that data input is sampled and data output is changed on opposite clock edges. The latch is open (transparent) during the first half of a serial clock cycle when an external clock source is selected (USICS1 = 1) and constantly open when an internal clock source is used (USICS1 = 0). The output will be changed immediately when a new MSB is written as long as the latch is open.

What it does:

  • SDA stuck low.
  • Both SCL and my heartbeat pin work as expected. Heartbeat is just there to prove the program returns to the main loop every so often.

My question is: What am I doing wrong, how can I make SDA follow USIDR?

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
*/

void i2cBegin( void ) {
        // 1. Start condition
        PORTB |= _BS( sclPin );                                 // Set SCL high

        USIDR = 0xff;                                           // Set SDA high
        // Shift out next bit
        USICR = maskUSICR |                                     // Universal Serial Interface Control Register
                _BS( USICLK );                                  // Clock strobe, shift bit out
        _delay_us( 10 );                                        // Maximum bitrate is 100kbps, 10 microseconds

        // Toggle SCL
        USICR = maskUSICR |                                     // Universal Serial Interface Control Register
                _BS( USITC );                                   // Set SCL low, Toggle Clock Port Pin
        _delay_us( 10 );                                        // Maximum bitrate is 100kbps, 10 microseconds

        USIDR = 0;                                              // Set SDA low
        // Shift out next bit
        USICR = maskUSICR |                                     // Universal Serial Interface Control Register
                _BS( USICLK );                                  // Clock strobe, shift bit out
        _delay_us( 10 );                                        // Maximum bitrate is 100kbps, 10 microseconds

        // Toggle SCL
        USICR = maskUSICR |                                     // Universal Serial Interface Control Register
                _BS( USITC );                                   // Release SCL, Toggle Clock Port Pin
}

int main( void ) {
        //
        // 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

        //
        // Endless loop
        //
        while ( 1 ) {
                i2cBegin();                                     // Device addressing
                PORTB ^= _BS( heartbeatPin );                   // Toggle heartbeat pin
        }
}
\$\endgroup\$
3
\$\begingroup\$

Check page 120 of the datasheet:

The output drivers are enabled by setting the corresponding bit for SDA and SCL in the DDRB register. When the output driver is enabled for the SDA pin it will force the line SDA low if the output of the USI Data Register or the corresponding bit in the PORTB register is zero. Otherwise, the SDA line will not be driven (i.e., it is released). When the SCL pin output driver is enabled the SCL line will be forced low if the corresponding bit in the PORTB register is zero, or by the start detector. Otherwise the SCL line will not be driven.

If your controller is pulling SDA low you have no chance of TWI ever working. You may need to put a dummy '1' into the corresponding PORTB register to force it high.

\$\endgroup\$
  • \$\begingroup\$ Yesss, the output pin is now toggling. I did that for SCL already but not for SDA. First issue solved, hopefully not too many to follow. \$\endgroup\$ – jippie Feb 23 '13 at 14:36
  • \$\begingroup\$ I changed PORTB |= _BS( sclPin ); to PORTB |= _BS( sclPin ) | _BS( sdaPin ); \$\endgroup\$ – jippie Feb 23 '13 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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