|
|
View previous topic :: View next topic |
Author |
Message |
AlphaParticle
Joined: 10 Jun 2012 Posts: 5
|
I2C doesn't work |
Posted: Wed Aug 15, 2012 6:25 am |
|
|
Hi everybody,
I’m trying to communicate two PICs using I2C. One of them is a 16F877A running at 4MHz (Slave). The other one is a 18F4455 running at 16MHz (Master). Both PICs have AN0 declared for analog readings, and both PICs also have a Port dedicated to show a byte (using leds). Before any I2C communication attempt, both PICs are showing the ADC value in their respective dedicated Ports. When the master “writes” a byte (its ADC) to the slave, the slave places this value on its dedicated port and turns on another led that indicates that the current reading comes from the master (and is not produced by its ADC). But if we change the ADC value in the slave, its dedicated Port will show the ADC again turning off the led that indicated that the value shown was written by the master. Likewise when the master reads a byte from the slave (the adc from the slave) it places this value on its dedicated port and turns on a led indicating this.
Everything works well when the master writes to the slave. But when the master reads from the slave, the slave gets blocked and it doesn’t respond to changes in its analog input.
I don’t know what’s wrong.. Could you please help me?
This is the code:
Master:
Code: |
#include <18F4455.h>
#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES HS //High speed Osc (> 4mhz for PCM/PCH) (>10mhz for PCD)
#FUSES NOPROTECT //Code not protected from reading
#FUSES NOBROWNOUT //No brownout reset
#FUSES BORV21 //Brownout reset at 2.1V
#FUSES NOPUT //No Power Up Timer
#FUSES NOCPD //No EE protection
#FUSES STVREN //Stack full/underflow will cause reset
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOWRT //Program memory not write protected
#FUSES NOWRTD //Data EEPROM not write protected
#FUSES IESO //Internal External Switch Over mode enabled
#FUSES FCMEN //Fail-safe clock monitor enabled
#FUSES PBADEN //PORTB pins are configured as analog input channels on RESET
#FUSES NOWRTC //configuration not registers write protected
#FUSES NOWRTB //Boot block not write protected
#FUSES NOEBTR //Memory not protected from table reads
#FUSES NOEBTRB //Boot block not protected from table reads
#FUSES NOCPB //No Boot Block code protection
#FUSES LPT1OSC //Timer1 configured for low-power operation
#FUSES MCLR //Master Clear pin enabled
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES PLL4 //Divide By 4(16MHz oscillator input)
#FUSES CPUDIV1 //No System Clock Postscaler
#FUSES USBDIV //USB clock source comes from PLL divide by 2
#FUSES VREGEN //USB voltage regulator enabled
#FUSES ICPRT //ICPRT enabled
#use delay(clock=16000000)
#use i2c(Master,Slow,sda=PIN_B0,scl=PIN_B1,force_hw)
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdlibm.h>
#include <string.h>
#BYTE PORT_B = 0xF81
void main()
{
int adc_value;
int old_value;
int received=0;
int i2c_data;
setup_adc_ports(AN0|VSS_VDD);
setup_adc(ADC_CLOCK_INTERNAL|ADC_TAD_MUL_0);
setup_psp(PSP_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
//Setup_Oscillator parameter not selected from Intr Oscillator Config tab
output_high(PIN_E0); //Blink to check power up
delay_ms(1000);
output_low(PIN_E0);
while(TRUE)
{
set_adc_channel(0);
adc_value = read_adc();
if((abs(adc_value-old_value)>16)&&(received==1))
{
received = 0;
output_low(PIN_E0); //ADC data has changed.. we will show ADC in PORT D
}
if(received==0)
{
output_d(adc_value); //Showing ADC in Port D.. awaiting data to write
}
if(bit_test(PORT_B,2))
{
//Sending a byte to the slave
i2c_start();
i2c_write(0xa0);
i2c_write(adc_value);
i2c_stop();
delay_ms(250);
while(bit_test(PORT_B,2));
}
if(bit_test(PORT_B,3))
{
//requesting a byte from the slave to send it to Port D
i2c_start();
i2c_write(0xa1);
i2c_data = i2c_read();
i2c_stop();
output_d(i2c_data);
received = 1;
old_value = adc_value;
output_high(PIN_E0);
delay_ms(250);
while(bit_test(PORT_B,3));
}
}
}
|
And the Slave:
Code: |
#include <16F877A.h>
#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES HS //High speed Osc (> 4mhz for PCM/PCH) (>10mhz for PCD)
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NODEBUG //No Debug mode for ICD
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOCPD //No EE protection
#FUSES NOWRT //Program memory not write protected
#use delay(clock=4000000)
#use i2c(Slave,Slow,sda=PIN_C4,scl=PIN_C3,force_hw,address=0xa0)
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdlibm.h>
#include <string.h>
int adc_value;
int old_value;
int received;
int i2c_value;
#int_SSP
void SSP_isr(void)
{
int i2c_status;
i2c_status = i2c_isr_state();
if(i2c_status<0x80)
{
i2c_value = i2c_read(); //Master is sending a byte
output_b(i2c_value); //Byte sent to Port B
received = 1; //First byte (0x00) will be address and
output_high(PIN_D7); //will be overwritten in Port B by real data
old_value = adc_value;
}
if(i2c_status>=0x80)
{
i2c_write(adc_value); //Master is requesting data
//We reply to 0x80 with a 'write' as CCS indicates
} //Next status (0x81) will send the real data
}
void main()
{
received = 0;
setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_INTERNAL);
setup_psp(PSP_DISABLED);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
enable_interrupts(INT_SSP);
enable_interrupts(GLOBAL);
set_tris_d(0x00);
output_d(0xFF);
delay_ms(1000);
output_d(0x00);
while(TRUE)
{
set_adc_channel(0);
adc_value = read_adc();
if((abs(adc_value-old_value)>16)&&(received==1))
{
received = 0;
output_low(PIN_D7);
}
if(received==0) //No I2C data received.. showing ADC in Port B
{
output_b(adc_value);
}
}
}
|
|
|
|
AlphaParticle
Joined: 10 Jun 2012 Posts: 5
|
|
Posted: Wed Aug 15, 2012 9:29 am |
|
|
I have found something more... When the slave gets blocked, the SDA line remains low for an unknown reason. When I separate the SDA line I can see that the master keeps it up, while the slave keeps it down, so the slave does not receive the stop pulse at the end of the transmission... maybe that's why it gets blocked..
I don't know why is this happening ... any ideas? |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1345
|
|
Posted: Wed Aug 15, 2012 10:51 am |
|
|
A couple ideas. Mostly you are not following standard I2C procedures. Somethings you should change:
1. Your master should be NACKing the last byte that it reads. Failure to do this can result in the slave being confused and the hardware locking up.
2. Your slave should be reading the I2C buffer as well as writing for the case of state == 0x80. You should really read the manual entry for i2c_isr_state(). It lists a lot of details for what needs to be done for each range of cases and even gives an example of an ISR to use. |
|
|
AlphaParticle
Joined: 10 Jun 2012 Posts: 5
|
SOLVED! |
Posted: Wed Aug 15, 2012 11:29 am |
|
|
Jeremiah, thank you very much for your ideas. The ACK in the last byte was causing the problem. Once the ACK has been replaced by a NACK, the SDA line got free and both PICs worked as they were expected to. I thought the problem was in the Slave, as it kept the SDA line low, but it was the Master's fault. Here's the new main function of the master:
Code: |
void main()
{
int adc_value;
int old_value;
int received=0;
int i2c_data;
setup_adc_ports(AN0|VSS_VDD);
setup_adc(ADC_CLOCK_INTERNAL|ADC_TAD_MUL_0);
setup_psp(PSP_DISABLED);
setup_wdt(WDT_OFF);
setup_timer_0(RTCC_INTERNAL);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_timer_3(T3_DISABLED|T3_DIV_BY_1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
//Setup_Oscillator parameter not selected from Intr Oscillator Config tab
output_high(PIN_E0); //Blink to check power up
delay_ms(1000);
output_low(PIN_E0);
while(TRUE)
{
set_adc_channel(0);
adc_value = read_adc();
if((abs(adc_value-old_value)>16)&&(received==1))
{
received = 0;
output_low(PIN_E0); //ADC data has changed.. we will show ADC in PORT D
}
if(received==0)
{
output_d(adc_value); //Showing ADC in Port D.. awaiting data to write
}
if(bit_test(PORT_B,2))
{
//Sending a byte to the slave
i2c_start();
i2c_write(0xa0);
i2c_write(adc_value);
i2c_stop();
delay_ms(250);
while(bit_test(PORT_B,2));
}
if(bit_test(PORT_B,3))
{
//requesting a byte from the slave to send it to Port D
i2c_start();
i2c_write(0xa1);
i2c_data = i2c_read(0); //Important!! in the last byte!!
i2c_stop();
output_d(i2c_data);
received = 1;
old_value = adc_value;
output_high(PIN_E0);
delay_ms(250);
while(bit_test(PORT_B,3));
}
}
}
|
The only difference is the "0" as a parameter in "i2c_read()".
Thank you |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Wed Aug 15, 2012 12:38 pm |
|
|
I had the same problem.... When I connected my scope to my SCL and SDA lines, I was able to see all the data but the I2C_stop did not work so here is my full code that I've been using the last 2 months successfully:
Add this to your .h file:
Code: |
#use i2c( MASTER, FAST, sda=PIN_C4, scl=PIN_C3, /*FORCE_HW*/ )
void init_ext_I2C();
void write_ext_I2C( unsigned int uiDeviceAddress, unsigned int uiMemoryAddress, unsigned int uiData );
unsigned int read_ext_I2C( unsigned int uiDeviceAddress, unsigned int uiMemoryAddress );
|
Then add this to your .c file:
Code: |
//************************************************************
//** init_ext_I2C
//** ============
//************************************************************
void init_ext_I2C()
{
output_float( PIN_C3 ); // SCL
output_float( PIN_C4 ); // SDA
}
//************************************************************
//** write_ext_I2C
//** =============
//************************************************************
void write_ext_I2C( unsigned int uiDeviceAddress, unsigned int uiMemoryAddress, unsigned int uiData )
{
i2c_start();
i2c_write( uiDeviceAddress ); //I2C device address e.g. 0x04
i2c_write( uiMemoryAddress ); //Memory location @ I2C address above
i2c_write( uiData ); //Data to write at that location
i2c_stop();
delay_ms( 11 );
return;
}
//************************************************************
//** read_ext_I2C
//** ============
//************************************************************
unsigned int read_ext_I2C( unsigned int uiDeviceAddress, unsigned int uiMemoryAddress )
{
unsigned int uiData;
i2c_start(); // Required for RANDOM-type read
i2c_write( uiDeviceAddress ); // Required for RANDOM-type read
i2c_write( uiMemoryAddress ); // Required for RANDOM-type read
i2c_start();
i2c_write( uiDeviceAddress | 0x01 );
uiData = i2c_read( 0 );
//i2c_stop(); FUNCTION DOES NOT STOP PROPERLY SO
//FIXED PROBLEM WITH TWO LINES BELOW
output_float( PIN_C3 ); // SCL HIGH
output_float( PIN_C4 ); // SDA HIGH
return( uiData );
} |
|
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Aug 15, 2012 12:55 pm |
|
|
Quote: | //i2c_stop(); FUNCTION DOES NOT STOP PROPERLY SO
//FIXED PROBLEM WITH TWO LINES BELOW
output_float( PIN_C3 ); // SCL HIGH
output_float( PIN_C4 ); // SDA HIGH
|
What is this proposed fix ? The i2c master should float the lines when it's
done with a transaction.
What is your Master PIC and your compiler version ?
What i2c slave chip are you using ? |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Wed Aug 15, 2012 1:35 pm |
|
|
As I stated, I had the same problem as the initial person and adding these two lines while commenting-out the I2C_stop() function solved my problem. If I remember correctly, it was related also to the ACK bit not being sent or something but it had to do with that bit I'm pretty sure.
I saw the problem when analyzing the data on my scope. I was able to capture all the data from the start to the stop and I was able to match every data and signalling bits with the clock bit until I got to the I2C_stop which was not what I expected (caused by the ACK bit). So I added these two lines and voilà, it all started working. I must have lost like 2 days trying to figure-out what what going-on and now its been two months that I've had this code and it works.
The PIC is an 18F4620 with compiler 4.056 and the I2C slave device is a Texas Instruments TLV320AIC3204 audio CODEC.
Works like a charm. And I know you've been around for a long time. Perhaps you could have a look at my two posts from today regarding interrupt timing. I'd appreciate it! :) |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19499
|
|
Posted: Thu Aug 16, 2012 1:14 am |
|
|
One important thing 'wrong' in the slave ISR shown, is when i2c_isr_state==0x80.
This particular state is 'unique'. It says the master has sent a byte, and want's to receive a byte back. You _must_ do an I2C_READ, and _then_ an I2C_WRITE.
The behaviour varies with the PIC. On some, just writing will be OK, but on others, unless you do the read first, the clock hold doesn't turn off, and the I2C bus will be hung.
Could be the problem.
If you look at the I2C examples, you will see that the read code is called when the state==0x80 as well.
So:
Code: |
#int_SSP
void SSP_isr(void) {
int i2c_status;
i2c_status = i2c_isr_state();
if(i2c_status<0x80) {
i2c_value = i2c_read(); //Master is sending a byte
output_b(i2c_value); //Byte sent to Port B
received = 1; //First byte (0x00) will be address and
output_high(PIN_D7); //will be overwritten in Port B by real data
old_value = adc_value;
}
if(i2c_status>=0x80) {
if (i2c_status==0x80) i2c_status=i2c_read(); //throw away the byte
i2c_write(adc_value); //Master is requesting data
//We reply to 0x80 with a 'write' as CCS indicates
} //Next status (0x81) will send the real data
}
|
Best Wishes |
|
|
|
|
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
|