Peripheral feature and how this peripheral works
The LPI2C is a low power Inter-Integrated Circuit(I2C) module that supports an efficient interface to an I2C bus as a master and/or as a slave.
Features
- Communication
- Standard, Fast, Fast+ and Ultra Fast modes are supported
- Clock stretching and arbitration
- Master operation
- 4words Command/transmit FIFO
- 4words receive FIFO
- Host request input to trigger the start of an I2C bus transfer
- Data match feature with flexible setting to discard unwanted data
- Configurable bus idle timeout and pin-stuck-low timeout
- Multi-master support, including synchronization and arbitration. Multi-master means any number of master nodes can be present. Additionally, master and slave roles may be changed between messages(after a STOP is sent)
- Slave operation
- Separate I2C slave registers to minimise software overhead because of master/slave switching
- Software-controllable ACK or NACK, with optional clock stretching on ACK/NACK bit
- Configurable clock stretching, to avoid transmit FIFO underrun and receive FIFO overrun errors
How this driver is designed to make this peripheral works
On abstraction level, the LPI2C driver provides two parallel layers for various cases with different requirements on flexibility: Functional layer and Transactional layer. Do not mix the usage of the data transfer APIs in these 2 layers, check the transaction APIs introduction in 'How to use this driver' part. To distinguish Transactional layer from Functional layer easily, all Transactional APIs take 'Transfer' in API name.
Functional Layer
It's provided with highly optimized implementation and highly flexible usage of the hardware details. User needs to know how to organize these functional APIs together to meet application requirement. The LPI2C driver provides master and slave feature enabling APIs named as LPI2C_MasterXxx and LPI2C_SlaveXxx, user can choose which needed in their application and use related interfaces. These APIs implement to configure basic features of LPI2C, they're highly separated according to different features. Application just needs to combine them on demand. For data transfer, there're also multiple APIs to finish the whole I2C transfer: START/REPEAT_START, SEND/RECEIVE and STOP.
Transactional Layer
It's provided with high abstraction, limited flexibility/optimization, and not all features are covered. It aims to let user implement LPI2C transaction with least knowledge requirement of this specific I2C peripheral and least coding effort. It achieves this goal by hiding the setup of interrupt processing inside driver and implementing software state machine for LPI2C transaction.
- Software Implementation Logic
- Double Weak Mechanism One of the key features of Transactional layer compared to Functional layer is user need not setup interrupt handling codes, but can still get IRQ service function executed when interrupt happens. This is achieved by the 'double weak' mechanism. Refer to the general section of API Reference Manual for detail. In brief, SDK place a weak function A for this peripheral's vector entry. The default implementation of this function calls another weak function B. By default, when a interrupt happen, the code will be executed as A (Weak) -> B (Weak). This driver implements the function B as non-weak inside, thus once user gets the driver file into the application project, when interrupt happens, code will be executed as A (Weak) -> B (Non-Weak, in this driver). If user wants to implement customized IRQ service routine, just define function A in application as non-weak.
- Transfer handle In transactional layer, transfer handle(defined as lpi2c_master_transfer_handle_t for master and defined as lpi2c_slave_transfer_handle_t for slave)is used to represent the instance. It contains not only the instance's base pointer and user callback, but also the configuration for the transfer and transfer context. So each time when entering the ISR, the program would know what to do according to last transfer state stored in the handle. Transactional layer is state retained thus user should not try to modify its member in application level. User need to initialize the handle before initiating any transfer by calling LPI2C_MasterTransferCreateHandle or LPI2C_SlaveTransferCreateHandle. Due to the handle's special role for transactional layer, after user allocates the transfer handle and installs it using create handle API, user should maintain the handle's memory section so it is active during the LPI2C's life cycle.
How to use this driver
General Control Macro
- FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL SDK general Macro, controls whether to ungate/gate peripheral's clock source inside driver. Set it non-zero value then driver will not ungate/gate clock source during init/deinit. User needs to handle clock configuration in application code.
- I2C_RETRY_TIMES When LPI2C transmit/receive data, need to wait for status flags. Set I2C_RETRY_TIMES as a non-zero value to return kStatus_LPI2C_Timeout if wait flags for setting times and flags have no changed. Driver will always wait certain flag if I2C_RETRY_TIMES is zero as default.
- I2C_SMBUS_ENABLE Set this macro to 1 to enable SMBus supporting.
Configuration Items Before Calling LPI2C Driver APIs
- Configure LPI2C peripheral clock tree.
- LPI2C pin mux and pad attribute should be set on demand.
- Disable the LPI2C clock control in driver through define FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL as 1 if user wants to enable/disable LPI2C clock outside driver.
- Set appropriate LPI2C interrupt priority in the project environment of lots of peripherals enabling their interrupts and needing to execute handler code.
The General Steps To Use LPI2C Driver
Common:
Function LPI2C_Init initialises I2C master and slave operations together, user can call LPI2C_GetDefaultConfig to get default setting and give this to LPI2C_Init to enable basic features for master and slave. If user only wants to use one of master/slave, please call LPI2C_MasterInit or LPI2C_SlaveInit to finish specified initialization work. Two similar LPI2C_xxxGetDefaultConfig APIs are also ready for master and slave, more information is in the following comments. If user wants to disable master or/and slave, there're LPI2C_Deinit, LPI2C_MasterDeinit and LPI2C_SlaveDeinit to do it.
- Note
- If FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL is not enabled, LPI2C clock will be disabled in Master/Slave Deinit function.
Master Operation:
- Set suitable value in structure _lpi2c_master_config to configure basic feature of LPI2C master mode, please check the comments in this structure to get more details. There's an API LPI2C_MasterGetDefaultConfig to get default setting covering conventional demand. User should set it according to specific application.
- Call LPI2C_MasterInit to initialise LPI2C peripheral which should enable features based on _lpi2c_master_config.
- Driver provides more APIs to configure special features except what LPI2C_MasterInit has done. Such as LPI2C_MasterSetDataMatch and LPI2C_MasterSetBaudRate, please read separate function description to know more. The benefit is these APIs can be used to enable or disable related features after LPI2C has been initialised.
- Note
- Just be careful these APIs will disable LPI2C master before setting peripheral register and enable after that, should call them when LPI2C master is idle.
For transaction, LPI2C driver provides three ways in two layers to transmit/receive I2C data with master.
- Functional layer
A group of APIs LPI2C_MasterStart, LPI2C_MasterRepeatedStart, LPI2C_MasterReceive, LPI2C_MasterSend and LPI2C_MasterStop to implement each procedure of I2C transfer.
- LPI2C_MasterStart It will check whether the I2C bus is busy(SCL low level) and wait for enough space in LPI2C Tx FIFO. If all transfer conditions are OK, SDA line will pull low to start I2C transfer and send out 7-bit slave address + 1-bit read/write direct.
- LPI2C_MasterRepeatedStart It just encapsulates LPI2C_MasterStart with a new name. It's better to use this API when starting a new transfer without stopping last one, this name is easier to understand.
- LPI2C_MasterReceive, LPI2C_MasterSend They will check FIFO space situation, and start to send or receive a bundle of data with I2C.
- LPI2C_MasterStop It releases SCL to high level to stop the I2C transfer.
- Transactional layer Blocking Transfer
- LPI2C_MasterTransferBlocking It sends/receives data according the parameters in lpi2c_master_transfer_t, it returns after the transfer is over or getting errors.
- lpi2c_master_transfer_t
Member | Comment |
u8ControlFlagMask | _lpi2c_master_transfer_control_flags |
u8SlaveAddress | The 7-bit slave address |
eDirection | Read or write lpi2c_data_direction_t |
pu8Command | Pointer to command code |
u8CommandSize | Length of command buffer to send in bytes |
pData | I2C transfer data buffer address |
u16DataSize | Number of bytes to transfer |
Uses u8ControlFlagMask, control to add/remove start/stop operation in this transfer. The eDirection indicates transfer is reading or writing. They make this API flexible to be used for sending or receiving. The u8SlaveAddress is I2C slave device address following the start flag. The pu8Command is usually used when user reads/writes I2C data from/to an external device such as EEPROM. If use Functional layer API to send this external device address, need to populate it into data buffer. With this API user can store it in pu8Command, it's more intuitive and easier to use. It can also take other commands for various I2C device. The u8CommandSize indicates byte size in pu8Command address. The pData and u16DataSize are the most parameters needed by any transfer without more mention.
- Transactional layer NonBlocking Transfer
- LPI2C_MasterTransferNonBlocking It also uses lpi2c_master_transfer_t to set transfer information, this part of usage is same as LPI2C_MasterTransferBlocking. But this API returns immediately without waiting the transfer over, it just starts the transfer and let interrupt handler in driver to finish transfer. Another difference is an extra parameter lpi2c_master_transfer_handle_t is used by this API, interrupt handler code needs this structure, but user needn't care about it and only need to call LPI2C_MasterTransferCreateHandle to initialize it then give it to LPI2C_MasterTransferNonBlocking. Meanwhile lpi2c_master_transfer_callback_t should be defined and given to LPI2C_MasterTransferCreateHandle if user hopes to get the transfer over status. Interrupt handler in driver will call this callback after the transfer is over or getting errors, please check related code snippet in Typical Use Case below to know more.
- LPI2C_MasterTransferAbort This API can be used when user wants to abort on-going transfer in Transactional layer NonBlocking Transfer.
- Note
- If system enters debug mode when I2C transfer is on-going, this transfer will abort. If want to debug LPI2C and not stop normal transfer, need to set #bDebugEnable as true.
Slave Operation:
- Set suitable value in structure _lpi2c_slave_config to configure basic feature of LPI2C slave mode, please check the comments in this structure to get more details. There's an API LPI2C_SlaveGetDefaultConfig to get default setting covering conventional demand. User should set it according to specific application.
- Call LPI2C_SlaveInit to initialise LPI2C peripheral which should enable features based on _lpi2c_master_config.
- For transaction, this LPI2C driver provides two ways in two layers to transmit/receive I2C data with slave.
- Functional layer
A group of APIs LPI2C_SlaveSend, LPI2C_SlaveReceive to implement I2C slave feature.
- LPI2C_SlaveGetStatusFlags Uses it to check whether the #kLPI2C_SlaveAddressInterruptValidFlag is set which means master device start a transfer with this slave device address.
- LPI2C_SlaveSend It will send a bundle of data with I2C when master side is ready to receive.
- LPI2C_SlaveReceive It will receive a bundle of data with I2C when master side is ready to send.
- Transactional layer
LPI2C_SlaveTransferNonBlocking This function returns immediately without waiting the transfer over, it just starts the transfer and let interrupt handler code in driver to manage transfer. Before using this API, LPI2C_SlaveTransferCreateHandle needs to be called to initialise the handler structure lpi2c_slave_transfer_handle_t and callback lpi2c_slave_transfer_callback_t. The actual transfer is on-going in LPI2C_SlaveTransferHandleIRQ and will call callback function according to transfer states, so then user needs to put important transfer information into lpi2c_slave_transfer_t in this user-defined callback function to finish transfer.
– lpi2c_slave_transfer_t eEvent lpi2c_slave_transfer_event_t, reason the callback is invoked u16ReceivedAddress Matching address send by master. pu8Data Transfer buffer u16DataSize Transfer size completionStatus Success or error code after transfer completed. u16TransferredCount Number of bytes actually transferred since start or last repeated start In callback, check eEvent to know various event in whole transfer then handle them. The u16ReceivedAddress stores the slave address sent by master when eEvent is kLPI2C_SlaveAddressMatchEvent The pu8Data and u16DataSize are the most important information in slave transfer, they indicate ready sent data buffer when eEvent is kLPI2C_SlaveTransmitEvent, indicate received data buffer when eEvent is kLPI2C_SlaveReceiveEvent. There're other events like kLPI2C_SlaveCompletionEvent, user need to manage these events according to specific situation to implement whole transfer, please check related code snippet about this API in 'Typical Use Case' below to know general usage.
- LPI2C_SlaveTransferAbort This API can be used when user wants to abort on-going transfer in Transactional layer.
Typical Use Case
Master Operation:
- Functional layer
* uint8_t master_txbuff[10] = {0};
* uint8_t master_rxbuff[10] = {0};
*
- Transactional layer Blocking Transfer
* lpi2c_master_transfer_t sMasterXfer = {0};
* uint8_t master_txbuff[10] = {0};
* uint8_t master_rxbuff[10] = {0};
* sMasterXfer.u8SlaveAddress = I2C_MASTER_SLAVE_ADDR_7BIT;
* sMasterXfer.pu8Command = NULL;
* sMasterXfer.u8CommandSize = 0;
* sMasterXfer.pData = master_txbuff;
* sMasterXfer.u16DataSize = 10;
* sMasterXfer.pData = master_rxbuff;
*
- Transactional layer NonBlocking Transfer
* lpi2c_master_transfer_handle_t g_m_handle;
* static void lpi2c_master_callback(lpi2c_master_transfer_handle_t *psHandle)
* {
* g_MasterCompletionFlag = true;
* }
*
* App_Function
* {
* lpi2c_master_transfer_t sMasterXfer = {0};
* uint8_t master_txbuff[10] = {0};
* uint8_t master_rxbuff[10] = {0};
* sMasterXfer.u8SlaveAddress = I2C_MASTER_SLAVE_ADDR_7BIT;
* sMasterXfer.pu8Command = NULL;
* sMasterXfer.u8CommandSize = 0;
* sMasterXfer.pData = master_txbuff;
* sMasterXfer.u16DataSize = 10;
* while (!g_MasterCompletionFlag)
* {
* }
* }
*
Slave Operation:
- Functional layer
* sSlaveConfig.u8Address0 = I2C_MASTER_SLAVE_ADDR_7BIT;
* {
* }
* {
* }
*
- Transactional layer
* volatile bool g_SlaveCompletionFlag = false;
* lpi2c_slave_transfer_handle_t g_s_handle = {0};
*
* static void lpi2c_slave_callback(lpi2c_slave_transfer_handle_t *psHandle)
* {
* switch (LPI2C_GET_SLAVE_TRANSFER_EVENT(psHandle))
* {
* LPI2C_GET_SLAVE_TRANSFER_DATA_POINTER(psHandle) = NULL;
* LPI2C_GET_SLAVE_TRANSFER_DATASIZE(psHandle) = 0;
* break;
* LPI2C_GET_SLAVE_TRANSFER_DATA_POINTER(psHandle) = &g_slave_buff[2];
* LPI2C_GET_SLAVE_TRANSFER_DATASIZE(psHandle) = g_slave_buff[1];
* break;
* LPI2C_GET_SLAVE_TRANSFER_DATA_POINTER(psHandle) = g_slave_buff;
* LPI2C_GET_SLAVE_TRANSFER_DATASIZE(psHandle) = I2C_DATA_LENGTH;
* break;
* g_SlaveCompletionFlag = true;
* LPI2C_GET_SLAVE_TRANSFER_DATA_POINTER(psHandle) = NULL;
* LPI2C_GET_SLAVE_TRANSFER_DATASIZE(psHandle) = 0;
* break;
* default:
* g_SlaveCompletionFlag = false;
* break;
* }
* }
*
* App_Function
* {
* sSlaveConfig.u8Address0 = I2C_MASTER_SLAVE_ADDR_7BIT;
* while (!g_SlaveCompletionFlag)
* {
* }
* }
*
EXCEPTION: SMBus is not supported in this driver.