implemented own I2C protocol

This commit is contained in:
King Kévin 2014-07-30 09:15:34 -07:00
parent 03dfdc0737
commit 791a7820c3
3 changed files with 116 additions and 150 deletions

View File

@ -1,159 +1,126 @@
/* software bit banging implementation #define __16f1847
* based on https://en.wikipedia.org/wiki/I2c example code #include <pic16f1847.h>
* #include <stdint.h>
* there is no external pull-up on SCL, and the internal one is too weak
* SCL must be driven
* but that is ok as we are the only master
* there is an external pull-up on SDA, and the internal one is too weak
* SDA goes to 3V instead of 5V due to the external pull-up
* but the 24LC256 still seems to work
*/
#include "I2C.h" #include "I2C.h"
// Hardware-specific support functions that MUST be customized: /* set clock high (default state) */
/* even when doing nothing, the max speed will be 100kHz, while the chip supports 400kHz */ #define release_SCL() LATB |= SCL
#define I2C_delay() /* set clock low */
#define hold_SCL() LATB &= ~SCL
/* set data high (default state) */
#define release_SDA() LATB |= SDA
/* set data low */
#define hold_SDA() LATB &= ~SDA
/* Set SCL as input and return current level of line, 0 or 1 */ /* a small delay for better clock */
bool read_SCL(void) #define WAIT 1
/*
void delay(void)
{ {
LATB |= SCL; /* set high (must be driven because no pull-up */ uint8_t wait=WAIT;
return 1; while (wait--);
}
*/
#define delay() __asm\
nop\
nop\
nop\
nop\
__endasm
/* read SDA
* don't forget to set SDA as input in the begining
* are set SDA as output in the end
*/
uint8_t read_SDA(void)
{
hold_SCL(); /* set clock to low for SDA to change */
delay(); /* wait for SDA to change */
delay();
delay();
release_SCL(); /* SDA should be valid when clock is high */
delay();
/* we don't verify the SCL state as only we can drive it */
if (PORTB&SDA) { /* read bit */
return 1;
} else {
return 0;
}
} }
/* Set SDA as input and return current level of line, 0 or 1 */ uint8_t read_ack(void)
bool read_SDA(void)
{ {
uint8_t ack = 0;
TRISB |= SDA; /* set as input */ TRISB |= SDA; /* set as input */
return (PORTB&SDA)!=0; ack = read_SDA();
TRISB &= ~SDA; /* set back to output */
hold_SCL();
return ack;
} }
/* Actively drive SCL signal low */ /* start bit is when SDA goes low while SCL is high */
void clear_SCL(void) void send_start(void)
{ {
LATB &= ~SCL; /* set low */ release_SCL();
delay();
release_SDA(); /* ensure SDA is high on the beginning */
delay();
hold_SDA();
} }
/* Actively drive SDA signal low */ void send_stop(void)
void clear_SDA(void)
{ {
TRISB &= ~SDA; /* set as output */ hold_SDA(); /* be sure SDA is low */
LATB &= ~SDA; /* set low */ release_SCL();
delay();
release_SDA(); /* set SDA high while SCL is high */
} }
void arbitration_lost(void) void send_1(void)
{ {
/* do nothing, since we are the only master */ hold_SCL(); /* value can change when SCL is low */
delay();
release_SDA(); /* set the right state */
delay();
release_SCL(); /* value will be read when SCL goes high */
} }
bool started = false; // global data
void i2c_start_cond(void) {
if (started) { // if started, do a restart cond
// set SDA to 1
read_SDA();
I2C_delay();
while (read_SCL() == 0) { // Clock stretching
// You should add timeout to this loop
}
// Repeated start setup time, minimum 4.7us
I2C_delay();
}
if (read_SDA() == 0) {
arbitration_lost();
}
// SCL is high, set SDA from 1 to 0.
clear_SDA();
I2C_delay();
clear_SCL();
started = true;
}
void i2c_stop_cond(void){
// set SDA to 0
clear_SDA();
I2C_delay();
// Clock stretching
while (read_SCL() == 0) {
// add timeout to this loop.
}
// Stop bit setup time, minimum 4us
I2C_delay();
// SCL is high, set SDA from 0 to 1
if (read_SDA() == 0) {
arbitration_lost();
}
I2C_delay();
started = false;
}
// Write a bit to I2C bus
void i2c_write_bit(bool bit) {
if (bit) {
read_SDA();
} else {
clear_SDA();
}
I2C_delay();
while (read_SCL() == 0) { // Clock stretching
// You should add timeout to this loop
}
// SCL is high, now data is valid
// If SDA is high, check that nobody else is driving SDA
if (bit && read_SDA() == 0) {
arbitration_lost();
}
I2C_delay();
clear_SCL();
}
// Read a bit from I2C bus
bool i2c_read_bit(void) {
bool bit;
// Let the slave drive data
read_SDA();
I2C_delay();
while (read_SCL() == 0) { // Clock stretching
// You should add timeout to this loop void send_0(void)
}
// SCL is high, now data is valid
bit = read_SDA();
I2C_delay();
clear_SCL();
return bit;
}
// Write a byte to I2C bus. Return 0 if ack by the slave.
bool i2c_write_byte(bool send_start, bool send_stop, unsigned char byte)
{ {
unsigned bit; hold_SCL(); /* value can change when SCL is low */
bool nack; delay();
if (send_start) { hold_SDA(); /* set the right state */
i2c_start_cond(); delay();
} release_SCL(); /* value will be read when SCL goes high */
}
/* send byte and get ack */
uint8_t send_byte(uint8_t byte)
{
uint8_t bit;
/* send every bit, MSb first */
for (bit = 0; bit < 8; bit++) { for (bit = 0; bit < 8; bit++) {
i2c_write_bit((byte & 0x80) != 0); if (byte&0x80) {
send_1();
} else {
send_0();
}
byte <<= 1; byte <<= 1;
} }
nack = i2c_read_bit(); return read_ack();
if (send_stop) {
i2c_stop_cond();
}
return nack;
}
// Read a byte from I2C bus
unsigned char i2c_read_byte(bool nack, bool send_stop)
{
unsigned char byte = 0;
unsigned bit;
for (bit = 0; bit < 8; bit++) {
byte = (byte << 1) | i2c_read_bit();
}
i2c_write_bit(nack);
if (send_stop) {
i2c_stop_cond();
}
return byte;
} }
uint8_t read_byte(void)
{
uint8_t byte = 0;
uint8_t bit;
TRISB |= SDA; /* set as input */
/* read 8 bits */
for (bit=0; bit<8; bit++) {
byte += read_SDA();
byte <<= 1;
}
TRISB &= ~SDA; /* set back to output */
send_0(); /* send ack */
return byte;
}

View File

@ -2,15 +2,11 @@
#include <pic16f1847.h> #include <pic16f1847.h>
#include <stdint.h> #include <stdint.h>
#define bool uint8_t
#define true 1
#define false 0
#define SCL _RB6 /* pin 12, external 24LC256 I2C EEPROM memory */ #define SCL _RB6 /* pin 12, external 24LC256 I2C EEPROM memory */
#define SDA _RB7 /* pin 13, external 24LC256 I2C EEPROM memory */ #define SDA _RB7 /* pin 13, external 24LC256 I2C EEPROM memory */
// Write a byte to I2C bus. Return 0 if ack by the slave. void send_start(void);
bool i2c_write_byte(bool send_start, bool send_stop, unsigned char byte); void send_stop(void);
uint8_t send_byte(uint8_t byte);
// Read a byte from I2C bus uint8_t read_byte(void);
unsigned char i2c_read_byte(bool nack, bool send_stop);

View File

@ -116,8 +116,8 @@ void init (void) {
* this PIC16F1847 offers hardware I2C, but not on those pins * this PIC16F1847 offers hardware I2C, but not on those pins
* so we also have to use software bit banging I2C * so we also have to use software bit banging I2C
*/ */
TRISB &= ~SCL; /* set as output (it must be driven because there is no pull-up */ TRISB &= ~(SCL|SDA); /* set as output (it must be driven because there is no pull-up */
LATB |= SCL; /* set clock as high (default pull-up state) */ LATB |= SCL|SDA; /* set as high (default pull-up state) */
PEIE = 1; /* enable peripheral interrupt (for timer 2) */ PEIE = 1; /* enable peripheral interrupt (for timer 2) */
GIE = 1; /* golablly enable interrupts */ GIE = 1; /* golablly enable interrupts */
@ -161,11 +161,14 @@ static void interrupt(void) __interrupt 0
if (switches&SWITCH1) { /* switch 1 changed */ if (switches&SWITCH1) { /* switch 1 changed */
if (PORTB&SWITCH1) { /* switch 1 released */ if (PORTB&SWITCH1) { /* switch 1 released */
led_off(); /* reset LED */ led_off(); /* reset LED */
i2c_write_byte(1,0,0xa0); send_start();
i2c_write_byte(0,1,0xa0); send_byte(0xa0);
// i2c_write_byte(0,1,0x17); send_byte(0x1c);
// i2c_write_byte(1,0,0xa1); send_byte(0x17);
// i2c_read_byte(0,0); send_start();
send_byte(0xa1);
read_byte();
send_stop();
} else { /* switch 1 pressed */ } else { /* switch 1 pressed */
led_on(); /* test LED */ led_on(); /* test LED */
} }