Interrupt based I2C functionality for the LPC1768. (If you want the other one, send a PM and I might make it). Also adds buffer functionality to the I2C bus.

Dependents:   Algoritmo_Fuzzy FlyBed1 FlyBedLight

MODI2C

MODI2C is an interrupt based (master) I2C library for the LPC1768 (if there is interest in using it for the other mbed variant I can look at it, but since I cannot compile for it there is no way I can test anything). It provided basic functionality that is similar to MODSERIAL, but it is not in any way connected and I am not in any way connected to the author of MODSERIAL, I am just very bad at making original names.

The default mbed I2C library works fine, but it is completely blocking. Since I2C access is fairly slow (100kbit/s in normal mode, 400kbit/s in fast mode), it can severely limit performance when a significant amount of I2C data needs to be received. For example in the case of a 9DOF IMU you will generally need to send three times a device address + register address, send device address again, and receive 6 bytes. Add overhead and you are at roughly 250 bits that need to be sent/received. In normal mode that would take 2.5ms, an mbed can do alot in that time.

MODI2C library offers interrupt based and buffered sending/receiving of I2C messages. Because I2C is not asynchronous, it cannot receive messages without a specific command to do so, unlike MODSERIAL. Instead of that, you have to tell it to receive a certain number of bytes, and when it is finished it will report back. When transmitting it will simply put the message in the buffer.

Usage

In most cases you can directly replace the standard I2C library with this one, although you will then not be able to use the advantages of interrupts and its buffer.

Write

int write(int address, char *data, int length, bool repeated = false, int *status = NULL);

The write statement is almost the same as the default write statements. There are however a few differences that need to be taken into account. The write staement is completely non-blocking. So when you want to write a byte it will not know if it receives an ACK or a NACK. The write command will always return zero, to make it compatible with standard mbed library, but this means it cannot be used to check if there is a device on a certain address. Easiest to do that is by using the read statement instead.

Aditionally there is an optional 5th parameter (the repeated parameter stays optional, you can use the 5th parameter without having supplied a repeated value). This tracks the current status of the write action. It stays zero until finished, when finished it will report back the status from the LPC1768's status register. This allows for example to see if an ACK has been received (0x28), but it is also an easy way to check if the command is finished.

Read

int read(int address, char *data, int length, bool repeated = false);

One on one replacement for the standard mbed command. It is also blocking, and returns zero on success, otherwise it will return the LPC's status code. So this can be used to check if there is a device at a specific address.

Read_nb

int read_nb(int address, char *data, int length, bool repeated = false, int *status=NULL);

This is the non-blocking read variant, and here is where it gets interesting. It works pretty much exactly the same as the write command, however in the case of the read command you usually want to know if the data has been updated. The status point can be used for this, the default way would be:

mod.write(MPU6050_ADDRESS*2, &registerAdd, 1, true);
mod.read_nb(MPU6050_ADDRESS*2, &registerResult, 1, &status);

//Do other calculations.

//Check if the read command has finished.
while (!status) wait_us(1);
pc.printf("Register holds 0x%02X\n\r", registerResult);

Without the wait_us(1) after the while it has the nasty tendency to freeze, so it is required.

Interrupts

An interrupt handler can be attached the same way as in for example the default Ticker library.

void attach(void (*function)(void), int operation = IRQ_I2C_BOTH);

template<typename T>
    void attach(T *object, void (T::*member)(void), int operation = IRQ_I2C_BOTH);

Operation specifies when to call the interrupt. By default it will do it always when an I2C operation is finished, but you can also supply IRQ_I2C_READ and IRQ_I2C_WRITE to only let it happen when read or write actions are completed.

Buffer

Both I2C periferals have a seperate buffer with as default size 10 commands (a read command that requests 100 bytes is still one command). This will generally be sufficient for I2C, but you can easily change it at compile time by defining something else.

#define I2C_BUFFER          50

When a new command is added while the buffer is full, it will block until there is again space in the buffer. The getQueue() command returns the current number of commands in the buffer. Take into account the buffer is shared between all MODI2C objects that use that periferal. So if you have two objects using both I2C busses they both will have a buffer of 10 commands, but if you have 100 MODI2C objects on a single I2C bus, they will have to share their buffer.

Performance

Compared to the default I2C library it is *slightly* faster at 100kbit/s, and *slightly* slower at 400kbit/s, but these differences are completely negligible. While testing I could access a fast mode devices (400kbit/s) at up to 1mbit/s with both the default I2C library and MODI2C. For both the overhead however becomes significant, MODI2C has slightly more overhead at these rates, but still it is not alot and the majority of its time it can still run user content.

During normal mode I2C transfers there is an overhead of roughly 6% by MODI2C, so 94% of the time it can run user content. Fast mode I2C roughly multiplies the overhead by a factor 4 (which was expected), so 25% of the time is taken by MODI2C, leaving 75% of the CPU cycles for user content.

Known issues and compatibility

In principle MODI2C and default mbed I2C can be used together without issues. However there are a few small issues and some common sense is needed.

After a normal I2C command is finished, do not immediatly send a MODI2C message. It results in some reliability issues, which I would have looked into if it is not so easily fixed by a very small delay. Adding delay_us(1); already fixes the issue. If you have some calculations between the two commands it is also fine, only a very short delay is required.

When a MODI2C command is finished you may immediatly send a normal I2C command. The common sense part: I assume we are talking here about a read command, so a blocking command. Then you are immediatly allowed to send a normal I2C command. If however the buffer is not empty yet and you will send a normal I2C message it will not work. Normal I2C will not care that MODI2C is still sending.

Finally there is the issue that mbed I2C and MODI2C handle repeated start conditions slightly different. I think the mbed library sends a repeated start condition during the next command, while the MODI2C library sends repeated start at the end of its current command. So if you are addressing a device where you need to send a repeated start, do not send it with one library while sending the next command with the other library. I cannot possibly imagine why you would want to do that anyway.

The end

My C++ knowledge was quite limitted until I made this library (and probably still), just like my knowledge of the LPC1768 registers/hardware. In the end it looks like everything works fairly well, but if it works with the default mbed library, but not with MODI2C, you should probably blame me.

Committer:
Sissors
Date:
Sat Jun 30 14:55:14 2012 +0000
Revision:
0:ff579e7e8efa
v1.0

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Sissors 0:ff579e7e8efa 1 #include "MODI2C.h"
Sissors 0:ff579e7e8efa 2
Sissors 0:ff579e7e8efa 3 //**********************************************************
Sissors 0:ff579e7e8efa 4 //*********************IRQ FUNCTIONS************************
Sissors 0:ff579e7e8efa 5 //**********************************************************
Sissors 0:ff579e7e8efa 6
Sissors 0:ff579e7e8efa 7
Sissors 0:ff579e7e8efa 8 void MODI2C::runUserIRQ(I2CData Data) {
Sissors 0:ff579e7e8efa 9
Sissors 0:ff579e7e8efa 10 if (IRQOp==IRQ_I2C_BOTH) //Always call if both
Sissors 0:ff579e7e8efa 11 callback.call();
Sissors 0:ff579e7e8efa 12 if ((IRQOp==IRQ_I2C_READ)&&(Data.address&0x01)) //Call if read and byte was read
Sissors 0:ff579e7e8efa 13 callback.call();
Sissors 0:ff579e7e8efa 14 if ((IRQOp==IRQ_I2C_WRITE)&&(!(Data.address&0x01))) //Call if write and byte was written
Sissors 0:ff579e7e8efa 15 callback.call();
Sissors 0:ff579e7e8efa 16 }
Sissors 0:ff579e7e8efa 17
Sissors 0:ff579e7e8efa 18 void MODI2C::IRQ1Handler( void ) {
Sissors 0:ff579e7e8efa 19 IRQHandler(&Buffer1, LPC_I2C1);
Sissors 0:ff579e7e8efa 20 }
Sissors 0:ff579e7e8efa 21
Sissors 0:ff579e7e8efa 22 void MODI2C::IRQ2Handler( void ) {
Sissors 0:ff579e7e8efa 23 IRQHandler(&Buffer2, LPC_I2C2);
Sissors 0:ff579e7e8efa 24 }
Sissors 0:ff579e7e8efa 25
Sissors 0:ff579e7e8efa 26 void MODI2C::IRQHandler( I2CBuffer *Buffer, LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 27 I2CData *Data = &Buffer->Data[0];
Sissors 0:ff579e7e8efa 28
Sissors 0:ff579e7e8efa 29 //Depending on the status register it determines next action, see datasheet
Sissors 0:ff579e7e8efa 30 //This is also pretty much copy pasting the datasheet
Sissors 0:ff579e7e8efa 31 //General options
Sissors 0:ff579e7e8efa 32 switch (I2CMODULE->I2STAT) {
Sissors 0:ff579e7e8efa 33 case(0x08):
Sissors 0:ff579e7e8efa 34 case(0x10):
Sissors 0:ff579e7e8efa 35 //Send Address
Sissors 0:ff579e7e8efa 36 I2CMODULE->I2DAT = Data->address;
Sissors 0:ff579e7e8efa 37 I2CMODULE->I2CONCLR = 1<<I2C_FLAG | 1<<I2C_START;
Sissors 0:ff579e7e8efa 38 break;
Sissors 0:ff579e7e8efa 39
Sissors 0:ff579e7e8efa 40 //All master TX options
Sissors 0:ff579e7e8efa 41
Sissors 0:ff579e7e8efa 42 //Address + W has been sent, ACK received
Sissors 0:ff579e7e8efa 43 //Data has been sent, ACK received
Sissors 0:ff579e7e8efa 44 case(0x18):
Sissors 0:ff579e7e8efa 45 case(0x28):
Sissors 0:ff579e7e8efa 46 if (Buffer->count==Data->length) {
Sissors 0:ff579e7e8efa 47 *Data->status=I2CMODULE->I2STAT;
Sissors 0:ff579e7e8efa 48 if (!Data->repeated)
Sissors 0:ff579e7e8efa 49 _stop(I2CMODULE);
Sissors 0:ff579e7e8efa 50 else {
Sissors 0:ff579e7e8efa 51 I2CMODULE->I2CONSET = 1<<I2C_START;
Sissors 0:ff579e7e8efa 52 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 53 }
Sissors 0:ff579e7e8efa 54 bufferHandler(I2CMODULE);
Sissors 0:ff579e7e8efa 55 } else {
Sissors 0:ff579e7e8efa 56 I2CMODULE->I2DAT = Data->data[Buffer->count];
Sissors 0:ff579e7e8efa 57 I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK; //I dont see why I have to enable that bit, but datasheet says so
Sissors 0:ff579e7e8efa 58 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 59 Buffer->count++;
Sissors 0:ff579e7e8efa 60 }
Sissors 0:ff579e7e8efa 61 break;
Sissors 0:ff579e7e8efa 62
Sissors 0:ff579e7e8efa 63 //Address + W has been sent, NACK received
Sissors 0:ff579e7e8efa 64 //Data has been sent, NACK received
Sissors 0:ff579e7e8efa 65 case(0x20):
Sissors 0:ff579e7e8efa 66 case(0x30):
Sissors 0:ff579e7e8efa 67 *Data->status=I2CMODULE->I2STAT;
Sissors 0:ff579e7e8efa 68 _stop(I2CMODULE);
Sissors 0:ff579e7e8efa 69 bufferHandler(I2CMODULE);
Sissors 0:ff579e7e8efa 70 break;
Sissors 0:ff579e7e8efa 71
Sissors 0:ff579e7e8efa 72 //Arbitration lost (situation looks pretty hopeless to me if you arrive here)
Sissors 0:ff579e7e8efa 73 case(0x38):
Sissors 0:ff579e7e8efa 74 _start(I2CMODULE);
Sissors 0:ff579e7e8efa 75 break;
Sissors 0:ff579e7e8efa 76
Sissors 0:ff579e7e8efa 77
Sissors 0:ff579e7e8efa 78 //All master RX options
Sissors 0:ff579e7e8efa 79
Sissors 0:ff579e7e8efa 80 //Address + R has been sent, ACK received
Sissors 0:ff579e7e8efa 81 case(0x40):
Sissors 0:ff579e7e8efa 82 //If next byte is last one, NACK, otherwise ACK
Sissors 0:ff579e7e8efa 83 if (Data->length <= Buffer->count + 1)
Sissors 0:ff579e7e8efa 84 I2CMODULE->I2CONCLR = 1<<I2C_ASSERT_ACK;
Sissors 0:ff579e7e8efa 85 else
Sissors 0:ff579e7e8efa 86 I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK;
Sissors 0:ff579e7e8efa 87 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 88 break;
Sissors 0:ff579e7e8efa 89
Sissors 0:ff579e7e8efa 90 //Address + R has been sent, NACK received
Sissors 0:ff579e7e8efa 91 case(0x48):
Sissors 0:ff579e7e8efa 92 *Data->status=I2CMODULE->I2STAT;
Sissors 0:ff579e7e8efa 93 _stop(I2CMODULE);
Sissors 0:ff579e7e8efa 94 bufferHandler(I2CMODULE);
Sissors 0:ff579e7e8efa 95 break;
Sissors 0:ff579e7e8efa 96
Sissors 0:ff579e7e8efa 97 //Data was received, ACK returned
Sissors 0:ff579e7e8efa 98 case(0x50):
Sissors 0:ff579e7e8efa 99 //Read data
Sissors 0:ff579e7e8efa 100 Data->data[Buffer->count]=I2CMODULE->I2DAT;
Sissors 0:ff579e7e8efa 101 Buffer->count++;
Sissors 0:ff579e7e8efa 102
Sissors 0:ff579e7e8efa 103 //If next byte is last one, NACK, otherwise ACK
Sissors 0:ff579e7e8efa 104 if (Data->length == Buffer->count + 1)
Sissors 0:ff579e7e8efa 105 I2CMODULE->I2CONCLR = 1<<I2C_ASSERT_ACK;
Sissors 0:ff579e7e8efa 106 else
Sissors 0:ff579e7e8efa 107 I2CMODULE->I2CONSET = 1<<I2C_ASSERT_ACK;
Sissors 0:ff579e7e8efa 108
Sissors 0:ff579e7e8efa 109 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 110 break;
Sissors 0:ff579e7e8efa 111
Sissors 0:ff579e7e8efa 112 //Data was received, NACK returned (last byte)
Sissors 0:ff579e7e8efa 113 case(0x58):
Sissors 0:ff579e7e8efa 114 //Read data
Sissors 0:ff579e7e8efa 115 *Data->status=I2CMODULE->I2STAT;
Sissors 0:ff579e7e8efa 116 Data->data[Buffer->count]=I2CMODULE->I2DAT;
Sissors 0:ff579e7e8efa 117 if (!Data->repeated)
Sissors 0:ff579e7e8efa 118 _stop(I2CMODULE);
Sissors 0:ff579e7e8efa 119 else {
Sissors 0:ff579e7e8efa 120 I2CMODULE->I2CONSET = 1<<I2C_START;
Sissors 0:ff579e7e8efa 121 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 122 }
Sissors 0:ff579e7e8efa 123 bufferHandler(I2CMODULE);
Sissors 0:ff579e7e8efa 124 break;
Sissors 0:ff579e7e8efa 125
Sissors 0:ff579e7e8efa 126 default:
Sissors 0:ff579e7e8efa 127 *Data->status=I2CMODULE->I2STAT;
Sissors 0:ff579e7e8efa 128 bufferHandler(I2CMODULE);
Sissors 0:ff579e7e8efa 129 break;
Sissors 0:ff579e7e8efa 130 }
Sissors 0:ff579e7e8efa 131 }
Sissors 0:ff579e7e8efa 132
Sissors 0:ff579e7e8efa 133
Sissors 0:ff579e7e8efa 134 //**********************************************************
Sissors 0:ff579e7e8efa 135 //*********************COMMAND BUFFER***********************
Sissors 0:ff579e7e8efa 136 //**********************************************************
Sissors 0:ff579e7e8efa 137
Sissors 0:ff579e7e8efa 138 void MODI2C::bufferHandler(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 139 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 140 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 141 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 142 } else {
Sissors 0:ff579e7e8efa 143 Buffer = &Buffer2;
Sissors 0:ff579e7e8efa 144 }
Sissors 0:ff579e7e8efa 145
Sissors 0:ff579e7e8efa 146 //Start user interrupt
Sissors 0:ff579e7e8efa 147 Buffer->Data[0].caller->runUserIRQ(Buffer->Data[0]);
Sissors 0:ff579e7e8efa 148
Sissors 0:ff579e7e8efa 149 removeBuffer(I2CMODULE);
Sissors 0:ff579e7e8efa 150
Sissors 0:ff579e7e8efa 151
Sissors 0:ff579e7e8efa 152
Sissors 0:ff579e7e8efa 153 if (Buffer->queue!=0)
Sissors 0:ff579e7e8efa 154 startBuffer(I2CMODULE);
Sissors 0:ff579e7e8efa 155 else
Sissors 0:ff579e7e8efa 156 _clearISR(I2CMODULE);
Sissors 0:ff579e7e8efa 157 }
Sissors 0:ff579e7e8efa 158
Sissors 0:ff579e7e8efa 159 //Returns true if succeeded, false if buffer is full
Sissors 0:ff579e7e8efa 160 bool MODI2C::addBuffer(I2CData Data, LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 161 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 162 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 163 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 164 } else {
Sissors 0:ff579e7e8efa 165 Buffer = &Buffer2;
Sissors 0:ff579e7e8efa 166 }
Sissors 0:ff579e7e8efa 167 if (Buffer->queue<I2C_BUFFER) {
Sissors 0:ff579e7e8efa 168
Sissors 0:ff579e7e8efa 169 if(Data.status == NULL) {
Sissors 0:ff579e7e8efa 170 Data.status = &defaultStatus;
Sissors 0:ff579e7e8efa 171 wait_us(1); //I blame the compiler that this is needed
Sissors 0:ff579e7e8efa 172 }
Sissors 0:ff579e7e8efa 173 *Data.status = 0;
Sissors 0:ff579e7e8efa 174
Sissors 0:ff579e7e8efa 175 Buffer->Data[Buffer->queue]=Data;
Sissors 0:ff579e7e8efa 176 Buffer->queue++;
Sissors 0:ff579e7e8efa 177
Sissors 0:ff579e7e8efa 178 //If queue was empty, set I2C settings, start conversion
Sissors 0:ff579e7e8efa 179 if (Buffer->queue==1) {
Sissors 0:ff579e7e8efa 180 startBuffer(I2CMODULE);
Sissors 0:ff579e7e8efa 181 }
Sissors 0:ff579e7e8efa 182
Sissors 0:ff579e7e8efa 183 return true;
Sissors 0:ff579e7e8efa 184 } else
Sissors 0:ff579e7e8efa 185 return false;
Sissors 0:ff579e7e8efa 186
Sissors 0:ff579e7e8efa 187 }
Sissors 0:ff579e7e8efa 188
Sissors 0:ff579e7e8efa 189 //Returns true if buffer still has data, false if empty
Sissors 0:ff579e7e8efa 190 bool MODI2C::removeBuffer(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 191 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 192 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 193 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 194 } else {
Sissors 0:ff579e7e8efa 195 Buffer= &Buffer2;
Sissors 0:ff579e7e8efa 196 }
Sissors 0:ff579e7e8efa 197
Sissors 0:ff579e7e8efa 198 if (Buffer->queue>0) {
Sissors 0:ff579e7e8efa 199 for (int i =0; i<Buffer->queue-1; i++)
Sissors 0:ff579e7e8efa 200 Buffer->Data[i]=Buffer->Data[i+1];
Sissors 0:ff579e7e8efa 201 Buffer->queue--;
Sissors 0:ff579e7e8efa 202 }
Sissors 0:ff579e7e8efa 203 if (Buffer->queue>0)
Sissors 0:ff579e7e8efa 204 return true;
Sissors 0:ff579e7e8efa 205 else
Sissors 0:ff579e7e8efa 206 return false;
Sissors 0:ff579e7e8efa 207 }
Sissors 0:ff579e7e8efa 208
Sissors 0:ff579e7e8efa 209 //Starts new conversion
Sissors 0:ff579e7e8efa 210 void MODI2C::startBuffer(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 211 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 212 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 213 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 214 } else {
Sissors 0:ff579e7e8efa 215 Buffer = &Buffer2;
Sissors 0:ff579e7e8efa 216 }
Sissors 0:ff579e7e8efa 217
Sissors 0:ff579e7e8efa 218 //Write settings
Sissors 0:ff579e7e8efa 219 Buffer->Data[0].caller->writeSettings();
Sissors 0:ff579e7e8efa 220 Buffer->count=0;
Sissors 0:ff579e7e8efa 221
Sissors 0:ff579e7e8efa 222 //Send Start
Sissors 0:ff579e7e8efa 223 _start(I2CMODULE);
Sissors 0:ff579e7e8efa 224
Sissors 0:ff579e7e8efa 225 //Start ISR (when buffer wasnt empty this wont do anything)
Sissors 0:ff579e7e8efa 226 _setISR(I2CMODULE);
Sissors 0:ff579e7e8efa 227
Sissors 0:ff579e7e8efa 228 }