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


        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
  • \$\begingroup\$ So you're bit-banging I2C, right? Did you know AVR-GCC has a macro _BV that is exactly the same as your _BS macro, and also your _BC macro is always zero? \$\endgroup\$ – vicatcu Feb 23 '13 at 20:25
  • \$\begingroup\$ Not exactly. I use the USI hardware for I2C/TWI, but it still requires the CPU to do fair bit of work. \$\endgroup\$ – jippie Feb 23 '13 at 20:29
  • \$\begingroup\$ @vicatcu I am aware of BV and BC. The reason for BC and BS is that I can use it to make the code more verbose when setting/clearing several bits in a single hardware register. As you may notice the definition for maskUSICR is exactly 8 bits regardless of some bits being set to 0. \$\endgroup\$ – jippie Feb 23 '13 at 20:38
  • \$\begingroup\$ @jippie... maskUSICR is exactly zero, not just some of the bits, maybe that's what you intended though \$\endgroup\$ – vicatcu Feb 23 '13 at 20:42
  • \$\begingroup\$ I disagree. According to the disassembly of the code: 3a: 80 e2 ldi r24, 0x20 ; 32 3c: 8d b9 out USICR, r24 ; 0x0d it is 32 (decimal). \$\endgroup\$ – jippie Feb 23 '13 at 20:48

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.

| improve this answer | |
  • \$\begingroup\$ Was not aware of the App Note. However it features 'C-code driver for TWI master', I don't see a single line of code quickly browsing the document. I'll Google for that in the morning. \$\endgroup\$ – jippie Feb 23 '13 at 20:34
  • \$\begingroup\$ @jippie I added a link to the software... you can find these by going to the atmel website, going under Design Support, choosing Application notes, and searching there \$\endgroup\$ – vicatcu Feb 23 '13 at 20:40
  • \$\begingroup\$ The disadvantage of using prefab code is the lower educational value of copy/paste, but I might be able to use it to pinpoint my error. \$\endgroup\$ – jippie Feb 23 '13 at 20:42
  • \$\begingroup\$ @jippie by all means you should read and understand the code prior to using it :-) \$\endgroup\$ – vicatcu Feb 23 '13 at 20:45

To make the pin toggle up and down, I changed the main loop with a write to USISR as follows:

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

            USISR   =                                               // SCL is released by setting correct status bits
                    _BS( USISIF ) |
                    _BS( USIOIF ) |
                    _BS( USIPF )  |
                    _BS( USIDC )  |
                    _BC( USICNT0 );

            // Toggle hearbeat Pin
            PINB = _BS( heartbeatPin );                             // Toggle heartbeat pin
| improve this answer | |

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.