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.
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.
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.
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.
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.
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, ®isterAdd, 1, true); mod.read_nb(MPU6050_ADDRESS*2, ®isterResult, 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.
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.
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.
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.
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.