Hello, Im a beginner in electronics. I have experience with physics and programming. My mission, currently, is to design and implement software I2C protocol for general use. Im experimenting with GY-68 barometer with BMP180 sensor. My short term goal is to read a 'check' register from the device, which should always return 0x55. I have tried two different sets of functions i have found online, but none of them worked. I realised I could not get them to work because I had no real understanding of how I2C protocol worked at the lowest level. So I started reading and got somewhere. :) This is all of the code Im using. I expect to get 0x55, but instead I always get 0xFF.
Im not asking for code that works, I want full understanding of the protocol, because Im planning to use it alot in the future. Its late where I am now, so maybe Im not making much sence. :)
Also Im coding in MPLabX, using XC8 compiler and PIC16f628A chip.
I will answer any questions, Im really looking forward to help in solving this.
:-+
#include <xc.h>
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
// CONFIG
#pragma config FOSC = INTOSCCLK // Oscillator Selection bits (INTOSC oscillator: CLKOUT function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP = OFF // Low-Voltage Programming Enable bit (RB4/PGM pin has PGM function, low-voltage programming enabled)
#pragma config CPD = OFF // Data EE Memory Code Protection bit (Data memory code protection off)
#pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off)
#define LED1def TRISB1
#define LED1 RB1
#define LED2def TRISB2
#define LED2 RB2
#define SDA RB4
#define SDA_IN TRISB4
#define SCL RB5
#define SCL_IN TRISB5
#define _XTAL_FREQ 4000000
void delay_ms(int milliseconds)
{
while(milliseconds > 0)
{
__delay_ms(1);
milliseconds--;
}
}
void Start(void)
{
SDA = 1;
delay_ms(1);
SCL = 1;
delay_ms(1);
SDA = 0;
delay_ms(1);
SCL = 0;
delay_ms(1);
}
void SendWriteAddress(void)
{
unsigned char writeAddress = 0xEE;
for (char i = 0; i < 8; i++)
{
if (writeAddress & 0x80)
SDA = 1;
else
SDA = 0;
delay_ms(1);
SCL = 1;
delay_ms(1);
SCL = 0;
delay_ms(1);
writeAddress <<= 1;
}
}
void SendRegisterAddress(void)
{
unsigned char registerAddress = 0xD0;
for (char i = 0; i < 8; i++)
{
if (registerAddress & 0x80)
SDA = 1;
else
SDA = 0;
delay_ms(1);
SCL = 1;
delay_ms(1);
SCL = 0;
delay_ms(1);
registerAddress <<= 1;
}
}
void SendReadAddress(void)
{
unsigned char readAddress = 0xD0;
for (char i = 0; i < 8; i++)
{
if (readAddress & 0x80)
SDA = 1;
else
SDA = 0;
delay_ms(1);
SCL = 1;
delay_ms(1);
SCL = 0;
delay_ms(1);
readAddress <<= 1;
}
}
unsigned char RecieveByte(void)
{
unsigned char recievedData = 0x00;
char binaryData[8] = {0};
SDA = 0;
SDA_IN = 1;
delay_ms(10); //Just in case.
for (char i = 0; i < 8; i++)
{
SCL = 1;
while (!SCL)
{
delay_ms(1);
}
binaryData[i] = SDA;
delay_ms(1);
SCL = 0;
}
delay_ms(10);
SDA_IN = 0;
//Convert binary to hex:
for (int i = 0; i < 8; i++)
{
recievedData <<= 1;
if (binaryData[i] == 1)
recievedData |= 1;
}
return recievedData;
}
void Stop(void)
{
SCL = 1;
delay_ms(1);
SDA = 1;
}
void main(void)
{
LED1def = 0;
LED1 = 0;
LED2def = 0;
LED2 = 0;
SDA_IN = 0;
SCL_IN = 0;
SDA = 1;
SCL = 1;
delay_ms(10);
unsigned char testVariable = 0xCC;
Start();
SendWriteAddress();
SendRegisterAddress();
Start();
SendReadAddress();
testVariable = RecieveByte();
Stop();
while(1)
{
for (char i = 0; i < 8; i++)
{
if (testVariable & 0x80)
{
LED1 = 1;
delay_ms(100);
LED1 = 0;
delay_ms(900);
}
else
{
LED2 = 1;
delay_ms(100);
LED2 = 0;
delay_ms(900);
}
testVariable <<= 1;
}
delay_ms(2000);
}
}
Here's an example of how to use the I2C peripheral on a PIC. This was written for an PIC16F1822, some details (like available status bits) might be different with the chip you use, but the general concept should be the same. The function supports write-restart-read, which is what you seem to need. The crucial thing to note is that the PIC notices when you write SSP1BUF, and starts sending automatically. It tells you when it's done by setting status bits, so in the code I wait for those bits to be set using the various while loops. If busy-looping isn't your thing, you could also configure interrupts, so you can do other stuff while the chip is communicating. You should never use fixed delays in code like this (with a few exception, like when a datasheet tells you that you need to wait 10ms after startup, or 750ms after sending a "measure" command before reading the result), because that isn't fast, reliable nor maintainable.
void i2c_init() {
// Configure RA1/SCL and RA2/SDA as digital inputs
TRISAbits.TRISA1 = 1;
TRISAbits.TRISA2 = 1;
ANSELAbits.ANSA1 = 0;
ANSELAbits.ANSA2 = 0;
// configure MSSP module for I2C
SSP1STAT = 0b10000000; // disable slew rate control and SMBus compatibility
SSP1CON1 = 0b00111000; // enable and configure module for I2C
SSP1ADD = 0; // should result in 100kHz I2C clock with 500kHz oscillator
}
uint8_t i2c_writeRead(uint8_t address, uint8_t *wData, uint8_t wLen, uint8_t *rData, uint8_t rLen) {
if ((wLen | rLen) == 0) { return 1; }
uint8_t success = 0;
address <<= 1;
if (wLen) {
SSP1CON2bits.SEN = 1; // send start
while (SSP1STATbits.S);
SSP1BUF = address; // address and write bit
while (SSP1STATbits.BF);
if (SSP1CON2bits.ACKSTAT) { goto end; }
do {
SSP1BUF = *wData;
++wData; --wLen;
while (SSP1STATbits.BF);
if (SSP1CON2bits.ACKSTAT) { goto end; }
} while (wLen);
}
if (rLen) {
if (wData) {
SSP1CON2bits.RSEN = 1; // send restart
while (SSP1CON2bits.RSEN);
} else {
SSP1CON2bits.SEN = 1; // send start
while (SSP1STATbits.S);
}
SSP1BUF = address | 1; // address and read bit
while (SSP1STATbits.BF);
if (SSP1CON2bits.ACKSTAT) { goto end; }
do {
SSP1CON2bits.RCEN = 1; // receive
while (SSP1CON2bits.RCEN);
*rData++ = SSP1BUF;
--rLen;
// send ACK (or NACK for last byte)
if (rLen) { SSP1CON2bits.ACKDT = 0; }
else { SSP1CON2bits.ACKDT = 1; }
SSP1CON2bits.ACKEN = 1;
while (SSP1CON2bits.ACKEN);
} while (rLen);
}
success = 1;
end:
SSP1CON2bits.PEN = 1;
while (SSP1CON2bits.PEN);
return success;
}