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 /*
Sissors 0:ff579e7e8efa 2
Sissors 0:ff579e7e8efa 3 . .
Sissors 0:ff579e7e8efa 4 / `. .' \
Sissors 0:ff579e7e8efa 5 .---. < > < > .---.
Sissors 0:ff579e7e8efa 6 | \ \ - ~ ~ - / / |
Sissors 0:ff579e7e8efa 7 ~-..-~ ~-..-~
Sissors 0:ff579e7e8efa 8 \~~~\.' `./~~~/
Sissors 0:ff579e7e8efa 9 \__/ \__/
Sissors 0:ff579e7e8efa 10 / .- . \
Sissors 0:ff579e7e8efa 11 _._ _.- .-~ ~-. / } \/~~~/
Sissors 0:ff579e7e8efa 12 _.-'q }~ / } { ; \__/
Sissors 0:ff579e7e8efa 13 {'__, / ( / { / `. ,~~| . .
Sissors 0:ff579e7e8efa 14 `''''='~~-.__( /_ | /- _ `..-' \\ //
Sissors 0:ff579e7e8efa 15 / \ =/ ~~--~~{ ./| ~-. `-..__\\_//_.-'
Sissors 0:ff579e7e8efa 16 { \ +\ \ =\ ( ~ - . _ _ _..---~
Sissors 0:ff579e7e8efa 17 | | { } \ \_\
Sissors 0:ff579e7e8efa 18 '---.o___,' .o___,' "Stegosaurus"
Sissors 0:ff579e7e8efa 19
Sissors 0:ff579e7e8efa 20
Sissors 0:ff579e7e8efa 21 Face it, dinos much cooler than copyright notices.*/
Sissors 0:ff579e7e8efa 22
Sissors 0:ff579e7e8efa 23 #include "mbed.h"
Sissors 0:ff579e7e8efa 24
Sissors 0:ff579e7e8efa 25
Sissors 0:ff579e7e8efa 26 #ifndef MODI2C_H
Sissors 0:ff579e7e8efa 27 #define MODI2C_H
Sissors 0:ff579e7e8efa 28
Sissors 0:ff579e7e8efa 29
Sissors 0:ff579e7e8efa 30 #define I2C_ENABLE 6
Sissors 0:ff579e7e8efa 31 #define I2C_START 5
Sissors 0:ff579e7e8efa 32 #define I2C_STOP 4
Sissors 0:ff579e7e8efa 33 #define I2C_FLAG 3
Sissors 0:ff579e7e8efa 34 #define I2C_ASSERT_ACK 2
Sissors 0:ff579e7e8efa 35
Sissors 0:ff579e7e8efa 36 #define IRQ_I2C_BOTH 0
Sissors 0:ff579e7e8efa 37 #define IRQ_I2C_READ 1
Sissors 0:ff579e7e8efa 38 #define IRQ_I2C_WRITE 2
Sissors 0:ff579e7e8efa 39
Sissors 0:ff579e7e8efa 40
Sissors 0:ff579e7e8efa 41
Sissors 0:ff579e7e8efa 42 #ifndef I2C_BUFFER
Sissors 0:ff579e7e8efa 43 #define I2C_BUFFER 10
Sissors 0:ff579e7e8efa 44 #endif
Sissors 0:ff579e7e8efa 45
Sissors 0:ff579e7e8efa 46
Sissors 0:ff579e7e8efa 47 /** Library that allows interrupt driven communication with I2C devices
Sissors 0:ff579e7e8efa 48 *
Sissors 0:ff579e7e8efa 49 * For now this is all in beta, so if you got weird results while the mbed I2C library works, it is probably my fault.
Sissors 0:ff579e7e8efa 50 * Similar to googles definition of beta, it is unlikely it will ever come out of beta.
Sissors 0:ff579e7e8efa 51 *
Sissors 0:ff579e7e8efa 52 * Example:
Sissors 0:ff579e7e8efa 53 * @code
Sissors 0:ff579e7e8efa 54 * #include "mbed.h"
Sissors 0:ff579e7e8efa 55 * #include "MODI2C.h"
Sissors 0:ff579e7e8efa 56 * #include "MPU6050.h"
Sissors 0:ff579e7e8efa 57 *
Sissors 0:ff579e7e8efa 58 * Serial pc(USBTX, USBRX); // tx, rx
Sissors 0:ff579e7e8efa 59 * MODI2C mod(p9, p10);
Sissors 0:ff579e7e8efa 60 *
Sissors 0:ff579e7e8efa 61 * int main() {
Sissors 0:ff579e7e8efa 62 * char registerAdd = MPU6050_WHO_AM_I_REG;
Sissors 0:ff579e7e8efa 63 * char registerResult;
Sissors 0:ff579e7e8efa 64 * int status;
Sissors 0:ff579e7e8efa 65 *
Sissors 0:ff579e7e8efa 66 * while (1) {
Sissors 0:ff579e7e8efa 67 * mod.write(MPU6050_ADDRESS*2, &registerAdd, 1, true);
Sissors 0:ff579e7e8efa 68 * mod.read_nb(MPU6050_ADDRESS*2, &registerResult, 1, &status);
Sissors 0:ff579e7e8efa 69 *
Sissors 0:ff579e7e8efa 70 * while (!status) wait_us(1);
Sissors 0:ff579e7e8efa 71 * pc.printf("Register holds 0x%02X\n\r", registerResult);
Sissors 0:ff579e7e8efa 72 * wait(2);
Sissors 0:ff579e7e8efa 73 * }
Sissors 0:ff579e7e8efa 74 * }
Sissors 0:ff579e7e8efa 75 * @endcode
Sissors 0:ff579e7e8efa 76 */
Sissors 0:ff579e7e8efa 77 class MODI2C {
Sissors 0:ff579e7e8efa 78 public:
Sissors 0:ff579e7e8efa 79 /**
Sissors 0:ff579e7e8efa 80 * Constructor.
Sissors 0:ff579e7e8efa 81 *
Sissors 0:ff579e7e8efa 82 * @param sda - mbed pin to use for the SDA I2C line.
Sissors 0:ff579e7e8efa 83 * @param scl - mbed pin to use for the SCL I2C line.
Sissors 0:ff579e7e8efa 84 */
Sissors 0:ff579e7e8efa 85 MODI2C(PinName sda, PinName scl);
Sissors 0:ff579e7e8efa 86
Sissors 0:ff579e7e8efa 87 /**
Sissors 0:ff579e7e8efa 88 * Write data on the I2C bus.
Sissors 0:ff579e7e8efa 89 *
Sissors 0:ff579e7e8efa 90 * This function should generally be a drop-in replacement for the standard mbed write function.
Sissors 0:ff579e7e8efa 91 * However this function is completely non-blocking, so it barely takes time. This can cause different behavior.
Sissors 0:ff579e7e8efa 92 * Everything else is similar to read_nb.
Sissors 0:ff579e7e8efa 93 *
Sissors 0:ff579e7e8efa 94 * @param address - I2C address of the slave (7 bit address << 1).
Sissors 0:ff579e7e8efa 95 * @param data - pointer to byte array that holds the data
Sissors 0:ff579e7e8efa 96 * @param length - amount of bytes that need to be sent
Sissors 0:ff579e7e8efa 97 * @param repeated - determines if it should end with a stop condition (default false)
Sissors 0:ff579e7e8efa 98 * @param status - (optional) pointer to integer where the final status code of the I2C transmission is placed. (0x28 is success)
Sissors 0:ff579e7e8efa 99 * @param return - returns zero
Sissors 0:ff579e7e8efa 100 */
Sissors 0:ff579e7e8efa 101 int write(int address, char *data, int length, bool repeated = false, int *status = NULL);
Sissors 0:ff579e7e8efa 102 int write(int address, char *data, int length, int *status);
Sissors 0:ff579e7e8efa 103
Sissors 0:ff579e7e8efa 104 /**
Sissors 0:ff579e7e8efa 105 * Read data non-blocking from the I2C bus.
Sissors 0:ff579e7e8efa 106 *
Sissors 0:ff579e7e8efa 107 * Reads data from the I2C bus, completely non-blocking. Aditionally it will always return zero, since it does
Sissors 0:ff579e7e8efa 108 * not wait to see how the transfer goes. A pointer to an integer can be added as argument which returns the status.
Sissors 0:ff579e7e8efa 109 *
Sissors 0:ff579e7e8efa 110 * @param address - I2C address of the slave (7 bit address << 1).
Sissors 0:ff579e7e8efa 111 * @param data - pointer to byte array where the data will be stored
Sissors 0:ff579e7e8efa 112 * @param length - amount of bytes that need to be received
Sissors 0:ff579e7e8efa 113 * @param repeated - determines if it should end with a stop condition
Sissors 0:ff579e7e8efa 114 * @param status - (optional) pointer to integer where the final status code of the I2C transmission is placed. (0x58 is success)
Sissors 0:ff579e7e8efa 115 * @param return - returns zero
Sissors 0:ff579e7e8efa 116 */
Sissors 0:ff579e7e8efa 117 int read_nb(int address, char *data, int length, bool repeated = false, int *status=NULL);
Sissors 0:ff579e7e8efa 118 int read_nb(int address, char *data, int length, int *status);
Sissors 0:ff579e7e8efa 119
Sissors 0:ff579e7e8efa 120 /**
Sissors 0:ff579e7e8efa 121 * Read data from the I2C bus.
Sissors 0:ff579e7e8efa 122 *
Sissors 0:ff579e7e8efa 123 * This function should should be a drop-in replacement for the standard mbed function.
Sissors 0:ff579e7e8efa 124 *
Sissors 0:ff579e7e8efa 125 * @param address - I2C address of the slave (7 bit address << 1).
Sissors 0:ff579e7e8efa 126 * @param data - pointer to byte array where the data will be stored
Sissors 0:ff579e7e8efa 127 * @param length - amount of bytes that need to be received
Sissors 0:ff579e7e8efa 128 * @param repeated - determines if it should end with a stop condition
Sissors 0:ff579e7e8efa 129 * @param return - returns zero on success, LPC status code on failure
Sissors 0:ff579e7e8efa 130 */
Sissors 0:ff579e7e8efa 131 int read(int address, char *data, int length, bool repeated = false);
Sissors 0:ff579e7e8efa 132
Sissors 0:ff579e7e8efa 133 /**
Sissors 0:ff579e7e8efa 134 * Sets the I2C bus frequency
Sissors 0:ff579e7e8efa 135 *
Sissors 0:ff579e7e8efa 136 * @param hz - the bus frequency in herz
Sissors 0:ff579e7e8efa 137 */
Sissors 0:ff579e7e8efa 138 void frequency(int hz);
Sissors 0:ff579e7e8efa 139
Sissors 0:ff579e7e8efa 140 /**
Sissors 0:ff579e7e8efa 141 * Creates a start condition on the I2C bus
Sissors 0:ff579e7e8efa 142 *
Sissors 0:ff579e7e8efa 143 * If you use this function you probably break something (but mbed also had it public)
Sissors 0:ff579e7e8efa 144 */
Sissors 0:ff579e7e8efa 145 void start ( void );
Sissors 0:ff579e7e8efa 146
Sissors 0:ff579e7e8efa 147 /**
Sissors 0:ff579e7e8efa 148 * Creates a stop condition on the I2C bus
Sissors 0:ff579e7e8efa 149 *
Sissors 0:ff579e7e8efa 150 * If you use this function you probably break something (but mbed also had it public)
Sissors 0:ff579e7e8efa 151 */
Sissors 0:ff579e7e8efa 152 void stop ( void );
Sissors 0:ff579e7e8efa 153
Sissors 0:ff579e7e8efa 154 /**
Sissors 0:ff579e7e8efa 155 * Removes attached function
Sissors 0:ff579e7e8efa 156 */
Sissors 0:ff579e7e8efa 157 void detach( void );
Sissors 0:ff579e7e8efa 158
Sissors 0:ff579e7e8efa 159 /**
Sissors 0:ff579e7e8efa 160 * Calls user function when I2C command is finished
Sissors 0:ff579e7e8efa 161 *
Sissors 0:ff579e7e8efa 162 * @param function - the function to call.
Sissors 0:ff579e7e8efa 163 * @param operation - when to call IRQ: IRQ_I2C_BOTH (default) - IRQ_I2C_READ - IRQ_I2C_WRITE
Sissors 0:ff579e7e8efa 164 */
Sissors 0:ff579e7e8efa 165 void attach(void (*function)(void), int operation = IRQ_I2C_BOTH);
Sissors 0:ff579e7e8efa 166
Sissors 0:ff579e7e8efa 167 /**
Sissors 0:ff579e7e8efa 168 * Calls user function when I2C command is finished
Sissors 0:ff579e7e8efa 169 *
Sissors 0:ff579e7e8efa 170 * @param object - the object to call the function on.
Sissors 0:ff579e7e8efa 171 * @param member - the function to call
Sissors 0:ff579e7e8efa 172 * @param operation - when to call IRQ: IRQ_I2C_BOTH (default) - IRQ_I2C_READ - IRQ_I2C_WRITE
Sissors 0:ff579e7e8efa 173 */
Sissors 0:ff579e7e8efa 174 template<typename T>
Sissors 0:ff579e7e8efa 175 void attach(T *object, void (T::*member)(void), int operation = IRQ_I2C_BOTH);
Sissors 0:ff579e7e8efa 176
Sissors 0:ff579e7e8efa 177 /**
Sissors 0:ff579e7e8efa 178 * Returns the current number of commands in the queue (including one currently being processed)
Sissors 0:ff579e7e8efa 179 *
Sissors 0:ff579e7e8efa 180 * Note that this is the number of commands, not the number of bytes
Sissors 0:ff579e7e8efa 181 *
Sissors 0:ff579e7e8efa 182 * @param return - number of commands in queue
Sissors 0:ff579e7e8efa 183 */
Sissors 0:ff579e7e8efa 184 int getQueue( void );
Sissors 0:ff579e7e8efa 185
Sissors 0:ff579e7e8efa 186
Sissors 0:ff579e7e8efa 187 private:
Sissors 0:ff579e7e8efa 188
Sissors 0:ff579e7e8efa 189 struct I2CData {
Sissors 0:ff579e7e8efa 190 MODI2C *caller;
Sissors 0:ff579e7e8efa 191 char address;
Sissors 0:ff579e7e8efa 192 char *data;
Sissors 0:ff579e7e8efa 193 int length;
Sissors 0:ff579e7e8efa 194 bool repeated;
Sissors 0:ff579e7e8efa 195 int *status;
Sissors 0:ff579e7e8efa 196 };
Sissors 0:ff579e7e8efa 197
Sissors 0:ff579e7e8efa 198 struct I2CBuffer {
Sissors 0:ff579e7e8efa 199 int queue;
Sissors 0:ff579e7e8efa 200 int count;
Sissors 0:ff579e7e8efa 201 I2CData Data[I2C_BUFFER];
Sissors 0:ff579e7e8efa 202 };
Sissors 0:ff579e7e8efa 203
Sissors 0:ff579e7e8efa 204 //Settings:
Sissors 0:ff579e7e8efa 205 int duty;
Sissors 0:ff579e7e8efa 206
Sissors 0:ff579e7e8efa 207 FunctionPointer callback;
Sissors 0:ff579e7e8efa 208 int IRQOp;
Sissors 0:ff579e7e8efa 209 void runUserIRQ( I2CData Data );
Sissors 0:ff579e7e8efa 210
Sissors 0:ff579e7e8efa 211 //Remove later:
Sissors 0:ff579e7e8efa 212 LPC_I2C_TypeDef *I2CMODULE;
Sissors 0:ff579e7e8efa 213
Sissors 0:ff579e7e8efa 214
Sissors 0:ff579e7e8efa 215
Sissors 0:ff579e7e8efa 216 //Whole bunch of static stuff, pretty much everything that is ever called from ISR
Sissors 0:ff579e7e8efa 217 static I2CBuffer Buffer1;
Sissors 0:ff579e7e8efa 218 static I2CBuffer Buffer2;
Sissors 0:ff579e7e8efa 219
Sissors 0:ff579e7e8efa 220 static void IRQHandler(I2CBuffer *Buffer, LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 221 static void IRQ1Handler(void);
Sissors 0:ff579e7e8efa 222 static void IRQ2Handler(void);
Sissors 0:ff579e7e8efa 223
Sissors 0:ff579e7e8efa 224 static void bufferHandler(LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 225 static bool addBuffer(I2CData Data, LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 226 static bool removeBuffer(LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 227 static void startBuffer(LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 228
Sissors 0:ff579e7e8efa 229 static int defaultStatus;
Sissors 0:ff579e7e8efa 230
Sissors 0:ff579e7e8efa 231 static void _start(LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 232 static void _stop(LPC_I2C_TypeDef *I2CMODULE);
Sissors 0:ff579e7e8efa 233 static void _clearISR( LPC_I2C_TypeDef *I2CMODULE );
Sissors 0:ff579e7e8efa 234 static void _setISR( LPC_I2C_TypeDef *I2CMODULE );
Sissors 0:ff579e7e8efa 235
Sissors 0:ff579e7e8efa 236
Sissors 0:ff579e7e8efa 237 void writeSettings( void );
Sissors 0:ff579e7e8efa 238 void writePinState( void );
Sissors 0:ff579e7e8efa 239 void setISR( void );
Sissors 0:ff579e7e8efa 240 void clearISR( void );
Sissors 0:ff579e7e8efa 241
Sissors 0:ff579e7e8efa 242
Sissors 0:ff579e7e8efa 243 DigitalOut led;
Sissors 0:ff579e7e8efa 244
Sissors 0:ff579e7e8efa 245 };
Sissors 0:ff579e7e8efa 246
Sissors 0:ff579e7e8efa 247
Sissors 0:ff579e7e8efa 248 #endif