Improvements to Olieman's MODI2C library. Supports calls from IRQ.
Dependents: FreeIMU FreeIMU_external_magnetometer FreeIMU
Fork of MODI2C by
Revision 0:ff579e7e8efa, committed 2012-06-30
- Comitter:
- Sissors
- Date:
- Sat Jun 30 14:55:14 2012 +0000
- Child:
- 1:eed116eb680a
- Commit message:
- v1.0
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MODI2C.cpp Sat Jun 30 14:55:14 2012 +0000 @@ -0,0 +1,214 @@ +#include "MODI2C.h" + +MODI2C::I2CBuffer MODI2C::Buffer1 = {0,0}; //Sets the initial buffer empty and count on zero +MODI2C::I2CBuffer MODI2C::Buffer2 = {0,0}; //Sets the initial buffer empty +//int MODI2C::status=0; +int MODI2C::defaultStatus=0; + + + + +MODI2C::MODI2C(PinName sda, PinName scl) : led(LED3) { + //Check which connection we are using, if not correct, go to error status + if ((sda==p9) && (scl==p10)) + I2CMODULE = LPC_I2C1; + else if ((sda==p28) && (scl==p27)) + I2CMODULE = LPC_I2C2; + else + error("MODI2C pins not valid"); + + //Default settings: + frequency(100000); + + writePinState(); +} + +int MODI2C::write(int address, char *data, int length, bool repeated, int *status) { + + I2CData Data; + //Store relevant information + address &= 0xFE; + Data.caller = this; + Data.address = address; + Data.repeated = repeated; + Data.data = data; + Data.length = length; + Data.status = status; + + while(!addBuffer(Data, I2CMODULE)); + + return 0; +} + +int MODI2C::write(int address, char *data, int length, int *status) { + return write(address, data, length, false, status); + } + +int MODI2C::read_nb(int address, char *data, int length, bool repeated, int *status) { + //Store relevant information + address |= 0x01; + + //isIdle check here + I2CData Data; + + + Data.caller = this; + Data.address = address; + Data.repeated = repeated; + Data.data = data; + Data.length = length; + Data.status = status; + + while(!addBuffer(Data, I2CMODULE)); + + return 0; +} + +int MODI2C::read_nb(int address, char *data, int length, int *status) { + return read_nb(address, data, length, false, status); + } + +int MODI2C::read(int address, char *data, int length, bool repeated) { + int stat; + //Store relevant information + address |= 0x01; + + //isIdle check here + I2CData Data; + + + Data.caller = this; + Data.address = address; + Data.repeated = repeated; + Data.data = data; + Data.length = length; + Data.status = &stat; + + while(!addBuffer(Data, I2CMODULE)); + + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer = &Buffer2; + } + + while(Buffer->queue!=0) + wait_us(1); + + if (stat==0x58) //Return zero if ended correctly, otherwise return return code. + return 0; + else + return stat; +} + + +void MODI2C::start( void ) { + _start(I2CMODULE); +} + +void MODI2C::stop( void ) { + _stop(I2CMODULE); +} + +void MODI2C::frequency(int hz) { + //The I2C clock by default runs on quarter of system clock, which is 96MHz + //So to calculate high/low count times, we do 96MHz/4/2/frequency + duty = 96000000/8/hz; + if (duty>65535) + duty=65535; + if (duty<4) + duty=4; +} + +int MODI2C::getQueue( void ) { + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer = &Buffer2; + } + return Buffer->queue; + } + + + +//******************************************* +//***********Internal functions************** +//******************************************* + + +void MODI2C::writeSettings( void ) { + I2CMODULE->I2CONSET = 1<<I2C_ENABLE; //Enable I2C + I2CMODULE->I2CONCLR = I2C_STOP; + I2CMODULE->MMCTRL = 0; //Disable monitor mode + I2CMODULE->I2SCLH = duty; + I2CMODULE->I2SCLL = duty; + +} + +void MODI2C::writePinState( void ) { + if (I2CMODULE == LPC_I2C1) { + LPC_PINCON->PINSEL0 |= 0x0000000F; //Sets pins as I2C + LPC_PINCON->PINMODE0 |= 0x0000000A; //Neither pull up nor pull down + LPC_PINCON->PINMODE_OD0 |= 0x00000003; //Open drain mode enabled + } else if (I2CMODULE == LPC_I2C2) { + LPC_PINCON->PINSEL0 |= (1<<21)|(1<<23); //Same story, different register settings + LPC_PINCON->PINMODE0 |= (1<<21)|(1<<23); + LPC_PINCON->PINMODE_OD0 |= (1<<10)|(1<<11); + } +} + + + +void MODI2C::attach( void (*function)(void), int operation) { + IRQOp = operation; + callback.attach(function); +} + +template<typename T> +void MODI2C::attach(T *object, void (T::*member)(void), int operation) { + IRQOp = operation; + callback.attach(object, member); +} + +void MODI2C::detach( void ) { + callback.attach(NULL); +} + +void MODI2C::_start(LPC_I2C_TypeDef *I2CMODULE) { + if (!(I2CMODULE->I2CONSET & 1<<I2C_START)) //If already sent, skip + I2CMODULE->I2CONSET = 1<<I2C_START; //Send start condition +} + +void MODI2C::_stop(LPC_I2C_TypeDef *I2CMODULE) { + I2CMODULE->I2CONSET = 1<<I2C_STOP; //Send stop condition + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; +} + +//Set interrupt vector +void MODI2C::setISR(void) { + _setISR(I2CMODULE); +} + +void MODI2C::_setISR(LPC_I2C_TypeDef *I2CMODULE) { + if (I2CMODULE == LPC_I2C1) { + NVIC_SetVector(I2C1_IRQn, (uint32_t)&IRQ1Handler); + NVIC_EnableIRQ(I2C1_IRQn); + } else if (I2CMODULE == LPC_I2C2) { + NVIC_SetVector(I2C2_IRQn, (uint32_t)&IRQ2Handler); + NVIC_EnableIRQ(I2C2_IRQn); + } +} + +void MODI2C::clearISR( void ) { + _clearISR(I2CMODULE); +} + +void MODI2C::_clearISR( LPC_I2C_TypeDef *I2CMODULE ) { + if (I2CMODULE == LPC_I2C1) { + NVIC_DisableIRQ(I2C1_IRQn); + } else if (I2CMODULE == LPC_I2C2) { + NVIC_DisableIRQ(I2C2_IRQn); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MODI2C.h Sat Jun 30 14:55:14 2012 +0000 @@ -0,0 +1,248 @@ +/* + + . . + / `. .' \ + .---. < > < > .---. + | \ \ - ~ ~ - / / | + ~-..-~ ~-..-~ + \~~~\.' `./~~~/ + \__/ \__/ + / .- . \ + _._ _.- .-~ ~-. / } \/~~~/ + _.-'q }~ / } { ; \__/ + {'__, / ( / { / `. ,~~| . . + `''''='~~-.__( /_ | /- _ `..-' \\ // + / \ =/ ~~--~~{ ./| ~-. `-..__\\_//_.-' + { \ +\ \ =\ ( ~ - . _ _ _..---~ + | | { } \ \_\ + '---.o___,' .o___,' "Stegosaurus" + + +Face it, dinos much cooler than copyright notices.*/ + +#include "mbed.h" + + +#ifndef MODI2C_H +#define MODI2C_H + + +#define I2C_ENABLE 6 +#define I2C_START 5 +#define I2C_STOP 4 +#define I2C_FLAG 3 +#define I2C_ASSERT_ACK 2 + +#define IRQ_I2C_BOTH 0 +#define IRQ_I2C_READ 1 +#define IRQ_I2C_WRITE 2 + + + +#ifndef I2C_BUFFER +#define I2C_BUFFER 10 +#endif + + +/** Library that allows interrupt driven communication with I2C devices + * + * For now this is all in beta, so if you got weird results while the mbed I2C library works, it is probably my fault. + * Similar to googles definition of beta, it is unlikely it will ever come out of beta. + * + * Example: + * @code + * #include "mbed.h" + * #include "MODI2C.h" + * #include "MPU6050.h" + * + * Serial pc(USBTX, USBRX); // tx, rx + * MODI2C mod(p9, p10); + * + * int main() { + * char registerAdd = MPU6050_WHO_AM_I_REG; + * char registerResult; + * int status; + * + * while (1) { + * mod.write(MPU6050_ADDRESS*2, ®isterAdd, 1, true); + * mod.read_nb(MPU6050_ADDRESS*2, ®isterResult, 1, &status); + * + * while (!status) wait_us(1); + * pc.printf("Register holds 0x%02X\n\r", registerResult); + * wait(2); + * } + * } + * @endcode + */ +class MODI2C { +public: + /** + * Constructor. + * + * @param sda - mbed pin to use for the SDA I2C line. + * @param scl - mbed pin to use for the SCL I2C line. + */ + MODI2C(PinName sda, PinName scl); + + /** + * Write data on the I2C bus. + * + * This function should generally be a drop-in replacement for the standard mbed write function. + * However this function is completely non-blocking, so it barely takes time. This can cause different behavior. + * Everything else is similar to read_nb. + * + * @param address - I2C address of the slave (7 bit address << 1). + * @param data - pointer to byte array that holds the data + * @param length - amount of bytes that need to be sent + * @param repeated - determines if it should end with a stop condition (default false) + * @param status - (optional) pointer to integer where the final status code of the I2C transmission is placed. (0x28 is success) + * @param return - returns zero + */ + int write(int address, char *data, int length, bool repeated = false, int *status = NULL); + int write(int address, char *data, int length, int *status); + + /** + * Read data non-blocking from the I2C bus. + * + * Reads data from the I2C bus, completely non-blocking. Aditionally it will always return zero, since it does + * not wait to see how the transfer goes. A pointer to an integer can be added as argument which returns the status. + * + * @param address - I2C address of the slave (7 bit address << 1). + * @param data - pointer to byte array where the data will be stored + * @param length - amount of bytes that need to be received + * @param repeated - determines if it should end with a stop condition + * @param status - (optional) pointer to integer where the final status code of the I2C transmission is placed. (0x58 is success) + * @param return - returns zero + */ + int read_nb(int address, char *data, int length, bool repeated = false, int *status=NULL); + int read_nb(int address, char *data, int length, int *status); + + /** + * Read data from the I2C bus. + * + * This function should should be a drop-in replacement for the standard mbed function. + * + * @param address - I2C address of the slave (7 bit address << 1). + * @param data - pointer to byte array where the data will be stored + * @param length - amount of bytes that need to be received + * @param repeated - determines if it should end with a stop condition + * @param return - returns zero on success, LPC status code on failure + */ + int read(int address, char *data, int length, bool repeated = false); + + /** + * Sets the I2C bus frequency + * + * @param hz - the bus frequency in herz + */ + void frequency(int hz); + + /** + * Creates a start condition on the I2C bus + * + * If you use this function you probably break something (but mbed also had it public) + */ + void start ( void ); + + /** + * Creates a stop condition on the I2C bus + * + * If you use this function you probably break something (but mbed also had it public) + */ + void stop ( void ); + + /** + * Removes attached function + */ + void detach( void ); + + /** + * Calls user function when I2C command is finished + * + * @param function - the function to call. + * @param operation - when to call IRQ: IRQ_I2C_BOTH (default) - IRQ_I2C_READ - IRQ_I2C_WRITE + */ + void attach(void (*function)(void), int operation = IRQ_I2C_BOTH); + + /** + * Calls user function when I2C command is finished + * + * @param object - the object to call the function on. + * @param member - the function to call + * @param operation - when to call IRQ: IRQ_I2C_BOTH (default) - IRQ_I2C_READ - IRQ_I2C_WRITE + */ + template<typename T> + void attach(T *object, void (T::*member)(void), int operation = IRQ_I2C_BOTH); + + /** + * Returns the current number of commands in the queue (including one currently being processed) + * + * Note that this is the number of commands, not the number of bytes + * + * @param return - number of commands in queue + */ + int getQueue( void ); + + +private: + + struct I2CData { + MODI2C *caller; + char address; + char *data; + int length; + bool repeated; + int *status; + }; + + struct I2CBuffer { + int queue; + int count; + I2CData Data[I2C_BUFFER]; + }; + + //Settings: + int duty; + + FunctionPointer callback; + int IRQOp; + void runUserIRQ( I2CData Data ); + + //Remove later: + LPC_I2C_TypeDef *I2CMODULE; + + + + //Whole bunch of static stuff, pretty much everything that is ever called from ISR + static I2CBuffer Buffer1; + static I2CBuffer Buffer2; + + static void IRQHandler(I2CBuffer *Buffer, LPC_I2C_TypeDef *I2CMODULE); + static void IRQ1Handler(void); + static void IRQ2Handler(void); + + static void bufferHandler(LPC_I2C_TypeDef *I2CMODULE); + static bool addBuffer(I2CData Data, LPC_I2C_TypeDef *I2CMODULE); + static bool removeBuffer(LPC_I2C_TypeDef *I2CMODULE); + static void startBuffer(LPC_I2C_TypeDef *I2CMODULE); + + static int defaultStatus; + + static void _start(LPC_I2C_TypeDef *I2CMODULE); + static void _stop(LPC_I2C_TypeDef *I2CMODULE); + static void _clearISR( LPC_I2C_TypeDef *I2CMODULE ); + static void _setISR( LPC_I2C_TypeDef *I2CMODULE ); + + + void writeSettings( void ); + void writePinState( void ); + void setISR( void ); + void clearISR( void ); + + + DigitalOut led; + +}; + + +#endif \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MODI2C_IRQ.cpp Sat Jun 30 14:55:14 2012 +0000 @@ -0,0 +1,228 @@ +#include "MODI2C.h" + +//********************************************************** +//*********************IRQ FUNCTIONS************************ +//********************************************************** + + +void MODI2C::runUserIRQ(I2CData Data) { + + if (IRQOp==IRQ_I2C_BOTH) //Always call if both + callback.call(); + if ((IRQOp==IRQ_I2C_READ)&&(Data.address&0x01)) //Call if read and byte was read + callback.call(); + if ((IRQOp==IRQ_I2C_WRITE)&&(!(Data.address&0x01))) //Call if write and byte was written + callback.call(); +} + +void MODI2C::IRQ1Handler( void ) { + IRQHandler(&Buffer1, LPC_I2C1); +} + +void MODI2C::IRQ2Handler( void ) { + IRQHandler(&Buffer2, LPC_I2C2); +} + +void MODI2C::IRQHandler( I2CBuffer *Buffer, LPC_I2C_TypeDef *I2CMODULE) { + I2CData *Data = &Buffer->Data[0]; + + //Depending on the status register it determines next action, see datasheet + //This is also pretty much copy pasting the datasheet + //General options + switch (I2CMODULE->I2STAT) { + case(0x08): + case(0x10): + //Send Address + I2CMODULE->I2DAT = Data->address; + I2CMODULE->I2CONCLR = 1<<I2C_FLAG | 1<<I2C_START; + break; + + //All master TX options + + //Address + W has been sent, ACK received + //Data has been sent, ACK received + case(0x18): + case(0x28): + if (Buffer->count==Data->length) { + *Data->status=I2CMODULE->I2STAT; + if (!Data->repeated) + _stop(I2CMODULE); + else { + I2CMODULE->I2CONSET = 1<<I2C_START; + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; + } + bufferHandler(I2CMODULE); + } else { + I2CMODULE->I2DAT = Data->data[Buffer->count]; + I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK; //I dont see why I have to enable that bit, but datasheet says so + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; + Buffer->count++; + } + break; + + //Address + W has been sent, NACK received + //Data has been sent, NACK received + case(0x20): + case(0x30): + *Data->status=I2CMODULE->I2STAT; + _stop(I2CMODULE); + bufferHandler(I2CMODULE); + break; + + //Arbitration lost (situation looks pretty hopeless to me if you arrive here) + case(0x38): + _start(I2CMODULE); + break; + + + //All master RX options + + //Address + R has been sent, ACK received + case(0x40): + //If next byte is last one, NACK, otherwise ACK + if (Data->length <= Buffer->count + 1) + I2CMODULE->I2CONCLR = 1<<I2C_ASSERT_ACK; + else + I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK; + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; + break; + + //Address + R has been sent, NACK received + case(0x48): + *Data->status=I2CMODULE->I2STAT; + _stop(I2CMODULE); + bufferHandler(I2CMODULE); + break; + + //Data was received, ACK returned + case(0x50): + //Read data + Data->data[Buffer->count]=I2CMODULE->I2DAT; + Buffer->count++; + + //If next byte is last one, NACK, otherwise ACK + if (Data->length == Buffer->count + 1) + I2CMODULE->I2CONCLR = 1<<I2C_ASSERT_ACK; + else + I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK; + + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; + break; + + //Data was received, NACK returned (last byte) + case(0x58): + //Read data + *Data->status=I2CMODULE->I2STAT; + Data->data[Buffer->count]=I2CMODULE->I2DAT; + if (!Data->repeated) + _stop(I2CMODULE); + else { + I2CMODULE->I2CONSET = 1<<I2C_START; + I2CMODULE->I2CONCLR = 1<<I2C_FLAG; + } + bufferHandler(I2CMODULE); + break; + + default: + *Data->status=I2CMODULE->I2STAT; + bufferHandler(I2CMODULE); + break; + } +} + + +//********************************************************** +//*********************COMMAND BUFFER*********************** +//********************************************************** + +void MODI2C::bufferHandler(LPC_I2C_TypeDef *I2CMODULE) { + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer = &Buffer2; + } + + //Start user interrupt + Buffer->Data[0].caller->runUserIRQ(Buffer->Data[0]); + + removeBuffer(I2CMODULE); + + + + if (Buffer->queue!=0) + startBuffer(I2CMODULE); + else + _clearISR(I2CMODULE); +} + +//Returns true if succeeded, false if buffer is full +bool MODI2C::addBuffer(I2CData Data, LPC_I2C_TypeDef *I2CMODULE) { + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer = &Buffer2; + } + if (Buffer->queue<I2C_BUFFER) { + + if(Data.status == NULL) { + Data.status = &defaultStatus; + wait_us(1); //I blame the compiler that this is needed + } + *Data.status = 0; + + Buffer->Data[Buffer->queue]=Data; + Buffer->queue++; + + //If queue was empty, set I2C settings, start conversion + if (Buffer->queue==1) { + startBuffer(I2CMODULE); + } + + return true; + } else + return false; + +} + +//Returns true if buffer still has data, false if empty +bool MODI2C::removeBuffer(LPC_I2C_TypeDef *I2CMODULE) { + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer= &Buffer2; + } + + if (Buffer->queue>0) { + for (int i =0; i<Buffer->queue-1; i++) + Buffer->Data[i]=Buffer->Data[i+1]; + Buffer->queue--; + } + if (Buffer->queue>0) + return true; + else + return false; +} + +//Starts new conversion +void MODI2C::startBuffer(LPC_I2C_TypeDef *I2CMODULE) { + I2CBuffer *Buffer; + if (I2CMODULE == LPC_I2C1) { + Buffer = &Buffer1; + } else { + Buffer = &Buffer2; + } + + //Write settings + Buffer->Data[0].caller->writeSettings(); + Buffer->count=0; + + //Send Start + _start(I2CMODULE); + + //Start ISR (when buffer wasnt empty this wont do anything) + _setISR(I2CMODULE); + +} \ No newline at end of file