View previous topic :: View next topic |
Author |
Message |
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
Who wants I2C master/slave code |
Posted: Tue Feb 15, 2005 9:53 pm |
|
|
Alright, I am tired of seeing so many people struggle with this topic. Some body (I ain't saying who) decided it would be a good communication bus for our modules to communicate with (I disagree). After about 7 years of fiddling with the stuff I think maybe I can give you folks some code that should work pretty well for you. Now we use this to pass messages back and forth in a multi-master system. Our protocol actually follows (well sort of) the Access.bus specification with some slight modifications. We added a checksum and a message acknowledgement.
So to the point:
What I want to know from those of you who would like some code is how you intend to use it. What are you trying to do between microcontrollers. I'll post some code that will best fit those cases. Now don't post talking to eeproms, realtime clocks and stuff like that. You can already find that code. I want those cases where people are trying to talk PIC to PIC |
|
|
dyeatman
Joined: 06 Sep 2003 Posts: 1934 Location: Norman, OK
|
|
Posted: Tue Feb 15, 2005 10:14 pm |
|
|
Mark,
I recently built a PIC based interface controller to tie a home automation controller to an RCS HVAC zone controller. I am planning to expand the capabilities to tie in additional systems by adding two more PICs on the same board. My prototype uses I2C for the tie-together. I for one would like to see what you have... Likely much better than what I have so far.
Dave |
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Wed Feb 16, 2005 3:11 am |
|
|
I am doing an engine controller, soon it will be expanded to control other things as well... new pic to do it and they must talk.
Second pic will receive data and process it. |
|
|
Guest
|
|
Posted: Wed Feb 16, 2005 3:29 am |
|
|
I want to connect a master and several slaves, where the master should issue commands received from a PC to specific i2c slaves. The slaves in turn should be able to interrupt to the master, and transfer data back to the master, who transfers it to the PC. |
|
|
Guest
|
|
Posted: Wed Feb 16, 2005 3:30 am |
|
|
I want to connect a master and several slaves, where the master should issue commands received from a PC to specific i2c slaves. The slaves in turn should be able to interrupt to the master, and transfer data back to the master, who transfers it to the PC. |
|
|
treitmey
Joined: 23 Jan 2004 Posts: 1094 Location: Appleton,WI USA
|
I2C message passing uses |
Posted: Wed Feb 16, 2005 9:53 am |
|
|
I have hardware that was handed to me that uses two 16F877A's for the two hardware UARTS. These two pics then send simple messages to each other. |
|
|
MikeW
Joined: 15 Sep 2003 Posts: 184 Location: Warrington UK
|
|
Posted: Wed Feb 16, 2005 12:39 pm |
|
|
I need PIC code to enable the PIC to be a master, or a slave device.
It would sit on the bus, with other devices (Philips micro), and do the collision detection, and arbitration etc.
I cannot get the source code for the philips micro, so it is going to difficult.
any help would be appreciated.
real e-mail is mikestefoy at hotmail dot com |
|
|
e
Joined: 02 Feb 2005 Posts: 9 Location: New York City
|
|
Posted: Wed Mar 02, 2005 3:06 pm |
|
|
yay! i've been struggling with i2c for weeks now, trying to synthesize all the different approaches and caveats found on this forum. my application is an interactive art installation involving a tree made of lots of dot matrix displays whose pixels disintegrate depending on proximity of viewers. i have something built from scratch working fairly consistently in slow mode but not fast mode. and am i the only one who finds it hard to believe CCS has built-in functions that don't really work? i mean, what's the point of built-in functions in a compiler package?
my i2c setup is fairly typical, i.e. 1 master with about 20-40 slaves, each of which drives clusters of LED displays. fast updating and zero errors are important. typical data packet involves address, command, and 1 or 2 bytes of data. occasionally will burst out 8 bytes of data per slave to re-write all pixels, meaning potentially 400 bytes fast enough that update appears simultaneous. not necessary for slaves to send anything back, although a status check would probably be a good idea. maximum bus length 15 feet. (capacitance might be an issue?)
(originally i had the display data just cascade serially from 1 uP but that ruled out simultaneous disintegration, so i've been rebuilding it using slave uPs and i2c. also tried simple rs232 between pics but felt like i2c was built for this so why not use it?) |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Mar 02, 2005 3:30 pm |
|
|
I haven't forgotten about this yet. Out of my department of 4 engineers and my boss, 3 people got laid off and one of them was my boss! Kind of busy right now, but I will post an example program that can be used as a multi-master or master/slave. |
|
|
Guest
|
|
Posted: Wed Mar 02, 2005 10:12 pm |
|
|
i'm curious to see what you think of PH Anderson's routines. He does things very differently than i2c code posted on this forum.
paraphrased code:
Code: | // Program 24_256_1.C (CCS Info PCM Compiler - PIC16F84)
// copyright, Peter H. Anderson, Scotland Co, NC, Mar, '99
#case
#include <16f84.h>
#include <string.h>
#include <defs_f84.h> // See Notes
// common i2c routines
byte i2c_in_byte(void);
void i2c_out_byte(byte o_byte);
void i2c_nack(void);
void i2c_ack(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_high_sda(void);
void i2c_low_sda(void);
void i2c_high_scl(void);
void i2c_low_scl(void);
// other routines
void delay_ms(long t);
void delay_10us(int t);
int num_to_char(int val);
#define SDA_PIN rb2 // RB.2
#define SCL_PIN rb1 // RB.1
#define SDA_DIR trisb2
#define SCL_DIR trisb1
void main(void) {
long mem_adr;
byte dat, n;
// demo write and read
while(1) {
mem_adr=0x0700;
for(n=0; n<16; n++) {
dat = 0xff - n;
random_write(0x00, mem_adr, dat);
++mem_adr;
}
mem_adr=0x0700;
for(n=0; n<16; n++) {
dat = random_read(0x00, mem_adr);
// display or use dat here
++mem_adr;
}
delay_ms(500);
}
}
// demo write & read routines
void random_write(byte dev_adr, long mem_adr, byte dat)
{
i2c_start();
i2c_out_byte(0xa0 | (dev_adr << 1));
i2c_nack();
i2c_out_byte((mem_adr >> 8) & 0xff); // high byte of memory address
i2c_nack();
i2c_out_byte(mem_adr & 0xff); // low byte of mem address
i2c_nack();
i2c_out_byte(dat); // and finally the data
i2c_nack();
i2c_stop();
delay_ms(25); // allow for EEPROM programming
}
byte random_read(byte dev_adr, long mem_adr)
{
byte y;
i2c_start();
i2c_out_byte(0xa0 | (dev_adr << 1));
i2c_nack();
i2c_out_byte((mem_adr >> 8) & 0xff);
i2c_nack();
i2c_out_byte(mem_adr & 0xff);
i2c_nack();
i2c_start(); // no intermediate stop
i2c_out_byte(0xa1 | (dev_adr << 1)); // read operation
i2c_nack();
y = i2c_in_byte(); // get data
i2c_stop();
return(y);
}
//////////// Common I2C Routines ///////////////
byte i2c_in_byte(void) {
byte i_byte, n;
i2c_high_sda();
for (n=0; n<8; n++) {
i2c_high_scl();
if (SDA_PIN) {
i_byte = (i_byte << 1) | 0x01; // msbit first
} else {
i_byte = i_byte << 1;
}
i2c_low_scl();
}
return(i_byte);
}
void i2c_out_byte(byte o_byte) {
byte n;
for(n=0; n<8; n++) {
if(o_byte&0x80) {
i2c_high_sda();
} else {
i2c_low_sda();
}
i2c_high_scl();
i2c_low_scl();
o_byte = o_byte << 1;
}
i2c_high_sda();
}
void i2c_nack(void) {
i2c_high_sda(); // data at one
i2c_high_scl(); // clock pulse
i2c_low_scl();
}
void i2c_ack(void) {
i2c_low_sda(); // bring data low and clock
i2c_high_scl();
i2c_low_scl();
i2c_high_sda();
}
void i2c_start(void) {
i2c_low_scl();
i2c_high_sda();
i2c_high_scl(); // bring SDA low while SCL is high
i2c_low_sda();
i2c_low_scl();
}
void i2c_stop(void) {
i2c_low_scl();
i2c_low_sda();
i2c_high_scl();
i2c_high_sda(); // bring SDA high while SCL is high
// idle is SDA high and SCL high
}
void i2c_high_sda(void) {
SDA_DIR = 1; // bring SDA to high impedance
delay_10us(5);
}
void i2c_low_sda(void) {
SDA_PIN = 0;
SDA_DIR = 0; // output a hard logic zero
delay_10us(5);
}
void i2c_high_scl(void) {
SCL_DIR = 1; // high impedance
delay_10us(5);
}
void i2c_low_scl(void) {
SCL_PIN = 0;
SCL_DIR = 0;
delay_10us(5);
}
// delay routines
void delay_10us(int t) {
#asm
BCF STATUS, RP0
DELAY_10US_1:
CLRWDT
NOP
NOP
NOP
NOP
NOP
NOP
DECFSZ t, F
GOTO DELAY_10US_1
#endasm
}
void delay_ms(long t) {
do {
delay_10us(100);
} while(--t);
} |
|
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Thu Mar 03, 2005 6:53 am |
|
|
His routines do not use the built in routines provided with the CCS compiler which gives him more control. Also, these routines are just basic master routines to access a slave. That is NOT what I am planning on posting. While the routines will do that, the routines that I am going to post are both the slave and master portions that will allow each pic to talk with the other. I didn't look at Peter's that closely but I would rather use his than the CCS functions because I can see what is going on. |
|
|
valemike Guest
|
|
Posted: Thu Mar 03, 2005 9:54 am |
|
|
Using CCS's library routines...
i2c_start();
ack = i2c_write(slave_address);
ack = i2c_write(data1);
ack = i2c_write(data2);
...
What happens if the slave has not yet digested the write of data1, and the master tries a write of data2?
From what i've seen in my debugging, there will be an ack of '1' on the failed write.
And clock stretching (CKP bit) by the slave only applies to when the master does an i2c_read(). This makes sense that the slave has to inhibit the bus while it's getting its outgoing data ready.
How then is the slave supposed to "inhibit" the master from bombarding it with i2c_write()s at a fast rate? The only way i have gotten around this is to put the mssp interrupt as priority (#priority) in the slave source code. Maybe if i can slow down the clock (with the newer 3.214+ compiler versions), then i won't see this problem.
I've also done some unorthodox clock stretching even when the master issues an i2c_write() (not a read). All the above methods combined has been failproof for me, but i know one day, there will be a problem in the fiield and i'm going to eat crow. |
|
|
valemike Guest
|
|
Posted: Thu Mar 03, 2005 9:55 am |
|
|
Using CCS's library routines...
i2c_start();
ack = i2c_write(slave_address);
ack = i2c_write(data1);
ack = i2c_write(data2);
...
What happens if the slave has not yet digested the write of data1, and the master tries a write of data2?
From what i've seen in my debugging, there will be an ack of '1' on the failed write.
And clock stretching (CKP bit) by the slave only applies to when the master does an i2c_read(). This makes sense that the slave has to inhibit the bus while it's getting its outgoing data ready.
How then is the slave supposed to "inhibit" the master from bombarding it with i2c_write()s at a fast rate? The only way i have gotten around this is to put the mssp interrupt as priority (#priority) in the slave source code. Maybe if i can slow down the clock (with the newer 3.214+ compiler versions), then i won't see this problem.
I've also done some unorthodox clock stretching even when the master issues an i2c_write() (not a read). All the above methods combined has been failproof for me, but i know one day, there will be a problem in the fiield and i'm going to eat crow. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Thu Mar 03, 2005 10:00 am |
|
|
If the master does not get the ACK (a 0) then it should handle what needs to be done. Most of the examples that you are going to find are pretty basic. They hope for a perfect world. Sometimes they are kludged with delays. Take a look at how many people write to an eeprom. They just stick a huge delay in there for the write to complete. The proper way is to check for an ACK on the next write. |
|
|
valemike Guest
|
|
Posted: Thu Mar 03, 2005 10:34 am |
|
|
Mark wrote: | If the master does not get the ACK (a 0) then it should handle what needs to be done. Most of the examples that you are going to find are pretty basic. They hope for a perfect world. Sometimes they are kludged with delays. Take a look at how many people write to an eeprom. They just stick a huge delay in there for the write to complete. The proper way is to check for an ACK on the next write. |
After skimming thru Microchip's App note AN735 just now, it says that a NACK indicates a hw/sw error, or simply an overrun. Even if it's an overrun, then what should the error handler do? Is it possible to simply re-try the write? From what i get out of the app note, it says to issue a restart, or stop/start.
I think my overruns are a result of the slave's interrupt latency handling of multiple frequent interrupts from other sources. It may just well be that a slave PIC needs to be given ample time to digest things. If there was a NACK, then everything has to be aborted, and the whole start/write_address/write_etc sequence has to be re-started anyways.
So such delays liberally sprinkled all over the place, which we both feel are kludges, are probably absolutely necessary. It seems the goal is NACK avoidance rather than NACK error handling. I can't see any other way to avoid a NACK than to inject delays. |
|
|
|