I2C and the very similar SMBus are in almost everything you'll even find it as a major part of computer motherboards.
The interface allows communication with up to 127 devices with only two wires.
For many sensors I2C is the standard way to get data.
So here we have two wires, and you've got the S clock and the S data, that's SCL and SDA.
The reason that the bus can communicate with 127 devices on only two wires is because those wires are open drain.
Open Drain means that the driving side only drives low.
Each of the wires have to have a pull up which means that when the line isn't being driven
low it gets pulled high. Now if you see a schematic one day and you see two resistors pulling up to the power rail
you'll know why that is.
The value of these resistors can vary quite a lot and that depends on the speed grade of the interface.
The smaller the resistance, the faster the response. ***OHM***
I2C is a really slow interface and a comparable one would be dial-up Internet.
Dial-up Internet was like 56 kilobits per second. I2C's lowest
standard speed is 100 kilobits per second so it's standard speed is double the speed of dial-up Internet.
Now a gigabit Internet connection, for example, is over ten thousand times that speed.
I2C until recently only had four speed grades
but in 2012 a fifth one was introduced. These speed grades might not be good enough for things like Internet or displays
but they are ample for most sensors and data acquisition applications.
The sample rate of common sensors, especially
mechanical ones, is mostly quite slow. Strain gauges which are on bridges only require a few hertz for the sample rate.
Even accelerometers rarely go above
tens of Kilohertz and the I2C bus in the megabit range easily accommodates the data requirements for those sensors.
Now if we have an accelerometer that samples at
32 Kilohertz, which is unrealistic, we only need 256 kilobits per second for an 8-bit accelerometer.
Every sample of an 8-bit accelerometer requires 8 bits of data to communicate so multiplying the speed
the sample rate of the excel Model with the bits you get
256 kilobits per second.
Let's assume that half a frame is our data and the other half is the address and so for this case it would be
512 kilobits per second which is far below the 1 megabit per second mode. Now even with our unrealistic accelerometer
we're still ok with 1 megabit per second.
So every transaction begins with a start condition. The start condition is when the data line gets pulled low and
then the clock line gets pulled low. After the start condition data is sent. The data
that is sent is usually in 8-bit blocks. Each of the eight bits is sent with a rising and falling
edge of the clock. This is called clocking out data. In the case that it's a read
transaction you would be clocking in data. The master still drives the clock
but it doesn't drive the data line. After the block is complete there are two different things that can happen. If
the slave Device is okay with the data that was sent then it will acknowledge the transaction
and it will pull the line low. If it's not okay it will allow the line to float high
which indicates that there's a problem with the transaction.
The first block of data that is transmitted is the address of the slave, The address is between 0 and
127 and this is a 7 bit address.
There are another type of addresses this is 10 bit addresses
but we're not going to talk about them here. If a device exists on the I2C bus with the address that was sent then an
acknowledgment will be received.
That's when the line is pulled low. If there isn't a device with the address that was sent then the acknowledge line will stay high.
Now there are some devices that claim to have 8-bit addresses
but this is usually a device that says it has two different addresses where the read address is this and the write address is that.
Notice that the least
significant bit is the only thing that changes in these addresses. This effectively means that even though they say it's an 8-bit address
it's actually a 7-bit address. You can take
version of the 8-bit address and say it is the 7-bit address.
After the address is sent, depending on what the read and write bit was, data is either received or it is sent from the micro-controller. If
the read-write bit was one then the master is receiving data. If it was zero then the master is sending data.
When the master is receiving data it just leaves the data line alone and allows the slave device to set the data bits.
If the data block is not acknowledged by the slave then it's probably the slave saying "stop reading,
there's no more data". If the master is writing data and the slave didn't acknowledge then there's a problem. If all went well
the master will probably then follow through with a stop condition. The stop condition is when the clock line is released, or
allowed to go high, and
then the data line does the same, it is allowed to go high.
Between blocks of data, for example between the address and the data blocks, the clock line is held low
but the data line is allowed to go high. If you were bit banging this, for example, this makes things quite simple because your function
that writes data only needs to clock out eight bits of data and
before and after the transaction have a start and stop condition. At the end of data sending
it's always required that the data line is released for acknowledgement, so the state between data blocks is always going to be okay.
I've always found it very useful to learn how to bit bang an algorithm, so that you fully understand it
so we're going to do that right now. We're going to bit bang the algorithm. So here we go.
Okay, so we're just going to put it in C++. The code I'm working on here is based on the example
for bit banging SPI I use some structures which help in developing these bit banging libraries.
I'm just going to work on the bit banging library now.
This is a simple implementation and a lot of it is based on the previous SPI
bit banging tutorial so the first thing I'm going to do is write this send function. The send function is a basic for loop which
sends eight bits
and this is going to be the fundamental thing that sent the address and the data and all that kind of thing. So the send function is
basically just a loop and it does a few different things and it accesses you know GPIO through
functions instead of
actual pin set and clear calls.
The reason that we're doing
functions instead of direct pin set and clears is because the functions kind of isolate the
platform-agnostic-ism
so that if I were to change platform from this visual studio environment to for example an MSP430
I would only have to change the code inside those functions so I can have those
functions in their own file and that file just includes everything that is platform dependent.
So I'm just modifying the SPI code just to you know make the names and logging system make any sense.
The way I actually do this is by having before and after any change of a pin
I basically just record the state of all the pins, and that's all I'm doing here.
That allows me to plot the data and see kind of like
the relative change
between each of the pins assuming that there's no
noticeable overhead from program execution.
Okay in the send function we're just going to store a temporary,
we're going to we're going to mask out that top bit and we're going to
store the temporary and probably like clock it out.
Clocking out is the rising and falling edge, so let's write that okay.
So you know a rising edge is setting it high then a falling edge is setting it low again.
Okay, so we need a start and a stop function and those just assert the start and the stop conditions
we talked about before. Those are going to be used in the I2C write and the first thing we're going to do in that function is
assert the start condition, then we're going to send the address of course and you know then the data and then then we're going to
assert the stop condition in between each of the the blocks of data
we send we're going to look for an acknowledgment.
So we need a function that converts and
7-bit address to the 8-bit Data address and that function is going to take whether it's a read or a write address and that
read and write is the lowest bit of a byte.
So all we're going to do is mask the address to make sure it really is seven bits then we're going to shift it across
so we have room for that read or write bit and then we're going to OR in that bit.
We also need a function to get an acknowledgment. An acknowledgement is basically
allowing the data line to go high and then clocking and reading the data from the slave, and that's what we have here.
Now if we don't receive an acknowledgement we do want to abort the write routine so let's check if we received an acknowledgement and then
figure out what we're going to do with that.
I think I'll probably just return true if it's successful and false if it's not.
Okay, so I'm going to make sure that I'm actually setting the output that's the SDA so let's call the function that gets that bit out
temporary and sets the pin.
Okay, so we're going to write to device
0x10 and we're going to write the data
81 in HEX so we should see that in the spreadsheet
we're going to generate. I need to make sure that the default states of all the pins are correct
so they've got to be all default high because by default the bus is
open, it's open drain.
Just going to update the write routine so that it does indeed stop if it receives the acknowledgement, it sends the stop condition.
I mean, it should probably do that anyway, but
it doesn't matter. Okay, so okay now it, now it's fixed.
So what I'm doing here is just setting the default state of the pins in the variable so that it's also
defaulted to zero on the actual variables.
And then what I'm doing here is just copying the values from the terminal that it output to, the print the I2C print command did that.
Okay, so I've got a scatter plot in Libre Calc here and let's just check it. Here's the start condition and
here we have the address, there are seven bits there, and here's the acknowledgment here, which we haven't received because we're
not actually talking to a device.
And the condition before the data byte is correct so it seems like our formation of the frame is correct.
Hope you learned a little bit about I2C. See you next time!