|
|
View previous topic :: View next topic |
Author |
Message |
tedlarson
Joined: 03 Oct 2003 Posts: 13
|
I2C slave writing troubles.... |
Posted: Fri Oct 03, 2003 3:19 pm |
|
|
I have been trying to write a simple I2C master/slave communications program, and I am having all kinds of difficulties.
Both are PIC18F252's. I am on CCS PCH 3.155.
I am using the hardware I2C, with 1k pullups.
I have some 24LC65's on the same bus. I can talk to them fine. I am pretty sure it is my slave code that is the problem, but I cannot isolate it.
The behavior I see is that I never receive the 'R' from the master. I always miss it. It's like some kind of timing problem.
Any help would be greatly appreciated.
Thanks,
- Ted
Here is my slave:
--------------------
#use I2C(slave,sda=I2C_SDA,scl=I2C_SCL, address=SLAVE_ADDR,FORCE_HW)
#INT_SSP
void ssp_interupt ()
{
static int lsb=0;
static int msb=0;
int cmd;
int readbyte=0;
if(!i2c_poll())
{
if(icstate==1)
{
i2c_write(msb);
i2c_write(lsb);
msb=lsb=0;
}
icstate=0;
}
else
{
readbyte = i2c_read();
switch(icstate)
{
case 0:
icstate=1;
return;
case 1:
cmd = readbyte;
if(cmd=='R')
{
fprintf(DEBUG, "YES!!!\r\n");
msb = 3;
lsb = 3;
}
else
msb=lsb=99;
icstate=0;
return;
}
}
}
Here is my master:
-----------------------
#use I2C(master, sda=I2C_SDA, scl=I2C_SCL, FORCE_HW)
i2c_start();
i2c_write(MOTORBOARD);
fprintf(DEBUG, "Sending command\r\n");
i2c_write('R');
i2c_start();
i2c_write(MOTORBOARD+1);
msb = i2c_read(1);
lsb = i2c_read(0);
i2c_stop();
fprintf(DEBUG, "Motorboard said (%D)(%D)\r\n", msb, lsb); |
|
|
tedlarson
Joined: 03 Oct 2003 Posts: 13
|
|
Posted: Sat Oct 04, 2003 5:08 pm |
|
|
I am really starting to get frustrated....
After fiddling with this problem for 2 days straight now....I am getting the feeling that the #use i2c stuff in the compiler doesn't work properly for building a slave that is timed properly. I went back and read MANY posts on this message board where everyone solved the problem by just taking control of the registers and DIYing a I2C slave.
All I want to do is build an I2C slave that can read 4 sequential bytes from a master, and send 4 sequential bytes in response. This sounds so simple....but it apparently isn't . The ex_slave.c example makes it look like it should be easy....unfortunately it isn't.
Does someone have a slave example USING the #use I2c that actually works for more than 1 byte up and 1 byte back that they could post?
Thanks,
- Ted |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Sun Oct 05, 2003 8:48 am |
|
|
Well I posted something on the old board:
http://www.ccsinfo.com/forum/viewtopic.php?t=6821&highlight=exslave+c
The first example is doing it yourself using the registers (the way I would do it). The second example I modified the ex_slave.c file.
For your example, did you try an remove the fprintf statement from the slave code. That will cause a delay and probably a failure. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Sun Oct 05, 2003 8:54 am |
|
|
One other thing, does SLAVE_ADDR == MOTORBOARD? If not, that will also be a problem. |
|
|
tedlarson
Joined: 03 Oct 2003 Posts: 13
|
|
Posted: Wed Oct 08, 2003 9:55 am |
|
|
Mark wrote: | One other thing, does SLAVE_ADDR == MOTORBOARD? If not, that will also be a problem. |
Yes...they are the same. Thanks for the reply!
After LOTs of fiddling with the ex_slave.c code, I was able to get the slave to respond with several bytes. However, you cannot just do a write, write, write to reply...The writing appears to NOT be synchronous. So, it is possible to send all the bytes faster than the receiver can receive the, and then you end up overrunning the master.
So...for example:
if (i2c_poll() == FALSE) {
if (fState == ADDRESS_READ) { //i2c_poll() returns false on the
i2c_write(1);
i2c_write(2);
i2c_write(3);
fState = NOTHING
}
}
Will NOT work. But, if you put delays (ugh) between all the writes...it will. It may be that the example you came up with, with the while loop writing from an array, has just enough delay in the loop, that it works.
My biggest problem is the receiving side of the slave. If I want to receive several bytes....for example:
...ex_slave.c...
...
else if (fState == CONTROL_READ) {
address = incoming;
fState = ADDRESS_READ;
}
else if (fState == ADDRESS_READ) {
buffer[address] = incoming;
a = i2c_read();
b = i2c_read();
c = i2c_read();
fState = NOTHING;
}
}
This will not work. Again...it is a timing problem between the master and the slave. After ALOT of trial and error, I have never been able to get the timing of this part to work properly.
I am going to try to take your "take direct control of registers" example and adapt it to what I am trying to do. I'll post my results.
Again....I am so frustrated that something that should be simple via the built in libraries, is so darn difficult. Timing problems are always the biggest PITA's to debug.
Thanks,
- Ted |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Oct 08, 2003 10:57 am |
|
|
Quote: |
This will not work. Again...it is a timing problem between the master and the slave. After ALOT of trial and error, I have never been able to get the timing of this part to work properly.
|
It is not really a timing issue. You will get an interrupt each time a byte is transmitted. You should only read 1 byte per interrupt. Think of this as you would RS232 data coming in from a serial port. The difference is that there are many flags that are set to indicate why the int was invoked (start, stop, data or address, read or write mode, etc.. )so that you can take the approiate action. Read the datasheet on the PIC and I believe it will be very clear to you. I wouldn't use the CCS functions. They are okay for some but not in all cases. |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Wed Oct 08, 2003 10:59 am |
|
|
Because the master triggers everything through the clock all delays between bytes should be in the master. When I do syncronous transfers using SPI the master over runs the slave if I dont place delays between bytes.
In the slave
Code: |
SPI_Buffer[0] = spi_read(SPI_Buffer[0]);
SPI_Buffer[1] = spi_read(SPI_Buffer[1]);
SPI_Buffer[2] = spi_read(SPI_Buffer[2]);
SPI_Buffer[3] = spi_read(SPI_Buffer[3]);
|
In the master
Code: |
SPI_Buffer[0] = spi_read(SPI_Buffer[0]); delay_cycles( 8 );
SPI_Buffer[1] = spi_read(SPI_Buffer[1]); delay_cycles( 8 );
SPI_Buffer[2] = spi_read(SPI_Buffer[2]); delay_cycles( 8 );
SPI_Buffer[3] = spi_read(SPI_Buffer[3]); delay_cycles( 8 );
|
If I remove the delay it stops working. Keep in mind that this is using direct addressing for all of these values. Also the interupts are disabled during the transfer. The clock is 2Mhz and baud is 1M baud in and out. At 8uS for data and 4uS pause per byte. The interupt flag in the master that signals the completion of clocking out data byte occures before the flag for recieved data byte in the slave(I guess). This would allow concurent loading of the shift register in the master without any pauses due to loading/unloading process time. In order to allow a good interupt driven transfer the hardware I2C and SPI should have been double buffered much like the USART has been. |
|
|
tedlarson
Joined: 03 Oct 2003 Posts: 13
|
|
Posted: Wed Oct 08, 2003 12:09 pm |
|
|
CCS tech support just sent me the following code.
It works very well. My problem is now solved. You can send 2 bytes up, and get 2 bytes back, with a 1 byte address...although it would be easy to extend to 2. I have this running between two PIC18F252's running at 40mhz and it seems solid.
Enjoy!
- Ted
<PRE>
typedef enum {NOTHING, CONTROL_READ,
ADDRESS_READ, READ_COMMAND_READ, DATA_READ_LOW, ADDRESS_READ_LOW} I2C_STATE;
I2C_STATE fState;
byte address;
long buffer[0x10]={0x103, 0x205, 0x307, 0x409, 0x50A, 0x60C, 0x710, 0x812, 0x914, 0xA16};
#INT_SSP
void ssp_interupt ()
{
byte incoming;
static byte dl, dh;
if (i2c_poll() == FALSE) {
if (fState == ADDRESS_READ) { //i2c_poll() returns false on the
i2c_write(make8(buffer[address], 1));//interupt receiving the second
fState = ADDRESS_READ_LOW; //command byte for random read operation
}
else if (fState == ADDRESS_READ_LOW) { //i2c_poll() returns false on the
i2c_write(make8(buffer[address], 0));
fState = NOTHING; //command byte for random read operation
}
}
else {
incoming = i2c_read();
if (fState == NOTHING){
fState = CONTROL_READ;
}
else if (fState == CONTROL_READ) {
address = incoming;
fState = ADDRESS_READ;
}
else if (fState == ADDRESS_READ) {
dh = incoming;
fState = DATA_READ_LOW;
}
else if (fState == DATA_READ_LOW) {
dl = incoming;
buffer[address] = make16(dh,dl);
fState = NOTHING;
}
}
}
</PRE> |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Oct 08, 2003 12:15 pm |
|
|
Do a test where you send only one byte and then stop (simulating some sort of error). Then try and do a correct sequence. I believe that you will find their state machine gets trashed! That is why it is better to look at the flags when reading the data. |
|
|
tedlarson
Joined: 03 Oct 2003 Posts: 13
|
|
Posted: Wed Oct 08, 2003 12:47 pm |
|
|
Mark wrote: | Do a test where you send only one byte and then stop (simulating some sort of error). Then try and do a correct sequence. I believe that you will find their state machine gets trashed! That is why it is better to look at the flags when reading the data. |
I totally agree with you. Except...I couldn't get any of the code which does flag reading to work at all. I have attached it below. I just get garbage in and out. The reads always return 0xff's.
Thanks,
-Ted
<PRE>
SLAVE CODE
---------------
#byte SSPADD = 0x93 // Slave address held here
#byte SSPCON1= 0x14 // SSP control reg 1
#byte SSPCON2= 0x91 // SSP control reg 2
#byte SSPBUF = 0x13 // Buffer for SSP data
#byte SSPSTAT= 0x94 // SSP status reg
#bit BF = SSPSTAT.0 // Buffer full status bit. Read-only bit
// Receive mode 1 = SSPBUF full.
// Tx mode 1 = data transmit in progress, SSPBUF full.
// 0 = complete, SSPBUF empty.
// (does not include ack and stop bits in either case)
#bit R_W = SSPSTAT.2 // Holds read/write status from last address match, valid
// to next start,stop, or not /ack bit.
// Slave mode 1 = read, Master mode transmit in progress.
#bit START= SSPSTAT.3 // Start bit has been detected last
#bit STOP = SSPSTAT.4 // Stop bit has been detected last
#bit D_A = SSPSTAT.5 // Data/addr bit. 1 = last byte received or transmitted
// was data.
#bit CKE = SSPSTAT.6 // 0 = input levels are i2c specs, 1 = SMB specs.
#bit SMP = SSPSTAT.7 // Slew rate control. 1 = disabled for 100kHz or 1MHz modes
// Slave mode 7 bits //
#bit SSPM0 = SSPCON1.0 // SSP mode bits 0
#bit SSPM1 = SSPCON1.1 // " 1
#bit SSPM2 = SSPCON1.2 // " 1
#bit SSPM3 = SSPCON1.3 // " 0
#bit CKP = SSPCON1.4 // Clock, 1 = enable clock, 0 = clock stretch
#bit SSPEN = SSPCON1.5 // SSP enable bit
#bit SSPOV = SSPCON1.6 // Receive overflow indicator bit. In receive mode, a
// byte is received while the SSPBUF is holding
// previous byte. Must clear in software.
#bit WCOL = SSPCON1.7 // Write collision detect bit. In slave mode, 1 means
// SSBUF written while transmitting previous word.
// Must clear in software.
// Bits 0 to 6 of SSPCON2 are for Master only //
#bit SEN = SSPCON2.0 // Initiate Start on SDA & SCL, hardware clears
#bit RSEN = SSPCON2.1 // Initiate repeated start, hardware clears
#bit PEN = SSPCON2.2 // Initiate Stop on SCA & SCL, hardware clears
#bit RCEN = SSPCON2.3 // Enables i2c receive mode
#bit ACKEN = SSPCON2.4 // Initiate Ack on SDA & SCL, xmit ACKDT bit, h/w clears
#bit ACKDT = SSPCON2.5 // Master receive mode only. Acknowledge bit, value
// sent when user initiates ack sequence after receive
#bit ACKSTAT= SSPCON2.6 // Master transmit mode, 1 = ack not received from slave
#bit GCEN = SSPCON2.7 // General call enable bit (slave mode only)
#define BUF_SIZE 16
int sbuf[BUF_SIZE]; // Buffer for i2c data to be received or transmitted
int sindex;
int rbuf[BUF_SIZE];
int rindex;
int addrbyte;
// Possible i2c states, SSPSTAT for the four relevant bits //
#define STATE1 0x09 // D/A=0,S=1,R/W=0,BF=1. Master tx, slave addr
#define STATE2 0x29 // D/A=1,S=1,R/W=0,BF=1. Master tx, data
#define STATE3 0x0C // D/A=0,S=1,R/W=1,BF=0. Master rx, slave addr+1
#define STATE4 0x2C // D/A=1,S=1,R/W=1,BF=0. Master rx, data with /ack
#define STATE5 0x28 // D/A=1,S=1,R/W=0,BF=0. Master rx, data with not /ack
//SLAVE INTERRUPT HANDLER..............
#INT_SSP
interrupt_ssp()
{
switch(SSPSTAT & 0x2D)
{ // Keep only bits D/A, S, R/W, BF
case STATE1 :
// Master write operation, address byte in SSPBUF
// Clear the data buffer
//for(rindex=0;rindex < 16; rindex++)
//rbuf[rindex]=0;
rindex = 0; // Reset the data buffer index
SSPOV = 0; // Clear address overflow flag, could be set from last
// time because after not /ack, SSPBUF was still full.
addrbyte = SSPBUF; // Dummy read to clear BF (read only bit)
break;
case STATE2 :
// Master write operation, data byte in SSPBUF
// Get data byte (also clears BF)
SSPOV = 0;
rbuf[rindex++]=SSPBUF;
break;
case STATE3 :
// Master has begun new read operation by initiating a START or RESTART
// then sending Slave address (read) byte (now in SSPBUF).
// Looks like clock enable bit CKP cleared on interrupt, so must
// set it again to allow Master to clock data byte out
// SCL held low, Master can be kept waiting here if necessary
sindex = 0; // Reset buffer index
SSPBUF = sbuf[sindex++]; // Load 1st byte from data buffer
CKP = 1; // Enable SCL for Master to shift byte out
break;
case STATE4 :
// Master read operation, last byte was data, SSPBUF empty
// Move next byte to SSPBUF and SSPSR
// Same comment for CKP bit as in STATE3
SSPBUF = sbuf[sindex++]; // Get next byte from data buffer
CKP = 1; // Enable SCL for Master to shift byte out
break;
case STATE5 :
// A not /ack (high) was received from Master in response to data
// byte received from Slave (last byte). Slave i2c logic is reset,
// and waits for next Master operation.
break;
default:
// Error, trap here. Watchdog will reset pic (must be enabled, and
//watchdog timer cleared in other loops)
//while(1);
break;
}
}
icbus_init()
{
sbuf[0]=3;
sbuf[1]=4;
sbuf[2]=5;
SSPCON1 = 0x36; // i2c slave mode, 7-bit address
SSPADD = SLAVE_ADDR; // Set slave address
SMP=1; //SLOW
enable_interrupts(INT_SSP);
}
main()
{
while(1)
{
delay_ms(100);
fprintf(DEBUG, "(%x)(%x)(%x)\r\n", rbuf[0], rbuf[1], rbuf[2]);
}
}
MASTER CODE
-----------------
i2c_start();
i2c_write(MOTORBOARD);
i2c_write(1);
i2c_write(2);
i2c_write(3);
i2c_write(MOTORBOARD+1);
a = i2c_read();
b = i2c_read();
c = i2c_read();
d = i2c_read(0);
i2c_stop();
fprintf(DEBUG, "Result is (%x)(%x)(%x)(%x)\r\n", a, b, c, d);
</PRE> |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|