Easy I2C: Pull-ups optional
- October 10th, 2011
- Posted in Uncategorized
- By lackawanna
- Write comment
The I2C interface is relatively uncomplicated to write for on the AVR, not withstanding all the details of the protocol and all the messages generated by the AVR hardware. The more advanced AVR microcontrollers, support the I2C two wire interface – three wires if counting ground. It is not called I2C in the Atmel documentation however. Instead it is called the 2-wire Serial Interface (TWI).
So let’s begin. First, the TWI interface needs to be initialized. The TWI bit rate register (TWBR) and the TWI prescaler bits set the frequency of the bus. The standard I2C bus runs at a frequency of 100 kHz. I used a lower rate of 10 kHz to provide for a less critical design.
#define TWI_PRE 1 // my TWI prescaler value #define TWI_FREQ 10000 // my TWI bit rate TWBR = (F_CPU / TWI_FREQ - 16) / TWI_PRE / 2; // set bit rate TWSR = (0<<TWPS0); // use a prescaler of one
Since the I2C bus can operate at relatively high speeds, the two bus lines need pull-up resistors that are external. The internal pull-ups of AVR MCUs with their large resistances, as high as 50 kohms according to the documentation, would degrade the bandwidth of the bus. Nevertheless I still choose to use internal pull-ups, but I did it at a cost of using a slower bus. So the next step is to enable the internal pull-ups on the TWI clock (SCL) and data (SDA) pins.
#define SCL_PORT PORTC // pin assignments specific to the ATmega328p #define SCL_BIT PC5 #define SDA_PORT PORTC #define SDA_BIT PC4 SCL_PORT |= _BV(SCL_BIT); // enable pull up on TWI clock line SDA_PORT |= _BV(SDA_BIT); // enable pull up on TWI data line
If external pull-ups are used, Myke Predko recommends 1 kohm resistors for 400 kHz buses and 10 kohm resistors for 100 kHz buses. Both TWI pins, SDA and SCL, operate using an open-drain design. So, while the fall time of the signal is determined by the output impedance of the driver and the load capacitance of the bus, the rise time is determined by the resistance of the pull-up and the load capacitance. The rise time of a TWI signal can therefore be calculated using the formula tr = 2.197 * R * C. According to one source a rise time of 1/2 to 1/3 of a bit-time is adequate.
To initiate a TWI transmission, a TWI operation must be enabled (TWEN) and the TWI interrupt flag (TWINT) must be cleared. This is done by setting the TWEN and TWINT bits in the TWI control register (TWCR). This clearing of the TWI interrupt flag immediately starts a TWI transmission. For certain TWI transmissions another bit has to be set. For example, to send a START or STOP condition the TWSTA or TWSTO bit must be set in addition.
Once a TWI transmission is started, you need to wait for it to complete. This can be done by polling the control register until the TWINT bit is set. The status of the TWI transmission can then be checked by reading the TWI status register (TWSR). For example, the routine twi_poll() below starts a transmission and then waits for the transmission to complete.
uint8_t twi_poll (uint8_t u)
{
TWCR = u | _BV(TWINT) | _BV(TWEN); // initiate a TWI transmission
if (u == _BV(TWSTO)) return 0; // don't poll if the STOP condition was sent
while (!(TWCR & _BV(TWINT))) ; // wait for the interrupt flag to be set
return (TWSR & 0xf8); // return the status code
}
The best analogy I can come up with to explain how to communicate with I2C perpherals is that you talk to them using packets. There is a write packet and a read packet. To perform a write transaction you send one write packet. To perform a read transaction, you use two packets, one to write the command and the other to read the results.
The beginning of a packet is indicated by the START condition, or if you are in the middle of a transaction the RESTART condition. The end of a packet is indicated by the STOP condition, or if you are again in the middle of a transaction the RESTART condition. The STOP condition is important not only because it ends the transaction but also because it forces the TWI hardware to release the bus lines.
To begin a transaction, the START condition is first sent. This is performed by setting the TWSTA bit in the TWI control register (TWCR).
u = twi_poll(_BV(TWSTA)); if (u != TW_START && u != TW_REP_START) return 0;
Next the 7-bit slave address of the peripheral and another bit, the read/write 8-th bit, are loaded into the TWI data register. The resulting 8-bit value is sent over the bus when the next TWI transmission is initiated. When the addressed device receives this value, or any data byte, the device will ACK its reception by pulling the data bus low in the 9-th bit position. If there is no acknowledgement, the code below releases the bus by jumping to code that sends a STOP condition and then errors out. To begin writing bytes to the device first send the following:
TWDR = slave_address | TW_WRITE; if (twi_poll(0) != TW_MT_SLA_ACK) goto release;
While there are a lot of other status codes to handle and the documentation gives a rather complex diagram of when and where they are generated, there is a definite sequence to it all, so if anything is not in the right order it usually means the transaction failed making it relatively straightforward to handle errors.
To write a byte over the TWI interface use:
TWDR = byte; if (twi_poll(0) != TW_MT_DATA_ACK) goto release;
To change the direction of the transaction and start reading bytes now instead of writing them, the RESTART condition must be sent as well as the slave address of the device with the read/write bit set to read.
if (twi_poll(_BV(TWSTA)) != TW_REP_START) goto release; // send RESTART TWDR = slave_address | TW_READ; // send SLA+R if (twi_poll(0) != TW_MR_SLA_ACK) goto release;
To read a byte, now that the direction of the bus has changed, use:
if (twi_poll(_BV(TWEA)) != TW_MR_DATA_ACK) goto release; byte = TWDR;
The TWEA bit tells the TWI interface on the AVR to acknowledge reception of the read byte by sending a ACK as the 9-th bit. If, however, this is the last byte to be read, the TWI must respond by sending a NACK instead to say it is finished reading. This is done by not setting the TWEA bit. In this case to read a byte use:
if (twi_poll(0) != TW_MR_DATA_NACK) goto release; byte = TWDR;
Lastly when the transaction is finished the STOP condition is sent releasing the bus. This is performed by setting the TWSTO bit.
twi_poll(_BV(TWSTO));
As an example, to change the frequency of the signal generated by the Silicon Labs DS1077 chip, I use the following code where “m” is the prescaler value and “n” is the divider value of the signal frequency to generate.
#include <compat/twi.h>
uint8_t twi_poll (uint8_t u)
{
TWCR = u | _BV(TWINT) | _BV(TWEN); // initiate a TWI transmission
if (u == _BV(TWSTO)) return 0; // don't poll if the STOP condition was sent
while (!(TWCR & _BV(TWINT))) ; // wait for the interrupt flag to be set
return (TWSR & 0xf8); // return the status code
}
uint8_t twi_send16 (uint8_t sla, uint8_t cmd, uint8_t msb, uint8_t lsb)
{
uint8_t u, status = 0;
u = twi_poll(_BV(TWSTA)); // send START
if (u != TW_START && u != TW_REP_START) return 0;
TWDR = sla | TW_WRITE; // send SLA+W
if (twi_poll(0) != TW_MT_SLA_ACK) goto release;
TWDR = cmd; // send command
if (twi_poll(0) != TW_MT_DATA_ACK) goto release;
TWDR = msb; // send msb
if (twi_poll(0) != TW_MT_DATA_ACK) goto release;
TWDR = lsb; // send lsb
if (twi_poll(0) != TW_MT_DATA_ACK) goto release;
status = 1; // success
release:
twi_poll(_BV(TWSTO)); // send STOP
return status;
}
uint8_t twi_receive16 (uint8_t sla, uint8_t cmd, uint8_t *msb, uint8_t *lsb)
{
uint8_t u, status = 0;
u = twi_poll(_BV(TWSTA)); // send START
if (u != TW_START && u != TW_REP_START) return 0;
TWDR = sla | TW_WRITE; // send SLA+W
if (twi_poll(0) != TW_MT_SLA_ACK) goto release;
TWDR = cmd; // send command
if (twi_poll(0) != TW_MT_DATA_ACK) goto release;
if (twi_poll(_BV(TWSTA)) != TW_REP_START) goto release; // send RESTART
TWDR = sla | TW_READ; // send SLA+R
if (twi_poll(0) != TW_MR_SLA_ACK) goto release;
if (twi_poll(_BV(TWEA)) != TW_MR_DATA_ACK) goto release;
*msb = TWDR; // read msb
if (twi_poll(0) != TW_MR_DATA_NACK) goto release;
*lsb = TWDR; // read lsb
status = 1; // success
release:
twi_poll(_BV(TWSTO)); // send STOP
return status;
}
...
#define DS1077_SLA 0xb0
#define DS1077_MUX 0x02
#define DS1077_DIV 0x01
if (!twi_send16(DS1077_SLA, DS1077_MUX, m>>1, m<<7)) return;
if (!twi_send16(DS1077_SLA, DS1077_DIV, n>>2, n<<6)) return;

lolvely ariticle~
WOW! now it perfectly worked. Thank you very much. I was having a great mistake of not using pull ups for I2C bus. And now I corrected it and it works great. Thanks a lot.
Thank you for the compliment. I changed the post title to be more descriptive and also added an image to illustrate the post better. I am glad you spurred me on to improve my write up.
You mentioned there as 10K for 100KHZ. But mostly I read 4K7 or 2K2 for 100 KHZ also depending on the bus capacitance. But I think its safe to use 10K too.
I’ll double check. See my usb post because I mention bus length. That might be a factor as well.
Can you make a detailed post like this on XBee (using max232 for connection with X-CTU)?
Thank you for the kind suggestion! It is very appreciated. You might want to consider one of the USB based xbees. Or you can use the uart-based xbees and interface from your computer through a FTDI cable. One of my best decisions ever was to buy a FTDI cable – it allowed me concentrate on AVR design instead of worrying about building a max232 circuit. Your question seems to indicate you want to run a xbee off your computer.
If however you want to run a xbee from your AVR you only need to use the UART, no level conversion is needed. Anyway, you have spurned me to write a wireless article. Until then see this site for a cool wireless/remote control design http://dl.dropbox.com/u/43021514/CWTD/RemoteControl/May15.html
Here is the quote from Myke Predko: “I2C connections between the PIC microcontroller’s I2C SDA (data) and SCL (clock) pins is very simple, with just a pull-up on each line, as shown in Fig. 16.15. I typically use a 1-kohm resistor for 400-kHz data transfers and a 10kohm resistor for 100-kHz data rates.”
hey,
i am using at2560. .i dont want use internal pullups.i am using bus at 100khz bus speed i hav used 2 4k7 pullups but i am not getting in which side should i give vcc to pull from slave or master.pls help will b highle apperiaciated and not to mention i am using stk600 and they use 2 different spi and voltage supplies and dont have common grnd or common vcc.any help,,,pls pls
It sounds like you are setting the external resistors right. 10k is sufficient at 100kHz, so 4.7k per line is more than enough. It does not matter I believe where the pullup resistor is placed on the line – say either close to the AVR master or at the end next to the slave – since the line will be short enough to be considered lumped. That is it will not behave as a transmission line where reflections will result should the line not be properly terminated at the master and the slave. Maybe you are having problems because of the stk600. Also don’t enable the pull-up as I did in the code above. That is make sure these two lines are omitted from your code:
SCL_PORT |= _BV(SCL_BIT); // enable pull up on TWI clock line
SDA_PORT |= _BV(SDA_BIT); // enable pull up on TWI data line
I should of pointed this out in my post.