3

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; 

Share