Guides‎ > ‎

I2C using Master Synchronous Serial Port (MSSP) Module

posted Apr 19, 2011, 7:01 PM by Danny Ng   [ updated Apr 19, 2011, 10:53 PM ]

I2C are widely use for to communicate with external peripheral such as port expender, EEPROM, Real Time Clock etc. The Master Synchronous Serial Port (MSSP) module in PIC18 can be used to communicated with I2C peripheral. MSSP module can be configure to work as SPI and I2C. In this example the module will be configured as I2C to communicate with a port expender from Microchip MCP23017. The circuit below is built up on breadboard to test the MSSP function as an I2C module. 
The DIL switch on Port B is used to control the LED on Port A of the IO expender. INTB from the IC is connected to the INT2 pin of the PIC so that the PORTA output of the Port expender can be updated with the value from the DIL switch. The code function will be implemented using 2 interrupt, one at the high priority location for the External interrupt and the other for MSSP module at the low interrupt. MSSP module had to be configured as I2C master mode for this example. The code below shows the setting to initialize MSSP module.

SSPADD = 80;
SSPSTAT = 0x80;
SSPCON1 = 0x28;
SSPCON2 = 0x00;

Function for sending and receiving data for I2C is created according to the required protocol to communicate with MCP23017. The code below shows the function i2cTransmit and i2cRecieve for receiving data and the interrupt routine for the handling of transmission/reception. The 2 function is used to load the data buffer for transmission and reception. The Interrupt function will pass the data accordingly to the MSSP module according to the protocol written in the datasheet of MCP23017.

unsigned char  I2Cbusy = 0;
unsigned char  I2CTransmitArray[20];
unsigned char  I2CTransmitElement;
unsigned char  I2CTransmitCurrent;
unsigned char  I2CReceived;

void i2cTransmit(unsigned char *data, unsigned char Element){
int i = 0;
if(I2Cbusy == 0){
while(i < Element){
I2CTransmitArray[i] = *data;
i++;
data++;
}
I2Cbusy = 1;
I2CTransmitCurrent = 0;
I2CTransmitElement = Element;
SSPCON2bits.SEN = 1;
}
}

void i2cReceive(unsigned address, unsigned regis){
if(I2Cbusy == 0){
I2CTransmitArray[0] = address;
I2CTransmitArray[1] = regis;
I2CTransmitArray[2] = 0; // dummy
I2CTransmitArray[3] = address | 1;
I2Cbusy = 2;
I2CTransmitCurrent = 0;
I2CTransmitElement = 4;
SSPCON2bits.SEN = 1;
}
}
/*****************Low priority interrupt vector **************************/
#pragma code low_vector=0x18
void interrupt_at_low_vector(void)
{
  _asm GOTO low_isr _endasm
}

#pragma code

/*****************Low priority ISR **************************/
#pragma interruptlow low_isr
void low_isr (void)
{
PIR1bits.SSPIF = 0;
if(I2Cbusy ==1){
if(I2CTransmitCurrent < I2CTransmitElement)
SSPBUF = I2CTransmitArray[I2CTransmitCurrent];
else if(I2CTransmitCurrent == I2CTransmitElement)
SSPCON2bits.PEN = 1;
else
I2Cbusy = 0;
}
else{
if(I2CTransmitCurrent == 2)
SSPCON2bits.RSEN = 1;
else if(I2CTransmitCurrent < I2CTransmitElement)
SSPBUF = I2CTransmitArray[I2CTransmitCurrent];
else if(I2CTransmitCurrent == I2CTransmitElement)
SSPCON2bits.RCEN = 1;
else if(I2CTransmitCurrent == I2CTransmitElement+1){
I2CReceived = SSPBUF;
SSPCON2bits.ACKDT = 1;
SSPCON2bits.ACKEN = 1;
}
else if(I2CTransmitCurrent == I2CTransmitElement+2)
SSPCON2bits.PEN = 1;
else
I2Cbusy = 0;
}
I2CTransmitCurrent++;
}

In the interrupt service routine, transmission and reception is differentiated based on the value in I2Cbusy; 1 for transmission and 2 for reception. The device address byte with write flag set need to be send to the module follow by the register address and the data to write. Step for writing N number of data is shown below. The steps below is initiated by the function i2cTransmit.
Steps to write register in MCP23017:
Start Condition -> Device Address + Write flag -> Register Address -> Data 1 -> Data 2 .......-> Data N -> Stop Condition
Below is the example code for the transmission of config1 in the code. The I2Cbusy check is to wait until the whole transmission of the config is done.

unsigned char config1[] = {0x40, 0x00, 0x00, 0xFF};
    i2cTransmit(config1, 4);
while(I2Cbusy != 0);

Reading data from one register location is implemented in for the read function although more than one data continuously can be achieve. Since for this example only 1 read in is required, a Nack is sent to the port expender once the on data is received. Based on the data sheet the step below is used to get data from a register location.
Steps to read register in MCP23017:
Start Condition -> Device Address + Write flag -> Register Address -> Restart Condition ->...
Device Address + Read flag -> Data from MCP23017 ->  NACK from Master ->Stop Condition
Only 1 data at the address will be read from MCP23017. Code below show reading of data from Device with address of 0x40 and register address of 0x13. The data read in will be stored in the variable I2CReceived.
    i2cReceive(0x40, 0x13);
while(I2Cbusy != 0);
output[2] = I2CReceived;

For the example data is read only when interrupt is generated by MCP23017. Connected to the INT2 data will be read when interrupt occur. MCP23017 must be configure to generate interrupt on the change of value on the pin by setting the GPINTEN byte in the register. When interrupt occurs I2C read is called to get the data from the port expender.

#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
  _asm GOTO high_isr _endasm
}

#pragma code
/*****************High priority ISR **************************/

#pragma interrupt high_isr
void high_isr (void)
{
INTCON3bits.INT2IF = 0;
i2cReceive(0x40, 0x13);
}

The function of the communication is illustrated by controlling the LED with the switches. Main code of the control is shown below. When there is changes to the input at the interrupt, the LED will be updated with the value on the input ports.

while(1){
if(I2Cbusy == 2){
while(I2Cbusy != 0);
output[2] = I2CReceived;
i2cTransmit(output, 3);
}
}

The pictures below show the changes to the LED according to the switch when the code is downloaded. Note the switch position and the LED.
 All LED Off  All LED Lighted
 On According to Switch On According to Switch
The full code and project files is attached below at the attachments.

ċ
I2C.rar
(23k)
Danny Ng,
Apr 19, 2011, 10:32 PM
Comments