|
|
View previous topic :: View next topic |
Author |
Message |
jsummerour
Joined: 15 Nov 2008 Posts: 4
|
I2C Slave lockups |
Posted: Sun Nov 16, 2008 9:29 pm |
|
|
I'm writing the firmware for a new servo controller board, which acts as an I2C slave device. I have all of the code debugged regarding the servo timing, but what I'm finding is that the i2c communications locks up after a varying amout of time. It might be 10 seconds, or it might be 5 minutes.
The PIC I'm using is an 18F452.
The application uses three timers, Timer1, Timer3, and has three ISRs, timer1_Isr(), timer3_Isr(), and ssp_Isr().
Timer 3 and Timer 1 are set up as high priority irqs, and SSP is a normal irq. However, I've implemented I2C clock stretching for SSP, to serve as flow control back to the host device (also a PIC in this case) when T1 and T3 are irq'ing SSP, and holding up I2C processing.
The ssp_Isr() follows the proper processing methodology mentioned in tons of forums here (see code below).
Specifically what I'm seeing is that once the board locks up, SDA and SCL are both low. I can reset power on the board, and communications resumes just fine (until it locks up again).
Additionally, the board only locks up when I'm sending I2C commands to it, and not when it's just sitting there spitting out PWM timing for the servos. Once the i2c comm locks up, the PWM output of the board continues to work just fine (verified with a Logic Analyzer). Also, I have verified that when T1 and T3 irqs are turned off, I2C communications functions properly, and doesn't lock up.
I have verified that the Clock Stretching is working. You actually can't communicate w/ the board at all without it (since T1 and T3 are higher priority irqs). However, I have to believe that this still isn't enough to prevent the other timers from clobber the i2c communications somehow.
Could some one look at our code below (I removed a lot of superfluous stuff that isn't neccessarily pertinent to this problem) and maybe suggest some ideas to try to get it to work? I would greatly appreciate it, as I'm at wits end on this, and I'm ready to finish this project and move on to the next one.
Thanks guys!
Jason
// =========
// Start of code
// =========
Code: | #include "18f452.h"
#fuses h4,nowdt,noprotect,put,nolvp
#device HIGH_INTS=TRUE
#use delay(clock=40000000)
#priority TIMER3,TIMER1,SSP
#use fast_io(a)
#use fast_io(b)
#use fast_io(c)
#use fast_io(d)
#use fast_io(e)
#byte PIC_SSPBUFF=0xFC9
#byte PIC_SSPADD=0xFC8
#byte PIC_SSPSTAT=0xFC7
#byte PIC_SSPCON1=0xFC6
#byte PIC_SSPCON2=0xFC5
#bit CKP = PIC_SSPCON1.4 // 1 = Release Clock, 0 = Holds Clock Low (clock stretch)
#bit SEN = PIC_SSPCON2.0 // 1 = Clock Stretching Enabled for Slave Tx and Rx, 0 = Clock Stretching Enabled for Slave Tx only
#bit BF = PIC_SSPSTAT.0 // B1 = data transmit in progress, 0 = data transmit complete
#bit WCOL = PIC_SSPCON1.7 // I2C Write Collision
#bit SSPOV =PIC_SSPCON1.6 // I2C Buffer Overflow
// ============================================================
// Valid address range for this device: 0x5A(90) to 0x64(100):
// ============================================================
#use i2c(slave, SDA=PIN_C4, SCL=PIN_C3, address=0x5A, slow, force_hw)
...
#INT_SSP
void ssp_interrupt()
{
BYTE servoId;
BYTE id;
BYTE state, incoming;
BYTE groupId;
unsigned long pos, time;
boolean mode;
state = i2c_isr_state();
// I2C Write
if (state < 0x80)
{
incoming = i2c_read();
buffer[bytecounter] = incoming;
bytecounter++;
// Don't overrun the buffer:
if (bytecounter >= MaxBufferSize)
{
bytecounter = 0;
CKP = 1; // Set CKP bit to enable Master to continue sending data
BF = 0; // Clear BF bit to Data Transmit complete
bovf = true;
return;
}
if (bytecounter == 3)
{
// Store servo id that we'll read next time around:
// i2c w 90 2 <servoId>
// 0 1 2 // Buffer pos
if (buffer[1] == 2)
{
servoToRead = buffer[2];
queryServoStatus = true;
bytecounter = 0;
CKP = 1; // Set CKP bit to enable Master to continue sending data
BF = 0; // Clear BF bit to Data Transmit complete
return;
}
}
// We got a full servo position/time command:
// Command Format:
// i2c w 90 4 <servo id> <position high> <position low> <time high> <time low> // 7 Bytes
// 0 1 2 3 4 5 6 // Buffer pos
if (bytecounter == 7)
{
// Store servo position/time immediately
if (buffer[1] == 4)
{
memcpy(setServoParams, buffer, 7);
setServoPos = true;
bytecounter = 0;
CKP = 1; // Set CKP bit to enable Master to continue sending data
BF = 0; // Clear BF bit to Data Transmit complete
return;
}
} }
// I2C Read
if (state == 0x80)
{
if (queryFirmwareVersion)
{
sendFirmwareVersion = true;
queryFirmwareVersion = false;
}
else if (queryServoStatus)
{
sendServoStatus = true;
queryServoStatus = false;
}
//CKP = 1; // Set CKP bit to enable Master to continue sending data
}
}
...
#int_TIMER3 HIGH
void TIMER3_isr()
{
...
}
...
#int_timer1 HIGH
void timer1_isr()
{
....
}
...
void main ()
{
BYTE servoId;
int data, eepromState;
unsigned long position, time;
disable_interrupts(GLOBAL);
disable_interrupts(INT_SSP);
disable_interrupts(INT_TIMER1);
disable_interrupts(INT_TIMER3);
// Enable I2C Clock Stretching
SEN = 1;
set_tris_a(0);
set_tris_b(0); // LED Pins 6,7
set_tris_c(0B00011000); // I2C I/O Pins 3,4
set_tris_d(0);
set_tris_e(0);
InitServoTable();
// Timer 1
setup_timer_1( T1_INTERNAL | T1_DIV_BY_8 );
// Timer 3 setup_timer_3( T3_INTERNAL | T3_DIV_BY_4 );
setup_timer_3( T3_INTERNAL | T3_DIV_BY_4 );
Port_b_pullups(true);
enable_interrupts(GLOBAL);
enable_interrupts(INT_SSP);
enable_interrupts(INT_TIMER3); // Timer 3 Overflow
set_timer3(65500);
while (1)
{
if (bovf)
{
bovf = false;
printf("BOVF");
}
else if (sendServoStatus)
{
SendServoInformation();
sendServoStatus = false;
}
else if (sendFirmwareVersion)
{
SendFirmwareInformation();
sendFirmwareVersion = false;
}
else if (setServoPos)
{
servoId = (byte)setServoParams[2];
if ((1 <= servoId) && (servoId <= 25))
{
position = ParsePosition(setServoParams[3], setServoParams[4]);
time = ParseTime(setServoParams[5], setServoParams[6]);
SetServoPosition(servoId, position, time);
//printf("s: %d to pos: %lu time: %lu\r\n", servoId, position, time);
}
setServoPos = false;
}
}
} |
|
|
|
future
Joined: 14 May 2004 Posts: 330
|
|
Posted: Mon Nov 17, 2008 3:42 am |
|
|
Try testing the SSPOV bit, if it is true then reset it. |
|
|
Ttelmah Guest
|
|
Posted: Mon Nov 17, 2008 4:31 am |
|
|
Yes.
I'd do two things. At the end of your ISR, test the SSPOV bit, and if it is set clear it, and also test the buffer full bit (which should be empty, since you have read the buffer in the ISR), and if it is still set, read the dummy byte.
So:
Code: |
//Make sure no error bits set
if (OV==1) OV=0;
if (BF==1) incoming=SSPBUF;
|
This is assuming you have OV, and BF defined as the overrun, and buffer full bits, and SSPBUF as the data register.
Then, add a 'clock' to one of your timers. In the I2C ISR, have this set to a large value (perhaps equivalent to about 10 seconds), and have the timer decrement it.
The timer code, would be something like:
Code: |
if (timeout) --timeout;
else {
//You will only get hre, if no call has occured to the I2C, in the total
//time needed to get this counter to = 0.
SSPEN=0;
DELAY_CYCLES(1);
SSPEN=1;
}
|
This disables, and re-enables the SSP port (assuming you have SSPEN defined), if no I2C transaction takes place in the period defined by the timeout of the counter.
This is a 'belt and braces' recovery strategy.
I'd suspect your actual problem is not reading the data register, in the write routines. The data sheet, has the following line in it for most (all?) chips:
"The user must read the SSPBUF, even if only transmitting data, to avoid setting overflow (must be cleared in software).".
It is a common error in the posted I2C routines.
If you want to keep completely CCS, then use:
Code: |
if (i2C_data_is_in()) incoming = i2c_read();
|
Before you call the switch statement, rather than only doing the read inside the read parts of the statement.
I have a coupl eof systems, using a 'ruggedised' high voltage I2C (12v, with high power transceivers), running s couple of dozen feet in qute a noisy environment, and with these tests ad recovery in place, I2C, has been 100% reliable.
Best Wishes |
|
|
jsummerour
Joined: 15 Nov 2008 Posts: 4
|
|
Posted: Mon Nov 17, 2008 10:07 am |
|
|
Future, and Ttelmah,
Awesome! These sound like some great suggestions! I will try them tonight when I get home.
I like the recovery strategy...Been trying to figure out how to implement something similar.
I'll post tonight and let you guys know if this fixes my problem. Thanks again for the fast reply! _________________ Jason |
|
|
jsummerour
Joined: 15 Nov 2008 Posts: 4
|
|
Posted: Sun Dec 14, 2008 8:14 pm |
|
|
Hmm.
I have tried the suggestions above, and I still see the same behavior. Any more ideas?
Here's my additions per the suggestions:
I put the recovery strategy counter in my 20msec timer 3.
Code: |
#byte PIC_SSPBUFF=0xFC9
#byte PIC_SSPADD=0xFC8
#byte PIC_SSPSTAT=0xFC7
#byte PIC_SSPCON1=0xFC6
#byte PIC_SSPCON2=0xFC5
#bit CKP = PIC_SSPCON1.4 // 1 = Release Clock, 0 = Holds Clock Low (clock stretch)
#bit SEN = PIC_SSPCON2.0 // 1 = Clock Stretching Enabled for Slave Tx and Rx, 0 = Clock Stretching Enabled for Slave Tx only
#bit BF = PIC_SSPSTAT.0 // B1 = data transmit in progress, 0 = data transmit complete
#bit WCOL = PIC_SSPCON1.7 // I2C Write Collision
#bit SSPOV = PIC_SSPCON1.6 // I2C Buffer Overflow
#bit SSPEN = PIC_SSPCON1.5 // Synchronus Serial Port Enable Bit
unsigned long sspTimer = 500;
#INT_SSP
void ssp_interrupt()
{
BYTE servoId;
BYTE id;
BYTE state, incoming;
BYTE groupId;
unsigned long pos, time;
boolean mode;
state = i2c_isr_state();
// I2C Write
if (state < 0x80)
{
incoming = i2c_read();
buffer[bytecounter] = incoming;
bytecounter++;
sspTimer = 500;
....
}
if (state == 0x80)
{
...
}
...
if (SSPOV==1)
{
SSPOV=0;
}
if (BF==1)
{
incoming = i2c_read();
}
}
// 16-Bit Timer
// 20 msec Timer
#int_TIMER3 HIGH
void TIMER3_isr()
{
...
// I2C recovery strategy...
if (sspTimer)
{
--sspTimer;
}
else
{
// Disable/Reenable SSP port
SSPEN = 0;
delay_cycles(1);
SSPEN = 1;
}
} |
Do this look correct? I would appreciate any more suggestions, as I'm pretty much at a standstill.
Best Regards!
Jason _________________ Jason |
|
|
Ttelmah Guest
|
|
Posted: Mon Dec 15, 2008 3:59 am |
|
|
OK.
I'd look at three things:
First try not using the CCS functions at all. I have had problems with 'unexplained' behaviour when using their functions, and it is possible you are hitting such a situation. I have used a whole 'suite' of different 'hombrew' functions in the past, and something like:
Code: |
#byte SSPSTAT=0xFC7
#byte SSPCON=0xFC6
#byte SSPCON2=0xFC5
#bit RW=SSPSTAT.2
#bit DA=SSPSTAT.5
#bit BF=SSPSTAT.0
#bit OV=SSPCON.6
#byte SSPBUF=0xFC9
#INT_SSP
void ssp_interupt ()
{
BYTE incoming;
static int8 state=0;
if ((DA==1) && (RW==0)) {
//Here a _data_ write transaction (read here)
incoming = SSPBUF;
if (state==0) {
state=1;
}
else if(state >= 1) { //Second received byte is data
buffer[state-1] = incoming;
state++;
//You will need to set _ACK_ here on your last byte, and clear it
//on the others
}
//Should never get here
else {
state=0;
}
}
else if (DA==0) {
//Here address
incoming=SSPBUF;
state=0;
if (RW==1) {
//Here master is requesting data
do {
WCOL=0;
SSPBUF=//Whatever you want to send...;
}
while (WCOL==1);
CKPREL=1;
}
}
else {
//Here data, and read request, not write
do {
WCOL=0;
SSPBUFF=//Load rest of data;
}
while (WCOL==1);
CKPREL=1;
}
//Make sure no error bits set
if (OV==1) OV=0;
if (BF==1) incoming=SSPBUF;
}
|
You'll need to sort this out a bit...
Second, I'd not have the code to set the timer counter, in the SSP interrupt. If the SSP is getting into an unhandled state, this interrupt could be being called, but the data not being properly handled. I'd have the code in something that reflects a _good_ transaction having happened.
Third, what hardware protection have you got on the I2C lines?. It is possible that what you are seeing, is the actual I2C hardware being 'spiked' by electrical noise, resulting it's internal latches hanging. Though the reset will clear a software problem with the port, or a han, in it's internal state machine, it won't clear such a hardware problem. My lines will always have protection circuitry where they enter the board.
Best Wishes |
|
|
rnielsen
Joined: 23 Sep 2003 Posts: 852 Location: Utah
|
|
Posted: Mon Dec 15, 2008 12:07 pm |
|
|
I'm not sure if this would help your problem but I always use a I2C bus accelerator LTC1694. It helps square up the signals.
Ronald |
|
|
Ttelmah Guest
|
|
Posted: Mon Dec 15, 2008 3:50 pm |
|
|
Agree wholeheartedly.
I use the P82B96, on longer links through noisy enviroments.
Best Wishes |
|
|
asmallri
Joined: 12 Aug 2004 Posts: 1635 Location: Perth, Australia
|
|
Posted: Mon Dec 15, 2008 3:53 pm |
|
|
Have you checked the I2C bus? Are the pull-ups in place? _________________ Regards, Andrew
http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!! |
|
|
jsummerour
Joined: 15 Nov 2008 Posts: 4
|
|
Posted: Fri Dec 19, 2008 11:08 am |
|
|
Sorry to take so long to respond...I really appreciate everyone's responses! I will verify the noise issues this weekend. And yes Asmallri, we do have pullups in place.
If it is due to noise, the LTC1694 chip sounds like a winner!
Best Regards! _________________ Jason |
|
|
|
|
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
|