NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.
Microcontroller Programming » Master/Slave I2C
May 04, 2011 by Noter |
Here is an example of master/slave communication between the nerdkit and another ATmega using the I2C interface. Similar to the master/slave SPI, in this example the master also sends a number to the slave, the slave increments the number and sends the result back to the master. Using TWI.c and TWI.h the I2C interface is implemented using interrupts so they must be enabled in both the master and slave for things to work. Slave processing actually occurs in an interrupt callback function when a data packet is received from the master. The slave modifies the buffer and then the master reads back the buffer. Version V1.2 of TWI.c and TWI.h can be found in the Access Serial EEPROM using I2C thread near the bottom.
===============================================================================
|
---|---|
June 06, 2013 by Ralphxyz |
So hey Paul, have you updated this code or made any changes? Ralph |
June 06, 2013 by Noter |
I don't remember making any updates. Haven't looked at these demo programs for a couple of years but I still use the TWI code. Lately I've been working on a project that uses a DS3232 RTC to time stamp measurement data as it is logged to eeprom. Both the DS3232 and eeprom use I2C and both are working fine with the TWI library code. Why do you ask? Have you been using the TWI code? |
June 06, 2013 by Ralphxyz |
I have a project coming up, that I am picturing using the TWI Master/Slave. I will be running a Temperature process (filament extruding) with a stepper motor. I am thinking of running the stepper off the slave freeing the master to monitor temperature and control. I sure appreciate the work you put into the I2C projects it really helped me. Ralph |
June 06, 2013 by Noter |
I'm glad you found it helpful. I like master/slave projects but it is extra work and so far I've been doing ok with interrupt driven programs that are able to use all available cycles. I don't use delay.h functions (don't even include delay.h) but use the built-in timers instead. I also like to run 18.432Mhz because it's the fastest crystal for for the mega168/328 that is still baud rate friendly. |
June 07, 2013 by Ralphxyz |
ha, I actually understand I2C better than interrupts. It would be interesting to do a stepper driver using interrupts. Ralph |
June 07, 2013 by Noter |
Sounds like you have an excellent opportunity to work more with interrupts. It will get easier with practice just like I2C did. |
May 06, 2014 by lsoltmann |
How easy or difficult is it to implement this if the master is a RaspberryPi using Python? I'm working on developing a flight computer for research and need to be able to read PWM signals from an RC receiver, manipulate them, and then send them to the servos. I've posted on the Adafruit forum about the RaspberryPi and PWM and it appears that it is not able to read or really produce a PWM signal (at least not at the speeds I need). A user suggested using one micro controller to read the PWM and another micro controller to send PWM. I immediately thought of the nerd kit. The code posted here appears to be exactly what I need, but since my programming is rather limited, I was wondering how to implement this if the master is using Python (or should I stick with C?). Would it be possible to just add the PWM code into the above slave code and then read accordingly from the RaspberryPi? Thanks! |
May 06, 2014 by Noter |
Yes, it should be about that easy. The slave doesn't care where or what the master is as long as the signals on the wire are correct. |
January 06, 2015 by lsoltmann |
I've working with the slave code, starting at a basic level, and am having some problems. I used the code as is, but added a
inside the while loop. In Python I have
and it gives back a value of 2. If I change the 3 to a 5, it gives back zero. What is the structure of the I2C_buffer/how should data be stored to it? Is the simple read_byte enough or should read_byte_data be used along with the position in the array? |
January 07, 2015 by BobaMosfet |
lsoltmann- In Noter's code, he declared the buffer thus:
That means it's a byte (unsigned char) buffer, 25 characters in size (consecutive). Therefore, the following simply puts 0x03 in the first byte of that array space:
BM |
January 08, 2015 by lsoltmann |
I guess my question would be how do I read from the I2C_buffer? I set I2C_buffer[0]=3 so I know the value and to see if I could read that value in python on a different device. I tried setting a few more of the array locations to known values and tried reading them in python using
and
where reg is the location in the I2C_buffer array. Both don't give the expected value back. |
January 08, 2015 by JimFrederickson |
My first questions would be: 1 - Where did you get the Python Program to Run I2C? For me that would be the first part. Try to validate that the Python Program is working with other Devices. We already know that "Noter's Program" is good. It does seem like you are more comfortable with a "Computer" though, so that is Get your "Master I2C Implementation" to work and then proceed to communicating with Without that you basically have a "2 Term Equation with 2 unkowns". ALWAYS, much harder to solve. |
January 09, 2015 by lsoltmann |
The python script I am using to read the MCU is one that I wrote for a calibration of some I2C sensors so I know it works. I'm running the script on a Raspberry Pi and I've gutted it to just the necessities
I went back into the I2C_slave program and inside the while(true) loop, added
to see if I could read back any of the values. read_byte appears to just read back what ever was read previously and read_byte_data gives back a value equal to reg+1 (I tried multiple different values for reg). Are the array locations in I2C_buffer equivalent to registers? |
January 09, 2015 by BobaMosfet |
Isoltmann-- Generally speaking, when we talk about buffers in any programming language, we are talking about a block of memory. Usually a consecutive block of elements of a given size. The Address is normally the physical memory location of any one of the elements. A single dimension array is nothing more than a consecutive block of ram of the specified type. For example-- each byte/char/uint8_t is exactly 8-bits in length. String those together consecutively and you have a buffer that has an address at the start and for each byte location in RAM. I2C_buffer looks like this in RAM. I'm just using location 0x00 as a starting point, but it would be wherever the linker locates it based on the make-file setup and architecture.
When you say: I2C_buffer[0] = 12; what you're saying is in reality:
See? You stuffed a 12 (0x0C) into location 0, at address 0x0000. The variable named I2C_buffer contains a single value: An Address: 0x0000, in my example above. that makes it a pointer. So when you access an array, you are indirectly accessing an offset index added to that base address. In my example, if you asked for the address of the 20th location in the I2C buffer:
'addr' would be a variable with a value of: 0x0014 Which would be the location marked with asterisks, below:
Hope that clarifies it. Sorry for typos, just banged this out on my way out the door. BM |
January 09, 2015 by JimFrederickson |
BM - I think there is a problem with your last post. c arrays/buffers are always referenced relative to "0". So the 1st location of an array is an index of "0", which is currently set to The "20th location" would be "0x13", which is currently set to "0x00". The "21st location" would be "0x14", which is the location marked with asterisks. |
January 09, 2015 by BobaMosfet |
JimFrederickson-- You are correct-- when I counted it, I started with zero... It didn't look quite right, but as I said I was rushing out the door.... :: hand to face :: :) Good catch. BM |
January 12, 2015 by lsoltmann |
BobaMosfet, thanks for the enlightenment on the buffers. What you said makes a lot of sense and I’m trying to implement it into the code. The ultimate goal of what I’m trying to do is measure a pulse length (which is proportional to RPM) and send it to a Raspberry Pi over I2C. The pulse length code I have working (thanks to Icarus, http://www.nerdkits.com/forum/thread/438/). Now I’m trying to send it over I2C to the Pi. The pulse length values range from 5000 usec to 200 usec. Lets say the pulse length is 1226 usec (defined as a uint32_t), since Noters code has the buffer as a uint8_t, the 1226 is divided up into two bytes, 202 and 4, and stored in the buffer. For simplicity, I hard coded
which based on what you said, would look like this in memory.
On the Pi I can see the MCU at an address of 0x27, which is what I set it at. Using the wiringPi I2C library (http://wiringpi.com/reference/i2c-library/), shouldn’t the commands
return the values located at I2C_buffer[0] and I2C_buffer[1] respectively? Based on some I2C tutorials (http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html) the “register” value is the internal address, which I thought was 0 and 1 (at least for the places I stored data). Also, where should the “data manipulation” take place, inside the while(true) loop in the main function or inside the handle_I2C_interrupt function? Thanks for all the help so far! |
January 13, 2015 by BobaMosfet |
Isoltmann- If your buffer is a uint32_t, that is 4 bytes in length (32 bits), not 2. Would look more like this: 1226 = 0x000004CA If MSB (most significant bit) is on the left in RAM it would appear thus:
Because we are dealing with a memory block, you can tell the compiler to address it in any format you wish. Noter's code specifies it as an array of bytes-- that nomenclature is FOR THE COMPILER ONLY. Not for the CPU/MCU. All that does is tell the linker what size the offsets are for the base pointer of the memory block. You could address his memory block as an array of unsigned longs (which is what a uint32 is), simply by adjusting how you address it by using type coercion (now called 'casting'). Casting is an incorrect word developed by a newer generation who didn't understand what was actually being done. Coercion is accurate because you are coercing or forcing a type to behave differently than what it was defined-- because you know what you are doing.
Now regarding the I2C communications, the Raspberry Pi link you sent makes me believe it wants to be the master, and your MCU would be the slave device. The 0x27 is supposed to be returned by the system as a unix file handle where it 'sees' an I2C device, and then you hand that value to the 'WiringPiI2C' library. In which case, you need to initialize the MCU as a slave device for your I2C protocol, give it an address (like '1') and go from there. Any location in RAM can be called a 'register' if you regularly use that location to handle a specific value. A register can be a byte, 2 bytes, 4, etc. When you call 'wiringPiI2CReadReg8(setup,0);' Raspberry PI expects the underlying I2C code to be valid on both ends, and the wiring correct. It will try to issue a command to the specified slave device, and will expect that slave device to return a value to it from that specified 'register' within the slave device. Raspberry Pi neither knows nor cares about anything on the MCU. All it expects is that the device understands I2C and responds accordingly. I would study the datasheet for the ATMEGA chip you are using, they have an I2C section which (usually) provides an example and a flow example also, so you can get a better understanding of the required interaction between the two devices. Raspberry Pi obfuscates all this stuff under the hood that you now have to learn in order to make it work on the embedded platform. BM |
January 13, 2015 by Noter |
Casting is conversion of type only, underlying data not converted. Coercion is conversion of type as well as data by the compiler. Look it up, they are not synonymous. |
January 13, 2015 by BobaMosfet |
Noter- Welcome back :) Actually, if you look at the underlying disassembly (which is what we're really talking about), they are the same. Casting/Coercion is nothing more than telling the linker how to address the object in question in a way other than that which it may have been typedef'd. In binary, there is no concept of 'type'-- just an address and an offset. BM |
January 14, 2015 by Noter |
By definition they are not the same. Most people are confused by the two because their syntax is the same and both are commonly referenced as casting. If you are looking at a cast of integers and structures then only simple type conversion is required and can lead one to believe it is all the same. However if you look at the assembly when casting (converting) to/from a floating point variable in a math operation you will see the compiler actually performs coercion for you. It's about the compiler and how it produces the 'binary' rather than the 'binary' itself. |
January 14, 2015 by BobaMosfet |
Noter brings up a good point, but although he and I like to reduce things to black and white in our own worlds, many people find a gray area here, and I think some of that is because there are other issues involved. It may help a newcomer to consider the context of implicit .v. explicit. For example, an implicit conversion is normally where the compiler performs extra work because the programmer didn't explicitly define the conversion desired. As in the case of using a char interchangeably with an int, as many people do. I can reference publications where it's described that a programmer can "...use a cast to coerce the type of a given expression into another type." So, perhaps it's more accurate to say coercion is performed by casting? It isn't different so much as it is the act of performing an explicit coercion. Perhaps this school of thought is the source of why people use the term interchangeably. For me personally, I was taught it was 'coercion' when I began coding in C in the 70's. Before that I was doing a lot of assembly-language, so I was already thinking about how things worked below the compiler level. As Noter mentioned the float, I wanted to be sure we addressed it. A 'float' is a special case, because it has a representation factor that relates to scale and magnitude, unlike any other type. Floats require extra steps by the compiler to alter that representation, showing in the disassembly (below). Here is an example with me explicitly casting/coercing, and then without-- compiler made the right choice and the end assembly is identical either way even though 'aULong' is declared as an 'unsigned long' and 'floatNum' is declared as a 'float'. You can see the extra 2 calls into the float library code for the representation conversion and then equality testing: Note that 'floatNum' is declared as a float and then initialized in main() to this value:
Now for the disassembly:
I use AVR Studio for an ATMEGA168, as that was convenient, and familiar to most everyone here. I also did a bad cast, trying to misuse the float, and you can see that the compiler takes my word for it and simply does a subtract, compare and appropriate branch:
And here, people can see what happens with an implicit conversion between an unsigned long taking the value of a float (according to the C-Standard it truncates0:
Hope the helps everyone. BM |
January 19, 2015 by BobaMosfet |
At the end of the day, controlling the binary is what you're after, and the compiler is nothing more than a tool, or layer between you and the binary. It's no different than talking to someone else-- you master your larynx and the language to allow you to communicate efficiently and effectively. The float is a good example of that above. You can successfully coerce a float to an unsigned long, without allowing the compiler to do anything with the representation. But why would this be useful? Well, just as a real-world example, you might find it worthwhile to be able to store a float in 4-bytes (it's raw representation) in an EEPROM, for retrieval later. Such as an in-the-field sensor device recording relative humidity. If you store it as an ASCII representation, it might take 12 bytes, but the raw data only requires 4 to store the same precision. Knowing how things really work, and what you can really do with the tools and objects you work with, is what allows you to become not just a great developer, but one of the few who know it as a black art and can craft things in ways the majority of developers will never achieve. Let your imagination and knowledge guide you. BM |
January 19, 2015 by Noter |
In your example above the AVR cross compiler is failing to convert the float to to long which is a bug in the compiler. Not that it matters much in the world of AVR but try the same test with the gcc compiler and you will see coercion performed every time as expected.
Compiled with "gcc -c -g -O0 -Wa,-a,-ad x.c > x.lst".
|
January 20, 2015 by Noter |
It occured to me this morning that maybe you encountered a bug because like most nerdkit users, you have an old version of avrgcc. I ran the same test again with avrgcc v3.4.3 and coercion was performed every time, the float was converted to unsigned long. It's a good idea to stay current with all your compilers. |
January 20, 2015 by BobaMosfet |
Noter, In the example above, I'm using GNU GCC with the WinAVR stub. The stub doesn't interfere with how the GCC compiler deals with coercion. It's not a bug, it's compiler 'implementation dependent' as this is not specified in the standard. This is one of the reasons it's important to understand your tools and what they generate. All you're showing is that your standard float library, that came with that compiler for the processor you're showing code for, is enforcing a type coercion in the examples you've given. And even so, I could still force that compiler to not do it with careful crafting of C. I don't allow a compiler to limit my capabilities-- it does what I want it to do because _I_ am the architect of my logic, not it. It cannot, nor will it ever understand what I am trying to achieve. As the ATMEGA code is what is pertinent to most folks here, I'm sticking with that in this forum. BM |
January 22, 2015 by Ralphxyz |
Thanks Paul and BM, I sure love your "discussions". It is interesting most of what BM says is how I learnt C. I spent a lot of time in the old C developer newsgroups and even had some answers to my questions answered by Ritchie himself. Of course I never became a programmer but have had some rich exposure. |
January 22, 2015 by BobaMosfet |
Ralph, Thanks. I think it's important for people to bear in mind that they are discussions. Noter and I view things differently, have different experiences and knowledge, but his knowledge is no less valuable or competent than mine. We both have done many, many things over the years and it works for each of us. The original thread gets derailed a bit, but by the same time, by expounding upon our differing views and exploring them, people in the forums that know less, get exposed to more useful information. The truth is, most of the really valuable information about programming is no longer available to most people wanting to learn. Computer sections in bookstores are so small now-- they used to be shelves and shelves. Most of the books in them have little useful information. Manufacturers used to give away reference manuals for their processors just to get people to consider their chip. There were dozens of magazines with all sorts of things (Byte, Dr. Dobbs Journal, etc...). BM |
January 22, 2015 by Noter |
BM, Interesting that AVR studio compiler truncates rather than converting from float to long like the other gcc compilers. I won't be testing that one since I haven't a single windows box left after moving all of them over to linux. Anyway, I hope we can agree that casting and coercion are not synonymous. Casting is conversion of type only while coercion is conversion of type and underlying data. May not match what you learned back in the stone age but now's always a good time to catch up. lol - I should know, I'm probably way older than you anyway. Ralph, If you want to be a programmer then all you have to do is program, program a lot. It takes lots of practice. Stick with a language until you get it by writing at least a handful of reasonably complex programs. I've been writing a gui program in python this week that controls an arduino over serial usb. First time I've really gotten into python and I like it. I've always liked developing with interpreters compared to compiled languages but no matter the language, it's always rewarding and fun to get a program working. |
December 13, 2015 by escartiz |
Reviving old thread to see if I can get some guidance. I am playing with 2 nerdkits to understand twi/i2c. I uploaded Noter's files intact, the only difference in the master is that since I don't have an lcd at the moment, I commented out all lcd lines. instead I am using uart to display the messages to my labtop. I am getting "Error at byte 1 expected 1, got 1". This makes no sense to me. If it is expecting 1 and the slave sends 1 isn't it suppose to request the next number instead of breaking? |
December 16, 2015 by BobaMosfet |
escartiz- TWI/I2C (hereafter just I2C) is relatively straightforward to use. All you have to do is the initial calculations to get the speed right (set TWBR and so on, based on appropriate calculations for your MCU speed). Once that's done, you need to know the address of the slave, usually there is an initial default value (like 0xA0, for example. Yours may differ). You need a buffer, like 60 bytes (depends on size of largest xmit/rcv data you might handle). Beyond that, you have 13 TWSR responses to deal with, 9 TWI commands, and then monitoring/setting a few flags and/or registers and the Hi/Lo relationship between SDA and SCL. Pay particular attention to the state each of these is in, because usually if the slave doesn't understand something, or didn't get an ACK from the MASTER it will hold the SCL low (to let the Master know it's still processing). Even without a scope, you can check this with a logic probe. I recommend, initially, you simply work with I2C in the simple form, just a Master and a Slave until you have that mastered, then add the rest of the logic to handle multiple, simultaneous I2C conversations. The datasheet has a bare-metal working example you can base your code off of. I wouldn't waste time jumping into someone else's code until after you've read the datasheet and have your mind around I2C to some extent-- then if you look at other code, you'll have a better idea what you're looking at when registers, lines, and flags are referenced, and the logic in that code will be more evident-- but ultimately learn the way that works best for you. Hopefully, you have a fast enough oscilloscope-- I know that I usually have my scope set to the 5us to 10us range so I can see the pulse train for I2C. BM |
Please log in to post a reply.
Did you know that one NerdKits customer discovered that his hamster ran several miles in a night using his kit and a Hall-effect sensor? Learn more...
|