So, I am all ready to build my next project. No new Logomatics are for sale yet, but that’s ok, I can pull mine out of my old project, as I haven’t destroyed it yet. Go to Sparkfun.com, order a nice GPS and three I2C sensor boards, get them home. I know I haven’t done I2C yet, but that’s only because I haven’t gotten any such parts, and now I have.
So, I hunt all over the board, through the schematics, through the LPC user guide, where is that darn SDA pin? Oh, there it is, hooked to the ‘stop’ button. The SCL pin is hooked to the green light. Drat. Here I was, thinking that SPI and I2C used the same pins.
So, it’s back to analog for me, right? Wrong!
Bit banging to the rescue! I’ll just make those pins work for me. It turns out that I2C is such a simple and well-thought-out protocol, that the master implementation is easy. Go ahead and hook this code into anything you want. The high-level functions are:
*i2c_setup, where you pick which pins and what baud rate you want
*i2c_stop, where you release the bus when you aren’t using it
*i2c_tx_string, where you can send a certain number of bytes to a particular slave
*i2c_rx_string, where you can request a certain number of bytes from a particular slave
The documentation is incomplete, so Use the Source, Luke. It is also good to search all over the net for documentation on I2C stuff. I don’t think this will make it into the generic Logomatic Kwan, as it is too hard to generalize
//i2c.h - header file for Logomatic bit-banging I2C protocol
#ifndef i2c_h
#define i2c_h
//A bit-banging implementation of the master part of the I2C two-wire protocol using any
//two general purpose I/O pins in P0.XX. Intended for the Sparkfun Logomatic since they
//wired the actual I2C port to the lights and buttons :(
//I2C timing: The bus frequency is more of what you'd call "guidelines" than actual rules.
//It represents a maximum rate, and in this implementation, is implemented by a busy wait.
//The wait is not compensated for overhead while actually doing the I2C bitbang, so it never
//really goes as fast as you ask. Also since this is the master, if it gets delayed by an
//interrupt, no biggie, since the slave has to follow the clock. This implementation
//respects clock stretches by slaves
//Use pin P0.17 (Pin 47, SCK) for SCL and pin P0.18 (Pin 53, MISO) for SDA on a logomatic
//Setup a bit-bang I2C port using two digital I/O pins
//input
// PCL - Pin P0.XX number to use as SCL clock line
// PDA - Pin P0.XX number to use as SDA data line
// freq - Approximate bus frequency in Hz
//return
// 0 is success
// some other number for failure
int i2c_setup(int PCL, int PDA, int freq);
//Release the bit-bang I2C port, setting the used pins to hi-Z
//return
// 0 is success
// some other number for failure
int i2c_stop(void);
//Transmit a string on the I2C port
//input
// addr - slave address to send to
// buf - pointer to byte string to send
// len - number of bytes to send
//return
// 0 is success
// some other number for failure
int i2c_tx_string(unsigned char addr, char* buf, int len);
//Receive a string on the I2C port
//input
// addr - slave address to receive from
// buf - pointer to byte buffer to receive
// len - number of bytes to receive
//return
// 0 is success
// some other number for failure
int i2c_rx_string(unsigned char addr, char* buf, int len);
#endif
//i2c.c - C source code for Logomatic bit-banging I2C protocol
#include <stdlib.h>
#include "LPC214x.h"
#include "i2c.h"
#include "setup.h"
//Was a start condition sent most recently? Used to do double-start
static unsigned char start = 0;
//==== Lowest level functions to manage the pins -- All LPC-specific code is here
//Number of times around the delay loop to go
static int I2CSPEED;
//SDA pin constants
static unsigned int sda; //P0.XX number of SDA pin
//SCL pin constants
static unsigned int scl; //P0.XX number of SCL pin
//Set SDA line to read and return the value on it.
//Since the line has a pull-up on it, this pulls the
//SDA line high if no one else is driving it
static unsigned char READSDA(void) {
IODIR0&=~(1<<sda);
return (IOPIN0>>sda) & 1;
}
//Set SDA line to write and pull it low
static void CLRSDA(void) {
IODIR0|=(1<<sda);
IOCLR0=(1<<sda);
}
//Set SCL line to read and return the value on it.
//Since the line has a pull-up on it, this pulls the
//SCL line high if no one else is driving it
static unsigned char READSCL(void) {
IODIR0&=~(1<<scl);
return (IOPIN0>>scl) & 1;
}
//Set SCL line to write and pull it low
static void CLRSCL(void) {
IODIR0|=(1<<scl);
IOCLR0=(1<<scl);
}
static void I2CDELAY(int clock_period) {
for(int i=0;i<clock_period;i++) asm volatile ("nop");
}
static void ARBITRATION_LOST(void) {
//Can't happen, only slaves on the bus
}
//==== Set up and tear down - Public interface, but LPC-specific code
int i2c_setup(int PSCL, int PSDA, int freq) {
scl=PSCL;
sda=PSDA;
//clear the pin select bits for the chosen pins, changing them to GPIO
if(PSCL<16) {
unsigned int pinmask=3<<(PSCL*2);
PINSEL0&=(~pinmask);
} else {
unsigned int pinmask=3<<((PSCL-16)*2);
PINSEL1&=(~pinmask);
}
if(PSDA<16) {
unsigned int pinmask=3<<(PSDA*2);
PINSEL0&=(~pinmask);
} else {
unsigned int pinmask=3<<((PSDA-16)*2);
PINSEL1&=(~pinmask);
}
//Say 10 cycles per empty for loop, so it runs at 6MHz
//To get the wait to run at 400kHz, loop 6M/400k=15 times
I2CSPEED=(CCLK/10)/freq;
return 0;
}
int i2c_stop() {
READSDA();
READSCL();
sda=-1;
scl=-1;
I2CSPEED=-1;
return 0;
}
//==== Bit-level functions
//Translated from Wikipedia pseudocode - http://en.wikipedia.org/wiki/I2C
static unsigned char read_bit(void) {
unsigned char bit;
// Let the slave drive data
READSDA();
I2CDELAY(I2CSPEED/2);
/* Clock stretching */
while (READSCL() == 0);
/* SCL is high, now data is valid */
bit = READSDA();
I2CDELAY(I2CSPEED/2);
CLRSCL();
return bit;
}
static void write_bit(unsigned char bit) {
//Put the bit on SDA by either letting it rise or pulling it down
if (bit) {
READSDA();
} else {
CLRSDA();
}
I2CDELAY(I2CSPEED/2);
/* Clock stretching - Let SCL rise and wait for slave to let it rise */
while (READSCL() == 0);
/* SCL is high, now data is being read */
/* If SDA is high, check that nobody else is driving SDA */
if (bit) {
if (READSDA() == 0) { //Oops, someone else pulled SDA down
ARBITRATION_LOST();
}
}
I2CDELAY(I2CSPEED/2);
CLRSCL();
}
static void start_cond(void) {
if (start) {
/* Let SDA rise */
READSDA();
I2CDELAY(I2CSPEED/2);
/* Clock stretching */
while (READSCL() == 0);
}
if (READSDA() == 0) {
ARBITRATION_LOST();
}
/* SCL is high, we waited for slave to raise it, so pull SDA down */
CLRSDA();
I2CDELAY(I2CSPEED/2);
// Now pull SCL down
CLRSCL();
start = 1;
}
static void stop_cond(void) {
/* Pull SDA down */
CLRSDA();
I2CDELAY(I2CSPEED/2);
/* Clock stretching - wait for slave to raise it*/
while (READSCL() == 0);
/* SCL is high, set SDA from 0 to 1 */
if (READSDA() == 0) {
ARBITRATION_LOST();
}
I2CDELAY(I2CSPEED/2);
start = 0;
}
//==== Byte-level protocol
//Translated from Wikipedia pseudocode - http://en.wikipedia.org/wiki/I2C
static unsigned char tx(int send_start, int send_stop, unsigned char byte) {
unsigned char bit;
unsigned char nack;
if (send_start) {
start_cond();
}
for (bit = 0; bit < 8; bit++) {
write_bit(byte & 0x80);
byte <<= 1;
}
nack = read_bit();
if (send_stop) {
stop_cond();
}
return nack;
}
static unsigned char rx (int nak, int send_stop) {
unsigned char byte = 0;
unsigned char bit;
for(bit = 0; bit < 8; bit++) {
byte <<= 1;
byte |= read_bit();
}
write_bit(nak);
if (send_stop) {
stop_cond();
}
return byte;
}
//==== Multi-byte protocol - Public interface to I2C bus
int i2c_tx_string(unsigned char addr, char* buf, int len) {
tx(1,0,addr << 1 | 0);
for(int i=0;i<len-1;i++) tx(0,0,buf[i]);
tx(0,1,buf[len-1]);
return 0;
}
int i2c_rx_string(unsigned char addr, char* buf, int len) {
tx(1,0,addr << 1 | 1);
for(int i=0;i<len-1;i++) buf[i]=rx(0,0);
buf[len-1]=rx(1,1);
return 0;
}
//Use the I2C bus to read an HMC5843 three-axis compass
//Compass command string and space for compass response
static char compassBuf[255]="\x02\x00";
#define COMPASS_ADDR 0x1E
...
//Set up the I2C bus using P0.17 (SCK) as SCL and P0.18 (MISO) as SDA, 100kHz
i2c_setup(17,18,100000);
...
//Write two bytes to the compass, commanding it into continuous readout mode
i2c_tx_string(COMPASS_ADDR,compassBuf,2);
//Read the compass
i2c_rx_string(COMPASS_ADDR,compassBuf+2,6);
...