Digital SM Part I: I2C PIC18F4550 Slave and PIC18F4550 Master
This year a collegue and I had to arrange the X-mas gifts for the company I work for. We had a list of about 15 options. One of them was a Raspberry Pi XBMC set (wonder who's idea this was). So I chose the R'Pi. One of the things a like about it, is the possibility to extend the via a breakout-board. The B-type has 17 GPIO's. Two of them can be used for I2C, where the R'Pi will be the master. Because I was not to familiar with both the R'pi and I2C, I decided to do I2C on a 'platform' I am familiar with: Microchip's PIC18F4550. I has support for I2C. It can run as master and as slave. Microchip provides libraries that handles the low level parts of USB, I2C, etc. The picture below shows the master-slave schema I used.
I2C schema PIC18F4550-master and PIC18F4550-slave with debugging facility
A short description of the code for the master.
First it configures the controller: RB0 and RB1 are configured as inputs, all other I/O pins are configured as outputs Configures the controller for I2C Set the baudrate Set bus idle condition Send the lient address + WRITE to client Send the data Set bus idle condition Restart I2C ommunication Send the client address + READ from client Read data from client Close I2C 'channel'
I extended the Microchip's example code with 'debug statements' in order to see if communication was successful and if not were in the process I got stuck. Each step increases the counter with one and the result is displayed at port D which is connected to 8 leds. At start all leds blink twice for master. Then a led loop D0->D7 is excuted. After that the actual program starts.
Upon success the pattern is 0b10101010.
/*
* File: main.c
* Author: Erwin van Dijk
*
* Created on December 30, 2013, 8:07 PM
*/
#include <p18f4550.h>
#include <stdio.h>
#include <stdlib.h>
#include <i2c.h>
#pragma config PLLDIV = 5 // 20 MHz Oscillator
#pragma config CPUDIV = OSC1_PLL2
#pragma config USBDIV = 2 // 48 MHz CPU-CLK
#pragma config FOSC = HSPLL_HS // Highspeed with phase locked loop
//#pragma config FOSC = HS // Highspeed, no phase locked loop
#pragma config FCMEN = OFF
#pragma config IESO = OFF
#pragma config DEBUG = OFF
//#ifdef __DEBUG
#pragma config PWRT = OFF
#pragma config WDT = OFF, WDTPS = 1024
//#else
// #pragma config PWRT = ON
// #pragma config WDT = ON, WDTPS = 32768
//#endif
#pragma config BOR = OFF, BORV = 0
#pragma config VREGEN = ON
#pragma config MCLRE = OFF
#pragma config LPT1OSC = OFF
#pragma config PBADEN = OFF
#pragma config CCP2MX = OFF
#pragma config STVREN = ON
#pragma config LVP = OFF
#pragma config ICPRT = ON//OFF
#pragma config XINST = OFF
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF
#pragma config CPB = OFF, CPD = OFF
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF
#pragma config WRTB = OFF, WRTC = OFF, WRTD = OFF
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTRB = OFF
#define _XTAL_FREQ 20000000
#define SCL TRISBbits.TRISB1 // I2C bus
#define SDA TRISBbits.TRISB0
#define SCL_IN PORTBbits.RB1 //
#define SDA_IN PORTBbits.RB0 //
void delay_sec(unsigned char sec);
void leds_test(void);
unsigned char I2C_Send[21] = "MICROCHIP:I2C_MASTER";
unsigned char I2C_Recv[21];
//************ I2C MASTER ****************************
void main(void) {
unsigned char sync_mode = 0, slew = 0, add1, w, data, status, length;
PORTA = 0;
TRISA = 0b00000000; // PORTD all outputs
PORTA = 0b11111111;
/*
Two pins are used for data transfer:
? Serial clock (I2C_SCL) ? RB1/AN10/INT1/SCK/I2C_SCL
? Serial data (I2C_SDA) ? RB0/AN12/INT0/FLT0/SDI/I2C_SDA
The user must configure these pins as inputs by setting
the associated TRIS bits.
*/
ADCON1 = 0b00001111; //AN0...AN12 as digital I/O
TRISB = 0b00000011; //RB0...RB1 as digital inputs, others output
PORTB = 0;
PORTB = 0b11111100;
PORTC = 0;
TRISC = 0b00000000; // PORTD all outputs
PORTC = 0b11111111;
PORTD = 0;
TRISD = 0b00000000; // PORTD all outputs
PORTD = 0b11111111;
PORTE = 0;
TRISE = 0b00000000; // PORTD all outputs
PORTE = 0b11111111;
leds_test();
for (w = 0; w < 20; w++){
I2C_Recv[w] = 0;
}
add1 = 0xA2; //address of the device (slave) under communication
CloseI2C(); //close i2c if was operating earlier
//------------------------INITIALISE THE I2C MODULE FOR MASTER MODE WITH 100KHz ---------------------------
sync_mode = MASTER;
slew = SLEW_OFF;
OpenI2C(sync_mode, slew);
SSPADD = 0x0A; //400kHz Baud clock(9) @8MHz
//check for bus idle condition in multi master communication
IdleI2C();
//--------------------START I2C---------------
PORTD = 0b00000001;
StartI2C();
PORTD = 0b00000010;
//**************write the address of the device for communication************
data = SSPBUF; //read any previous stored content in buffer to clear buffer full status
do
{
status = WriteI2C(add1 | 0x00); //write the address of slave
if (status == -1) //check if bus collision happened
{
PORTD = 0b00000011;
data = SSPBUF; //upon bus collision detection clear the buffer,
SSPCON1bits.WCOL = 0; // clear the bus collision status bit
}
} while (status != 0); //write untill successful communication
PORTD = 0b00000100;
//R/W BIT IS '0' FOR FURTHER WRITE TO SLAVE
//***********WRITE THE THE DATA TO BE SENT FOR SLAVE****************
while (putsI2C(I2C_Send) != 0); //write string of data to be transmitted to slave
PORTD = 0b00000101;
//-------------TERMINATE COMMUNICATION FROM MASTER SIDE---------------
IdleI2C();
PORTD = 0b00000110;
//-----------------RESTART I2C COMMUNICATION---------------------------------------
RestartI2C();
PORTD = 0b00000111;
IdleI2C();
PORTD = 0b00001000;
//**************write the address of the device for communication************
data = SSPBUF; //read any previous stored content in buffer to clear buffer full status
//R/W BIT IS '1' FOR READ FROM SLAVE
add1 = 0xA2;
do {
status = WriteI2C(add1 | 0x01); //write the address of slave
if (status == -1) //check if bus collision happened
{
PORTD = 0b00001001;
data = SSPBUF; //upon bus collision detection clear the buffer,
SSPCON1bits.WCOL = 0; // clear the bus collision status bit
}
} while (status != 0); //write untill successful communication
PORTD = 0b00001010;
//******************* Recieve data from slave ******************************
while (getsI2C(I2C_Recv, 20)); //recieve data string of lenght 20 from slave
I2C_Recv[20] = '\0';
PORTD = 0b00001011;
NotAckI2C(); //send the end of transmission signal through nack
while (SSPCON2bits.ACKEN != 0); //wait till ack sequence is complete
PORTD = 0b00001100;
//********************* close I2C *****************************************
CloseI2C(); //close I2C module
PORTD = 0b10101010;
while (1); //End of program
}
void delay_sec(unsigned char sec) {
unsigned char counter1 = 0;
unsigned char counter2 = 0;
unsigned char counter3 = 0;
unsigned char counter4 = 0;
for (counter1 = 0; counter1 < sec; counter1++) {
for (counter2 = 0; counter2 < 50; counter2++) {
for (counter3 = 0; counter3 < 100; counter3++) {
for (counter4 = 0; counter4 < 100; counter4++) {
Nop();
// (5 * 2) * 100 * 100 * 100
}
}
}
}
}
void leds_test(void) {
PORTD = 0b11111111;
delay_sec(1);
PORTD = 0b00000000;
delay_sec(1);
PORTD = 0b00000000;
delay_sec(1);
PORTD = 0b11111111;
delay_sec(1);
PORTD = 0b00000001;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
PORTD = PORTD << 1;
delay_sec(1);
}
A short description of the code for the slave.
First it configures the controller: RB0 and RB1 are configured as inputs, all other I/O pins are configured as outputs (same as master) Configures the controller for I2C as SLAVE It sets the address (0xA2) It waits for an address send by master Then it reads data from master Wait for I2C stop Wait for next address call Verify that master is ready for data reception Send data to master Close I2C 'channel'
Again I extended the Microchip's example code with 'debug statements'. At start all leds blink once for slave. Then a led loop D0->D7 is excuted. After that the actual program starts.
Upon success the pattern is 0b01010101.
/*
* File: main.c
* Author: Erwin van Dijk
*
* Created on December 30, 2013, 8:07 PM
*/
#include <p18f4550.h>
#include <stdio.h>
#include <stdlib.h>
#include <i2c.h>
#pragma config PLLDIV = 5 // 20 MHz Oscillator
#pragma config CPUDIV = OSC1_PLL2
#pragma config USBDIV = 2 // 48 MHz CPU-CLK
#pragma config FOSC = HSPLL_HS // Highspeed with phase locked loop
//#pragma config FOSC = HS // Highspeed, no phase locked loop
#pragma config FCMEN = OFF
#pragma config IESO = OFF
#pragma config DEBUG = OFF
//#ifdef __DEBUG
#pragma config PWRT = OFF
#pragma config WDT = OFF, WDTPS = 1024
//#else
// #pragma config PWRT = ON
// #pragma config WDT = ON, WDTPS = 32768
//#endif
#pragma config BOR = OFF, BORV = 0
#pragma config VREGEN = ON
#pragma config MCLRE = OFF
#pragma config LPT1OSC = OFF
#pragma config PBADEN = OFF
#pragma config CCP2MX = OFF
#pragma config STVREN = ON
#pragma config LVP = OFF
#pragma config ICPRT = ON//OFF
#pragma config XINST = OFF
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF
#pragma config CPB = OFF, CPD = OFF
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF
#pragma config WRTB = OFF, WRTC = OFF, WRTD = OFF
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTRB = OFF
#define _XTAL_FREQ 20000000
unsigned char databyte;
unsigned char state = 0;
void delay_sec(unsigned char sec);
void leds_test(void);
unsigned char I2C_Send[21] = "MICROCHIP:I2C_SLAVE";
unsigned char I2C_Recv[21];
//************ I2C SLAVE ****************************************
void main(void) {
unsigned char sync_mode = 0, slew = 0, add1, status, temp, w, length = 0;
PORTA = 0;
TRISA = 0b00000000; // PORTD all outputs
PORTA = 0b11111111;
/*
Two pins are used for data transfer:
? Serial clock (I2C_SCL) ? RB1/AN10/INT1/SCK/I2C_SCL
? Serial data (I2C_SDA) ? RB0/AN12/INT0/FLT0/SDI/I2C_SDA
The user must configure these pins as inputs by setting
the associated TRIS bits.
*/
ADCON1 = 0b00001111; //AN0...AN12 as digital I/O
TRISB = 0b00000011; //RB0...RB1 as digital inputs, others output
PORTB = 0;
PORTB = 0b11111100;
PORTC = 0;
TRISC = 0b00000000; // PORTD all outputs
PORTC = 0b11111111;
PORTD = 0;
TRISD = 0b00000000; // PORTD all outputs
PORTD = 0b11111111;
PORTE = 0;
TRISE = 0b00000000; // PORTD all outputs
PORTE = 0b11111111;
leds_test();
for (w = 0; w < 20; w++)
I2C_Recv[w] = 0;
CloseI2C(); //close i2c if was operating earlier
//------------------------INITIALISE THE I2C MODULE FOR MASTER MODE WITH 100KHz ---------------------------
sync_mode = SLAVE_7;
slew = SLEW_OFF;
OpenI2C(sync_mode, slew);
state++;
PORTD = state;
SSPADD = 0xA2; //initialze slave address
//********************* Read the address sent by master from buffer **************
while (DataRdyI2C() == 0); //WAIT UNTILL THE DATA IS TRANSMITTED FROM master
state++;
PORTD = state;
temp = ReadI2C();
state++;
PORTD = state;
//********************* Data reception from master by slave *********************
do {
while (DataRdyI2C() == 0); //WAIT UNTILL THE DATA IS TRANSMITTED FROM master
I2C_Recv[length++] = getcI2C(); // save byte received
} while (length != 20);
state++;
PORTD = state;
//******************** write sequence from slave *******************************
while (SSPSTATbits.S != 1); //wait untill STOP CONDITION
state++;
PORTD = state;
//********************* Read the address sent by master from buffer **************
while (DataRdyI2C() == 0); //WAIT UNTILL THE DATA IS TRANSMITTED FROM master
temp = ReadI2C();
state++;
PORTD = state;
//********************* Slave transmission ************************************
if (SSPSTAT & 0x04) //check if master is ready for reception
while (putsI2C(I2C_Send)); // send the data to master
state++;
PORTD = state;
//-------------TERMINATE COMMUNICATION FROM MASTER SIDE---------------
CloseI2C(); //close I2C module
PORTD = 0b01010101;
while (1); //End of program
}
void delay_sec(unsigned char sec) {
unsigned char counter1 = 0;
unsigned char counter2 = 0;
unsigned char counter3 = 0;
unsigned char counter4 = 0;
for (counter1 = 0; counter1 < sec; counter1++) {
for (counter2 = 0; counter2 < 50; counter2++) {
for (counter3 = 0; counter3 < 100; counter3++) {
for (counter4 = 0; counter4 < 100; counter4++) {
Nop();
// (5 * 2) * 100 * 100 * 100
}
}
}
}
}
void leds_test(void) {
PORTD = 0b11111111;
delay_sec(1);
PORTD = 0b00000001;
delay_sec(1);
PORTD = 0b00000010;
delay_sec(1);
PORTD = 0b00000100;
delay_sec(1);
PORTD = 0b00001000;
delay_sec(1);
PORTD = 0b00010000;
delay_sec(1);
PORTD = 0b00100000;
delay_sec(1);
PORTD = 0b01000000;
delay_sec(1);
PORTD = 0b10000000;
delay_sec(1);
}
I2C: successful run with two PIC18F4550's
As you can see both master and slave show there 'successful'-state. So the very basic I2C communication succeeded. Unfortunately: when I checked the compared the data read from slave with the actual data, it is only equal for the first byte. Writing multiple bytes from master to slave works fine, but reading from slave only works for the first byte. Bummer!