During a recent project, I needed several switches for setting control values. The AVRs did not have enough I/O pins, so I had to think of something. I could have tried a complex input system with a keyboard and display, but the ATtiny2313 would have run out of resources. Fortunately, Atmel has provided a way around this problem by including an interface that can link to additional chips (such as memory or I/O ports) with a simple two wire interface. That's right, by using just two I/O pins on an AVR we can access many additional I/O pins, and other resources as well.
This two wire interface is formally known as the Inter-Integrated Circuit bus, or just the I2C bus and was invented by NXP when it was still Philips Semiconductors. If you're reading this Instructable then you've probably heard of the I2C bus and may even have used it on a PIC or other microcontroller. While conceptually very simple, and supported by hardware resources on the AVRs, software drivers are still necessary to use the I2C bus. Atmel provides Application Notes (see the Resources later in this Instructable), but these are incomplete and don't show any examples beyond communicating with another AVR device.
It is not the purpose of this Instructable to teach anyone how to create I2C drivers for the AVRs. Rather, I'll provide expanded versions of the Atmel drivers for ATtiny2313 and ATmega168 devices, I'll explain the requirements and restrictions that apply when using these, and I'll show you working examples of I2C devices. After you work through this Instructable you'll be able to use the I2C bus successfully in your AVR projects. Obviously, you can ignore the drivers for either tiny or MEGA if you're only interested in one of them. For those interested in learning more about the I2C bus, I'll provide links to appropriate material.
Remove these ads by
Signing UpStep 1: What's All this I2C stuff anyway?
The electrical requirements are pretty simple. The master and the slaves must use the same level for Vcc, the grounds must be connected, and the SCL and SDA lines must be pulled up to Vcc. The value of the pull-up resistors is precisely determined by a calculation based on the total capacitance on the bus, but practically can be pretty much any value between 1.8K and 10K. I start with 5.1K and use lower values until it works. This usually isn't an issue unless you have a lot of devices or long lengths of wire between devices.
The nominal data rate on the I2C bus is 100Kbits/second. Rates of 400Kbits/second, 1Mbits/second, and beyond are possible as well, but aren't supported by the drivers in this Instructable. All I2C devices will work at 100Kbits/second.
The ATtiny2313 and the ATmega168 each implement the I2C bus differently. ATtiny2313 uses the Universal Serial Interface (USI) hardware - which can also be used for the SPI bus. ATmega168 has dedicated hardware for the I2C bus known as the Two Wire Interface (TWI). Once the drivers are written, these differences are mostly transparent to the user. One significant difference is in the software: The ATmega168 I2C driver is interrupt driven while that for the ATtiny2313 is not. This means that an ATmega168 program does not have to wait for I2C data transfers to take place, but only needs to wait before initiating another transfer, or until data arrives from a read operation. The examples and discussion to follow should make this clear.
I2C addresses are 7 bits long, so up to 127 devices can be on the bus if each has a unique address. As shown in the figure, this 7 bit address is shifted left one bit and the least significant bit is used to flag a read or write of the device at the address. Thus the complete slave address is an 8 bit byte. The actual address is partially determined internally to the device and can't be changed (4 most significant bits), and partially determined by bits that may be connected to device pins (3 least significant bits) that can be tied high or low to set a specific address.
Sounds confusing, but an example will make this clear. The PCA8574A data sheet shows that the four most significant bits of the I2C address will always be 0111. The next three bits are determined by the settings on pins AD0, AD1 and AD2. These pins can be tied to ground or to the positive voltage supply (5 volts) to represent 0 or 1 respectively. So the range of possible addresses is 38 to 3F hexadecimal, as shown in the other figure from the PCA8574 data sheet. So by changing the address bit settings, up to 8 PCA8574As can be on the I2C bus at the same time. Each will respond to its specific slave address only. If even more I/O ports are needed, the PCA8574 can be used. The only difference between the PCA8574 and the PCA8574A is that the I2C slave address range of the PCA8574 is 20 to 27 hexadecimal.
Determining the address of a given device can be confusing since some data sheets consider the read/write bit to be part of the address. Read the data sheet carefully and keep in mind that the slave address will be 7 bits long. The read/write bit should be treated separately. Again, an example will help. The data sheet for the 24C16 EEPROM we'll experiment with says the first (most significant) four bits of the slave address are 1010. The next three bits can be determined by A0, A1 and A2; but note the data sheet also covers 24C01 through 24C08 which are smaller sized EEPROMs. The figure from the data sheet shows that the settings of these address bits are ignored as the size increases and are completely ignored for the 24C16. That is, the last three bits don't matter and the 24C16 really uses all I2C slave addresses 50 through 57 hexadecimal. The range of slave addresses will actually address different sections within the 24C16. The first 256 bytes are at address 50h, the next 256 at 51h, and so on up to the last 256 at 57h - for a total of 2K bytes. Since the address of the PCF8570 RAM we also experiment with is in this range, the 24C16 and the PCF8570 can't be used together.






































Visit Our Store »
Go Pro Today »




I'm trying to use the random read function and it makes sense but for one step:
in USI_TWI_Start_Transceiver_With_Data, when you wrote the start address (in write mode) and you re-set the buffer, go back into address mode, it seems like you're also missing setting USI_TWI_state.masterWriteDataMode to false. Without that, I ran through the loop and I can't see how you'd ever end up in the "read" section.
What am *I* missing?
Thanks,
Dan.
You ask a good question. You are obviously interested in understanding the code, not just using it. The only thing you are "missing" is how the masterWriteDataMode bit gets cleared - not your fault since the naming and commenting are not clear. Let me try to explain how it works.
The statement
USI_TWI_state.errorState = 0;
clears the masterWriteDataMode bit (line 217). That's why the addressMode bit must be reset at line 218.
(This is not well documented. USI_TWI_state.errorState is a union that includes the mode bits. Setting the uchar errorState to 0 sets all the bits in the union to 0 also. This behavior is documented by a comment in the USI_TWI_Start_Read_Write routine and should have been duplicated everywhere it's done.)
The operation of the loop should make sense now.
Let me know if it's still not clear or if you have other questions.
the problem i have is that I #included te USI_TWI_Master.h file with no poblem but the c file gives me a hard time. i had to copy paste the entire file right into my code and its hell to debug.
what is the correct way to use the USI_TWI_Master.c file? cant i just dump both files into my compiler's file path and have them available for future proyects?
#include "USI_TWI_Master.h"
#include "USI_TWI_MAster.c"
will this work?
HTH.
How do I get my AVR Studio 4 to build your code? In other words, how do I get AVR Studio to process your makefile?
Newbie here, I know.
Also, is there a way using I2C to be both a master and slave? I'm thinking of having one be the master, and the slave toggling a GPIO when it wants to communicate...initiating a read, for example.
Any code that would help me with that? I am relatively new to this. :-(
I2C devices are either Master or Slave, not both. Slaves do not speak unless spoken to (by the Master). The correct way to have a Slave communicate is for it to be continuously polled by the Master. Respond with a meaningless value if there is no valid response ready. If you have a device that frequently needs to send bursts of data, then make it the Master and have the receiving device be the Slave and just listen.
If you have only programmable devices on the bus, it is possible for them to switch roles as Master and Slave, but they must do this independently of the I2C Bus when no one is using the bus. Do this only if you're sure other ways won't work since you'll be on your own developing the code.
Have fun with your I2C experiments!
Others have mentioned that there is a TWAR register...but I searched the ATtiny44 datasheet for such a register and none exists.
Thanks again for the great advice!
This becomes an advanced amateur project. You'll learn a lot making this work, but expect to spend some time learning. Good luck and have fun.
Good Luck!
I'm making a turntable for modeltrains which uses a lot of I/O and only has 2 wires available (along with supply of course) to control all functions on the rotating part of the bridge.
Because of the fact that the controller (ATMEGA8) does have to do all the controll-functions (motor / locks / position sensors etc.) as well as the reception and feedback of the loconet signals (protocol for controlling aceccories on a model rail layout) I'm bound to run the ATMEGA8 at the full 16MHz. Will that be possible with your TWI library? When yes, what do i have to modify? Or are there other options. Thanks in advance!
To use the TWI at 16MHz, all you need to do is adjust the SCL frequency. This is described in the data sheet under the TWI port, Bit Rate Generator Unit. If you change the value of TWI_TWBR from 0x0C to 0x48, that should do it. TWI_TWBR is defined in TWI_Master.h.
Here's the calculation:
SCLfreq = Processor Clock /(16 + 2*(TWBR)*4^TWPS)
SCLfreq=16MHz/(16 + 2(72)*4^0) = 16MHz/160 = 100KHz
HTH. Let me know if this doesn't make sense.
http://www.onsemi.com/PowerSolutions/parametrics.do?id=2311&lctn=home
This is a print, study, & keep Instructable. Thanks for all the information.
CHEERS
Great article.
Ralph
Ralph
Thanks for your attention to detail!
Have you tried setting the pin and then twiddling the DDR instead of the actual pin. ISTR that being the usual way to do open collector-ish lines.
Your comments remind me that I need to update this Instructable with my latest code that now handles addresses longer than two bytes. (See discussion with p2otoole.)
If you use the code from Atmel for the slave, be aware that they handle addressing in a non-standard way. Have a look at the data sheets for the standard approach. The data sheets for the parts (EEPROM and Port Expander) I used are a good place to start. You could also have a look at some of the I2C displays that exist to see what they do. Or invent your own scheme. It's your project after all.
I have a question about the source code.
I went over the code written for ATtiny2313. You select External, positive edge for the Shift register and Software clock strobe (USITC) for 4-bit Counter (USICS1=1, USICS0=0, USICLK=1). In the function unsigned char USI_TWI_Start_Transceiver_With_Data() you define the constant below.
unsigned char const tempUSISR_8bit = (1<<USISIF)|(1<<USIOIF)|(1<<USIPF)|(1<<USIDC)| // Prepare register value to: Clear flags, and
(0x0<<USICNT0); // set USI to shift 8 bits i.e. count 16 clock edges.
Why the program has to count 16 clock edges for 8-bit transfer? I thought the programhas to count 16 clock edges for 8-bit transfer when USICS1=1, USICS0=0, USICLK=0 or when USICS1=1, USICS0=1, USICLK=0.
Please explain.
Thank you so much,
Other than that, this program was what I have been looking for, and I know its gonna work for me,
Thank you
The ATmega8 does not have a CLKPR register. To set the clock prescaler to give you a 4MHz clock you have to blow fuse bits. With that done, the TWI looks like it would work the same.
The 24LC512 lives in the I2C address block from 50h to 57h. You pick the exact address by tying A0-A2 high or low to select the three least significant bits. That is, all lo = 50h, A0 hi, rest lo = 51h, etc.
But there's an additional wrinkle (isn't there always? ;)} The 24LC512 uses two address bytes to address the 64K memory space. Mostly not a problem, you just put in an extra address byte and make the buffer 1 byte bigger. Skip one additional byte when you try to read from the buffer as well. The block read and write size is 128 bytes, so that needs to change if you want to take advantage of that capability. Finally, the TWi_Start_Random_Read read routine must be modified since you have to send two address bytes instead of one address byte before sending the second START signal.
Unfortunately I don't have a 24LC512 to experiment with so I can't guarantee this will work, but I think all that needs to be done is to pass in an additional argument to the function telling it how long the address is. Right now that value is always 2, but if you add the argument then you could pass in either 2 or 3. If you want to contact me privately I'll send you modified code to try out. If it works, we could post it as an update.