|
|
View previous topic :: View next topic |
Author |
Message |
Seaborgium
Joined: 23 Jul 2012 Posts: 14
|
I2C Master Ignoring Clock-Stretching/Bizarre Behavior |
Posted: Thu Aug 09, 2012 8:03 pm |
|
|
I recently had 10-bit I2C communication working perfectly with a PIC16LF1503 master and PIC16F886 slave. The master sent the slave a data address, and the slave replied with the value stored in that data address in an array. You can see the code here.
I ported the master code to a PIC18F4550 and the results were absolutely catastrophic. Suddenly nothing worked anymore. Firstly, for some reason, the master would ignore clock stretching. I had a program in which the master had an i2c_write, and the slave would delay a little while (flashing an LED) before using i2c_read and releasing the clock. Supposedly, the slave would flash the LED and the master's i2c_write would hang while the clock was stretched; then, the slave would release the clock, and the master would continue. However, in practice, the slave/master flashed LEDs at the same time.
I'm almost certain the slave was stretching the clock correctly. I turned bit SEN on, manually cleared CKP at the beginning of the interrupt, even told the slave to output CKP to an LED (the LED displayed 0, so CKP was definitely clear). However, the master continued to ignore clock stretching. All my communications were totally broken unless I manually inserted delays after each I2C command in the master code.
After inserting delays in the code, I managed to get communication kind of working. I had the slave light up LEDs to indicate what point it was at in the I2C communication. I was able to get the slave to recognize its 10-bit address, receive data from the master, and write data to the master (though the master would not receive it correctly).
However, I just tried to slim down the code so I could post it on this forum, and now it doesn't even get that far. But, only LEDs 1 and 2 ever light up, indicating that the slave never gets past the address-recognition part.
I'm tearing my hair out over this. Here's the code. Does anyone know what's wrong?
Master.c:
Code: |
#include <main.h>
#use i2c(Master, Slow, I2C1, FORCE_SW, stream=module)
int16 requestData(int16 slaveAddress, unsigned int dataAddress) {
int16 data;
int slaveHigh; //High byte of slave address
int slaveLow; //Low byte of slave address
//Split slave address into low and high byte
slaveHigh = (slaveAddress>>7)&0xFE; //Get first 2 bits; clear R/W bit
slaveHigh = slaveHigh|0xF0; //"11110XXX" signifies 10-bit address; add this.
slaveLow = slaveAddress&0xFF; //Get lower 8 bits
//Instruct slave that you wish to write
delay_ms(1);
i2c_start(module);
delay_ms(1);
i2c_write(module, slaveHigh); //High byte
delay_ms(1);
i2c_write(module, slaveLow); //Low byte
delay_ms(100);
//Give data address
i2c_write(module, dataAddress);
delay_ms(100);
//Restart; instruct slave that you wish to read
i2c_start(module);
delay_ms(100);
i2c_write(module, slaveHigh|1); //High byte with R/W set
delay_ms(100);
//Receive data and finish
data=i2c_read(module, 1);//<<8; //Read high byte and ACK
//delay_ms(1);
//data+=i2c_read(module, 0); //Read low byte and end transmission
i2c_stop(module);
return data;
}
//Calculates the slave address and data address corresponding to
//a specific sensor number, then gets that sensor value.
int16 requestSensorVal(int16 sensorNum) {
int16 slaveAddress;
unsigned int dataAddress;
dataAddress=sensorNum%16;
slaveAddress=(sensorNum-dataAddress)/16;
return requestData(slaveAddress, dataAddress);
}
void main() {
while(true) {
delay_ms(1000);
requestData(0x0, 0);
} |
Master.h:
Code: | #include <18F4550.h>
#device adc=16
#FUSES NOWDT //No Watch Dog Timer
#FUSES CPUDIV1 //No system clock postscaler
#FUSES HSPLL //High speed xtal
#FUSES PLL5 //??
#FUSES NOPROTECT //No code pritection
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#use delay(clock=48000000) //Configured for 48 MHz
|
Slave.c:
Code: | #include <Slave.h>
//Define output pins
#define LED PIN_A0
#define LED2 PIN_A1
#define LED3 PIN_A2
#define LED4 PIN_A3
#define LED5 PIN_A4
#define LED0 PIN_A5
//#define MOSFET PIN_C5
//Define addressing state
#define NOADDR 0 //Before being assigned an address
#define GOTADDRHIGH 1 //Has received high byte of address
#define GOTADDR 2 //Has been assigned address
//General call bit
#BYTE SSPCON2=0x91
#BIT GCEN=SSPCON2.7
#BIT SEN=SSPCON2.0
//10-bit addressing bits
#BYTE SSPCON=0x14
#BIT SSPM0=SSPCON.0
#BIT CKP=SSPCON.4
#BYTE SSPSTAT=0x94
#BIT UPDATEADDR=SSPSTAT.1
#BIT RWBIT=SSPSTAT.2
//Change # of sensors per module to correct number in sensorVals
int writeBuffer [2]; //Holds one 10-bit integer to write through i2c
int16 sensorVals [16];
int addrState; //State of address assignment
//10-bit addressing stuff
int1 firstByteReceived; //Records whether first byte of 10-bit address has been received
int16 addrHigh; //High byte of address
int16 addrLow; //Low byte of address
void flash(int ledsToFlash) {
output_high(LED0);
if ((ledsToFlash&0b1)==0b1)
output_high(LED);
if ((ledsToFlash&0b10)==0b10)
output_high(LED2);
if ((ledsToFlash&0b100)==0b100)
output_high(LED3);
if ((ledsToFlash&0b1000)==0b1000)
output_high(LED4);
if ((ledsToFlash&0b10000)==0b10000)
output_high(LED5);
delay_ms(400);
output_low(LED);
output_low(LED2);
output_low(LED3);
output_low(LED4);
output_low(LED5);
output_low(LED0);
delay_ms(200);
}
#int_SSP
void i2c_interrupt() {
if (UPDATEADDR) { //Just received one of two start bytes
if (!firstByteReceived) { //Just received first byte
firstByteReceived=TRUE;
i2c_slaveAddr(addrLow); //Set low byte
output_high(LED);
i2c_read();
}
else { //Just received second byte
firstByteReceived=FALSE;
i2c_slaveAddr(addrHigh); //Set high byte
output_high(LED2);
i2c_read();
}
return;
}
//Get state
int state;
state = i2c_isr_state();
if (state==0||state==0x80) { //Master has issued start command
i2c_read(); //Clear address from read buffer.
}
if (RWBIT==TRUE) { //Master is waiting for data
output_high(LED4);
i2c_write(0); //Write byte
//i2c_write(writeBuffer[state&0x01111111]); //Write appropriate byte, based on how many have already been written
}
else if ((state&0b01111111)>0) { //Master has sent data; read.
if (addrState==GOTADDR) {
output_high(LED3);
i2c_read();
//You already have an address; this must be a data request.
//int16 toSend;
//toSend=sensorVals[i2c_read()]; //Grab appropriate sensor value.
//writeBuffer[0] = toSend>>8;
//writeBuffer[1] = toSend&0xFF;
}
else if (addrState==NOADDR) {
//Have not been assigned an address yet
//This is the first byte of the address
addrState=GOTADDRHIGH;
addrHigh=i2c_read(); //Get high byte data
}
else if (addrState==GOTADDRHIGH) {
//Have been assigned high byte of address
//Message is the low byte of the address
addrState=GOTADDR;
GCEN=FALSE; //Stop responding to general calls
i2c_slaveAddr(addrHigh); //Set address to high byte
//output_low(MOSFET); //Open up transistor gate
addrLow=i2c_read();
}
}
}
void main() {
SEN=TRUE; //Allow clock stretching
setup_comparator(NC_NC_NC_NC);// This device COMP currently not supported by the PICWizard
//output_high(MOSFET); //Lock up the gate immediately
enable_interrupts(INT_SSP); //Enable interrupts
enable_interrupts(GLOBAL);
GCEN=TRUE; //Allow general calls
SSPM0=TRUE; //10-bit addressing
firstByteReceived=FALSE;
//addrState=NOADDR; //Have not been assigned an address yet
addrState=GOTADDR;
while (true) { //Constantly refresh sensor values
sensorVals[0]=0;
sensorVals[1]=1;
sensorVals[2]=2;
sensorVals[3]=3;
sensorVals[4]=4;
sensorVals[5]=5;
sensorVals[6]=6;
sensorVals[7]=7;
}
} |
Slave.h:
Code: | #include <16F886.h>
#device adc=16
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOMCLR //Master Clear pin used for I/O
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOPROTECT //No read protection
#FUSES NOWRT //No write protection
#use delay(int=250000)
#use i2c(Slave, Slow, sda=PIN_C4, scl=PIN_C3, force_hw, address=0xA0)
|
|
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19509
|
|
Posted: Fri Aug 10, 2012 2:13 am |
|
|
Have you looked at the chip errata?.
I think number 40, on the 'family' sheet might be the problem....
Best Wishes |
|
|
Seaborgium
Joined: 23 Jul 2012 Posts: 14
|
|
Posted: Fri Aug 10, 2012 10:58 am |
|
|
Hm, I see. However, the errata sheet says that the solution would be to prevent clock stretching. If I can't stretch the clock, how can I make sure the slave has enough time to retrieve data from the sensor array, etc.? Is the only solution to manually add delays in the master code, or use a different microchip without this problem?
Thank you so much for your help! This has been really frustrating. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Fri Aug 10, 2012 11:15 am |
|
|
Quote: | Have you looked at the chip errata?.
I think number 40, on the 'family' sheet might be the problem....
|
I don't think that's it, because he's doing a software master:
Code: |
Master.c:
#use i2c(Master, Slow, I2C1, FORCE_SW, stream=module)
|
In the "family" errata, item 40 is referring to a hardware MSSP master:
http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en010300
But he's doing bit-banging i2c so it doesn't apply.
Also, this test program is invalid because you've commented out the
NACK line, which is required by the i2c spec on the last i2c read in the
master program:
Quote: |
//Receive data and finish
data=i2c_read(module, 1);//<<8; //Read high byte and ACK
//delay_ms(1);
//data+=i2c_read(module, 0); //Read low byte and end transmission
i2c_stop(module);
return data;
} |
In your Slave program you are declaring a variable in the middle of code,
which is not supported in CCS. Move it to the start of the function:
Quote: |
//Get state
int state;
state = i2c_isr_state();
|
Furthermore, during initial testing I would not run my Slave PIC at 250
KHz. Change it to 8 MHz. Get it working and then consider lowering
the slave frequency. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19509
|
|
Posted: Fri Aug 10, 2012 3:24 pm |
|
|
I had missed he was doing software I2C on the master.
I'd guess the problem is _speed_. The older implementation with the LF1503 master, was probably running significantly slower than the 4550. With the slave only running at 250KHz, it probably does not assert the stretching, till after the 4550 has already tested the line. With nearly 200:1 clock speed difference, I'd expect problems. Try raising the clock speed of the slave, so it is proportionately 'as fast' relative to the master, as on the older system.
Generally, There often seem to be problems if there is a very wide disparity between the device speeds.
Best Wishes |
|
|
Seaborgium
Joined: 23 Jul 2012 Posts: 14
|
|
Posted: Fri Aug 10, 2012 6:45 pm |
|
|
Ah! Sorry, I lowered the speed when I was trying to test current draw vs. clock speed, and I forgot to raise it again.
Thanks for the help! I'll increase the speed, make those other modifications, and try again. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19509
|
|
Posted: Sun Aug 12, 2012 7:49 am |
|
|
Just did a minimal 'start, send byte, stop' routine coded for the 4550, and can confirm, the compiler _does_ correctly wait for the SCL line to be released:
Code: |
0052: BSF F93.1 //float the SCL line
0054: BTFSS F81.1 //See if it goes high
0056: BRA 0054 //loop if not
|
It waits here if the clock line is being held low. This is called 6uSec after it sends the last edge of the last data bit (at 48MHz).
So the question then is how long the 886 will take to assert this condition, running at 250K?. The picture show, that the low, is not asserted, till the interrupt flag is set. This is at the start of the next machine cycle _after_ the event. Instruction clock is 250K/4 = 62500Hz, and the worst case would potentially be if the event occurred just after the previous T1 cycle, giving just under 32uSec, and best case just on 16uSec.
This also allows us to give a figure to 'minimum speed for slave', of 1.33Mhz, with a master at 48MHz. So basically no slower than master/36, explaining why very wide speed differences do give problems with I2C.....
So slave at 2MHz, should work.
Best Wishes |
|
|
Seaborgium
Joined: 23 Jul 2012 Posts: 14
|
|
Posted: Mon Aug 13, 2012 11:17 am |
|
|
I changed the clock back to the original (8 MHz), and it seems to be working fine so far (I'm still in the process of getting it back to its previous functionality).
However, this information is quite valuable, since I'm also considering how low of a clock speed I can use - I'm trying to reduce current draw.
Again, thank you so much! |
|
|
|
|
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
|