Other Parts Discussed in Thread: MSPDRIVERLIB
Tool/software: TI-RTOS
Hi,
I am trying to implement an unconventional I2C read transaction:
In TI-RTOS is it possible to modify the I2CUSCIB.c file to be able to send a read followed by the register address before waiting to read for data from the slave?
/* * Copyright (c) 2015, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <stdint.h> #include <stdbool.h> #include <stdlib.h> #include <xdc/runtime/Error.h> #include <xdc/runtime/Assert.h> #include <xdc/runtime/Diags.h> #include <xdc/runtime/Log.h> #include <ti/drivers/i2c/I2CUSCIB.h> #include <ti/sysbios/BIOS.h> #include <ti/sysbios/family/msp430/ClockFreqs.h> #include <ti/sysbios/hal/Hwi.h> #include <ti/sysbios/knl/Semaphore.h> /* driverlib header files */ #include <inc/hw_regaccess.h> #include <usci_b_i2c.h> #define ERROR_INTERRUPTS (USCI_B_I2C_NAK_INTERRUPT | \ USCI_B_I2C_ARBITRATIONLOST_INTERRUPT) #define ALL_INTERRUPTS (USCI_B_I2C_RECEIVE_INTERRUPT | \ USCI_B_I2C_TRANSMIT_INTERRUPT | \ ERROR_INTERRUPTS) extern const I2C_Config I2C_config[]; /* Prototypes */ void I2CUSCIB_close(I2C_Handle handle); int I2CUSCIB_control(I2C_Handle handle, unsigned int cmd, void *arg); void I2CUSCIB_init(I2C_Handle handle); I2C_Handle I2CUSCIB_open(I2C_Handle handle, I2C_Params *params); static void I2CUSCIB_primeTransfer(I2CUSCIB_Object *object, I2CUSCIB_HWAttrs const *hwAttrs, I2C_Transaction *transferMessage); bool I2CUSCIB_transfer(I2C_Handle handle, I2C_Transaction *transaction); static void I2CUSCIB_blockingCallback(I2C_Handle handle, I2C_Transaction *msg, bool transferStatus); static void I2CUSCIB_completeTransfer(I2C_Handle handle); /* I2C function table for I2CI2CUSCIB implementation */ const I2C_FxnTable I2CUSCIB_fxnTable = { I2CUSCIB_close, I2CUSCIB_control, I2CUSCIB_init, I2CUSCIB_open, I2CUSCIB_transfer }; static const uint32_t bitRate[] = { USCI_B_I2C_SET_DATA_RATE_100KBPS, /* I2C_100kHz = 0 */ USCI_B_I2C_SET_DATA_RATE_400KBPS /* I2C_400kHz = 1 */ }; /* Default I2C params */ extern const I2C_Params I2C_defaultParams; /* * ======== I2CUSCIB_close ======== */ void I2CUSCIB_close(I2C_Handle handle) { I2CUSCIB_Object *object = handle->object; I2CUSCIB_HWAttrs const *hwAttrs = handle->hwAttrs; /* Check to see if a I2C transaction is in progress */ Assert_isTrue(object->headPtr == NULL, NULL); /* Mask I2C interrupts */ USCI_B_I2C_disableInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); /* Disable the I2C Master */ USCI_B_I2C_disable(hwAttrs->baseAddr); Semaphore_destruct(&(object->mutex)); if (object->transferMode == I2C_MODE_BLOCKING) { Semaphore_destruct(&(object->transferComplete)); } object->isOpen = false; Log_print1(Diags_USER1, "I2C: Object closed 0x%x", hwAttrs->baseAddr); return; } /* * ======== I2CUSCIB_control ======== * @pre Function assumes that the handle is not NULL */ int I2CUSCIB_control(I2C_Handle handle, unsigned int cmd, void *arg) { /* No implementation yet */ return (I2C_STATUS_UNDEFINEDCMD); } /* * ======== I2CUSCIB_hwiIntFxn ======== * Hwi interrupt handler to service the I2C peripheral * * The handler is a generic handler for a I2C object. */ void I2CUSCIB_hwiIntFxn(UArg index) { unsigned int key; uint8_t intStatus; I2CUSCIB_Object *object = (&I2C_config[index])->object; I2CUSCIB_HWAttrs const *hwAttrs = (&I2C_config[index])->hwAttrs; /* Get the interrupt status of the I2C controller */ intStatus = USCI_B_I2C_getInterruptStatus(hwAttrs->baseAddr,ALL_INTERRUPTS); Log_print1(Diags_USER2, "Status Reg: 0x%x", intStatus); /* Clear interrupt source */ USCI_B_I2C_clearInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); /* Check for I2C Errors */ if ((intStatus & ERROR_INTERRUPTS) || (object->mode == I2CUSCIB_ERROR)) { /* Some sort of error happened! */ object->mode = I2CUSCIB_ERROR; /* Try to send a STOP bit to end all I2C communications immediately * * USCI_B_I2C_masterReceiveMultiByteStop only sends a STOP condition * which is needed here */ USCI_B_I2C_masterReceiveMultiByteStop(hwAttrs->baseAddr); Log_print2(Diags_USER1, "I2C:(%p) ISR I2C Bus fault (Status Reg: 0x%x)", hwAttrs->baseAddr, intStatus); } /* No errors, now check what we need to do next */ switch (object->mode) { /* * ERROR case is OK because if an Error is detected, a STOP bit is * sent; which in turn will call another interrupt. This interrupt * call will then post the transferComplete semaphore to unblock the * I2C_transfer function */ case I2CUSCIB_ERROR: case I2CUSCIB_IDLE_MODE: I2CUSCIB_completeTransfer((I2C_Handle)&I2C_config[index]); break; case I2CUSCIB_WRITE_MODE: if (object->writeCountIdx) { /* If we're on the last byte to write and have data to read */ if ((object->writeCountIdx == 1) && (!object->readCountIdx)) { /* Next state: Idle mode */ object->mode = I2CUSCIB_IDLE_MODE; /* Send last byte with STOP bit */ USCI_B_I2C_masterSendMultiByteFinish(hwAttrs->baseAddr, *(object->writeBufIdx)); USCI_B_I2C_clearInterrupt(hwAttrs->baseAddr, USCI_B_I2C_TRANSMIT_INTERRUPT); Log_print2(Diags_USER2, "I2C:(%p) ISR I2CUSCIB_WRITE_MODE:" "Data to write: 0x%x;" "Writing w/ STOP bit", hwAttrs->baseAddr, *(object->writeBufIdx)); I2CUSCIB_completeTransfer((I2C_Handle)&I2C_config[index]); } else { /* Write data contents into data register */ USCI_B_I2C_masterSendMultiByteNext(hwAttrs->baseAddr, *(object->writeBufIdx)); Log_print3(Diags_USER2, "I2C:(%p) ISR I2CUSCIB_WRITE_MODE: " "Data to write: 0x%x; " "To slave: 0x%x", hwAttrs->baseAddr, *(object->writeBufIdx), object->currentTransaction->slaveAddress); object->writeBufIdx++; object->writeCountIdx--; } } /* * Done writing data to the I2C slave. If no data needed to be read, * the ISR would not get here as it would have finished the I2C * transfer in the logical check above. */ else { /* Next state: Receive mode */ object->mode = I2CUSCIB_READ_MODE; /* Switch into Receive mode */ USCI_B_I2C_setMode(hwAttrs->baseAddr, USCI_B_I2C_RECEIVE_MODE); if (object->readCountIdx > 1) { /* Send a repeated START without a STOP */ USCI_B_I2C_masterReceiveMultiByteStart(hwAttrs->baseAddr); Log_print1(Diags_USER2, "I2C:(%p) ISR " "I2CUSCIB_WRITE_MODE: -> I2CUSCIB_READ_MODE; " "Reading w/ RESTART", hwAttrs->baseAddr); } else { /* * Send a repeated START with a STOP since it's the * last byte to be read. */ USCI_B_I2C_masterReceiveSingleStart(hwAttrs->baseAddr,*(object->writeBufIdx)); Log_print1(Diags_USER2, "I2C:(%p) ISR " "I2CUSCIB_WRITE_MODE: -> I2CUSCIB_READ_MODE; " "Reading w/ RESTART and STOP", hwAttrs->baseAddr); } } break; /* I2CUSCIB_WRITE_MODE */ case I2CUSCIB_READ_MODE: /* Data read from RXBUF and next byte has already been shifted */ object->readCountIdx--; if (object->readCountIdx == 1) { /* Timing critical block which mustn't be simplified - START */ key = Hwi_disable(); USCI_B_I2C_masterReceiveMultiByteStop(hwAttrs->baseAddr); *(object->readBufIdx) = USCI_B_I2C_masterReceiveMultiByteNext(hwAttrs->baseAddr); Hwi_restore(key); /* Timing critical block which mustn't be simplified - END */ Log_print2(Diags_USER2, "I2C:(%p) ISR I2CUSCIB_READ_MODE: " "Read data byte: 0x%x ", hwAttrs->baseAddr, *(object->readBufIdx)); object->readBufIdx++; } else { *(object->readBufIdx) = USCI_B_I2C_masterReceiveMultiByteNext(hwAttrs->baseAddr); Log_print2(Diags_USER2, "I2C:(%p) ISR I2CUSCIB_READ_MODE: " "Read data byte: 0x%x ", hwAttrs->baseAddr, *(object->readBufIdx)); if (object->readCountIdx) { /* Increment buffer pointer */ object->readBufIdx++; } else { /* Next state: Idle mode */ object->mode = I2CUSCIB_IDLE_MODE; I2CUSCIB_completeTransfer((I2C_Handle)&I2C_config[index]); } } break; /* I2CUSCIB_READ_MODE */ default: object->mode = I2CUSCIB_ERROR; break; } return; } /* * ======== I2CUSCIB_completeTransfer ======= */ static void I2CUSCIB_completeTransfer(I2C_Handle handle) { I2CUSCIB_Object *object = handle->object; Log_print1(Diags_USER2, "I2C:(%p) ISR Transfer Complete", ((I2CUSCIB_HWAttrs const *)(handle->hwAttrs))->baseAddr); /* * Perform callback in a HWI context, thus any tasks or SWIs * made ready to run won't start until the interrupt has * finished */ object->transferCallbackFxn(handle, object->currentTransaction, !((bool)object->mode)); /* See if we need to process any other transactions */ if (object->headPtr == object->tailPtr) { /* No other transactions need to occur */ object->currentTransaction = NULL; object->headPtr = NULL; Log_print1(Diags_USER2,"I2C:(%p) ISR No other I2C transaction in queue", ((I2CUSCIB_HWAttrs const *)(handle->hwAttrs))->baseAddr); } else { /* Another transfer needs to take place */ object->headPtr = object->headPtr->nextPtr; Log_print2(Diags_USER2, "I2C:(%p) ISR Priming next I2C transaction " "(%p) from queue", ((I2CUSCIB_HWAttrs const *)(handle->hwAttrs))->baseAddr, (UArg)object->headPtr); I2CUSCIB_primeTransfer(object, (I2CUSCIB_HWAttrs const *)(handle->hwAttrs), object->headPtr); } return; } /* * ======== I2CUSCIB_init ======== */ void I2CUSCIB_init(I2C_Handle handle) { /* Mark the object as available */ ((I2CUSCIB_Object *)(handle->object))->isOpen = false; } /* * ======== I2CUSCIB_open ======== */ I2C_Handle I2CUSCIB_open(I2C_Handle handle, I2C_Params *params) { unsigned int key; uint32_t clockFreq; USCI_B_I2C_initMasterParam usciI2cParams; Semaphore_Params semParams; I2CUSCIB_Object *object = handle->object; I2CUSCIB_HWAttrs const *hwAttrs = handle->hwAttrs; /* Determine if the device index was already opened */ key = Hwi_disable(); if (object->isOpen == true) { Hwi_restore(key); return (NULL); } /* Mark the handle as being used */ object->isOpen = true; Hwi_restore(key); /* Store the I2C parameters */ if (params == NULL) { /* No params passed in, so use the defaults */ params = (I2C_Params *) &I2C_defaultParams; } /* Save parameters */ object->transferMode = params->transferMode; object->transferCallbackFxn = params->transferCallbackFxn; /* * Create threadsafe handles for this I2C peripheral * Semaphore to provide exclusive access to the I2C peripheral */ Semaphore_Params_init(&semParams); semParams.mode = Semaphore_Mode_BINARY; Semaphore_construct(&(object->mutex), 1, &semParams); /* * Store a callback function that posts the transfer complete * semaphore for synchronous mode */ if (object->transferMode == I2C_MODE_BLOCKING) { /* * Semaphore to cause the waiting task to block for the I2C * to finish */ Semaphore_construct(&(object->transferComplete), 0, &semParams); /* Store internal callback function */ object->transferCallbackFxn = I2CUSCIB_blockingCallback; } else { /* Check to see if a callback function was defined for async mode */ Assert_isTrue(object->transferCallbackFxn != NULL, NULL); } /* Get the I2C clock input frequency */ switch (hwAttrs->clockSource) { case USCI_B_I2C_CLOCKSOURCE_ACLK: clockFreq = ClockFreqs_getFrequency(ClockFreqs_Clock_ACLK); Log_print1(Diags_USER1, "ClockFreqs_getFrequency ACLK: %d", clockFreq); break; case USCI_B_I2C_CLOCKSOURCE_SMCLK: clockFreq = ClockFreqs_getFrequency(ClockFreqs_Clock_SMCLK); Log_print1(Diags_USER1, "ClockFreqs_getFrequency SMCLK: %d", clockFreq); break; default: Log_error0("I2C: Error determining clock source"); I2CUSCIB_close(handle); return (NULL); } /* Specify the idle state for this I2C peripheral */ object->mode = I2CUSCIB_IDLE_MODE; /* Clear the head pointer */ object->headPtr = NULL; object->tailPtr = NULL; Log_print1(Diags_USER1, "I2C: Object created 0x%x", hwAttrs->baseAddr); usciI2cParams.selectClockSource = hwAttrs->clockSource; usciI2cParams.i2cClk = clockFreq; usciI2cParams.dataRate = bitRate[params->bitRate]; USCI_B_I2C_initMaster(hwAttrs->baseAddr, &usciI2cParams); /* Clear any pending interrupts */ USCI_B_I2C_clearInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); /* Enable I2CUSCIB Module to start operations */ USCI_B_I2C_enable(hwAttrs->baseAddr); /* Unmask I2CUSCIB interrupts */ USCI_B_I2C_enableInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); /* Return the handle */ return (handle); } /* * ======== I2CUSCIB_primeTransfer ======= */ static void I2CUSCIB_primeTransfer(I2CUSCIB_Object *object, I2CUSCIB_HWAttrs const *hwAttrs, I2C_Transaction *transaction) { /* Store the new internal counters and pointers */ object->currentTransaction = transaction; object->writeBufIdx = transaction->writeBuf; object->writeCountIdx = transaction->writeCount; object->readBufIdx = transaction->readBuf; object->readCountIdx = transaction->readCount; Log_print2(Diags_USER1, "I2C:(%p) Starting transaction to slave: 0x%x", hwAttrs->baseAddr, object->currentTransaction->slaveAddress); /* Specify the I2C slave address */ USCI_B_I2C_setSlaveAddress(hwAttrs->baseAddr, object->currentTransaction->slaveAddress); /* Start transfer in Transmit mode */ if (object->writeCountIdx) { /* Update the I2C mode */ object->mode = I2CUSCIB_WRITE_MODE; USCI_B_I2C_setMode(hwAttrs->baseAddr, USCI_B_I2C_TRANSMIT_MODE); /* Start the I2C transfer by sending the start bit */ USCI_B_I2C_masterSendStart(hwAttrs->baseAddr); Log_print1(Diags_USER2, "I2C:(%p) I2CUSCIB_IDLE_MODE: -> I2CUSCIB_WRITE_MODE; " "Writing w/ START", hwAttrs->baseAddr); } /* Start transfer in Receive mode */ else { /* Update the I2C mode */ object->mode = I2CUSCIB_READ_MODE; USCI_B_I2C_setMode(hwAttrs->baseAddr, USCI_B_I2C_RECEIVE_MODE); if (object->readCountIdx > 1) { /* Start the I2C transfer in master receive mode */ USCI_B_I2C_masterReceiveMultiByteStart(hwAttrs->baseAddr); Log_print1(Diags_USER2, "I2C:(%p) I2CUSCIB_IDLE_MODE: -> I2CUSCIB_READ_MODE; " "Reading w/ ACK", hwAttrs->baseAddr); } else { /* * Start the I2C transfer in master receive mode by sending a START * and STOP bit */ USCI_B_I2C_masterReceiveSingleStart(hwAttrs->baseAddr,*(object->writeBufIdx)); Log_print1(Diags_USER2, "I2C:(%p) I2CUSCIB_IDLE_MODE: -> I2CUSCIB_READ_MODE; " "Reading w/ NACK", hwAttrs->baseAddr); } } } /* * ======== I2CUSCIB_transfer ======== */ bool I2CUSCIB_transfer(I2C_Handle handle, I2C_Transaction *transaction) { unsigned int key; bool ret = false; I2CUSCIB_Object *object = handle->object; I2CUSCIB_HWAttrs const *hwAttrs = handle->hwAttrs; /* Check if anything needs to be written or read */ if ((!transaction->writeCount) && (!transaction->readCount)) { /* Nothing to write or read */ return (ret); } if (object->transferMode == I2C_MODE_CALLBACK) { /* Check if a transfer is in progress */ key = Hwi_disable(); if (object->headPtr) { /* Transfer in progress */ /* * Update the message pointed by the tailPtr to point to the next * message in the queue */ object->tailPtr->nextPtr = transaction; /* Update the tailPtr to point to the last message */ object->tailPtr = transaction; /* I2C is still being used */ Hwi_restore(key); return (true); } else { /* Store the headPtr indicating I2C is in use */ object->headPtr = transaction; object->tailPtr = transaction; } Hwi_restore(key); } /* Acquire the lock for this particular I2C handle */ Semaphore_pend(Semaphore_handle(&(object->mutex)), BIOS_WAIT_FOREVER); /* * I2CUSCIB_primeTransfer is a longer process and * protection is needed from the I2C interrupt */ USCI_B_I2C_disableInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); I2CUSCIB_primeTransfer(object, hwAttrs, transaction); USCI_B_I2C_enableInterrupt(hwAttrs->baseAddr, ALL_INTERRUPTS); if (object->transferMode == I2C_MODE_BLOCKING) { Log_print1(Diags_USER1,"I2C:(%p) Pending on transferComplete semaphore", hwAttrs->baseAddr); /* * Wait for the transfer to complete here. * It's OK to block from here because the I2C's Hwi will unblock * upon errors */ Semaphore_pend(Semaphore_handle(&(object->transferComplete)), BIOS_WAIT_FOREVER); Log_print1(Diags_USER1, "I2C:(%p) Transaction completed", hwAttrs->baseAddr); /* Hwi handle has posted a 'transferComplete' check for Errors */ if (object->mode == I2CUSCIB_IDLE_MODE) { Log_print1(Diags_USER1, "I2C:(%p) Transfer OK", hwAttrs->baseAddr); ret = true; } } else { /* Always return true if in Asynchronous mode */ ret = true; } /* Release the lock for this particular I2C handle */ Semaphore_post(Semaphore_handle(&(object->mutex))); /* Return the number of bytes transfered by the I2C */ return (ret); } /* * ======== I2CUSCIB_blockingCallback ======== */ static void I2CUSCIB_blockingCallback(I2C_Handle handle, I2C_Transaction *msg, bool transferStatus) { I2CUSCIB_Object *object = handle->object; Log_print1(Diags_USER1, "I2C:(%p) posting transferComplete semaphore", ((I2CUSCIB_HWAttrs const *)(handle->hwAttrs))->baseAddr); /* Indicate transfer complete */ Semaphore_post(Semaphore_handle(&(object->transferComplete))); }