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.