I am using a Thunderboard Sense microcontroller, plugged into my desktop (Windows) with a USB, and the Simplicity IDE with the coding language C. I am trying to write to a register using the I2C_Transfer function, to initialise the board's IMU sensor. However the transfer is always 'in progress' and never completes.
I first wake the board's interrupt controller, then enable I2C commands, then initialise I2C, and then attempt the transfer. To do this I first call the I2C_TransferInit function, and then poll the I2C_Transfer function, waiting for it to complete.
I looked at the code for the I2C_Transfer function, and found that it gets stuck with transfer->state = i2cStateWFStopSent
, with the state never continuing on to i2cStateDone
. This appears to be because the interrupt flag register is not doing something which it should (the value of the pending
variable, which can be seen in the code for the I2C_Transfer function, is set by this interrupt (I think)).
My code is below. To find the section where the error occurs, search for the comment 'ERROR IS HERE'.
/* Board headers */
#include "boards.h"
#include "ble-configuration.h"
#include "board_features.h"
/* Bluetooth stack headers */
#include "bg_types.h"
#include "native_gecko.h"
#include "gatt_db.h"
#include "aat.h"
/* Libraries containing default Gecko configuration values */
#include "em_emu.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_i2c.h"
#ifdef FEATURE_BOARD_DETECTED
#include "bspconfig.h"
#include "pti.h"
#endif
/* Device initialization header */
#include "InitDevice.h"
#ifdef FEATURE_SPI_FLASH
#include "em_usart.h"
#include "mx25flash_spi.h"
#endif /* FEATURE_SPI_FLASH */
#ifndef MAX_CONNECTIONS
#define MAX_CONNECTIONS 4
#endif
uint8_t bluetooth_stack_heap[DEFAULT_BLUETOOTH_HEAP(MAX_CONNECTIONS)];
#ifdef FEATURE_PTI_SUPPORT
static const RADIO_PTIInit_t ptiInit = RADIO_PTI_INIT;
#endif
/* Gecko configuration parameters (see gecko_configuration.h) */
static const gecko_configuration_t config = {
.config_flags = 0,
.sleep.flags = SLEEP_FLAGS_DEEP_SLEEP_ENABLE,
.bluetooth.max_connections = MAX_CONNECTIONS,
.bluetooth.heap = bluetooth_stack_heap,
.bluetooth.heap_size = sizeof(bluetooth_stack_heap),
.bluetooth.sleep_clock_accuracy = 100, // ppm
.gattdb = &bg_gattdb_data,
.ota.flags = 0,
.ota.device_name_len = 3,
.ota.device_name_ptr = "OTA",
#ifdef FEATURE_PTI_SUPPORT
.pti = &ptiInit,
#endif
};
/* Flag for indicating DFU Reset must be performed */
uint8_t boot_to_dfu = 0;
// Print to putty
int _write_r(struct _reent *r, int fd, const void *data, unsigned int count)
{
char *c = (char *)data;
for (unsigned int i = 0; i < count; i++)
{
USART_Tx(USART0, c[i]);
}
return count;
}
void main(void)
{
#ifdef FEATURE_SPI_FLASH
MX25_init();
MX25_DP();
/* We must disable SPI communication */
USART_Reset(USART1);
#endif /* FEATURE_SPI_FLASH */
/* Initialize peripherals */
enter_DefaultMode_from_RESET();
/* Initialize stack */
gecko_init(&config);
// Wake interrupt controller
unsigned int pin = 0;
GPIO_PinOutClear(gpioPortD, pin);
// Enable I2C
I2C_Enable(I2C0, true);
// Initialise I2C commands
I2C_Init_TypeDef i2cInit = I2C_INIT_DEFAULT;
I2C_Init(I2C0, &i2cInit);
// Enable IMU sensor
printf("\n\r\nDefining sequence to be sent\n\r");
I2C_TransferSeq_TypeDef seq;
seq.addr = 0x90;
seq.flags = 0x0001;
seq.buf[0].data[0] = 0x00;
seq.buf[0].len = 1;
seq.buf[1].data[0] = 0x01;
seq.buf[1].len = 1;
printf("Attempting to start transfer\n\r");
I2C_TransferReturn_TypeDef I2C_Status;
I2C_Status = I2C_TransferInit(I2C0, &seq);
printf("Transfer started\n\r");
while (I2C_Status == i2cTransferInProgress)
{
// ERROR IS HERE: this loop never stops running (the transfer is never completed)
I2C_Status = I2C_Transfer(I2C0);
}
printf("Transfer finished\n\r");
// Initialise variables
int IMU = 1;
while (1)
{
// IMU = read IMU value here
printf("IMU value: %d \n\r", IMU);
/* Event pointer for handling events */
struct gecko_cmd_packet* evt;
/* Check for stack event. */
evt = gecko_wait_event();
/* Handle events */
switch (BGLIB_MSG_ID(evt->header)) {
/* This boot event is generated when the system boots up after reset.
* Here the system is set to start advertising immediately after boot procedure. */
case gecko_evt_system_boot_id:
/* Set advertising parameters. 100ms advertisement interval. All channels used.
* The first two parameters are minimum and maximum advertising interval, both in
* units of (milliseconds * 1.6). The third parameter '7' sets advertising on all channels. */
gecko_cmd_le_gap_set_adv_parameters(160, 160, 7);
/* Start general advertising and enable connections. */
gecko_cmd_le_gap_set_mode(le_gap_general_discoverable, le_gap_undirected_connectable);
break;
case gecko_evt_le_connection_closed_id:
/* Check if need to boot to dfu mode */
if (boot_to_dfu) {
/* Enter to DFU OTA mode */
gecko_cmd_system_reset(2);
} else {
/* Restart advertising after client has disconnected */
gecko_cmd_le_gap_set_mode(le_gap_general_discoverable, le_gap_undirected_connectable);
}
break;
/* Events related to OTA upgrading
----------------------------------------------------------------------------- */
/* Check if the user-type OTA Control Characteristic was written.
* If ota_control was written, boot the device into Device Firmware
Upgrade (DFU) mode. */
case gecko_evt_gatt_server_user_write_request_id:
if (evt->data.evt_gatt_server_user_write_request.characteristic ==
gattdb_ota_control) {
/* Set flag to enter to OTA mode */
boot_to_dfu = 1;
/* Send response to Write Request */
gecko_cmd_gatt_server_send_user_write_response(
evt->data.evt_gatt_server_user_write_request.connection,
gattdb_ota_control,
bg_err_success);
/* Close connection to enter to DFU OTA mode */
gecko_cmd_endpoint_close(evt-
>data.evt_gatt_server_user_write_request.connection);
}
break;
default:
break;
}
}
}
/** @} (end addtogroup app) */
/** @} (end addtogroup Application) */
Here is the code for the I2C_Transfer function:
I2C_TransferReturn_TypeDef I2C_Transfer(I2C_TypeDef *i2c)
{
uint32_t tmp;
uint32_t pending;
I2C_Transfer_TypeDef *transfer;
I2C_TransferSeq_TypeDef *seq;
EFM_ASSERT(I2C_REF_VALID(i2c));
/* Support up to 2 I2C buses */
if (i2c == I2C0) {
transfer = i2cTransfer;
}
#if (I2C_COUNT > 1)
else if (i2c == I2C1) {
transfer = i2cTransfer + 1;
}
#endif
#if (I2C_COUNT > 2)
else if (i2c == I2C2) {
transfer = i2cTransfer + 2;
}
#endif
else {
return i2cTransferUsageFault;
}
seq = transfer->seq;
for (;; ) {
pending = i2c->IF;
/* If some sort of fault, abort transfer. */
if (pending & I2C_IF_ERRORS) {
if (pending & I2C_IF_ARBLOST) {
/* If arbitration fault, it indicates either a slave device */
/* not responding as expected, or other master which is not */
/* supported by this SW. */
transfer->result = i2cTransferArbLost;
} else if (pending & I2C_IF_BUSERR) {
/* A bus error indicates a misplaced start or stop, which should */
/* not occur in master mode controlled by this SW. */
transfer->result = i2cTransferBusErr;
}
/* If error situation occurred, it is difficult to know */
/* exact cause and how to resolve. It will be up to a wrapper */
/* to determine how to handle a fault/recovery if possible. */
transfer->state = i2cStateDone;
goto done;
}
switch (transfer->state) {
/***************************************************/
/* Send first start+address (first byte if 10 bit) */
/***************************************************/
case i2cStateStartAddrSend:
if (seq->flags & I2C_FLAG_10BIT_ADDR) {
tmp = (((uint32_t)(seq->addr) >> 8) & 0x06) | 0xf0;
/* In 10 bit address mode, the address following the first */
/* start always indicate write. */
} else {
tmp = (uint32_t)(seq->addr) & 0xfe;
if (seq->flags & I2C_FLAG_READ) {
/* Indicate read request */
tmp |= 1;
}
}
transfer->state = i2cStateAddrWFAckNack;
i2c->TXDATA = tmp;/* Data not transmitted until START sent */
i2c->CMD = I2C_CMD_START;
goto done;
/*******************************************************/
/* Wait for ACK/NACK on address (first byte if 10 bit) */
/*******************************************************/
case i2cStateAddrWFAckNack:
if (pending & I2C_IF_NACK) {
i2c->IFC = I2C_IFC_NACK;
transfer->result = i2cTransferNack;
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
} else if (pending & I2C_IF_ACK) {
i2c->IFC = I2C_IFC_ACK;
/* If 10 bit address, send 2nd byte of address. */
if (seq->flags & I2C_FLAG_10BIT_ADDR) {
transfer->state = i2cStateAddrWF2ndAckNack;
i2c->TXDATA = (uint32_t)(seq->addr) & 0xff;
} else {
/* Determine whether receiving or sending data */
if (seq->flags & I2C_FLAG_READ) {
transfer->state = i2cStateWFData;
if (seq->buf[transfer->bufIndx].len == 1) {
i2c->CMD = I2C_CMD_NACK;
}
} else {
transfer->state = i2cStateDataSend;
continue;
}
}
}
goto done;
/******************************************************/
/* Wait for ACK/NACK on second byte of 10 bit address */
/******************************************************/
case i2cStateAddrWF2ndAckNack:
if (pending & I2C_IF_NACK) {
i2c->IFC = I2C_IFC_NACK;
transfer->result = i2cTransferNack;
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
} else if (pending & I2C_IF_ACK) {
i2c->IFC = I2C_IFC_ACK;
/* If using plain read sequence with 10 bit address, switch to send */
/* repeated start. */
if (seq->flags & I2C_FLAG_READ) {
transfer->state = i2cStateRStartAddrSend;
}
/* Otherwise expected to write 0 or more bytes */
else {
transfer->state = i2cStateDataSend;
}
continue;
}
goto done;
/*******************************/
/* Send repeated start+address */
/*******************************/
case i2cStateRStartAddrSend:
if (seq->flags & I2C_FLAG_10BIT_ADDR) {
tmp = ((seq->addr >> 8) & 0x06) | 0xf0;
} else {
tmp = seq->addr & 0xfe;
}
/* If this is a write+read combined sequence, then read is about to start */
if (seq->flags & I2C_FLAG_WRITE_READ) {
/* Indicate read request */
tmp |= 1;
}
transfer->state = i2cStateRAddrWFAckNack;
/* We have to write START cmd first since repeated start, otherwise */
/* data would be sent first. */
i2c->CMD = I2C_CMD_START;
i2c->TXDATA = tmp;
goto done;
/**********************************************************************/
/* Wait for ACK/NACK on repeated start+address (first byte if 10 bit) */
/**********************************************************************/
case i2cStateRAddrWFAckNack:
if (pending & I2C_IF_NACK) {
i2c->IFC = I2C_IFC_NACK;
transfer->result = i2cTransferNack;
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
} else if (pending & I2C_IF_ACK) {
i2c->IFC = I2C_IFC_ACK;
/* Determine whether receiving or sending data */
if (seq->flags & I2C_FLAG_WRITE_READ) {
transfer->state = i2cStateWFData;
} else {
transfer->state = i2cStateDataSend;
continue;
}
}
goto done;
/*****************************/
/* Send a data byte to slave */
/*****************************/
case i2cStateDataSend:
/* Reached end of data buffer? */
if (transfer->offset >= seq->buf[transfer->bufIndx].len) {
/* Move to next message part */
transfer->offset = 0;
transfer->bufIndx++;
/* Send repeated start when switching to read mode on 2nd buffer */
if (seq->flags & I2C_FLAG_WRITE_READ) {
transfer->state = i2cStateRStartAddrSend;
continue;
}
/* Only writing from one buffer, or finished both buffers */
if ((seq->flags & I2C_FLAG_WRITE) || (transfer->bufIndx > 1)) {
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
goto done;
}
/* Reprocess in case next buffer is empty */
continue;
}
/* Send byte */
i2c->TXDATA = (uint32_t)(seq->buf[transfer->bufIndx].data[transfer->offset++]);
transfer->state = i2cStateDataWFAckNack;
goto done;
/*********************************************************/
/* Wait for ACK/NACK from slave after sending data to it */
/*********************************************************/
case i2cStateDataWFAckNack:
if (pending & I2C_IF_NACK) {
i2c->IFC = I2C_IFC_NACK;
transfer->result = i2cTransferNack;
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
} else if (pending & I2C_IF_ACK) {
i2c->IFC = I2C_IFC_ACK;
transfer->state = i2cStateDataSend;
continue;
}
goto done;
/****************************/
/* Wait for data from slave */
/****************************/
case i2cStateWFData:
if (pending & I2C_IF_RXDATAV) {
uint8_t data;
unsigned int rxLen = seq->buf[transfer->bufIndx].len;
/* Must read out data in order to not block further progress */
data = (uint8_t)(i2c->RXDATA);
/* Make sure not storing beyond end of buffer just in case */
if (transfer->offset < rxLen) {
seq->buf[transfer->bufIndx].data[transfer->offset++] = data;
}
/* If we have read all requested data, then the sequence should end */
if (transfer->offset >= rxLen) {
/* If there is only one byte to receive we need to transmit the
NACK now, before the stop. */
if (1 == rxLen) {
i2c->CMD = I2C_CMD_NACK;
}
transfer->state = i2cStateWFStopSent;
i2c->CMD = I2C_CMD_STOP;
} else {
/* Send ACK and wait for next byte */
i2c->CMD = I2C_CMD_ACK;
if ( (1 < rxLen) && (transfer->offset == (rxLen - 1)) ) {
/* If there is more than one byte to receive and this is the next
to last byte we need to transmit the NACK now, before receiving
the last byte. */
i2c->CMD = I2C_CMD_NACK;
}
}
}
goto done;
/***********************************/
/* Wait for STOP to have been sent */
/***********************************/
case i2cStateWFStopSent:
if (pending & I2C_IF_MSTOP) {
i2c->IFC = I2C_IFC_MSTOP;
transfer->state = i2cStateDone;
}
goto done;
/******************************/
/* Unexpected state, SW fault */
/******************************/
default:
transfer->result = i2cTransferSwFault;
transfer->state = i2cStateDone;
goto done;
}
}
done:
if (transfer->state == i2cStateDone) {
/* Disable interrupt sources when done */
i2c->IEN = 0;
/* Update result unless some fault already occurred */
if (transfer->result == i2cTransferInProgress) {
transfer->result = i2cTransferDone;
}
}
/* Until transfer is done keep returning i2cTransferInProgress */
else {
return i2cTransferInProgress;
}
return transfer->result;
}
Here is the code for the I2C_TransferInit function:
I2C_TransferReturn_TypeDef I2C_TransferInit(I2C_TypeDef *i2c,
I2C_TransferSeq_TypeDef *seq)
{
I2C_Transfer_TypeDef *transfer;
EFM_ASSERT(I2C_REF_VALID(i2c));
EFM_ASSERT(seq);
/* Support up to 2 I2C buses */
if (i2c == I2C0) {
transfer = i2cTransfer;
}
#if (I2C_COUNT > 1)
else if (i2c == I2C1) {
transfer = i2cTransfer + 1;
}
#endif
#if (I2C_COUNT > 2)
else if (i2c == I2C2) {
transfer = i2cTransfer + 2;
}
#endif
else {
return i2cTransferUsageFault;
}
/* Check if in busy state. Since this SW assumes single master, we can */
/* just issue an abort. The BUSY state is normal after a reset. */
if (i2c->STATE & I2C_STATE_BUSY) {
i2c->CMD = I2C_CMD_ABORT;
}
/* Make sure user is not trying to read 0 bytes, it is not */
/* possible according to I2C spec, since slave will always start */
/* sending first byte ACK on address. The read operation can */
/* only be stopped by NACKing a received byte, ie minimum 1 byte. */
if (((seq->flags & I2C_FLAG_READ) && !(seq->buf[0].len))
|| ((seq->flags & I2C_FLAG_WRITE_READ) && !(seq->buf[1].len))
) {
return i2cTransferUsageFault;
}
/* Prepare for a transfer */
transfer->state = i2cStateStartAddrSend;
transfer->result = i2cTransferInProgress;
transfer->offset = 0;
transfer->bufIndx = 0;
transfer->seq = seq;
/* Ensure buffers are empty */
i2c->CMD = I2C_CMD_CLEARPC | I2C_CMD_CLEARTX;
if (i2c->IF & I2C_IF_RXDATAV) {
(void)i2c->RXDATA;
}
/* Clear all pending interrupts prior to starting transfer. */
i2c->IFC = _I2C_IFC_MASK;
/* Enable those interrupts we are interested in throughout transfer. */
/* Notice that the I2C interrupt must also be enabled in the NVIC, but */
/* that is left for an additional driver wrapper. */
i2c->IEN |= I2C_IF_NACK | I2C_IF_ACK | I2C_IF_MSTOP
| I2C_IF_RXDATAV | I2C_IF_ERRORS;
/* Start transfer */
return I2C_Transfer(i2c);
}
/** @} (end addtogroup I2C) */
/** @} (end addtogroup emlib) */
#endif /* defined(I2C_COUNT) && (I2C_COUNT > 0) */
Here are some relevant links:
The data sheet for the microcontroller I am using - UG250: Thunderboard Sense User's Guide
The I2C documentation - EFR32 Blue Gecko 1 Software Documentation
P.S. I couldn't find many relevant tags, if anyone else can think of some please add them; I am new to the IoT forum so am not sure what the commonly used tags are.