mbed-os/targets/TARGET_NXP/TARGET_LPC176X/i2c_api.c

428 lines
11 KiB
C
Raw Normal View History

/* mbed Microcontroller Library
* Copyright (c) 2006-2013 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mbed_assert.h"
#include "i2c_api.h"
#include "cmsis.h"
#include "pinmap.h"
static const PinMap PinMap_I2C_SDA[] = {
{P0_0 , I2C_1, 3},
{P0_10, I2C_2, 2},
{P0_19, I2C_1, 3},
{P0_27, I2C_0, 1},
{NC , NC , 0}
};
static const PinMap PinMap_I2C_SCL[] = {
{P0_1 , I2C_1, 3},
{P0_11, I2C_2, 2},
{P0_20, I2C_1, 3},
{P0_28, I2C_0, 1},
{NC , NC, 0}
};
#define I2C_CONSET(x) (x->i2c->I2CONSET)
#define I2C_CONCLR(x) (x->i2c->I2CONCLR)
#define I2C_STAT(x) (x->i2c->I2STAT)
#define I2C_DAT(x) (x->i2c->I2DAT)
#define I2C_SCLL(x, val) (x->i2c->I2SCLL = val)
#define I2C_SCLH(x, val) (x->i2c->I2SCLH = val)
static const uint32_t I2C_addr_offset[2][4] = {
{0x0C, 0x20, 0x24, 0x28},
{0x30, 0x34, 0x38, 0x3C}
};
2013-04-11 16:19:34 +00:00
static inline void i2c_conclr(i2c_t *obj, int start, int stop, int interrupt, int acknowledge) {
I2C_CONCLR(obj) = (start << 5)
| (stop << 4)
| (interrupt << 3)
| (acknowledge << 2);
}
2013-04-11 16:19:34 +00:00
static inline void i2c_conset(i2c_t *obj, int start, int stop, int interrupt, int acknowledge) {
I2C_CONSET(obj) = (start << 5)
| (stop << 4)
| (interrupt << 3)
| (acknowledge << 2);
}
// Clear the Serial Interrupt (SI)
2013-04-11 16:19:34 +00:00
static inline void i2c_clear_SI(i2c_t *obj) {
i2c_conclr(obj, 0, 0, 1, 0);
}
2013-04-11 16:19:34 +00:00
static inline int i2c_status(i2c_t *obj) {
return I2C_STAT(obj);
}
// Wait until the Serial Interrupt (SI) is set
static int i2c_wait_SI(i2c_t *obj) {
int timeout = 0;
while (!(I2C_CONSET(obj) & (1 << 3))) {
timeout++;
if (timeout > 100000) return -1;
}
return 0;
}
2013-04-11 16:19:34 +00:00
static inline void i2c_interface_enable(i2c_t *obj) {
I2C_CONSET(obj) = 0x40;
}
2013-04-11 16:19:34 +00:00
static inline void i2c_power_enable(i2c_t *obj) {
switch ((int)obj->i2c) {
case I2C_0: LPC_SC->PCONP |= 1 << 7; break;
case I2C_1: LPC_SC->PCONP |= 1 << 19; break;
case I2C_2: LPC_SC->PCONP |= 1 << 26; break;
}
}
void i2c_init(i2c_t *obj, PinName sda, PinName scl) {
// determine the SPI to use
I2CName i2c_sda = (I2CName)pinmap_peripheral(sda, PinMap_I2C_SDA);
I2CName i2c_scl = (I2CName)pinmap_peripheral(scl, PinMap_I2C_SCL);
obj->i2c = (LPC_I2C_TypeDef *)pinmap_merge(i2c_sda, i2c_scl);
MBED_ASSERT((int)obj->i2c != NC);
// enable power
i2c_power_enable(obj);
// set default frequency at 100k
i2c_frequency(obj, 100000);
i2c_conclr(obj, 1, 1, 1, 1);
i2c_interface_enable(obj);
pinmap_pinout(sda, PinMap_I2C_SDA);
pinmap_pinout(scl, PinMap_I2C_SCL);
2013-04-11 16:19:34 +00:00
}
2013-04-11 16:19:34 +00:00
inline int i2c_start(i2c_t *obj) {
int status = 0;
Fix NXP LPCxxxx i2c_start() handling of repeated start In repeating start scenarios, there was a bug in the I2C driver for various NXP LPCxxxx parts which could allow an extra I/O from the previous operation to leak through. The scenario I encountered which triggered this bug went like so: * The higher level application code would first do an I2C write that doesn't send a stop bit (use repeating start instead.) * The higher level application code would then issues an I2C read operation which begin with a call to i2c_start(). * i2c_start() would clear the SI bit (interrupt flag) at the top of its implementation. * i2C_start() would then get interrupted right after clearing the SI bit. * While the CPU is off running the ISR code, the I2C peripheral repeats the last byte written to I2CDAT and then sets the SI bit to indicate that the write has completed. * The ISR returns to allow the i2c_start() to continue execution. * i2c_start() would then set the STA bit but it is too late. * i2c_start() waits for the SI bit to be set but it is already set because of the completed byte write and not because of the repeated start as expected. For me this bug would cause strange interactions between my ISRs and the code using I2C to read from the MPU-6050 IMU. I would be getting valid orientation data and then all of a sudden I would start receiving what looked like random values but I think it was just reading from the incorrect offset in the device's FIFO. It appears that atleast one other person has seen this before but it was never root caused since it required specific timing to reproduce: https://developer.mbed.org/forum/bugs-suggestions/topic/4254/ This bug can be found in most of the NXP I2C drivers and this commit contains a fix for them all. I however only have the LPC1768 and LPC11U24 for testing. My fix does the following: * No longer clears the SI bit in the i2c_conclr() call near the beginning of the i2c_start() function. It was this clear that previously caused the problem as described above. * The second part of the fix was to instead clear the SI bit after the STA (start) bit has been set with the i2c_conset() call. * The clearing of the SI bit should be skipped if there isn't an active interrupt when first entering i2c_start(). If you clear the SI bit when there isn't an active interrupt then the code is likely to skip over the interrupt for the start bit which was just sent and then allow the I2C peripheral to start sending the slave address before it has even been loaded into the I2CDAT register.
2016-05-25 07:20:06 +00:00
int isInterrupted = I2C_CONSET(obj) & (1 << 3);
// 8.1 Before master mode can be entered, I2CON must be initialised to:
// - I2EN STA STO SI AA - -
Fix NXP LPCxxxx i2c_start() handling of repeated start In repeating start scenarios, there was a bug in the I2C driver for various NXP LPCxxxx parts which could allow an extra I/O from the previous operation to leak through. The scenario I encountered which triggered this bug went like so: * The higher level application code would first do an I2C write that doesn't send a stop bit (use repeating start instead.) * The higher level application code would then issues an I2C read operation which begin with a call to i2c_start(). * i2c_start() would clear the SI bit (interrupt flag) at the top of its implementation. * i2C_start() would then get interrupted right after clearing the SI bit. * While the CPU is off running the ISR code, the I2C peripheral repeats the last byte written to I2CDAT and then sets the SI bit to indicate that the write has completed. * The ISR returns to allow the i2c_start() to continue execution. * i2c_start() would then set the STA bit but it is too late. * i2c_start() waits for the SI bit to be set but it is already set because of the completed byte write and not because of the repeated start as expected. For me this bug would cause strange interactions between my ISRs and the code using I2C to read from the MPU-6050 IMU. I would be getting valid orientation data and then all of a sudden I would start receiving what looked like random values but I think it was just reading from the incorrect offset in the device's FIFO. It appears that atleast one other person has seen this before but it was never root caused since it required specific timing to reproduce: https://developer.mbed.org/forum/bugs-suggestions/topic/4254/ This bug can be found in most of the NXP I2C drivers and this commit contains a fix for them all. I however only have the LPC1768 and LPC11U24 for testing. My fix does the following: * No longer clears the SI bit in the i2c_conclr() call near the beginning of the i2c_start() function. It was this clear that previously caused the problem as described above. * The second part of the fix was to instead clear the SI bit after the STA (start) bit has been set with the i2c_conset() call. * The clearing of the SI bit should be skipped if there isn't an active interrupt when first entering i2c_start(). If you clear the SI bit when there isn't an active interrupt then the code is likely to skip over the interrupt for the start bit which was just sent and then allow the I2C peripheral to start sending the slave address before it has even been loaded into the I2CDAT register.
2016-05-25 07:20:06 +00:00
// - 1 0 0 x x - -
// if AA = 0, it can't enter slave mode
Fix NXP LPCxxxx i2c_start() handling of repeated start In repeating start scenarios, there was a bug in the I2C driver for various NXP LPCxxxx parts which could allow an extra I/O from the previous operation to leak through. The scenario I encountered which triggered this bug went like so: * The higher level application code would first do an I2C write that doesn't send a stop bit (use repeating start instead.) * The higher level application code would then issues an I2C read operation which begin with a call to i2c_start(). * i2c_start() would clear the SI bit (interrupt flag) at the top of its implementation. * i2C_start() would then get interrupted right after clearing the SI bit. * While the CPU is off running the ISR code, the I2C peripheral repeats the last byte written to I2CDAT and then sets the SI bit to indicate that the write has completed. * The ISR returns to allow the i2c_start() to continue execution. * i2c_start() would then set the STA bit but it is too late. * i2c_start() waits for the SI bit to be set but it is already set because of the completed byte write and not because of the repeated start as expected. For me this bug would cause strange interactions between my ISRs and the code using I2C to read from the MPU-6050 IMU. I would be getting valid orientation data and then all of a sudden I would start receiving what looked like random values but I think it was just reading from the incorrect offset in the device's FIFO. It appears that atleast one other person has seen this before but it was never root caused since it required specific timing to reproduce: https://developer.mbed.org/forum/bugs-suggestions/topic/4254/ This bug can be found in most of the NXP I2C drivers and this commit contains a fix for them all. I however only have the LPC1768 and LPC11U24 for testing. My fix does the following: * No longer clears the SI bit in the i2c_conclr() call near the beginning of the i2c_start() function. It was this clear that previously caused the problem as described above. * The second part of the fix was to instead clear the SI bit after the STA (start) bit has been set with the i2c_conset() call. * The clearing of the SI bit should be skipped if there isn't an active interrupt when first entering i2c_start(). If you clear the SI bit when there isn't an active interrupt then the code is likely to skip over the interrupt for the start bit which was just sent and then allow the I2C peripheral to start sending the slave address before it has even been loaded into the I2CDAT register.
2016-05-25 07:20:06 +00:00
i2c_conclr(obj, 1, 1, 0, 1);
// The master mode may now be entered by setting the STA bit
// this will generate a start condition when the bus becomes free
i2c_conset(obj, 1, 0, 0, 1);
Fix NXP LPCxxxx i2c_start() handling of repeated start In repeating start scenarios, there was a bug in the I2C driver for various NXP LPCxxxx parts which could allow an extra I/O from the previous operation to leak through. The scenario I encountered which triggered this bug went like so: * The higher level application code would first do an I2C write that doesn't send a stop bit (use repeating start instead.) * The higher level application code would then issues an I2C read operation which begin with a call to i2c_start(). * i2c_start() would clear the SI bit (interrupt flag) at the top of its implementation. * i2C_start() would then get interrupted right after clearing the SI bit. * While the CPU is off running the ISR code, the I2C peripheral repeats the last byte written to I2CDAT and then sets the SI bit to indicate that the write has completed. * The ISR returns to allow the i2c_start() to continue execution. * i2c_start() would then set the STA bit but it is too late. * i2c_start() waits for the SI bit to be set but it is already set because of the completed byte write and not because of the repeated start as expected. For me this bug would cause strange interactions between my ISRs and the code using I2C to read from the MPU-6050 IMU. I would be getting valid orientation data and then all of a sudden I would start receiving what looked like random values but I think it was just reading from the incorrect offset in the device's FIFO. It appears that atleast one other person has seen this before but it was never root caused since it required specific timing to reproduce: https://developer.mbed.org/forum/bugs-suggestions/topic/4254/ This bug can be found in most of the NXP I2C drivers and this commit contains a fix for them all. I however only have the LPC1768 and LPC11U24 for testing. My fix does the following: * No longer clears the SI bit in the i2c_conclr() call near the beginning of the i2c_start() function. It was this clear that previously caused the problem as described above. * The second part of the fix was to instead clear the SI bit after the STA (start) bit has been set with the i2c_conset() call. * The clearing of the SI bit should be skipped if there isn't an active interrupt when first entering i2c_start(). If you clear the SI bit when there isn't an active interrupt then the code is likely to skip over the interrupt for the start bit which was just sent and then allow the I2C peripheral to start sending the slave address before it has even been loaded into the I2CDAT register.
2016-05-25 07:20:06 +00:00
// Clearing SI bit when it wasn't set on entry can jump past state
// 0x10 or 0x08 and erroneously send uninitialized slave address.
if (isInterrupted)
i2c_clear_SI(obj);
i2c_wait_SI(obj);
status = i2c_status(obj);
Fix NXP LPCxxxx i2c_start() handling of repeated start In repeating start scenarios, there was a bug in the I2C driver for various NXP LPCxxxx parts which could allow an extra I/O from the previous operation to leak through. The scenario I encountered which triggered this bug went like so: * The higher level application code would first do an I2C write that doesn't send a stop bit (use repeating start instead.) * The higher level application code would then issues an I2C read operation which begin with a call to i2c_start(). * i2c_start() would clear the SI bit (interrupt flag) at the top of its implementation. * i2C_start() would then get interrupted right after clearing the SI bit. * While the CPU is off running the ISR code, the I2C peripheral repeats the last byte written to I2CDAT and then sets the SI bit to indicate that the write has completed. * The ISR returns to allow the i2c_start() to continue execution. * i2c_start() would then set the STA bit but it is too late. * i2c_start() waits for the SI bit to be set but it is already set because of the completed byte write and not because of the repeated start as expected. For me this bug would cause strange interactions between my ISRs and the code using I2C to read from the MPU-6050 IMU. I would be getting valid orientation data and then all of a sudden I would start receiving what looked like random values but I think it was just reading from the incorrect offset in the device's FIFO. It appears that atleast one other person has seen this before but it was never root caused since it required specific timing to reproduce: https://developer.mbed.org/forum/bugs-suggestions/topic/4254/ This bug can be found in most of the NXP I2C drivers and this commit contains a fix for them all. I however only have the LPC1768 and LPC11U24 for testing. My fix does the following: * No longer clears the SI bit in the i2c_conclr() call near the beginning of the i2c_start() function. It was this clear that previously caused the problem as described above. * The second part of the fix was to instead clear the SI bit after the STA (start) bit has been set with the i2c_conset() call. * The clearing of the SI bit should be skipped if there isn't an active interrupt when first entering i2c_start(). If you clear the SI bit when there isn't an active interrupt then the code is likely to skip over the interrupt for the start bit which was just sent and then allow the I2C peripheral to start sending the slave address before it has even been loaded into the I2CDAT register.
2016-05-25 07:20:06 +00:00
// Clear start bit now that it's transmitted
i2c_conclr(obj, 1, 0, 0, 0);
return status;
}
inline int i2c_stop(i2c_t *obj) {
int timeout = 0;
// write the stop bit
i2c_conset(obj, 0, 1, 0, 0);
i2c_clear_SI(obj);
// wait for STO bit to reset
while(I2C_CONSET(obj) & (1 << 4)) {
timeout ++;
if (timeout > 100000) return 1;
}
return 0;
}
2013-04-11 16:19:34 +00:00
static inline int i2c_do_write(i2c_t *obj, int value, uint8_t addr) {
// write the data
I2C_DAT(obj) = value;
2013-04-11 16:19:34 +00:00
// clear SI to init a send
i2c_clear_SI(obj);
// wait and return status
2013-04-11 16:19:34 +00:00
i2c_wait_SI(obj);
return i2c_status(obj);
}
2013-04-11 16:19:34 +00:00
static inline int i2c_do_read(i2c_t *obj, int last) {
// we are in state 0x40 (SLA+R tx'd) or 0x50 (data rx'd and ack)
if(last) {
i2c_conclr(obj, 0, 0, 0, 1); // send a NOT ACK
} else {
i2c_conset(obj, 0, 0, 0, 1); // send a ACK
}
// accept byte
i2c_clear_SI(obj);
// wait for it to arrive
i2c_wait_SI(obj);
// return the data
return (I2C_DAT(obj) & 0xFF);
}
void i2c_frequency(i2c_t *obj, int hz) {
// [TODO] set pclk to /4
uint32_t PCLK = SystemCoreClock / 4;
2013-04-11 16:19:34 +00:00
uint32_t pulse = PCLK / (hz * 2);
// I2C Rate
I2C_SCLL(obj, pulse);
I2C_SCLH(obj, pulse);
}
// The I2C does a read or a write as a whole operation
// There are two types of error conditions it can encounter
// 1) it can not obtain the bus
// 2) it gets error responses at part of the transmission
//
// We tackle them as follows:
// 1) we retry until we get the bus. we could have a "timeout" if we can not get it
// which basically turns it in to a 2)
// 2) on error, we use the standard error mechanisms to report/debug
//
// Therefore an I2C transaction should always complete. If it doesn't it is usually
// because something is setup wrong (e.g. wiring), and we don't need to programatically
// check for that
int i2c_read(i2c_t *obj, int address, char *data, int length, int stop) {
int count, status;
status = i2c_start(obj);
if ((status != 0x10) && (status != 0x08)) {
i2c_stop(obj);
return I2C_ERROR_BUS_BUSY;
}
2013-04-11 16:19:34 +00:00
status = i2c_do_write(obj, (address | 0x01), 1);
if (status != 0x40) {
i2c_stop(obj);
return I2C_ERROR_NO_SLAVE;
}
// Read in all except last byte
for (count = 0; count < (length - 1); count++) {
int value = i2c_do_read(obj, 0);
status = i2c_status(obj);
if (status != 0x50) {
i2c_stop(obj);
return count;
}
data[count] = (char) value;
}
// read in last byte
int value = i2c_do_read(obj, 1);
status = i2c_status(obj);
if (status != 0x58) {
i2c_stop(obj);
return length - 1;
}
data[count] = (char) value;
// If not repeated start, send stop.
if (stop) {
i2c_stop(obj);
}
return length;
}
int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop) {
int i, status;
status = i2c_start(obj);
if ((status != 0x10) && (status != 0x08)) {
i2c_stop(obj);
return I2C_ERROR_BUS_BUSY;
}
2013-04-11 16:19:34 +00:00
status = i2c_do_write(obj, (address & 0xFE), 1);
if (status != 0x18) {
i2c_stop(obj);
return I2C_ERROR_NO_SLAVE;
}
for (i=0; i<length; i++) {
2013-04-11 16:19:34 +00:00
status = i2c_do_write(obj, data[i], 0);
if(status != 0x28) {
i2c_stop(obj);
return i;
}
}
// clearing the serial interrupt here might cause an unintended rewrite of the last byte
// see also issue report https://mbed.org/users/mbed_official/code/mbed/issues/1
// i2c_clear_SI(obj);
2013-04-11 16:19:34 +00:00
// If not repeated start, send stop.
if (stop) {
i2c_stop(obj);
}
return length;
}
void i2c_reset(i2c_t *obj) {
i2c_stop(obj);
}
int i2c_byte_read(i2c_t *obj, int last) {
return (i2c_do_read(obj, last) & 0xFF);
}
int i2c_byte_write(i2c_t *obj, int data) {
int ack;
2013-04-11 16:19:34 +00:00
int status = i2c_do_write(obj, (data & 0xFF), 0);
switch(status) {
case 0x18: case 0x28: // Master transmit ACKs
ack = 1;
break;
case 0x40: // Master receive address transmitted ACK
ack = 1;
break;
case 0xB8: // Slave transmit ACK
ack = 1;
break;
default:
ack = 0;
break;
}
return ack;
}
void i2c_slave_mode(i2c_t *obj, int enable_slave) {
if (enable_slave != 0) {
i2c_conclr(obj, 1, 1, 1, 0);
i2c_conset(obj, 0, 0, 0, 1);
} else {
i2c_conclr(obj, 1, 1, 1, 1);
}
}
int i2c_slave_receive(i2c_t *obj) {
int status;
int retval;
status = i2c_status(obj);
switch(status) {
case 0x60: retval = 3; break;
case 0x70: retval = 2; break;
case 0xA8: retval = 1; break;
default : retval = 0; break;
}
return(retval);
}
int i2c_slave_read(i2c_t *obj, char *data, int length) {
int count = 0;
int status;
do {
i2c_clear_SI(obj);
i2c_wait_SI(obj);
status = i2c_status(obj);
if((status == 0x80) || (status == 0x90)) {
data[count] = I2C_DAT(obj) & 0xFF;
}
count++;
} while (((status == 0x80) || (status == 0x90) ||
(status == 0x060) || (status == 0x70)) && (count < length));
// Clear old status and wait for Serial Interrupt.
i2c_clear_SI(obj);
i2c_wait_SI(obj);
2015-12-13 22:29:50 +00:00
// Obtain new status.
status = i2c_status(obj);
if(status != 0xA0) {
i2c_stop(obj);
}
i2c_clear_SI(obj);
return count;
}
int i2c_slave_write(i2c_t *obj, const char *data, int length) {
int count = 0;
int status;
if(length <= 0) {
return(0);
}
do {
2013-04-11 16:19:34 +00:00
status = i2c_do_write(obj, data[count], 0);
count++;
} while ((count < length) && (status == 0xB8));
if ((status != 0xC0) && (status != 0xC8)) {
i2c_stop(obj);
}
i2c_clear_SI(obj);
return(count);
}
void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask) {
uint32_t addr;
if ((idx >= 0) && (idx <= 3)) {
addr = ((uint32_t)obj->i2c) + I2C_addr_offset[0][idx];
*((uint32_t *) addr) = address & 0xFF;
addr = ((uint32_t)obj->i2c) + I2C_addr_offset[1][idx];
*((uint32_t *) addr) = mask & 0xFE;
}
}
const PinMap *i2c_master_sda_pinmap()
{
return PinMap_I2C_SDA;
}
const PinMap *i2c_master_scl_pinmap()
{
return PinMap_I2C_SCL;
}
const PinMap *i2c_slave_sda_pinmap()
{
return PinMap_I2C_SDA;
}
const PinMap *i2c_slave_scl_pinmap()
{
return PinMap_I2C_SCL;
}