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
}
}
Best Answer
Check page 120 of the datasheet:
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.