|
|
View previous topic :: View next topic |
Author |
Message |
nmeyer
Joined: 09 Jul 2004 Posts: 70
|
I2C slave issue on PIC16F690 |
Posted: Thu Feb 14, 2008 8:57 am |
|
|
Hello,
I am having issues with a circuti design where i use a PIC16F690 to be used as a i2c slave device. I am using the int_ssp routine that i have seen many times on the forum here and in the ccs examples. However, it appears that in my circuit it is not consistantly working. We have been able to capture periods where the device does not respond to a command or it appears to give back incorrect data. When we watch the data with a scope we have been able to catch times when the slave device does not give an Ack or it appears to give the ack too late. I am running out of ideas on what to try to resolve this. Does anyone have a clue what could be done to either fix this or test it further to see what is happening? My issue is that is is VERY random and might take hours worth of communication to catch. Compiler Vr 4.062
Code: |
#include <16F690.h>
#device adc=10
//#priority ssp,rtcc,ext
#use delay(clock=8000000, RESTART_WDT)
#fuses WDT,INTRC_IO, PUT, MCLR, NOBROWNOUT, NOCPD, NOPROTECT
#use i2c(MASTER,slow,sda=PIN_C4,scl=PIN_C6)
#include <math.h>
#use fast_io(A)
#define sw_in PIN_A2 //interrupt pin
#define LED4 PIN_A4 //60% led, green
#define LED2 PIN_A5 //10-20% led, yellow
#use fast_io(B)
#define SMBD_E PIN_B4 //smbus data external
#define LED1 PIN_B5 //100% led, green
#define SMBC_E PIN_B6 //smbus clock external
#use fast_io(C)
#define LED5 PIN_C0 //80% led, green
#define SMBD_I PIN_C4 //smbus data, internal
#define LED3 PIN_C5 //40% led, green
#define SMBC_I PIN_C6 //smbus clock, internal
#define LED6 PIN_C7 //10% or less led, red
//#define INTS_PER_SECOND 30
#define INTS_PER_SECOND 122 //244
#BYTE SSPCON1 = 0x14
////////////////////////////////////////////////////////////////////////////////
int address,lsb_ch_crnt,lsb_percent,msb_status;
char i,int_count,bus_free,fault,change_current,chg_term=0,set=0;
int lsb_current,msb_current,count=0,msb,lsb_volt,lsb_status;
int lsb_percent_send,lsb_temp_send,lsb_volt_send,lsb_ch_crnt_send;
int lsb_status_send,msb_status_send;
long time1,seconds,voltage,percent,status,ch_current;
long time_cnt=0,value,loop;
int32 new_val;
signed long current,temperature;
signed int i2c_temp,lsb_temp,lsb;
////////////////////////////////////////////////////////////////////////////////
#int_rtcc
void clock_isr(void)
{
if(--int_count==0) //decrement counter until 0
{
++seconds; //count up seconds
int_count=INTS_PER_SECOND; //reset counter
if (seconds>=65000)
seconds=65000;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void read_sm_percent (void)
{
i2c_start(); //read capacity
i2c_write(0x16);
i2c_write(0x0D); //relative state of charge
i2c_start();
i2c_write(0x17); //tell battery to write data
percent=i2c_read(0); //read the data
i2c_stop();
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
lsb_percent=make8(percent,0);
}
////////////////////////////////////////////////////////////////////////////////
void read_sm_temperature (void)
{
i2c_start(); //read temperature
i2c_write(0x16); //battery address
i2c_write(0x08); //temperature register
i2c_start();
i2c_write(0x17); //tell battery to write data
lsb=i2c_read(); //read in data bytes
msb=i2c_read(0);
i2c_stop();
temperature=make16(msb,lsb); //make word
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
temperature=temperature-2740; //change to celcius
i2c_temp=temperature/10; //make degrees 1C
lsb_temp=make8(i2c_temp,0);
}
////////////////////////////////////////////////////////////////////////////////
void read_sm_voltage (void)
{
i2c_start(); //read capacity
i2c_write(0x16); //battery address
i2c_write(0x09); //voltage register
i2c_start();
i2c_write(0x17); //tell battery to write data
lsb=i2c_read(); //read data bytes
msb=i2c_read(0);
i2c_stop();
voltage=make16(msb,lsb); //make word
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
voltage=voltage/100;
lsb_volt=make8(voltage,0);
}
////////////////////////////////////////////////////////////////////////////////
void read_sm_current (void)
{
i2c_start(); //read capacity
i2c_write(0x16); //battery address
i2c_write(0x0A); //current register
i2c_start();
i2c_write(0x17); //tell battery to write data
lsb=i2c_read(); //read bytes
msb=i2c_read(0);
i2c_stop();
current=make16(msb,lsb); //combine into a word
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
}
////////////////////////////////////////////////////////////////////////////////
void read_sm_charging_current (void)
{
i2c_start(); //read status
i2c_write(0x16); //battery address
i2c_write(0x14); //current register
i2c_start();
i2c_write(0x17); //tell battery to write data
lsb=i2c_read(); //read bytes
msb=i2c_read(0);
i2c_stop();
ch_current=make16(msb,lsb); //combine into a word
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
ch_current=ch_current/64;
lsb_ch_crnt=make8(ch_current,0);
}
////////////////////////////////////////////////////////////////////////////////
void read_sm_status (void)
{
i2c_start(); //read status
i2c_write(0x16); //battery address
i2c_write(0x16); //status register
i2c_start();
i2c_write(0x17); //tell battery to write data
lsb=i2c_read();
msb=i2c_read(0);
i2c_stop();
status=make16(msb,lsb);
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
lsb_status=make8(status,0);
msb_status=make8(status,1);
}
////////////////////////////////////////////////////////////////////////////////
#int_ext
void rb0_isr(void)
{
#use i2c(Master,slow,sda=PIN_C4,scl=PIN_C6) //change to internal smbus
delay_ms(20);
while (!input(sw_in)) //watch switch depression
{
if ((temperature>=400) || (temperature<=50) || (voltage<=180))
fault=1; //look for fault condition
else
fault=0;
if (fault==1) //fault is there
{
output_low(LED5); //flash led
delay_ms(100);
output_high(LED5);
delay_ms(100);
}
if (fault==0) //if no fault, show capacity
{
if (percent<=129 && percent>=80) //in different LED percentages
output_low(LED1);
else
output_high(LED1);
if (percent>=60)
output_low(LED2);
else
output_high(LED2);
if (percent>=40)
output_low(LED3);
else
output_high(LED3);
if (percent>=20)
output_low(LED4);
else
output_high(LED4);
if (percent<=19 && percent>=10)
output_low(LED5);
else
output_high(LED5);
if (percent<=9 && percent>=0)
output_low(LED6);
else
output_high(LED6);
delay_ms(100);
}
}
fault=0;
output_high(LED1); //turn led off
output_high(LED2); //turn led off
output_high(LED3); //turn led off
output_high(LED4); //turn led off
output_high(LED5); //turn led off
output_high(LED6); //turn led off
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
output_float(SMBC_E); //release data line
output_float(SMBD_E); //release clock line
#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B4,scl=PIN_B6,address=0x12,RESTART_WDT)
//change back to external smbus
}
#use delay(clock=8000000, RESTART_WDT)
////////////////////////////////////////////////////////////////////////////////
#INT_SSP
void ssp_interupt (void) //detect external smbus activity
{
BYTE incoming, state;
state = i2c_isr_state();
#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B4,scl=PIN_B6,address=0x12,RESTART_WDT)
if(state < 0x80) //Master is sending data
{
if(state == 1 ) //First received byte is address
{
incoming = i2c_read(1);
address = incoming;
}
if(state == 2) //Second received byte is data
{
incoming = i2c_read(1);
msb_current = incoming;
change_current=1;
}
}
if(state == 0x80) //Master is requesting data
{
if(address==0x00)
lsb=lsb_status_send;
if(address==0x01) //i2c register
lsb=msb_status_send;
if(address==0x02) //i2c register
lsb=lsb_volt_send;
if(address==0x04) //i2c register
lsb=lsb_temp_send;
if(address==0x08) //i2c register
lsb=lsb_percent_send;
if(address==0x14) //smbus register
lsb=lsb_ch_crnt_send;
i2c_write(lsb); //write first byte
bit_set(SSPCON1,4); //set to let clock float
bit_clear(SSPCON1,5); //turn off serial
output_float(SMBC_E); //let go of clock line
output_float(SMBD_E); //let go of data line
bit_set(SSPCON1,5); //reanable serial
} //added due to errata with PIC
output_float(SMBC_E); //let go of clock line
output_float(SMBD_E); //let go of data line
}
#use delay(clock=8000000, RESTART_WDT)
////////////////////////////////////////////////////////////////////////////////
void write_sm_current (void)
{
#use i2c(MASTER,slow,sda=PIN_C4,scl=PIN_C6)
i2c_start();
i2c_write(0x16);
i2c_write(0x00);
i2c_write(0x06);
i2c_write(0x06);
i2c_stop();
delay_ms(1000);
i2c_start();
i2c_write(0xA0);
i2c_write(0x1A);
i2c_write(msb_current);
i2c_write(lsb_current);
i2c_stop();
delay_ms(20);
i2c_start();
i2c_write(0x16);
i2c_write(0x7D);
i2c_write(0x80);
i2c_write(0x00);
i2c_stop();
i2c_start();
i2c_write(0x16);
i2c_write(0x7D);
i2c_write(0x00);
i2c_write(0x00);
i2c_stop();
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B4,scl=PIN_B6,address=0x12,RESTART_WDT)
}
/*////////////////////////////////////////////////////////////////////////////////
void sm_free (void)
{
while (count<=16)
{
if (input(SMBC_I))
bus_free=1;
else
{
bus_free=0;
count=17;
}
delay_us(3);
count=count+1;
}
count=0;
}
//////////////////////////////////////////////////////////////////////////////*/
////////////////////////////////////////////////////////////////////////////////
void sm_free (void)
{
while (count<=25)
{
if ((input(SMBC_I))&&(input(SMBD_I)))
bus_free=1;
else
{
bus_free=0;
count=26;
}
delay_us(3);
count=count+1;
}
count=0;
}
////////////////////////////////////////////////////////////////////////////////
void main()
{
//#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B1,scl=PIN_B4,address=0x12,RESTART_WDT)
//#use i2c(MASTER,slow,sda=PIN_B2,scl=PIN_B5)
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
output_float(SMBC_E); //release data line
output_float(SMBD_E); //release clock line
output_high(LED1); //turn led off
output_high(LED2); //turn led off
output_high(LED3); //turn led off
output_high(LED4); //turn led off
output_high(LED5); //turn led off
output_high(LED6); //turn led off
setup_oscillator(osc_8mhz);
setup_adc_ports(NO_ANALOGS);
//setup_wdt(WDT_ON|WDT_36MS);
setup_wdt(WDT_ON|WDT_36MS);
//setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_32);
set_rtcc(0);
enable_interrupts(INT_RTCC);
ext_int_edge(0,H_TO_L);
enable_interrupts(INT_EXT);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
//port_b_pullups(false); //added to see if this could be used on chg
set_tris_a(0b00000100);
set_tris_b(0b01010000);
set_tris_c(0b01010000);
int_count=INTS_PER_SECOND; //used for timer
seconds=0; //clear out variable
time1=1; //counter for time,30 seconds
loop=0;
while (TRUE)
{
output_float(SMBC_I); //release data line
output_float(SMBD_I); //release clock line
output_float(SMBC_E); //release data line
output_float(SMBD_E); //release clock line
output_high(LED1); //turn led off
output_high(LED2); //turn led off
output_high(LED3); //turn led off
output_high(LED4); //turn led off
output_high(LED5); //turn led off
output_high(LED6); //turn led off
sleep();
if (!(input(SMBC_E))) //check for clock low
time_cnt=time_cnt+1; //count times throug loop
else //that clock is low
{
time_cnt=0; //if it is not, reset counter
set=0;
}
if((time_cnt==0xFF) && (set==0))
{ //if clock has been low for 0.065s
msb_current=0; //write charge current to 0
lsb_current=0;
write_sm_current(); //reset counters
time_cnt=0;
set=1;
}
/*if((percent==100) && (chg_term==0)) //if battery full
chg_term=1; //set termination flag
if(percent<=100) //if pack less than 80%
chg_term=0; //clear termination flag
if(chg_term==1) //if flag set
{
msb_current=0; //write charge current to 0
lsb_current=0;
write_sm_current();
}*/
//output_low(LED3);
delay_us(5);
//output_high(LED3);
delay_us(5);
if(seconds>=time1) //15 seconds has elapsed
{
#use i2c(MASTER,slow,sda=PIN_C4,scl=PIN_C6) //change smbus pins
delay_ms(10);
sm_free();
if (bus_free==1)
read_sm_voltage();
if (lsb_volt>=160)
{
lsb_volt_send=lsb_volt;
sm_free(); //check to see if bus is free
if (bus_free==1) //read values
{
read_sm_percent();
lsb_percent_send=lsb_percent;
}
sm_free();
if (bus_free==1)
{
read_sm_temperature();
lsb_temp_send=lsb_temp;
}
sm_free();
if (bus_free==1)
{
read_sm_current();
}
sm_free();
if (bus_free==1)
{
read_sm_status();
lsb_status_send=lsb_status;
msb_status_send=msb_status;
}
sm_free();
if (bus_free==1)
{
read_sm_charging_current();
lsb_ch_crnt_send=lsb_ch_crnt;
}
}
seconds=0;
#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B4,scl=PIN_B6,address=0x12,RESTART_WDT)
} //change smbus pins
if((change_current==1) && (address==0x81))
{
if(msb_current>0x1F)
value=0x1F;
else
value=msb_current;
new_val=value*64;
msb_current=make8(new_val,0);
lsb_current=make8(new_val,1);
write_sm_current();
change_current=0;
}
}
}
|
|
|
|
Matro Guest
|
Config |
Posted: Thu Feb 14, 2008 9:25 am |
|
|
Hi,
your I2C is configured as a master in your #use instruction. ;-)
Just try replace "master" by "slave".
Matro |
|
|
Matro Guest
|
Oops |
Posted: Thu Feb 14, 2008 9:28 am |
|
|
I'm already sleeping. I don't read correctly your code.
Sorry for that I will have a deeper look at it. |
|
|
Matro Guest
|
|
Posted: Thu Feb 14, 2008 9:35 am |
|
|
The problem is (in my opinion) that your code do nothing when you receive the matching address with a "write" flag (case where i2c_isr_state() will return 0x00).
It's important that you read the SSPBUF register even in that case because that's the only way to clear the BF flag and avoid a buffer overflow.
You can have the same problem with all cases that you don't catch in your SSP interrupt routine. ;-)
Regards,
Matro |
|
|
nmeyer
Joined: 09 Jul 2004 Posts: 70
|
|
Posted: Thu Feb 14, 2008 9:40 am |
|
|
Sorry, I am using the PIC as both a master and a slave, i forgot to put that in the original post. The Master part has no problems at all just the slave part. I am not sure i understand what you are stating. Won't the statement "if (state<=0x80)" catch the 0x00 case you are refering to? If not, could you try to explain a bit more? Thanks |
|
|
rnielsen
Joined: 23 Sep 2003 Posts: 852 Location: Utah
|
|
Posted: Thu Feb 14, 2008 9:50 am |
|
|
Looking at your code, briefly, I can see a couple of things.
You have multiple #use delay() statements in your code. You only need one at the beginning of your code.
You have a #use i2c() statement _inside_ your ISR. Not a good idea. Since you're already inside the ISR it would make no sense to make a command to use it once you're already inside it.
It appears that you are trying to drive a second I2C bus with the Slave. Instead of trying to toggle back and forth, why not try bit-banging the sencondary bus with the software portion. This way you would not need to worry about re-declaring each bus. I've done it with a few projects before and it has worked nicely. Or, if you want to try it, create a multi-master setup and you only need to worry about one bus.
One suggestion. If you're using a scope to monitor the signals, try driving a spare output high upon entering the ISR and then drive the output low just before you exit the ISR. Monitor this output with your scope and you will be able to see when the ISR is entered and exited. This will show how the timing is working. I'm currently doing this with one of my projects and it helps a lot to see how to adjust delays in my Master so it doesn't send commands faster than the Slave can handle them.
Ronald |
|
|
Matro Guest
|
|
Posted: Thu Feb 14, 2008 9:59 am |
|
|
nmeyer wrote: | Sorry, I am using the PIC as both a master and a slave, i forgot to put that in the original post. The Master part has no problems at all just the slave part. I am not sure i understand what you are stating. Won't the statement "if (state<=0x80)" catch the 0x00 case you are refering to? If not, could you try to explain a bit more? Thanks |
The "if (state <= 0x80à" statement will catch the 0x00 case but due to the following code it will never read the SSPBUF.
Don't make a confusion between SSPIF and BF flags.
The SSPIF (interrupt flag) will be correctly cleared because the interrupt is catched. But the BF bit of the SSP will not be cleared because you never read the SSPBUF.
Just try this code for your interrupt routine and tell me about :
Code: |
#INT_SSP
void ssp_interupt (void) //detect external smbus activity
{
BYTE incoming, state;
state = i2c_isr_state();
#use i2c(SLAVE,FORCE_HW,fast,sda=PIN_B4,scl=PIN_B6,address=0x12,RESTART_WDT)
if(state < 0x80) //Master is sending data
{
if(state == 1 ) //First received byte is address
{
incoming = i2c_read(1);
address = incoming;
}
else if(state == 2) //Second received byte is data
{
incoming = i2c_read(1);
msb_current = incoming;
change_current=1;
}
else
{
i2c_read()
}
}
else if(state == 0x80) //Master is requesting data
{
if(address==0x00)
lsb=lsb_status_send;
if(address==0x01) //i2c register
lsb=msb_status_send;
if(address==0x02) //i2c register
lsb=lsb_volt_send;
if(address==0x04) //i2c register
lsb=lsb_temp_send;
if(address==0x08) //i2c register
lsb=lsb_percent_send;
if(address==0x14) //smbus register
lsb=lsb_ch_crnt_send;
i2c_write(lsb); //write first byte
bit_set(SSPCON1,4); //set to let clock float
bit_clear(SSPCON1,5); //turn off serial
output_float(SMBC_E); //let go of clock line
output_float(SMBD_E); //let go of data line
bit_set(SSPCON1,5); //reanable serial
} //added due to errata with PIC
else
{
i2c_write(0x00);
}
output_float(SMBC_E); //let go of clock line
output_float(SMBD_E); //let go of data line
}
|
|
|
|
nmeyer
Joined: 09 Jul 2004 Posts: 70
|
|
Posted: Thu Feb 14, 2008 10:22 am |
|
|
At this point i have updated my test board and it is running fine with my i2c emulator. Next i have to program it into my "real world" test set up and let it run. This takes hours of run time to gather the read writes and determine if it is functioning correctly. Thanks |
|
|
Matro Guest
|
|
Posted: Thu Feb 14, 2008 10:31 am |
|
|
You're welcome.
Good luck! |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Feb 14, 2008 12:08 pm |
|
|
There is a problem with your coding style. You have too many global
variables. With so many globals, it's too easy to use the wrong one
in a routine, or to confuse the data size (is it 8 or 16 bits ?) or the type.
Most of your routines can be re-written to get rid of the globals and
use local variables instead. Also have the function return the value
that it gets or calculates, instead of putting it into a global. By doing
it this way, you will reduce the possibility of accidently coding a subtle
and hard-to-find error into a program. |
|
|
nmeyer
Joined: 09 Jul 2004 Posts: 70
|
|
Posted: Fri Feb 15, 2008 8:35 am |
|
|
Matro, The changes did not help in the real world test. Thanks |
|
|
nmeyer
Joined: 09 Jul 2004 Posts: 70
|
|
Posted: Fri Feb 15, 2008 4:25 pm |
|
|
rnielsen wrote: | Looking at your code, briefly, I can see a couple of things.
You have multiple #use delay() statements in your code. You only need one at the beginning of your code.
You have a #use i2c() statement _inside_ your ISR. Not a good idea. Since you're already inside the ISR it would make no sense to make a command to use it once you're already inside it.
It appears that you are trying to drive a second I2C bus with the Slave. Instead of trying to toggle back and forth, why not try bit-banging the sencondary bus with the software portion. This way you would not need to worry about re-declaring each bus. I've done it with a few projects before and it has worked nicely. Or, if you want to try it, create a multi-master setup and you only need to worry about one bus.
One suggestion. If you're using a scope to monitor the signals, try driving a spare output high upon entering the ISR and then drive the output low just before you exit the ISR. Monitor this output with your scope and you will be able to see when the ISR is entered and exited. This will show how the timing is working. I'm currently doing this with one of my projects and it helps a lot to see how to adjust delays in my Master so it doesn't send commands faster than the Slave can handle them.
Ronald |
The multiple #use_delays were a suggestion either from some one else on this forum or from some CCS information i had. As far as the toggling back and forth between i2c declares, i would like to use the slave as hardware so that my ssp_int functions fine. If you have any sample programs showing how to drive the master i2c part with just software please share some. I am know see how to do it differently than what i have done. Thanks |
|
|
Ttelmah Guest
|
|
Posted: Sat Feb 16, 2008 6:13 am |
|
|
The multiple #use delay approach, ensures separate delay code is used in the interrupt handlers, to that used in the main. It requires _one_ such declaration, in front of the interrupt handlers, and _one_ in front of the other code. No more.
Best Wishes |
|
|
i2c victim Guest
|
i2c_read() on address match? |
Posted: Wed Mar 05, 2008 1:06 pm |
|
|
Matro wrote: | The problem is (in my opinion) that your code do nothing when you receive the matching address with a "write" flag (case where i2c_isr_state() will return 0x00).
It's important that you read the SSPBUF register even in that case because that's the only way to clear the BF flag and avoid a buffer overflow.
You can have the same problem with all cases that you don't catch in your SSP interrupt routine. ;-)
Regards,
Matro |
This is a good point, and the PIC will not ACK if there is a buffer overflow, according to PIC datasheet.
However, in the CCS manual page 156, there is some code that does nothing if i2c_isr_state() returns 0. This seems bad. Am I missing something? |
|
|
|
|
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
|