AVR basics: using the I2C bus #1 – bit rate

The AVR ATMEGA328P TWI module

When I first started playing around with Arduinos I quickly grew to like the I2C bus, and for a couple of good reasons. First, it was easy to use. And second, there are lots of fascinating devices out there, including displays and sensors, designed to work with it.

Created by Philips Semiconductor, now NXP, the Inter-Integrated Circuit bus was designed to connect devices, and particularly chips, in close proximity. The name is pronounced I-squared-C and is more properly written I²C, but I2C is easier to type.

The bus isn’t staggeringly fast, at a base standard of 100kHz. Faster versions are available – the 400kHz fastmode and even 1MHz, 3.4MHz and 5MHz options – but the original slow speed is still the most commonly used by tinkerers and hobbyists like me.

But it does have some advantages. The first is that the slave devices on the bus are addressable. Unlike SPI, where you need a separate line (and GPIO pin) for each device in order to activate it, with I2C you just preface traffic with the the device’s address (a one-byte number) to select which device should respond. And it’s also possible to have slaves interact with multiple masters, although that’s beyond the scope of this current discussion.

In fact, I2C requires just two lines – a two-way data line (SDA) and a clock line (SCL) – which is why it is also known as the Two-Wire Interface (TWI), especially by manufacturers who don’t want to pay NXP’s licence fee. As we’ll see, the AVR’s registers and bits used to manage this bus mostly start with the letters ‘TW’. So that’s that mystery solved.

With this series of posts, we’ll be looking at using an AVR ATMEGA or similar in master mode.

A bit complicated

There is a bit more complexity to using I2C compared to SPI. But once you get your head around a few concepts then it becomes much easier. One of the reasons I prefer using C++ to C is that you can write classes that hide much of the complexity. Yes, you can do this with functions too, but I like the ‘black box’ style of encapsulation of classes. All the wires are hidden.

I mention this because there’s one bit of calculating you need to do with I2C, to set the bit rate for the clock, that is easy to mess up. Sticking it in a class method or function makes a lot of sense. So let’s deal with that now.

By the way, I’m dealing here with microcontrollers such as the ATMEGA328P that have proper I2C interfaces and the registers to go with them. Some devices, like the ATTINY84A that I’m currently messing about with, have an I2C mode, but via a Universal Serial Interface (USI) that is controlled in a different way and none of our concern – for now, anyway.

Setting the bit rate

The master device on an I2C bus is responsible for creating the clock pulses on the SCL line.

I’m going to assume we’re dealing with devices that operate at either 100kHz or 400kHz. Achieving the bit rates necessary for the very fast I2C modes is beyond the capabilities of the ATMEGAs and ATTINYs I like to use.

You need to set the bit rate, which is the frequency of the SCL clock. It’s set using the TWBR register and you calculate it using the formula:

That’s how it appears in the datasheet. You can also write it as:

SCLfreq = F_CPU / (16 + (2 * TWBR * Prescaler) )

That still looks a bit ferocious, so let’s break it down and also rearrange it, because the thing we’re after (TWBR) is buried.

The SCLfreq is something we already know – it’s the SCL clock frequency we want to use. F_CPU is also a known – it’s the clock speed for your microcontroller. Let’s rearrange this by multiplying both sides by (16 + (2 * TWBR * Prescaler) ) and dividing by SCLfreq:

16 + (2 * TWBR * Prescaler)  = F_CPU / SCLfreq

We can also subtract 16 from both sides:

2 * TWBR * Prescaler  = (F_CPU / SCLfreq) – 16

And now, to get the TWBR that we’re after, divide both sides by (2 * Prescaler):

TWBR  = ((F_CPU / SCLfreq) – 16) / (2 * Prescaler)

(You’ll notice that I like to use lots of parentheses. I think it makes things clearer. If you prefer to rely on operator precedence, be my guest.)

In your code, you should have F_CPU defined somewhere anyway, so for an SCL frequency of 100kHz, the formula would be:

TWBR  = ((F_CPU / 100000) – 16) / (2 * Prescaler)

If we assume, say, a microcontroller clock speed of 16MHz and a prescaler value of 1, this becomes:

TWBR  = ((16000000 / 100000) – 16) / 2

Which is 72, which we’ll need as an unsigned integer. In my I2C class I have an _init() method that, in part, looks like this:

void SMD_I2C_Device::_init(uint8_t address, unsigned long busSpeed)
{
    /* ... some stuff ... */
    uint8_t prescaler_options[] = {1, 4, 16, 64};
    uint8_t prescaler_value = prescaler_options[TWSR & 0b00000011]; // mask out status bits
    unsigned long baseFreq = F_CPU / busSpeed;
    TWBR = (uint8_t)((baseFreq - 16) / (2 * prescaler_value));
    /* ... other stuff .... */
}

We need to talk

Okay, so there’s a couple of things that we need to talk about here. And the first is that prescaler.

The prescaler is defined in the two lowest bits (0 and 1) of the I2C status register, TWSR. These bits are known as TWPS1 and TWPS0. The values are set like this:

Bit 1
TWPS1
Bit 0
TWPS0
Prescaler
value
0 0 1
0 1 4
1 0 16
1 1 64

Here’s the thing, though – I haven’t yet worked out why anyone uses a prescaler value of anything but one. In fact, Atmel recommends that you leave it at the default of 1 and set your bit rate purely by messing with TWBR.

If I had to guess, I’d say the prescaler is available for microcontrollers with very high clock speeds so that they can operate with devices with modest SCL speeds – ie, 100kHz – because then you’d need to get the value for TWBR down to something that can be stored in a single byte. But that’s just a guess.

When using the ATMEGAs that I like, a prescaler value of 1 (ie, TWPS0 and TWPS1 both set to 0) is just fine and that’s the power-up default.

So I could replace those two lines (203-204) that determine prescaler_value by just having:

uint8_t prescaler_value = 1;

But I put those lines in on the offchance that one day I might want to be able to set the prescaler value.

I could also probably do without the array and just have 4 raised to the power of (TWSR & 0b00000011). That involves including the math.h library.

uint8_t prescaler_value = pow(4,(TWSR & 0b00000011));

That’s more concise, probably more efficient and almost completely opaque.

[UPDATE: That’s actually the approach I’ve gone with – don’t know why. The first version of this post had the line wrong – I was getting confused between languages and had forgotten that with C/C++ you have to import a library to do something as simple as raise to a power. Jeez…]

Doing a few sums, though, does show how you can run into issues if you’re not careful.

Most of what I’ve done with I2C to date has been with an ATMEGA328P rolling along with an external oscillator giving a clock speed of a respectable 16MHz. But let’s say you were using a microcontroller with an internal oscillator of 8MHz and a default clock division of 8, giving a 1MHz clock speed. So, let’s try to control an I2C device at 400kHz from this chip. Plugging the numbers into the equation gives a TWBR of –6.75 (yes, that’s minus). Oops! Somehow I don’t think that’s happening. So, under these circumstances, we’re stuck with the 100kHz SCL speed.

Also be aware that Atmel advises that the CPU frequency of any slave device (should you be using, say, an ATTINY as a slave) should be at least 16 times higher than the SCL frequency.

That’s it for now, more about setting things up and actually using the bus in later posts.

Quick update: Just to check that my maths was okay, I recompiled a recent project that used an I2C-based LCD screen. It was actually a project I set up to write SPI code. And I put the scope on the SCL line. And lo! A nice 100kHz signal.

 

Leave a Reply

Your email address will not be published. Required fields are marked *