Peripheral feature and how this peripheral works
The inter-integrated circuit(I2C) module provides a method of communication between a number of devices.
Features
- Protocol level support
- Compatible with The I2C-Bus Specification
- Support for System Management Bus (SMBus) Specification, version 2
- Multiple power modes
- Run mode, basic operation mode
- Wait mode, the module continues to operate when the core is in wait mode and can generate wakeup interrupt
- Stop mode, the module is inactive in stop mode with low power consumption, its registers remain unchanged unless stop mode wakeup on slave address match is enabled and address match happens which waking up MCU
- Support bus events generation and bus events detection with interrupt signals
- Bus busy/idle detection
- One byte transfer finish detection
- Acknowledge bit generation and detection
- START and STOP signal generation for master mode, detection for slave mode
- Repeated START signal generation for master mode, detection for slave mode
- Arbitration lost detection for master mode
- Address match, general call and alert response detection for slave mode
- Master operation
- If more than one master try to control I2C bus at the same time, clock synchronization procedure determines the clock frequency on SCL, data arbitration procedure on SDA determines masters' relative priority
- When arbitration lost happens during addressing, I2C module will switch to slave mode.
- Slave operation
- Flexible slave addressing mode, including 7-bit address, 10-bit address, secondary address and range address match
- Low power mode wakeup on slave address match
- General call listening
- Alert address listening for SMBus operation
- Software programmable features
- Software controlled ACK bit
- For I2C master mode, support up to 192 clock rate divider
- For I2C slave mode, support up to 192 SDA hold time and SCL start/stop hold time setting
- Programmable input glitch filter. Support up to 15 module clock cycles of signal glitch filter
- Optional high drive capacity of I2C pads
- Support holding off the enter to stop mode untll data transfer finishes on certain platforms
- Optional double buffer mode which disables the clock strech for high-speed mode on certain platforms
- DMA support
How this peripheral works
Once I2C module configuration is finished, and its clock is ungated, the I2C module is ready to initiate I2C transfer on bus as master or process bus events as slave.
When MCU is in stop mode, if a primary/range/general call address match occurs, and also the I2C module's low power wakeup is enabled, the 'addressed as slave' interrupt will be generated that wakes up the MCU.
How this driver is designed to make this peripheral works
On abstraction level, the I2C driver provides 2 parallel layers for different users with different requirements on flexibility: Functional layer and Transactional layer. Do not mix the usage of these 2 layers.
- Functional Layer is provided with highly optimized implementation and highly flexible usage of the peripheral features. All hardware features' configuration are supported. It requires user to have decent understanding of the hardware detail to know how to organize these functional APIs together to meet application requirement.
- Transactional layer is provided with high abstraction, limited flexibility/optimization, and not all features are covered. Their aim is to let user implement I2C transaction with least knowledge requirement of this specific I2C peripheral and least coding effort. It achieves this goal by hiddening the setup of interrupt processing inside driver and implementing complete software state machine for I2C transaction. To distinguish Transactional layer from Functional layer easily, all Transactional APIs have 'Transfer' in API name.
Functional Layer
- Preface
- Design purpose To provide various peripheral configuration and bus operation interfaces, covering all hardware features. Users can customize their code freely with functional APIs, especially when application has critical requirements of code size and performance.
- Advantages:
- Functional APIs are simple and optimized, with proper usage user can customize their code to be highly optimized
- Disadvantages:
- It requires user to have deep understanding of peripheral features, in order to organize functional APIs to meet the application requirements
- The functional APIs that each driver provides are peripheral specific, which adds the work load porting application to another platform
- Sub API Groups
- Module Init/Deinit Sub-group <ACTION item="" add="" ref="" here="" to="" jump="" to="" api="" group>="">
- Provide interfaces to initialize I2C module.
- Note
- For peripherals whose operations can be classified as master or slave, 3 Init APIs are provided. For use cases when I2C only operates as either master or slave, out of consideration of decreasing code size and better execution efficiency, user can call either I2C_MasterInit or I2C_SlaveInit to only configure master/slave related features. Otherwise, call I2C_Init to configure all features.
- Structure list
- Hardware Status Flags Sub-group <ACTION item="" add="" ref="" here="" to="" jump="" to="" api="" group>="">
- Provide interfaces to get and clear hardware status flags, such as address match flag, interrupt pending flag
- Enumeration list -_i2c_status_flags covers all I2C hardware status
- Interrupt Sub-group <ACTION item="" add="" ref="" here="" to="" jump="" to="" api="" group>="">
- Provide interfaces to enable, disable or get the enabled interrupt source, such as start detect interrupt.
- Peripheral Configuration Sub-group <ACTION item="" add="" ref="" here="" to="" jump="" to="" api="" group>="">
- Common Peripheral configuration: Provide interfaces to configure I2C peripheral hardware features used in both master and slave operation, such as high drive, stop hold.
- Master Peripheral configuration: Provide interfaces to configure I2C peripheral hardware features used only in master operation, such as baudrate configuration.
- Slave Peripheral configuration: Provide interfaces to configure I2C peripheral hardware features used only in slave operation, such as low power wake up.
- Bus Operation Sub-group <ACTION item="" add="" ref="" here="" to="" jump="" to="" api="" group>="">
- Common Bus Operation: Provide interfaces to execute bus operations applied in both master and slave mode, such send an ACK signal.
- Master Bus Operation: Provide interfaces to execute master bus operations, such as send a start/stop.
- Slave Bus Operation: Provide interfaces to execute slave operations, such as send a piece of data after address matched.
Transactional Layer
- Preface
- Design purpose To let user set up and carry out transfer quickly from the begining without worring about detailed hardware features and software operations. After transactional APIs are called, the whole transfer process is handled by driver internally without further operation from user until transfer ends.
- Advantages:
- User can perform transfer with these APIs without peripheral specific knowledge
- They are totally state retained, all bus operations and configurations are handled inside the driver which simplifies the user application code
- Transactional APIs use common programming model, different peripherals of same bus protocol have same usage on application level. This makes code integration and porting simplier
- Disadvantages:
- Transactional APIs are based on common programming model of I2C protocol, they are of little peripheral specific so they have average optimization on performance and code size
- Transactional APIs is provided with high abstraction with limited flexibility/optimization, so not all features are covered. For instance, 10-bit addressing mode is not supported
- 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 breif, 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 his/her own IRQ service routine, just define A in application as non-weak.
- Transfer handle In transactional layer, transfer handle(defined as i2c_master_transfer_handle_t for master and defined as i2c_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 I2C_MasterTransferCreateHandle or I2C_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 I2C’s life cycle.
- User callback and Transfer Handling Framework The Transactional Layer's IRQ service routine is hidden inside the driver, but user can still insert his/her own processing logic inside IRQ service using callback. User can code his/her own logic inside the callback function and register it into the transfer handle when calling I2C_MasterTransferCreateHandle or I2C_SlaveTransferCreateHandle.
- Master transfer. I2C master initiates and controls the I2C transfer on bus, so the Transactional layer helps user to do a full transaction using a strict software transfer state machine. The transfer state is maintained by transfer handle to indicate which state the transfer is at. During the transfer the bus event is driven by master itself so all hardware status and bus events are handled internally without user's involvement. Only when the transfer finishes the callback is invoked.
- Slave transfer. I2C slave monitors the I2C bus events, so unlike the master transfer logic, the Transactional layer helps user to do transaction in reactions to the detected bus event. Only a loose transfer state is maintained by transfer handle to indicate whether the bus is idle. A event-driven framework is implemented meaning callback is invoked to expose the event to user anytime a bus event is detected. User is expected to get involved in the transfer using call back, by deciding the following steps in reaction to the event.
- Sub API Groups
- Master Transfer Sub-group Provide interaces to initiate an I2C transfer as master, in polling way or interrupt way. They all have same Prefix 'I2C_MasterTransfer'
- Slave Transfer Sub-group Provide interaces to set I2C ready to monitor and handle any bus events as slave. Unlike master, as slave I2C must be ready to detect bus events at any time, we only provide I2C transfer in interrupt way as slave. They all have same prefix 'I2C_SlaveTransfer'
How to use this driver
API Return Types
APIs with non-void return have 3 return types: uint8_t/uint32_t, bool and status_t. All APIs that issue bus event during execution, including Functional APIs from Bus Operation Sub-group and all transactional APIs, all use status_t as their return type. The return tells user the bus's current status as the result of API's execution. <ACTION item="" add="" ref="" here="" to="" jump="" to="" anonymous="" enum="" definition="" of="" the="" status>="">
General Control Macro
- FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL SDK genneral Macro, controls whether to ungate/gate peripheral's clock source inside driver. Set it to none-zero value then driver will not ungate/gate clock source during initialization/de-initialization.
- I2C_RETRY_TIMES I2C specific Macro, controls the retry times when checking the module's status flags. During I2C transfer, at certain stages program needs to wait for some flag to be set/cleared then folowing steps can proceed. Set it to non-zero value then driver retries the check I2C_RETRY_TIMES. If it counts down to zero but the flag remains unchanged, API returns kStatus_I2C_Timeout. Set it to zero then the driver keeps waiting until the flag changes.
- I2C_SMBUS_ENABLE <ACTION item="" support="" smbus="" feature="" in="" transactional="" apis>=""> I2C specific Macro, controls whether to use this driver in SMBus mode. Set it to zero if user wish to use driver for simple I2C transfer.
- I2C_MASTER_FACK_CONTROL I2C specific Macro, controls whether to enable FACK feature in simple I2C transfer. For MCUs whose I2C has double buffer support and its double buffer is enabled, set it to non-zero value can lower the transfer speed by clock stretch.
Configuration Items Before Calling I2C Driver APIs
- Mux the I2C SDA/SCL signals to on-board pins, and configure them with expected feature.
- Select and attach proper clock source to I2C module.
- Ungate the I2C clock if user wish to do it outside driver by setting FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL to non-zero value.
- If user wish to use interrupt for I2C transfer while other modules' interrupt sources are also enabled, set proper interrupt priority before initiating I2C transfer, so that I2C IRQ service can be properly executed.
Functional layer usage model
- Configuration Items Before Calling Driver APIs
- Except the items described in the general part, define the I2C interrupt entry in the application code if interrupt is needed for this peripheral usage.
- Call flow
Transactional layer usage model
- Call flow for polling master transfer
- Call flow for interrupt transfer
Typical Use Cases
These code snipets shows the general calling sequence to use functional APIs and transactional APIs. For detail usage on appilcation level, refer to I2C driver examples in SDK package under boards/<board_name>/driver_examples/i2c.
- Functional APIs <ACTION item="" add="" ref="" here="" to="" jump="" to="" i2c="" driver="" examples>="">
*
*
*
* uint8_t master_txData[3] = {1,2,3};
* kI2C_ReceiveNakFlag)))
* {
* }
* else
* {
* }
*
*
* uint8_t slave_rxData[3] = {};
*
- Transactional APIs
- Master transfer
*
*
*
* uint8_t master_txBuff[32];
* sMasterTransfer.u32Command = NULL;
* sMasterTransfer.
pu8Data = master_txBuff;
* sMasterTransfer.dataSize = 32;
*
*
*
*
* i2c_master_transfer_handle_t master_handle;
*
- Slave transfer
*
*
*
* uint8_t slave_Buff[32];
* sSlaveTransfer.
pu8Data = slave_Buff;
* sSlaveTransfer.dataSize = 32;
*
*
* i2c_slave_transfer_handle_t slave_handle;
*