PIC32MX: I2C Communication between PIC32s

From Mech
Jump to: navigation, search

Contents

Overview

I2C is pronounced "I squared C" and stands for Inter-Integrated Circuit. This protocol was designed by Phillips Semiconductors around 1992 to allow easy communication between components on the same circuit board and can achieve transfer rates of up to 400 kbit/sec. I2C is a 2 line (plus common ground) communication method for one master device to control up to 112 slave devices. While it is possible to have multiple masters on the same I2C bus, this page will only deal with a one master configuration.

Basic Operation

The two lines are named SCL1 and SDA1 where SCL1 is the CLock line and SDA1 is the DAta line. I2C requires the lines to be high unless the master or the slave is pulling the line down, so you will need to use pull up resistors on both the clock and data lines.

Line Name Description
SCL1 Serial Clock Line Output from master
SDA1 Data Line The single data line for sending/receiving


The data line is read each time the clock line goes high, as shown in the diagram below:

I2C Data Transfer.jpg
  • S - As the master pulls down the data line, the falling edge of the data line signifies the start condition.
  • P - As the master allows the data line to rise, the rising edge of the data line signifies the stop condition.
  • B - Individual bits being transferred.

The data line can only change while the clock line is low. The data line is only read when the clock line is high and therefore 1 bit can be transferred per cycle of the clock line.

The master initially sends a start bit followed by the 7-bit address of the slave it wishes to communicate with. The next bit represents whether it wishes to write(0) to or read(1) from the slave.

The slave then responds with an acknowledge bit. The transmission continues according to whether the master is attempting to read to or write from the device.

The master then ends the transmission with a stop bit. Another option is to send another start bit to continue the transfer in a combined message.

Circuit

I2C circuit.PNG

Code

In the code below, the master sends one of two bytes (0xAA or 0x23) to the slave at address 0x40. The master then receives a byte back from the slave. If the slave receives a 0xAA, it lights its LEDs. When the master begins sending, the slave triggers an interrupt (if the slave address is a match), which does one of four actions based on whether the master is reading or writing and whether the last byte was a data or address byte. As mentioned, when the master is writing the slave stores the byte, and when the master is reading the slave sends back the last stored byte.

Master Code

/********************************************************
 * I2C_Master.c: Master code for I2C communication.	*
 * 	Both PICS use I2C1 module to send/ receive data.*
 *	The master sends different values to a slave,   *
 *	which uses an interrupt to respond accordingly.	*
 *							*
 * Hardware: 2 PIC32MX460F512L PICs on NU32 boards	*
 ********************************************************
 * Thomas Peterson, James Rein, Eric West		*
 * ME333 Winter 2010					*
 * File Created: 	05-FEB-2010			*
 * Last Modified: 	14-FEB-2010			*
 *******************************************************/

#include "HardwareProfile.h"
#include <plib.h>

#define SYSCLK	(80000000)
#define PBCLK  (SYSCLK)
#pragma config FPBDIV = DIV_1				//Sets PBCLK to SYSCLK

#define Fsck	50000
#define BRG_VAL 	((PBCLK/2/Fsck)-2)
 
#define Nop() asm( "nop" )                 //No-operation; asm stands for assembly, using an assembly command in C.  Cool!

#define INPUT_A9       PORTAbits.RA9
#define INPUT_A10       PORTAbits.RA10


//function declaration for sending data and selecting slave address
void SendData(int,unsigned int);
void Delayms( unsigned t);

/*
This function is a delay function, causing the program to wait for approximately 4 * cnt cycles
1 cycle is 1/SYSCLK seconds.
*/
void i2c_wait(unsigned int cnt)
{
	while(--cnt)
	{
		Nop();
		Nop();
	}
}


/* Main function */
int main(void)
{
    // Configure the proper PB frequency and the number of wait states.
	SYSTEMConfigPerformance(SYS_FREQ);

	// Set all analog pins to be digital I/O
   	AD1PCFG = 0xFFFF;

	//Setup TRIS bits for switches and I2C pins
	TRISAbits.TRISA14=0;
	TRISAbits.TRISA15=0;

	//Initialize all of the LED pins
	mInitAllLEDs();
       mInitAllSwitches()

	unsigned char SlaveAddress;   //Slave address variable to tell the master where to send the data.  
                                     //Will be re-assigned for multiple slaves.

	//Enable I2C channel and set the baud rate to BRG_VAL)
	OpenI2C1( I2C_EN, BRG_VAL );

	int rcv;			//For received data

	//While loop to test LED functionality 
	while(1) {
		if (swProgram) {				//First button pressed
			while(swProgram) { Nop(); }	//Wait for release
			mLED_2_Toggle();			//Toggle LED2
			SendData(0xAA,0x40);  			//Sends hex data 0xAA to slave address 0x40
			rcv = RcvData(0x40);			//Receives data from address 0x40				
			Delayms(100);
		}
		if (swUser) {				//Second button pressed
			while(swUser) { Nop(); }
			mLED_1_Toggle();			//Toggle LED1
			SendData(0x23,0x40);    		//Sends hex data 0xAA to slave address 0x40
			rcv = RcvData(0x40);		        //Receives data from address 0x40			
			Delayms(100);		
		}
	}//while loop ending

	return 0;
}  //ending main 


/*****************************************************
 * RcvData(unsigned int address)		     *
 *					  	     *
 * Gets a byte of data from I2C slave device at      *
 *  ADDRESS.					     *
 *						     *
 * Returns: Received data			     *
 ****************************************************/
int RcvData(unsigned int address) {
	StartI2C1();				//Send line start condition
	IdleI2C1();			        //Wait to complete
	MasterWriteI2C1((address << 1) | 1);	//Write out slave address OR 1 (read command)
	IdleI2C1();				//Wait to complete
	int rcv = MasterReadI2C1();		//Read in a value
	StopI2C1();				//Send line stop condition
	IdleI2C1();				//Wait co complete
	return rcv;				//Return read value
}



/***************************************************
 * SendData(int data, unsigned int address)        *
 *						    *
 * Sends a byte of data (DATA) over the I2C line   *
 *	to I2C address ADDRESS			    *
 *						    *
 * Returns: nothing				    *
 ***************************************************/
void SendData (int data, unsigned int address){
	StartI2C1();	        //Send the Start Bit
	IdleI2C1();		//Wait to complete

	MasterWriteI2C1((address << 1) | 0);  //Sends the slave address over the I2C line.  This must happen first so the 
                                             //proper slave is selected to receive data.
	IdleI2C1();	        //Wait to complete

	MasterWriteI2C1(data);  //Sends data byte over I2C line
	IdleI2C1();		//Wait to complete

	StopI2C1();	        //Send the Stop condition
	IdleI2C1();	        //Wait to complete

} //end function



void Delayms( unsigned t)
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ
{
    OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);
    while (t--)
    {  // t x 1ms loop
        WriteTimer1(0);
        while (ReadTimer1() < SYS_FREQ/256/1000);
	}
	CloseTimer1();
} // Delayms

Slave Code

/***********************************************************************
 * PIC32 I2C Slave Code                
 ***********************************************************************/

#include "GenericTypeDefs.h"
#include "Compiler.h"
#include "HardwareProfile.h"
#include <plib.h>

#define SYSCLK	(80000000)
#define PBCLK  (SYSCLK)

#define Fsck	50000
#define BRG_VAL 	((PBCLK/2/Fsck)-2)

// this is the modules Slave Address
#define SLAVE_ADDRESS 0x40

// volatile variables to hold the switch and led states
volatile unsigned char dataRead = 0;

///////////////////////////////////////////////////////////////////
//
//	InitI2C
//
// 	Perform initialisation of the I2C module to operate as a slave
//
///////////////////////////////////////////////////////////////////
void InitI2C(void)
{
	unsigned char temp;
	
	// Enable the I2C module with clock stretching enabled
	OpenI2C1(I2C_ON | I2C_7BIT_ADD | I2C_STR_EN, BRG_VAL);
	
	// set the address of the slave module, address matching is with bits
	// 7:1 of the message compared with bits 6:0 of the ADD SFR so we
	// need to shift the desired address 1 bit. 
	I2C1ADD = SLAVE_ADDRESS; // >> 1;
	I2C1MSK = 0;
	
	// configure the interrupt priority for the I2C peripheral
	mI2C1SetIntPriority(I2C_INT_PRI_3 | I2C_INT_SLAVE);

	// clear pending interrupts and enable I2C interrupts
	mI2C1SClearIntFlag();
	EnableIntSI2C1;
}

///////////////////////////////////////////////////////////////////
//
//	main routine
// 
//	This code example demonstrates using the PIC32 as an I2C slave
//	
//
///////////////////////////////////////////////////////////////////
int main (void)
{
	// set for 80MHz operation
	SYSTEMConfigPerformance(SYSCLK);
	// set the Pbus to be 40000000
	mOSCSetPBDIV(OSC_PB_DIV_2);
	// disable the JTAG port
	mJTAGPortEnable(0);
	// enable interrupts
	INTEnableSystemMultiVectoredInt();
	
	InitI2C();
	mInitAllLEDs();

	// main loop
	while (1) {
		/* If global variable "dataRead" is set high during interrupt, turn on all LEDs */
		if (dataRead == 0xAA)
		{
			mLED_0_On();
			mLED_1_On();
			mLED_2_On();			
			mLED_3_On();			
			
		}
	}
}

///////////////////////////////////////////////////////////////////
//
// Slave I2C interrupt handler
// This handler is called when a qualifying I2C events occurs
// this means that as well as Slave events 
// Master and Bus Collision events will also trigger this handler.
//
///////////////////////////////////////////////////////////////////
void __ISR(_I2C_1_VECTOR, ipl3) _SlaveI2CHandler(void)
{
	mLED_1_On();
	unsigned char temp;
	static unsigned int dIndex;
	
	// check for MASTER and Bus events and respond accordingly
	if (IFS0bits.I2C1MIF == 1) {
		mI2C1MClearIntFlag();
		return;		
	}
	if (IFS0bits.I2C1BIF == 1) {
		mI2C1BClearIntFlag();
		return;
	}
	mLED_1_Off();
	mLED_2_On();
	
	// handle the incoming message
	if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 0)) {
		// R/W bit = 0 --> indicates data transfer is input to slave
		// D/A bit = 0 --> indicates last byte was address  
		
		// reset any state variables needed by a message sequence	
		// perform a dummy read of the address
		temp = SlaveReadI2C1();
		
		mLED_3_On();
		mLED_2_Off();
		// release the clock to restart I2C
		I2C1CONbits.SCLREL = 1; // release the clock

	} else if ((I2C1STATbits.R_W == 0) && (I2C1STATbits.D_A == 1)) {
		// R/W bit = 0 --> indicates data transfer is input to slave
		// D/A bit = 1 --> indicates last byte was data
		
		mLED_3_On();
		mLED_2_On();
		// writing data to our module, just store it in adcSample
		dataRead = SlaveReadI2C1();
		
		// release the clock to restart I2C
		I2C1CONbits.SCLREL = 1; // release clock stretch bit

	} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 0)) {
		// R/W bit = 1 --> indicates data transfer is output from slave
		// D/A bit = 0 --> indicates last byte was address
		mLED_0_On();
		mLED_2_Off();
		// read of the slave device, read the address 
		temp = SlaveReadI2C1();
		dIndex = 0;
		SlaveWriteI2C1(dataRead);
	} else if ((I2C1STATbits.R_W == 1) && (I2C1STATbits.D_A == 1)) {
		// R/W bit = 1 --> indicates data transfer is output from slave
		// D/A bit = 1 --> indicates last byte was data
		mLED_0_On();
		mLED_2_On();
		
		// output the data until the MASTER terminates the
		// transfer with a NACK, continuing reads return 0
		if (dIndex == 0) {
			SlaveWriteI2C1(dataRead);
			dIndex++;
		} else
			SlaveWriteI2C1(0);
	}
	
	// finally clear the slave interrupt flag
	mI2C1SClearIntFlag();		
}
Personal tools