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 MODI2C::I2CBuffer MODI2C::Buffer1 = {0,0}; //Sets the initial buffer empty and count on zero
Sissors 0:ff579e7e8efa 4 MODI2C::I2CBuffer MODI2C::Buffer2 = {0,0}; //Sets the initial buffer empty
Sissors 0:ff579e7e8efa 5 //int MODI2C::status=0;
Sissors 0:ff579e7e8efa 6 int MODI2C::defaultStatus=0;
Sissors 0:ff579e7e8efa 7
Sissors 0:ff579e7e8efa 8
Sissors 0:ff579e7e8efa 9
Sissors 0:ff579e7e8efa 10
Sissors 0:ff579e7e8efa 11 MODI2C::MODI2C(PinName sda, PinName scl) : led(LED3) {
Sissors 0:ff579e7e8efa 12 //Check which connection we are using, if not correct, go to error status
Sissors 0:ff579e7e8efa 13 if ((sda==p9) && (scl==p10))
Sissors 0:ff579e7e8efa 14 I2CMODULE = LPC_I2C1;
Sissors 0:ff579e7e8efa 15 else if ((sda==p28) && (scl==p27))
Sissors 0:ff579e7e8efa 16 I2CMODULE = LPC_I2C2;
Sissors 0:ff579e7e8efa 17 else
Sissors 0:ff579e7e8efa 18 error("MODI2C pins not valid");
Sissors 0:ff579e7e8efa 19
Sissors 0:ff579e7e8efa 20 //Default settings:
Sissors 0:ff579e7e8efa 21 frequency(100000);
Sissors 0:ff579e7e8efa 22
Sissors 0:ff579e7e8efa 23 writePinState();
Sissors 0:ff579e7e8efa 24 }
Sissors 0:ff579e7e8efa 25
Sissors 0:ff579e7e8efa 26 int MODI2C::write(int address, char *data, int length, bool repeated, int *status) {
Sissors 0:ff579e7e8efa 27
Sissors 0:ff579e7e8efa 28 I2CData Data;
Sissors 0:ff579e7e8efa 29 //Store relevant information
Sissors 0:ff579e7e8efa 30 address &= 0xFE;
Sissors 0:ff579e7e8efa 31 Data.caller = this;
Sissors 0:ff579e7e8efa 32 Data.address = address;
Sissors 0:ff579e7e8efa 33 Data.repeated = repeated;
Sissors 0:ff579e7e8efa 34 Data.data = data;
Sissors 0:ff579e7e8efa 35 Data.length = length;
Sissors 0:ff579e7e8efa 36 Data.status = status;
Sissors 0:ff579e7e8efa 37
Sissors 0:ff579e7e8efa 38 while(!addBuffer(Data, I2CMODULE));
Sissors 0:ff579e7e8efa 39
Sissors 0:ff579e7e8efa 40 return 0;
Sissors 0:ff579e7e8efa 41 }
Sissors 0:ff579e7e8efa 42
Sissors 0:ff579e7e8efa 43 int MODI2C::write(int address, char *data, int length, int *status) {
Sissors 0:ff579e7e8efa 44 return write(address, data, length, false, status);
Sissors 0:ff579e7e8efa 45 }
Sissors 0:ff579e7e8efa 46
Sissors 0:ff579e7e8efa 47 int MODI2C::read_nb(int address, char *data, int length, bool repeated, int *status) {
Sissors 0:ff579e7e8efa 48 //Store relevant information
Sissors 0:ff579e7e8efa 49 address |= 0x01;
Sissors 0:ff579e7e8efa 50
Sissors 0:ff579e7e8efa 51 //isIdle check here
Sissors 0:ff579e7e8efa 52 I2CData Data;
Sissors 0:ff579e7e8efa 53
Sissors 0:ff579e7e8efa 54
Sissors 0:ff579e7e8efa 55 Data.caller = this;
Sissors 0:ff579e7e8efa 56 Data.address = address;
Sissors 0:ff579e7e8efa 57 Data.repeated = repeated;
Sissors 0:ff579e7e8efa 58 Data.data = data;
Sissors 0:ff579e7e8efa 59 Data.length = length;
Sissors 0:ff579e7e8efa 60 Data.status = status;
Sissors 0:ff579e7e8efa 61
Sissors 0:ff579e7e8efa 62 while(!addBuffer(Data, I2CMODULE));
Sissors 0:ff579e7e8efa 63
Sissors 0:ff579e7e8efa 64 return 0;
Sissors 0:ff579e7e8efa 65 }
Sissors 0:ff579e7e8efa 66
Sissors 0:ff579e7e8efa 67 int MODI2C::read_nb(int address, char *data, int length, int *status) {
Sissors 0:ff579e7e8efa 68 return read_nb(address, data, length, false, status);
Sissors 0:ff579e7e8efa 69 }
Sissors 0:ff579e7e8efa 70
Sissors 0:ff579e7e8efa 71 int MODI2C::read(int address, char *data, int length, bool repeated) {
Sissors 0:ff579e7e8efa 72 int stat;
Sissors 0:ff579e7e8efa 73 //Store relevant information
Sissors 0:ff579e7e8efa 74 address |= 0x01;
Sissors 0:ff579e7e8efa 75
Sissors 0:ff579e7e8efa 76 //isIdle check here
Sissors 0:ff579e7e8efa 77 I2CData Data;
Sissors 0:ff579e7e8efa 78
Sissors 0:ff579e7e8efa 79
Sissors 0:ff579e7e8efa 80 Data.caller = this;
Sissors 0:ff579e7e8efa 81 Data.address = address;
Sissors 0:ff579e7e8efa 82 Data.repeated = repeated;
Sissors 0:ff579e7e8efa 83 Data.data = data;
Sissors 0:ff579e7e8efa 84 Data.length = length;
Sissors 0:ff579e7e8efa 85 Data.status = &stat;
Sissors 0:ff579e7e8efa 86
Sissors 0:ff579e7e8efa 87 while(!addBuffer(Data, I2CMODULE));
Sissors 0:ff579e7e8efa 88
Sissors 0:ff579e7e8efa 89 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 90 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 91 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 92 } else {
Sissors 0:ff579e7e8efa 93 Buffer = &Buffer2;
Sissors 0:ff579e7e8efa 94 }
Sissors 0:ff579e7e8efa 95
Sissors 0:ff579e7e8efa 96 while(Buffer->queue!=0)
Sissors 0:ff579e7e8efa 97 wait_us(1);
Sissors 0:ff579e7e8efa 98
Sissors 0:ff579e7e8efa 99 if (stat==0x58) //Return zero if ended correctly, otherwise return return code.
Sissors 0:ff579e7e8efa 100 return 0;
Sissors 0:ff579e7e8efa 101 else
Sissors 0:ff579e7e8efa 102 return stat;
Sissors 0:ff579e7e8efa 103 }
Sissors 0:ff579e7e8efa 104
Sissors 0:ff579e7e8efa 105
Sissors 0:ff579e7e8efa 106 void MODI2C::start( void ) {
Sissors 0:ff579e7e8efa 107 _start(I2CMODULE);
Sissors 0:ff579e7e8efa 108 }
Sissors 0:ff579e7e8efa 109
Sissors 0:ff579e7e8efa 110 void MODI2C::stop( void ) {
Sissors 0:ff579e7e8efa 111 _stop(I2CMODULE);
Sissors 0:ff579e7e8efa 112 }
Sissors 0:ff579e7e8efa 113
Sissors 0:ff579e7e8efa 114 void MODI2C::frequency(int hz) {
Sissors 0:ff579e7e8efa 115 //The I2C clock by default runs on quarter of system clock, which is 96MHz
Sissors 0:ff579e7e8efa 116 //So to calculate high/low count times, we do 96MHz/4/2/frequency
Sissors 0:ff579e7e8efa 117 duty = 96000000/8/hz;
Sissors 0:ff579e7e8efa 118 if (duty>65535)
Sissors 0:ff579e7e8efa 119 duty=65535;
Sissors 0:ff579e7e8efa 120 if (duty<4)
Sissors 0:ff579e7e8efa 121 duty=4;
Sissors 0:ff579e7e8efa 122 }
Sissors 0:ff579e7e8efa 123
Sissors 0:ff579e7e8efa 124 int MODI2C::getQueue( void ) {
Sissors 0:ff579e7e8efa 125 I2CBuffer *Buffer;
Sissors 0:ff579e7e8efa 126 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 127 Buffer = &Buffer1;
Sissors 0:ff579e7e8efa 128 } else {
Sissors 0:ff579e7e8efa 129 Buffer = &Buffer2;
Sissors 0:ff579e7e8efa 130 }
Sissors 0:ff579e7e8efa 131 return Buffer->queue;
Sissors 0:ff579e7e8efa 132 }
Sissors 0:ff579e7e8efa 133
Sissors 0:ff579e7e8efa 134
Sissors 0:ff579e7e8efa 135
Sissors 0:ff579e7e8efa 136 //*******************************************
Sissors 0:ff579e7e8efa 137 //***********Internal functions**************
Sissors 0:ff579e7e8efa 138 //*******************************************
Sissors 0:ff579e7e8efa 139
Sissors 0:ff579e7e8efa 140
Sissors 0:ff579e7e8efa 141 void MODI2C::writeSettings( void ) {
Sissors 0:ff579e7e8efa 142 I2CMODULE->I2CONSET = 1<<I2C_ENABLE; //Enable I2C
Sissors 0:ff579e7e8efa 143 I2CMODULE->I2CONCLR = I2C_STOP;
Sissors 0:ff579e7e8efa 144 I2CMODULE->MMCTRL = 0; //Disable monitor mode
Sissors 0:ff579e7e8efa 145 I2CMODULE->I2SCLH = duty;
Sissors 0:ff579e7e8efa 146 I2CMODULE->I2SCLL = duty;
Sissors 0:ff579e7e8efa 147
Sissors 0:ff579e7e8efa 148 }
Sissors 0:ff579e7e8efa 149
Sissors 0:ff579e7e8efa 150 void MODI2C::writePinState( void ) {
Sissors 0:ff579e7e8efa 151 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 152 LPC_PINCON->PINSEL0 |= 0x0000000F; //Sets pins as I2C
Sissors 0:ff579e7e8efa 153 LPC_PINCON->PINMODE0 |= 0x0000000A; //Neither pull up nor pull down
Sissors 0:ff579e7e8efa 154 LPC_PINCON->PINMODE_OD0 |= 0x00000003; //Open drain mode enabled
Sissors 0:ff579e7e8efa 155 } else if (I2CMODULE == LPC_I2C2) {
Sissors 0:ff579e7e8efa 156 LPC_PINCON->PINSEL0 |= (1<<21)|(1<<23); //Same story, different register settings
Sissors 0:ff579e7e8efa 157 LPC_PINCON->PINMODE0 |= (1<<21)|(1<<23);
Sissors 0:ff579e7e8efa 158 LPC_PINCON->PINMODE_OD0 |= (1<<10)|(1<<11);
Sissors 0:ff579e7e8efa 159 }
Sissors 0:ff579e7e8efa 160 }
Sissors 0:ff579e7e8efa 161
Sissors 0:ff579e7e8efa 162
Sissors 0:ff579e7e8efa 163
Sissors 0:ff579e7e8efa 164 void MODI2C::attach( void (*function)(void), int operation) {
Sissors 0:ff579e7e8efa 165 IRQOp = operation;
Sissors 0:ff579e7e8efa 166 callback.attach(function);
Sissors 0:ff579e7e8efa 167 }
Sissors 0:ff579e7e8efa 168
Sissors 0:ff579e7e8efa 169 template<typename T>
Sissors 0:ff579e7e8efa 170 void MODI2C::attach(T *object, void (T::*member)(void), int operation) {
Sissors 0:ff579e7e8efa 171 IRQOp = operation;
Sissors 0:ff579e7e8efa 172 callback.attach(object, member);
Sissors 0:ff579e7e8efa 173 }
Sissors 0:ff579e7e8efa 174
Sissors 0:ff579e7e8efa 175 void MODI2C::detach( void ) {
Sissors 0:ff579e7e8efa 176 callback.attach(NULL);
Sissors 0:ff579e7e8efa 177 }
Sissors 0:ff579e7e8efa 178
Sissors 0:ff579e7e8efa 179 void MODI2C::_start(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 180 if (!(I2CMODULE->I2CONSET & 1<<I2C_START)) //If already sent, skip
Sissors 0:ff579e7e8efa 181 I2CMODULE->I2CONSET = 1<<I2C_START; //Send start condition
Sissors 0:ff579e7e8efa 182 }
Sissors 0:ff579e7e8efa 183
Sissors 0:ff579e7e8efa 184 void MODI2C::_stop(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 185 I2CMODULE->I2CONSET = 1<<I2C_STOP; //Send stop condition
Sissors 0:ff579e7e8efa 186 I2CMODULE->I2CONCLR = 1<<I2C_FLAG;
Sissors 0:ff579e7e8efa 187 }
Sissors 0:ff579e7e8efa 188
Sissors 0:ff579e7e8efa 189 //Set interrupt vector
Sissors 0:ff579e7e8efa 190 void MODI2C::setISR(void) {
Sissors 0:ff579e7e8efa 191 _setISR(I2CMODULE);
Sissors 0:ff579e7e8efa 192 }
Sissors 0:ff579e7e8efa 193
Sissors 0:ff579e7e8efa 194 void MODI2C::_setISR(LPC_I2C_TypeDef *I2CMODULE) {
Sissors 0:ff579e7e8efa 195 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 196 NVIC_SetVector(I2C1_IRQn, (uint32_t)&IRQ1Handler);
Sissors 0:ff579e7e8efa 197 NVIC_EnableIRQ(I2C1_IRQn);
Sissors 0:ff579e7e8efa 198 } else if (I2CMODULE == LPC_I2C2) {
Sissors 0:ff579e7e8efa 199 NVIC_SetVector(I2C2_IRQn, (uint32_t)&IRQ2Handler);
Sissors 0:ff579e7e8efa 200 NVIC_EnableIRQ(I2C2_IRQn);
Sissors 0:ff579e7e8efa 201 }
Sissors 0:ff579e7e8efa 202 }
Sissors 0:ff579e7e8efa 203
Sissors 0:ff579e7e8efa 204 void MODI2C::clearISR( void ) {
Sissors 0:ff579e7e8efa 205 _clearISR(I2CMODULE);
Sissors 0:ff579e7e8efa 206 }
Sissors 0:ff579e7e8efa 207
Sissors 0:ff579e7e8efa 208 void MODI2C::_clearISR( LPC_I2C_TypeDef *I2CMODULE ) {
Sissors 0:ff579e7e8efa 209 if (I2CMODULE == LPC_I2C1) {
Sissors 0:ff579e7e8efa 210 NVIC_DisableIRQ(I2C1_IRQn);
Sissors 0:ff579e7e8efa 211 } else if (I2CMODULE == LPC_I2C2) {
Sissors 0:ff579e7e8efa 212 NVIC_DisableIRQ(I2C2_IRQn);
Sissors 0:ff579e7e8efa 213 }
Sissors 0:ff579e7e8efa 214 }