Experience Seamless Communication with WTD I2C Driver

WTD I2C Driver

WTD I2C drives are delivered as a functional project targeted for the request MCU. The Driver demo can be built with our ASCII based bi-directional shell application (3 wire TTL UART) allowing a channel for evaluation, test and debugging. Included with this deliver is our I2C DEMO board and custom harness targeting the MCU manufacture EVALUATION board. All connectors are provided and populated w/MCU manufacture EVALUATION board and fully tested.

  • Interfaces to all I2C devices are include:
  • NXP PCA9543 (2-way switch)
  • NXP PCA9539A 2-Port 16-Bit port expander
  • NXP PCF8523 RTC
  • NXP PCT2075 Temperature sensor
  • Micro Chip AT24256 EEPROM
A diagram of the computer system and its components.
A diagram of the layers of a computer.

I2C

Describes WTD I2C drivers. Compatible with BAREMETAL or RTOS runtime environment, multi-core and single core. Multi-master mode provides convenient networking for platforms with multiple MCUs, WTD I2C multi-master drives provide collision avoidance and collision detection with BUS reset functions. Detailed architecture available upon request.

Current MCU ports:

  • NXP

    • LPC8xx

    • S32K, K32

    • iMX

  • AURIX™

    • TC277/297/299 (non ADAS variants)

    • TC377/397/399 (non ADAS variants)

WTD I2C Driver

Source files:

  • platform\i2c\inc\i2cdriver.h
  • platform\i2c\inc\i2cmanager.h
  • platform\i2c\src\i2cdriver.c
  • platform\i2c\src\i2cmanager.c

The WTD I2C driver is composed of low-level and high-level components. Access to the routines of the low-level i2cdriver.c file are restricted to the high-level i2cmanager.c file. Access to the routines of i2cmanager.c is available to any application source file that needs to communicate over the I2C bus as a master/multi-master or slave. the I2C drives apply to Bare-Metal and RTOS runtime environments.

i2cdriver File

The lowest layer of the I2C driver is non-blocking and interrupt and/or DMA based. The peripheral is configured as a FIFO flow controller such that interrupt processing, combined with state variable information, can asynchronously control the I2C bus without application interaction. The upper i2cmanager layer initiates the read or write transaction and waits in a regularly executed state machine loop for a callback notification issued by an i2cdriver interrupt when the bus transaction is complete. For multi-core MCUs NOTE: the core that executes the i2cmanager state machine function must also be the same core that services the I2C interrupts and/or DMA. Core interrupt table and interrupt priority values are assigned in i2cdriver.h.

The following four functions are exported by i2cdriver.h:

  • int32_t i2cDriver_InitModule(I2cDrvModule_t module, boolean state)This routine enables or disables the selected I2C peripheral as per the boolean argument. The I2CCntrl_t structure is used to configure the driver and is hardcoded in the variable declarations near the top of the i2cdriver.c file. The i2cCntrl_t structure sets the I2C peripheral behaviour for the following features:
    • master/slave/multi-master mode
    • addressing
    • baudrate
    • pin assignment
    • peripheral kernel behaviour as FIFO flow controller
    • Interrupt
    • DMA
    • Transaction notification handler

    The maximum number of BYTES per bus transaction is configurable in i2cddriver.h:

    #define I2CDRV_TXD_RXD_BUFFER_LENGTH (0xnnu)

  • int32_t i2cDriver_Write(I2cDrvModule_t i2cModule, uint16_t u16Add, uint8_t, u8Size, uint8_t* pu8Data)This routine configures the driver transaction structure for a "eDrvWrite" operation and copies the left-shifted address byte (with RnW bit clear) and data payload to a static buffer. Peripheral kernel processing begins with a write of the total number of transmitted bytes to the MCUs I2C modlue control register. The "eDrvWrite" state of the transaction struct causes the ISR/DMA to copy 8-bit data from the static buffer to the modules TxD register. When the data has been exhausted by this or subsequent data transfers (slave ACKS all bytes sent) the peripheral will generate a protocol transmit complete interrupt. The protocol ISR then invokes the registered callback with a boolean status to notify the i2cmanager layer of transaction completion (TRUE=Success/FALSE=Failure).

    i2cModule Enumerated MCU I2C peripheral module
    u16Add uint16_t the device slave address
    u8Size uint8_t number of data BYTES to tranmit (includes slave register address)
    pu8Data uint8_t* pointer to TxD data buffer of length u8Size\

    Example: Write to device address 0x46 on module1 bus the value 0xBEEF starting at register address 0x2112

    uint8_t u8Buff = {0x21, 0x12, 0xBE, 0xEF};

    i2cDriver_Write(eDrvModule0, 0x0046, 4u, u8Buff);

  • int32_t i2cDriver_Read(I2cDrvModule_t i2cModule, uint16_t u16Add, uint8_t u8Size, uint8_t* pu8Data)This routine configures the driver transaction structure for a "eDrvRead" operation, with the number of bytes to be read from the slave and copies the left-shifted address byte (with RnW bit set) directly to the TXD register.
    Peripheral kernel processing begins with a write to the MCUs I2C module control register. The RxD interrupt interprets this "eDrvRead" operation as a write/read turnaround and updates the state of the transaction struct to "eDrvRecieve" so that subsequent RxD interrupts/DMA caused by slave writes will be interpreted as read operations whereby bus data is copied to the data pointer supplied by the caller. The slave continuously writes to the bus as long as the master issues ACK and clock signals. When the peripheral kernel determines that sufficient data has been successfully received it will generate a protocol interrupt with the TX_END bit set. This is handled in the protocol ISR which prompts the peripheral kernel to send a NACK to the slave to inform it of stoppage, set a stop condition on the bus and then reset it's state to listening mode. The protocol ISR then invokes the registered callback with a boolean status to notify the i2cmanager layer of transaction completion (TRUE=Success/FALSE=Failure).

i2cModule Enumerated MCU I2C peripheral module
u16Add uint16_t the device slave address
u8Size uint8_t number of data BYTES to receive (includes slave register address)
pu8Data uint8_t* pointer to data buffer of length u8Size\

Example: Read from device address 0x46 on module1 bus 16 bits from current index

uint8_t u8Buff = {0x00, 0x00};

i2cDriver_Read(eDrvModule0, 0x0046, 2u, u8Buff);

  • int32_t i2cDriver_RegisterTransactionResultCallback(boolean bState, I2cDrvModule_t module, I2CDRV_TransactionStatus lpCallBack)This routine passes the caller supplied function that is invoked in the peripheral protocol and error ISRs to notify the i2cmanager layer of the driver status. This routine is executed during the init phase of the i2cmanager state machine.The callback function signature is defined in i2cdriver.h as follows:

    typedef void (*I2CDRV_TransactionStatus)(boolean bStatus)

Interrupts

Interrupts are dependant on MCU manufacturer, WTD configures hardware to be transparent to the manager layer:

  • i2cDriver_I2CnErrorIsr

    If an bus or FIFO error occurs the registered callback function will be invoked with a FALSE argument to notify the i2cmanager state machine.

  • i2cDriver_I2CnProtocolIsr

    This interrupt captures the raw status of the I2C bus protocol:

    • RX Indicates the switch between the transmitting and receiving phase of a slave read operation. The .addOp element of the transaction is switched from "eDrvRead" to "eDrvRecieve" inside this ISR so that the data transfer interrupts will copy data from the bus to the transaction buffer.
    • TX_END Indicates the end of a successful data transfer. Further data transfer and protocol interrupts are disabled. The ENDDCTRL.B.SETEND bit is set inside this case of the ISR to notify the peripheral kernel to switch state and set the appropriate bus handling operation (ie: stop condition). The registered callback function is invoked with a TRUE argument to notify the i2cmanager state machine.
    • NACK Indicates the reception of a slave NACK terminating the transaction. The registered callback function is invoked with a FALSE argument to notify the i2cmanager state machine.
  • i2cDriver_I2CnxDIsr

    A data transfer interrupt is issued for tx or rx transactions when the data is availabe in the TxD or RxD buffers based on FIFO configuration. The transaction .addOp state determines whether the ISR is to copy a data byte from the transaction buffer to the bus ("eDrvWrite") or to copy a data byte from the bus to the transaction buffer ("eDrvRecieve").

  • i2cDriver_I2CnXdDMA

When DMA channes are used for either TxD and/or RxD bursts complete.

i2cmanager File

The high-level i2cmanager layer contains application accessible routines to open/close the I2C peripheral and to write/read data to/from connected I2C slave devices.

The i2cmanager layer manages a transaction FIFO queue and callback mechanism within a state machine that must be executed regularly and frequently to effectively operate the I2C peripheral. Prior to posting any transactions, the application must first initialize the state machine and the low-level driver with the following routine:

int32_t i2cmanager_Start(I2CMGRBus_t bus)

The WTD implementation executes the I2C state machine in a tight main loop of an core along with other state machines that require similar regular servicing. The I2C state machine is executed when the following function is called:

void i2cmanager_Run(void)

An active bus can be disabled with the following routine:

int32_t i2cmanager_Shutdown(I2CMGRBus_t bus)

A circular FIFO queue of I2CTransactionNode_t structures and a I2CBusCntrl_t structure array are declared in the variable section at the top of i2cmanager.c.
The size of the transaction queue is configurable in i2cmanager.h:

#define I2C_QUEUE_LENGTH (0xnnu)

There are three types of message transports supported; write, read and random read. Writes can be used in a synchronous 'send-and-forget' mode that does not wait for a callback function to return however the callback is useful for monitoring the status of the write, especially if subsequent bus operations are conditional on the success of the write. Reads likewise have a synchronous mode however this method should be avoided in a non-blocking implementation of the driver. Since I2C is a relatively slow bus, a read callback is the preferred method of passing data returned by a slave device to the application without blocking other CPU processes for significant amounts of time. Synchronization is typically accomplished with bit field manipulations of a globally scoped volatile semaphore variable using intrinsic 'test-and-set' macros.
When a transaction request is successfully placed in the queue a non-negative integer handle is returned that can be used to request transaction status or data. Read callbacks use this handle to obtain slave data via the following function:

void i2cmanager_GetReceivedData(I2CMGRBus_t bus, uint32_t u32Transaction, uint16_t u16Length, uint8_t* pu8Buffer)

Blocking reads can be implemented by using the following routine in a local state machine loop however this method is discouraged because it is exclusive of other bus users and other CPU processes.

I2CState_t i2cmanager_TransactionStatus(I2CMGRBus_t bus, uint32_t u32Transaction)

  • Write

    Write without callback notification:
    Note: when lpNotify == NULL lpParam is ignored.

    int32_t i2cmanager_QueueWrite(I2CMGRBus_t bus, uint16_t u16SlaveAddress, uint8_t u8Length, uint8_t* pu8Data, LpI2cCallBack lpNotify, void* lpParam)

    Example:

      int Send_And_Forget_Write(uint8_t addr, uint8_t data) {  
          uint8_t tx_buffer[2];  
          tx_buffer[0] = addr; 
          tx_buffer[1] = data;  
          return i2cmanager_QueueWrite(eI2cBus0, (uint16_t)addr, 2, tx_buffer, NULL, NULL);  
      }
    

    Write with callback notification:

    int32_t i2cmanager_QueueWrite(I2CMGRBus_t bus, uint16_t u16SlaveAddress, uint8_t u8Length, uint8_t* pu8Data, LpI2cCallBack lpNotify, void* lpParam)

  • Example:
      static void Write_Callback(boolean bStatus, void* lpParam) {
          /* Process synchronization via 'test-and-set' intrinsic with
          globally scoped semaphore */
          sem_Set(&global_flag_var, MY_WRITE_FLAG_BIT);
      }
    
      int Write_With_Callback(uint8_t addr) {  
          uint8_t tx_buffer[2];  
          tx_buffer[0] = addr; 
          tx_buffer[1] = 0xaa;  
          return i2cmanager_QueueWriteCb(eI2cBus0, (uint16_t)addr, 2, tx_buffer, Write_Callback, NULL);  
      }
    
  • Read

All I2C read functions require a Callback. If a null parameter is passed for lpNotify the request is dumped and failure is retruned form i2cmanager_QueueRead())

Read with callback notification (u8CommandLength = 0u)

int32_t i2cmanager_QueueRead(I2CMGRBus_t bus, uint16_t u16SlaveAddress, uint8_t u8CommandLength, uint8_t u8ReadLength, uint8_t* pu8Buffer, LpI2cCallBack lpNotify, void* lpParam)

The zero value of the u8CommandLength variable signifies that this read operation will not be preceeded by a write operation in the low-level driver to set the slave read address. This presupposes that the slave device has no addressing or a default address or that it retains an internal address set by a previous operation.

Example:

    static __far int32_t handle;
    static __far uint8_t read_buf[2];

    static void Read_MyNotify(boolean bStatus, void* lpParam) {
        if (bStatus == TRUE) {
            /* Copy received data to local global array */
            i2cmanager_GetReceivedData(eI2cBus0, handle, 2, read_buf);
        }
        /* Process synchronization via 'test-and-set' intrinsic with
        globally scoped semaphore */
        sem_Set(&global_flag_var, MY_READ_FLAG_BIT);            
    }

    int Read_With_Callback(uint8_t addr) {   
        handle = i2cmanager_QueueRead(eI2cBus0, (uint16_t)addr, 0, 2, NULL, Read_MyNotify, NULL);
        return handle;  
    }
  • Random ReadRead with callback notification (u8CommandLength != 0u)

    int32_t i2cmanager_QueueRead(I2CMGRBus_t bus, uint16_t u16SlaveAddress, uint8_t u8CommandLength, uint8_t u8ReadLength, uint8_t* pu8Buffer, LpI2cCallBack lpNotify, void* lpParam)

    The non-zero value of the u8CommandLength variable signifies that this read operation will be preceeded by a write operation in the low-level driver to set the slave read address (write/read turnaround). The following example shows the use of the I2CObjId_t structure for passing information to the notifyFunction.

    Example:

      static __far I2CObjId_t i2c_oid;
      static __far uint8_t read_buf[4];
    
      static void Rnd_Read_Notify(boolean bStatus, void* lpParam) {  
          I2CObjId_t* pParam = ((I2CObjId_t*)lpParam);
    
          if ((bStatus == TRUE) && (pParam != NULL)) {
              /* Copy received data to local global array */
              i2cmanager_GetReceivedData(eI2cBus0, pParam->i32Id, pParam->u8Length, read_buf);
          }
          /* Process synchronization via 'test-and-set' intrinsic with
          globally scoped semaphore */
          sem_Set(&global_flag_var, MY_READ_FLAG_BIT);            
      }
    
      int Random_Read_With_Callback(uint8_t addr, uint16_t dev_addr) {
          uint8_t write_buf[2];
    
          /* Convert 16-bit slave internal address to a byte array */
          write_buf[0] = (uint8_t)(dev_addr >> 8);
          write_buf[1] = (uint8_t)dev_addr;
          i2c_oid.u8Length = sizeof(read_buf);  
          i2c_oid.i32Id = i2cmanager_QueueReadCb(eI2cBus0, (uint16_t)addr, sizeof(write_buf), sizeof(read_buf), write_buf, Rnd_Read_Notify, (void*)&i2c_oid);
          return i2c_oid.i32Id;  
      }