|
|
View previous topic :: View next topic |
Author |
Message |
coolcool23
Joined: 09 Feb 2012 Posts: 1
|
PIC 18F4520 Slave I2C issues |
Posted: Sun Apr 01, 2012 8:31 pm |
|
|
Hello,
This is my first post on the forums, but I'm having a very tricky problem with I2C on the PIC 18F4520 dev board and I'm looking to try and get some information on what might be happening. I've done a serious amount of troubleshooting and I'm at a loss, so I'm hoping that someone can provide some insight.
The code I'm writing is for a school design project, so I'm more or less on my own. Basically the way it works is this: We'll be using a 18F67J60 (currently using the 3.3V dev board) as an I2C master to communicate back and forth between several PIC 16F1827 controllers. (Currently we are using the 18F4520 dev board to test the basic code)
Since originally writing the code, I've had issues getting consistent transfers across the I2C bus. Originally I was doing a bunch of fancy dynamic addressing (which you might be able to see in the code) but was getting strange results. I've recently stripped down both the slave and the master to some very basic code similar to what is provided in the examples for an I2C master, but I can't get the bus to operate as I expect from the documentation for any length of time. As it stands, the bus currently works through a few data transfers, and then goes either high/or low on the data and/or clock lines. It seems to be the slave which is doing this because when it is disconnected, the master seems to operate fine, iterating forever through it's loop.
Below is a screenshot of what I am talking about when the slave is connected to the bus:
http://imgur.com/4pRur
I'm currently using 4.7k resistors on both the data and clock lines, but similar strange behavior occurs with 220 ohm resistors.
My code is below:
Master:
Code: |
#include <Sub_Controller_I2C.h>
#include <stdlibm.h>
#use i2c(Master,sda=PIN_E0,scl=PIN_E1)
#use standard_io(B)
static volatile BYTE assignAddr;
void pollSensors()
{
//Go into a search mode for sensors that have not yet been initialized.
initializeSensors();
//convert the next assignable address to an index and subtract one,
//representing the last index in the sensor list to be assigned an address.
BYTE sensorLimit = ((assignAddr-8)/2);
//if that index is less than or equal to 0, then no sensors have been
//assigned and nothing should be done before moving to the configuration
//stage for new sensors.
if(sensorLimit<=0) {}
else
{
BYTE x = 0;
for(x = 0; x < sensorLimit; x++)
{
i2c_start();
//contact the address of the spot in memory,
//requesting a read operation.
if(!i2c_write((x*2)+8))
{
BYTE y;
for(y = 0; y<(*sensorList[(assignAddr-8)/2]).typeSize; y++)
{
//dump the recieved data into the correct spot for the type
(*sensorList[(assignAddr-8)/2]).data[y] = i2c_read();
}
}
else
{
//there has been an error on a previously active port and it should
//be noted somewhere in the program.
}
i2c_stop();
}
}
//the sub-controller should now have the most recent data from all sensors.
}
//This function actively searches for and defines found sensors on the I2C bus
//after initialization.
void initializeSensors()
{
delay_ms(1);
BYTE portCount = 0;
//start checking each port for a sensor
for(portCount = 0; portCount<8; portCount++)
{
//pulse the select line high for the given port
output_b(1<<(portCount+1));
//Try to contact an unconfigured sensor on that port and
//give it a unique address.
if(assignAddr >=0x56)
{
//if the assignable address exceeds the maximum valid address,
//we have a problem and it should be reported.
ERRORsensorAddr = 1;
}
else //otherwise contact as normal with current assign address
{
i2c_start();
//if a device responds to the write with an aknowledgement
if(i2c_write(0x56) == 0)
{
i2c_write(assignAddr); //send it the next assignable address.
i2c_stop(); //then stop talking on the bus
//allocate a new sensor structure for the next available
//sensor pointer.
// sensorList[(assignAddr-8)/2] = (SENSOR*)malloc(sizeof(SENSOR));
//register what port this new sensor is sitting on in
//the physical world
// (*sensorList[(assignAddr-8)/2]).port = portCount;
i2c_start(); //start again by talking to the new address
//contact the same sensor, but now at the new address
//and request a read of all applicable types.
i2c_write(assignAddr|0x01);
//first thing is to get the total number of types
BYTE numTypes = i2c_read();
//store the total number of types of this sensor
//sent to us into the structure for use later.
// (*sensorList[(assignAddr-8)/2]).typeSize = numTypes;
BYTE count; //create temporary count variable
for(count = 0; count < numTypes; count++)
{
//record the types as the sensor sends them, and
//post increment the count variable
// (*sensorList[(assignAddr-8)/2]).type[count] = i2c_read();
i2c_read();
}
assignAddr +=2; //increase to the next assignable address.
}
i2c_stop();
}
}
output_b(0x00); //bring all select lines low
}
//This function initializes the I2C ports necessary to poll sensors.
void initializeI2C()
{
enable_interrupts(GLOBAL);
//enable_interrupts(INT_SSP);
ERRORsensorAddr = 0;
DELAY_MS(1); //wait for sensors to run initialization routines
assignAddr = 0x08; //set the assignment address to the first valid one
}
void main()
{
initializeI2C();
while(TRUE){
//initializeSensors();
delay_us(10);
i2c_start();
if(i2c_write(0x8) == 0) i2c_write(0x55);
i2c_stop();
delay_us(10);
i2c_start();
if(i2c_write(0x08|0x01) == 0)
{
BYTE lcv;
BYTE temp;
for(lcv = 0; lcv<=2; lcv++)
{
temp = i2c_read();
}
}
i2c_stop();
};
}
//****** Header file code:
#include <18F67J60.h>
#device adc=16
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#use delay(clock=20000000)
//This structure represents a SENSOR "object" and contains all data relevant to the given device.
typedef struct{
BYTE port;
BYTE type[5];
BYTE data[5];
BYTE typeSize;
}SENSOR;
volatile BYTE ERRORsensorAddr;
/*116 is the maximum number of sensors that can be addressed on the same bus using I2C 7 bit mode.
Using this project's method of a port select line, 8 will be assignable since there can only be eight physical devices attatched.
Address of the sensor can be obtained by the following relationship: Address = (array index * 2) + 8*/
volatile SENSOR* sensorList[8];
|
Slave:
Code: |
#include <Sensor_I2C.h>
//usable address range falls between 0x08 and 0x78 due to reservations,
//0xF0 is the reserved address for new sensors.
#use i2c(SLAVE, sda=PIN_C4,scl=PIN_C3,address=0x08)
#use standard_io(A,B,E)
#INT_SSP
void ssp_interupt()
{
BYTE incoming, state;
state = i2c_isr_state();
if(state <= 0x80) //Master is sending data
{
incoming = i2c_read();
//output_low(PIN_A5);
//if(state == 1)
//{
//I2C_SlaveAddr(incoming);
//}
}
if(state >= 0x80) //Master is requesting data
{
//output_low(PIN_B5);
i2c_write(state);
}
/*
//Master has written data; i2c_read() will immediately return the data
if(state <= 0x7F)
{
//Address match received with R/W bit clear,
//perform i2c_read( ) to read the I2C address.
if(input(I2C_SEL) == 1)
{
output_low(PIN_A5); //debug output
incoming = i2c_read(1);
if(state == 1)
{
I2C_SlaveAddr(incoming);
}
}
else
{
incoming = i2c_read(0);
}
}
//Transmission completed and acknowledged; respond with i2c_write()
//to pre-load the transmit buffer for the next transation (the next
//I2C read performed by master will read this byte).
else
{
//Read and ack the transmission on the first valid address match,
//but still send a data value this time.
if(state == 0x80) incoming = i2c_read();
//if we are asked to send data in initialization mode, send types
if(input(I2C_SEL) == 1)
{
if(state == 0x80)
{
//if this is the first read with the initialization select
//still high, write the size of types the device has
i2c_write(size);
}
else
{
//otherwise just startwriting types, since the state should be at 81
i2c_write(type[state - 0x81]);
}
}
else //otherwise we are not ininitialization mode and we should just write the data back.
{
i2c_write(data[state - 0x80]);
}
}
*/
}
void initializeI2C()
{
//output_low(PIN_B4);
//prep the type and data arrays with dummy data
//type[0] = 0x45;
//data[0] = 0x5A;
//size = 1;
enable_interrupts(GLOBAL);
enable_interrupts(INT_SSP);
}
void main()
{
//initializeI2C();
enable_interrupts(GLOBAL);
enable_interrupts(INT_SSP);
while (TRUE)
{}
}
//********* Header file code:
#include <18F4520.h>
#device adc=16
#FUSES NOWDT //No Watch Dog Timer
#FUSES WDT128 //Watch Dog Timer uses 1:128 Postscale
#FUSES LP //Low power osc < 200 khz
#FUSES NOBROWNOUT //No brownout reset
#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=20000)
#define I2C_SEL PIN_E0
volatile BYTE type[5];
volatile BYTE data[5];
volatile BYTE size;
|
My code is pretty rough, but it's been flipped around a lot of times for troubleshooting. I was hoping to clean it up when the problem was resolved.
I haven't been able to try using the hardware peripheral instead of doing it through software because neither dev board provides a pin out for the hardware pins; the custom boards we are manufacturing will have them connected, though.
Other than that, I have tried flipping around the code many times in different fashions and I haven't been able to get it to work reliably. Any thoughts? I can't think of any other information to list right now, but let me know if I forgot to include something helpful. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19520
|
|
Posted: Mon Apr 02, 2012 2:42 am |
|
|
I seriously doubt if you will ever get this to work, with the slave running at 20000Hz. It'll take about 1/150th second just to get into the interrupt routine. Though the hardware 'holds' things using the clock stretching, when devices take time to respond, this normally only works right, when the clocks on master and slave are at least of the same _magnitude_. A factor of 1000* on the clocks, is just a recipe for disaster. You are using a bus that is running at 100KHz, and the slave needs to be able to at least perform one instruction per cycle of this, to have any hope of working reliably. I'd look at 400 to 500KHz, as a minimum clock that is likely to work.
220R, is too low for I2C pull-ups even at 3.3v. VOl, is defined to be no higher than 0.4v, and the minimum sink current of the drivers is 3mA, so Rp (min) = 966R. 4.7K is rather high for a 3.3v bus, if multiple devices are going to be connected. Use something like 1.5KR.
You may have an issue with voltage. Your current slave chip (4520), is not specified to run below 4.2V. Assuming you are running at 5v, a master I2C device at 3.3v, won't meet the VOh specs for the bus. The I2C input pins on the 4520, require a signal to reach 0.7Vdd. Assuming 5v operation, this is 3.5v.....
Best Wishes |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Mon Apr 02, 2012 1:56 pm |
|
|
In addition to everything Ttelmah said, there are several other issues:
Quote: |
if(sensorLimit<=0) {}
else
{
BYTE numTypes = i2c_read();
if(i2c_write(0x08|0x01) == 0)
{
BYTE lcv;
BYTE temp;
if(!i2c_write((x*2)+8))
{
BYTE y;
|
You're declaring variables in the middle of code in various places in your
program. That's not supported by CCS. It may appear to work. It's not
supported and can cause erratic program operation. All local variables
must be declared at the start of each function.
Quote: |
for(y = 0; y<(*sensorList[(assignAddr-8)/2]).typeSize; y++)
{
//dump the recieved data into the correct spot for the type
(*sensorList[(assignAddr-8)/2]).data[y] = i2c_read();
|
These expressions are way too complicated for an initial test of an i2c
slave program. I strongly suggest making a simple program for the
first cut.
Quote: | #use i2c(SLAVE, sda=PIN_C4,scl=PIN_C3,address=0x08) |
You're using a reserved address for the slave. That's a no-no. See
the i2c spec, page 17. Address 8 is reserved for "Hs-mode master code". http://www.nxp.com/documents/user_manual/UM10204.pdf
Quote: |
for(y = 0; y<(*sensorList[(assignAddr-8)/2]).typeSize; y++)
{
(*sensorList[(assignAddr-8)/2]).data[y] = i2c_read();
|
These expressions are way too complicated for an initial test.
Make a simple slave program for initial testing.
Quote: | static volatile BYTE assignAddr;
|
I don't see how 'volatile' is necessary. Also, I don't think 'static' is
needed, either. You would only need it if you were using several modules
(files) with the same variable name in it. You would only do that if you
were using, I think, CCS's Multiple Compilation Units. But do we know
that MCU's even work, for sure ? I don't. I don't even know if you're
using them. I recommend, don't.
Quote: |
#INT_SSP
void ssp_interupt()
{
else
{
incoming = i2c_read(0);
}
|
In your Slave code, you're doing a NACK in one of the calls to i2c_read()
in the isr. But in fact, CCS looks at the #use i2c() statement, and if you
declare it as a slave, the compiler will ignore the ACK/NACK parameter.
It produces the same ASM code in the .LST file whether or not you put in
a 0 or a 1 (or leave it blank). Furthermore, none of the CCS examples
attempt to do a NACK in slave code, so I don't know where you got that
idea.
Then, in your master code, you should do a NACK on the last call
to i2c_read() in a transaction. But you don't. That needs to be fixed. |
|
|
|
|
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
|