I couldn't (quickly) find some simple I2C code to demonstrate using the MSSP peripheral, so wrote some.
It's untested, so may still contain some bugs. I'd appreciate any comments to make it clearer, or confirmation that it works if anyone cares to try it!
- Code: Select all
// PIC16F877A Configuration Bit Settings
// 'C' source line config statements
// CONFIG
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#define _XTAL_FREQ 20000000
#define DS1307_ADDR 0xD0 //I2C slave address of DS1307 (8 bit format)
#define SECONDS_REG 0 //offset of the "seconds" register in the DS1307
void i2c_init(void)
{
TRISCbits.TRISC3 = 1; // SCL as input
TRISCbits.TRISC4 = 1; // SDA as input
SSPCONbits.SSPM = 0b1000; // Master mode using SSPADD as baud control
SSPADD = 49; //100kHz clock @ 20MHz Fosc
SSPCONbits.SSPEN = 1; //enable SSP
}
// Send an I2C START
// Return 0 if all ok, 1 if bus collision
__bit i2c_start(void)
{
BCLIF = 0; //Clear 'Bus collision" flag
SEN = 1; //initiate a START cycle
while (SEN); //wait until it has been sent
return BCLIF; //return value of BCLIF flag
}
// Send an I2C STOP
void i2c_stop(void)
{
PEN = 1; //initiate a STOP cycle
while (PEN); //wait until it has been sent
}
// Send an I2C REPEATED START
void i2c_restart(void)
{
RSEN = 1; //initiate a REPEATED START cycle
while (RSEN); //wait until it has been sent
}
//Send one byte. Return 0 if ACK received, or 1 if NAK received
__bit i2c_sendbyte(unsigned char dat)
{
SSPBUF = dat;
while (R_W); //wait until byte sent and ACK/NAK received
return ACKSTAT;
}
//Receive one byte. ackflag=0 to send ACK, or 1 to send NAK in reply
unsigned char i2c_recvbyte(unsigned char ackflag)
{
RCEN = 1; // initiate a RECEIVE cycle
ACKDT = ackflag; //specify if we should send ACK or NAK after receiving
while (RCEN); //wait until RECEIVE has completed
ACKEN = 1; //initiate an ACK cycle
while (ACKEN); //wait until it has completed
return SSPBUF;
}
//Send an array of data to an I2C device.
//Return 0 if all OK, 1 if bus error, 2 if slave address NAK, 3 if slave register NAK, 4 if slave data NAK
unsigned char i2c_writeblock(unsigned char slave_address, unsigned char start_reg, unsigned char buflen, const unsigned char * bufptr)
{
if (i2c_start() ) //send a start, and check if it succeeded
return 1; //abort if bus collision
//send the I2C slave address (force R/W bit low)
if (i2c_sendbyte(slave_address & 0xFE))
{
i2c_stop(); //if address was NAKed, terminate the cycle
return 2; //and return error code
}
//send the device register index
if (i2c_sendbyte(start_reg))
{
i2c_stop(); //if register was NAKed, terminate the cycle
return 3; //and return error code
}
//send the data. buflen might be zero!
for (; buflen>0; --buflen)
{
if (i2c_sendbyte(*bufptr++))
{
i2c_stop(); //if register was NAKed, terminate the cycle
return 4; //and return error code
}
}
i2c_stop();
return 0; //no error
}
//Receive an array of data from an I2C device.
//Return 0 if all OK, 1 if bus error, 2 if slave address NAK, 3 if slave register NAK
unsigned char i2c_readblock(unsigned char slave_address, unsigned char start_reg, unsigned char buflen, unsigned char * bufptr)
{
//do a dummy zero length write cycle to set the register address
unsigned char retval = i2c_writeblock(slave_address, start_reg,0,0);
if (retval)
{
return retval; //abort if there was an error
}
//now start the READ cycle
if (i2c_start() ) //send a start, and check if it succeeded
return 1; //abort if bus collision
//send the I2C slave address (force the R/W bit high)
if (i2c_sendbyte(slave_address | 0x01))
{
i2c_stop(); //if address was NAKed, terminate the cycle
return 2; //and return error code
}
//receive the data.
for (; buflen>0; --buflen)
{
unsigned char ackflag = (buflen == 1); //1 if this is the last byte to receive => send NAK
*bufptr++ = i2c_recvbyte(ackflag);
}
i2c_stop();
return 0; //no error
}
const unsigned char rtc_data[] =
{
0x56, //56 seconds
0x34, //34 minutes
0x12, //12 hours & 24 hour mode
0x01, //01 day=Sunday
0x03, //03 date
0x12, //12 month=dec
0x01, //01 year=2001
};
unsigned char rd_buf[7];
void main(void) {
i2c_init();
//write some dummy fixed data to the RTC chip
if (i2c_writeblock(DS1307_ADDR, SECONDS_REG, sizeof(rtc_data), rtc_data) )
{
// here if failed
} else
{
// here if succeeded
}
//read data back from the RTC chip
if (i2c_readblock(DS1307_ADDR, SECONDS_REG, sizeof(rd_buf), rd_buf) )
{
// here if failed
} else
{
// here if succeeded
}
while(1); //endless loop to avoid exiting main() function)
}
(n.b. I would strongly recommend a newer PIC, and a newer RTC chip if anyone was doing a new project!)