5.11 I2C Guide
This chapter describes how to use the I2C driver and examples in the N32G41x standard peripheral library. The driver is provided by n32g41x_i2c.h and n32g41x_i2c.c with 46 public functions that cover reset and initialization, timing and address configuration, data transfer, PEC checking, DMA transfer, digital and analog filtering, interrupts and flags, and timeout detection; the projects/n32g41x_EVAL/examples/I2C/ directory holds 10 projects that demonstrate polling, interrupt and DMA EEPROM access, master/slave and 10-bit addressing, and PMBus master/slave communication. Read the speed, address format, filter and timeout details together with the “Internal Integrated Circuit Bus (I2C)” chapter of the user manual, and take the errata sheet as the authority on known chip issues.
5.11.1 Related files
| Relative path | Purpose |
|---|---|
firmware/n32g41x_std_periph_driver/inc/n32g41x_i2c.h |
Defines the I2C_InitParam structure and the bus-mode, address-mode, speed, ACK, PEC, filter and timeout macros, and declares all 46 I2C functions |
firmware/n32g41x_std_periph_driver/src/n32g41x_i2c.c |
Implements those 46 functions: peripheral reset and initialization, start/stop conditions, address and data register access, PEC computation, DMA enable, digital and analog filter configuration, interrupt and flag operations, and timeout detection |
projects/n32g41x_EVAL/examples/I2C/ |
I2C example root directory, with 10 examples: EEPROM_DMA, EEPROM_Int, EEPROM_Polling, I2C_10bit, I2C_Master, I2C_Master_Int, I2C_Slave, I2C_Slave_Int, PMBUS_Master, PMBUS_Slave |
projects/n32g41x_EVAL/examples/I2C/EEPROM_Polling/ |
Project layout of that example: MDK-ARM/, EWARM/, inc/, src/, readme.txt |
projects/n32g41x_EVAL/bsp/ |
Board support files shared by the examples: log output and millisecond delay functions |
5.11.2 API reference
The following 46 public functions are listed in the declaration order of n32g41x_i2c.h in 9 groups; each entry gives a summary and the original comment above the function in n32g41x_i2c.c.
Reset and initialization
I2C_DeInit, I2C_Init, I2C_InitStruct
Clock and enable
I2C_ConfigFastModeDutyCycle, I2C_Enable, I2C_GetRegister
Timing and address configuration
I2C_GenerateStart, I2C_GenerateStop, I2C_ConfigAck, I2C_ConfigDatacktc, I2C_SendAddr7bit, I2C_ConfigOwnAddr2, I2C_EnableDualAddr, I2C_EnableGeneralCall, I2C_ConfigNackLocation, I2C_ConfigSmbusAlert, I2C_EnableArp, I2C_EnableExtendClk, I2C_EnableSoftwareReset
Data transfer
I2C_SendData, I2C_RecvData
PEC check
I2C_ConfigPecLocation, I2C_SendPEC, I2C_ComputePec, I2C_GetPec
DMA transfer
I2C_EnableDMA, I2C_EnableDmaLastSend
Digital and analog filtering
I2C_SetSDADigitalFilterWidth, I2C_SetSCLDigitalFilterWidth, I2C_SetSDAAnalogFilterWidth, I2C_EnableSDAAnalogFilter, I2C_SetSCLAnalogFilterWidth, I2C_EnableSCLAnalogFilter
Interrupts and flags
I2C_ConfigInt, I2C_CheckEvent, I2C_GetLastEvent, I2C_GetFlag, I2C_ClrFlag, I2C_GetIntStatus, I2C_ClrIntPendingBit
Timeout detection
I2C_SetTimeoutLowLimit, I2C_SetTimeoutHighLimit, I2C_ConfigTimeoutInt, I2C_EnableTimeoutDetect, I2C_GetTimeoutFlag, I2C_ClrTimeoutFlag
void I2C_DeInit(const I2C_Module *I2Cx)Deinitializes the I2Cx peripheral registers to their default reset values.
name I2C_DeInit.
fun Deinitializes the I2Cx peripheral registers to their default reset values.
param I2Cx :
- I2C1
- I2C2
return none
void I2C_Init(I2C_Module *I2Cx, const I2C_InitType *I2C_InitParam)Initializes the I2Cx peripheral according to the specified parameters in the I2C_InitParam.
name I2C_Init.
fun Initializes the I2Cx peripheral according to the specified parameters in the I2C_InitParam.
param I2Cx :
- I2C1
- I2C2
param I2C_InitParam :
- ClkSpeed(common speed:100000(100KHz) - 400000(400KHz) - 1000000(1MHz)):
- between 1 to 1000000
- BusMode:
- I2C_BUSMODE_I2C
- I2C_BUSMODE_SMBDEVICE
- I2C_BUSMODE_SMBHOST
- FmDutyCycle(If the ClkSpeed does not exceed 100KHz,duty is fixed at 1:1,
otherwise it can be configured as 2:1 or 16:9):
- I2C_FMDUTYCYCLE_16_9
- I2C_FMDUTYCYCLE_2
- OwnAddr1:
- between 0 to 0x3FF
- AckEnable:
- I2C_ACKEN
- I2C_ACKDIS
- AddrMode:
- I2C_ADDR_MODE_7BIT
- I2C_ADDR_MODE_10BIT
return none
void I2C_InitStruct(I2C_InitType *I2C_InitParam)Fills each I2C_InitParam member with its default value.
name I2C_InitStruct.
fun Fills each I2C_InitParam member with its default value.
param (The input parameters must be the following values):
- ClkSpeed
- BusMode
- FmDutyCycle
- OwnAddr1
- AckEnable
- AddrMode
return none
void I2C_ConfigFastModeDutyCycle(I2C_Module *I2Cx, uint32_t DutyCycle)Selects the specified I2C fast mode duty cycle.
name I2C_ConfigFastModeDutyCycle.
fun Selects the specified I2C fast mode duty cycle.
param I2Cx :
- I2C1
- I2C2
param DutyCycle :
- I2C_FMDUTYCYCLE_16_9
- I2C_FMDUTYCYCLE_2
return none
void I2C_Enable(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C peripheral.
name I2C_Enable.
fun Enables or disables the specified I2C peripheral.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
uint32_t I2C_GetRegister(const I2C_Module *I2Cx, uint8_t I2C_Register)Reads the specified I2C register and returns its value.
name I2C_GetRegister.
fun Reads the specified I2C register and returns its value.
param I2Cx :
- I2C1
- I2C2
param I2C_Register :
- I2C_REG_CTRL1 CTRL1 register
- I2C_REG_CTRL2 CTRL2 register
- I2C_REG_OADDR1 OADDR1 register
- I2C_REG_OADDR2 OADDR2 register
- I2C_REG_DAT DAT register
- I2C_REG_STS1 STS1 register
- I2C_REG_STS2 STS2 register
- I2C_REG_CLKCTRL CLKCTRL register
- I2C_REG_TMRISE TMRISE register
- I2C_REG_GFLTRCTRL GFLTRCTRL register
return The value of the selected register.
void I2C_GenerateStart(I2C_Module *I2Cx, FunctionalState Cmd)Enable or disables Generate a START condition.
name I2C_GenerateStart.
fun Enable or disables Generate a START condition.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_GenerateStop(I2C_Module *I2Cx, FunctionalState Cmd)Enable or disables Generate a STOP condition.
name I2C_GenerateStop.
fun Enable or disables Generate a STOP condition.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_ConfigAck(I2C_Module *I2Cx, FunctionalState Cmd)Enable or disables the acknowledgement.
name I2C_ConfigAck.
fun Enable or disables the acknowledgement.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_ConfigDatacktc(I2C_Module *I2Cx, FunctionalState Cmd)Enable or disables the Datacktc.
name I2C_ConfigDatacktc.
fun Enable or disables the Datacktc.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_SendAddr7bit(I2C_Module *I2Cx, uint8_t address, uint8_t mode)Transmits the address byte to select the slave device.
name I2C_SendAddr7bit.
fun Transmits the address byte to select the slave device.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param address (The input parameters must be the following values):
- 0 to 0xFF
param mode (The input parameters must be the following values):
- I2C_DIRECTION_SEND Transmitter mode
- I2C_DIRECTION_RECV Receiver mode
return none
void I2C_ConfigOwnAddr2(I2C_Module *I2Cx, uint8_t address)Set the specified I2C own address2.
name I2C_ConfigOwnAddr2.
fun Set the specified I2C own address2.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param address(The input parameters must be the following values)
- 0 to 0xFF
return none
void I2C_EnableDualAddr(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C dual addressing mode.
name I2C_EnableDualAddr.
fun Enables or disables the specified I2C dual addressing mode.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_EnableGeneralCall(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C general call feature.
name I2C_EnableGeneralCall.
fun Enables or disables the specified I2C general call feature.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_ConfigNackLocation(I2C_Module *I2Cx, uint32_t position)Selects the specified I2C NACK position in master receiver mode. This function is useful in I2C Master Receiver mode when the number of data to be received is equal to 2. In this case, this function should be called (with parameter I2C_NACK_POS_NEXT) before data reception starts,as described in the 2-byte reception procedure recommended in User Manual in Section: Master receiver.
name I2C_ConfigNackLocation.
fun Selects the specified I2C NACK position in master receiver mode.
This function is useful in I2C Master Receiver mode when the number
of data to be received is equal to 2. In this case, this function
should be called (with parameter I2C_NACK_POS_NEXT) before data
reception starts,as described in the 2-byte reception procedure
recommended in User Manual in Section: Master receiver.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param position (The input parameters must be the following values):
- I2C_NACK_POS_NEXT Indicates that the next byte will be the last received byte.
- I2C_NACK_POS_CURRENT Indicates that the current byte is the last received byte.
return none
note ACKPOS bit can only be used in 2-byte receiving configuration and must be configured before
receiving data.
For the second byte of NACK, the I2C_CTRL1.ACKEN bit must be cleared after the
I2C_STS1.ADDRF bit is cleared.
To detect the PEC of the second byte, the I2C_CTRL1.PEC bit must be set after the ACKPOS bit is
configured and when the ADDR event is extended.
void I2C_ConfigSmbusAlert(I2C_Module *I2Cx, uint32_t polarity)Set the SMBus Alert pin high or low for the specified I2C.
name I2C_ConfigSmbusAlert.
fun Set the SMBus Alert pin high or low for the specified I2C.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param polarity (The input parameters must be the following values):
- I2C_SMBALERT_LOW SMBAlert pin driven low
- I2C_SMBALERT_HIGH SMBAlert pin driven high
return none
void I2C_EnableArp(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C Address Resolution Protocol(ARP).
name I2C_EnableArp.
fun Enables or disables the specified I2C Address Resolution Protocol(ARP).
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_EnableExtendClk(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C clock extending.
name I2C_EnableExtendClk.
fun Enables or disables the specified I2C clock extending.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_EnableSoftwareReset(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C software reset.
name I2C_EnableSoftwareReset.
fun Enables or disables the specified I2C software reset.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
note This bit can be used when the I2C_STS2.BUSY bit is set to 1 and no stop condition is detected
on the bus.
void I2C_SendData(I2C_Module *I2Cx, uint8_t data)Sends a data byte through the I2Cx peripheral.
name I2C_SendData.
fun Sends a data byte through the I2Cx peripheral.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param data: 0x00~0xFF
return none
uint8_t I2C_RecvData(const I2C_Module *I2Cx)Returns the most recent received data by the I2Cx peripheral.
name I2C_RecvData.
fun Returns the most recent received data by the I2Cx peripheral.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
return The value of the received data
void I2C_ConfigPecLocation(I2C_Module *I2Cx, uint32_t position)Selects the specified I2C PEC position.
name I2C_ConfigPecLocation.
fun Selects the specified I2C PEC position.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param position (The input parameters must be the following values):
- I2C_PEC_POS_NEXT Indicates that the next byte will be the last received byte or the next byte is PEC.
- I2C_PEC_POS_CURRENT Indicates that the current byte is the last received byte or the current byte is PEC.
return none
note This function configures the same bit (ACKPOS) as I2C_ConfigNackLocation()
but is intended to be used in SMBUS mode while I2C_ConfigNackLocation()
is intended to be used in I2C mode.
void I2C_SendPEC(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the specified I2C PEC transfer.
name I2C_SendPEC.
fun Enables or disables the specified I2C PEC transfer.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
note When arbitration is lost, the calculation of PEC is invalid.
void I2C_ComputePec(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the PEC value calculation of the transferred bytes.
name I2C_ComputePec.
fun Enables or disables the PEC value calculation of the transferred bytes.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
uint8_t I2C_GetPec(const I2C_Module *I2Cx)Get the PEC value for the specified I2C.
name I2C_GetPec.
fun Get the PEC value for the specified I2C.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
return The PEC value
void I2C_EnableDMA(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables I2C DMA transfer.
name I2C_EnableDMA.
fun Enables or disables I2C DMA transfer.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_EnableDmaLastSend(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables I2C DMA last transfer.
name I2C_EnableDmaLastSend.
fun Enables or disables I2C DMA last transfer.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
note This bit is used in the master receiving mode, so that a NACK can be generated when
data is received for the last time.
void I2C_SetSDADigitalFilterWidth(I2C_Module *I2Cx, uint8_t width)SDA digital filter width selection.
name I2C_SetSDADigitalFilterWidth.
fun SDA digital filter width selection.
param I2Cx :
- I2C1
- I2C2
param width :
- 0x00 Disable the digital filter.
- 0x01 ~ 0x0F The numbers of pclk cycles.
return none
void I2C_SetSCLDigitalFilterWidth(I2C_Module *I2Cx, uint8_t width)SCL digital filter width selection.
name I2C_SetSCLDigitalFilterWidth.
fun SCL digital filter width selection.
param I2Cx :
- I2C1
- I2C2
param width :
- 0x00 Disable the digital filter.
- 0x01 ~ 0x0F The numbers of pclk cycles.
return none
void I2C_SetSDAAnalogFilterWidth(I2C_Module *I2Cx, uint16_t width)SDA analog filter width selection.
name I2C_SetSDAAnalogFilterWidth.
fun SDA analog filter width selection.
param I2Cx :
- I2C1
- I2C2
param width :
- I2C_ANALOG_FILTER_WIDTH_5NS
- I2C_ANALOG_FILTER_WIDTH_15NS
- I2C_ANALOG_FILTER_WIDTH_25NS
- I2C_ANALOG_FILTER_WIDTH_35NS
return none
void I2C_EnableSDAAnalogFilter(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the SDA analog filter.
name I2C_EnableSDAAnalogFilter.
fun Enables or disables the SDA analog filter.
param I2Cx :
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_SetSCLAnalogFilterWidth(I2C_Module *I2Cx, uint16_t width)SCL analog filter width selection.
name I2C_SetSCLAnalogFilterWidth.
fun SCL analog filter width selection.
param I2Cx :
- I2C1
- I2C2
param width :
- I2C_ANALOG_FILTER_WIDTH_5NS
- I2C_ANALOG_FILTER_WIDTH_15NS
- I2C_ANALOG_FILTER_WIDTH_25NS
- I2C_ANALOG_FILTER_WIDTH_35NS
return none
void I2C_EnableSCLAnalogFilter(I2C_Module *I2Cx, FunctionalState Cmd)Enables or disables the SCL analog filter.
name I2C_EnableSCLAnalogFilter.
fun Enables or disables the SCL analog filter.
param I2Cx :
- I2C1
- I2C2
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_ConfigInt(I2C_Module *I2Cx, uint32_t I2C_IT, FunctionalState Cmd)Enables or disables the specified I2C interrupts.
name I2C_ConfigInt.
fun Enables or disables the specified I2C interrupts.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param I2C_IT (The input parameters must be the following values):
- I2C_INT_BUF Buffer interrupt mask
- I2C_INT_EVENT Event interrupt mask
- I2C_INT_ERR Error interrupt mask
param Cmd:
- ENABLE
- DISABLE
return none
ErrorStatus I2C_CheckEvent(const I2C_Module *I2Cx, uint32_t I2C_event)Checks whether the last I2Cx Event is equal to the one passed as parameter.
name I2C_CheckEvent.
fun Checks whether the last I2Cx Event is equal to the one passed as parameter.
param I2Cx :
- I2C1
- I2C2
param I2C_event :
- I2C_EVT_SLAVE_SEND_ADDR_MATCHED EV1
- I2C_EVT_SLAVE_RECV_ADDR_MATCHED EV1
- I2C_EVT_SLAVE_SEND_ADDR2_MATCHED EV1
- I2C_EVT_SLAVE_RECV_ADDR2_MATCHED EV1
- I2C_EVT_SLAVE_GCALLADDR_MATCHED EV1
- I2C_EVT_SLAVE_DATA_RECVD EV2
- (I2C_EVT_SLAVE_DATA_RECVD | I2C_FLAG_DUALFLAG) EV2
- (I2C_EVT_SLAVE_DATA_RECVD | I2C_FLAG_GCALLADDR) EV2
- I2C_EVT_SLAVE_DATA_SENDED EV3
- (I2C_EVT_SLAVE_DATA_SENDED | I2C_FLAG_DUALFLAG) EV3
- (I2C_EVT_SLAVE_DATA_SENDED | I2C_FLAG_GCALLADDR) EV3
- I2C_EVT_SLAVE_ACK_MISS EV3_2
- I2C_EVT_SLAVE_STOP_RECVD EV4
- I2C_EVT_MASTER_MODE_FLAG EV5
- I2C_EVT_MASTER_TXMODE_FLAG EV6
- I2C_EVT_MASTER_RXMODE_FLAG EV6
- I2C_EVT_MASTER_DATA_RECVD_FLAG EV7
- I2C_EVT_MASTER_DATA_SENDING EV8
- I2C_EVT_MASTER_DATA_SENDED EV8_2
- I2C_EVT_MASTER_MODE_ADDRESS10_FLAG EV9
return SUCCESS or ERROR
note If an error occurs (that is, an error flag is set in addition to the flags parameter entered), the function may still return SUCCESS.
In this case, you can use error interrupts to handle error events.
uint32_t I2C_GetLastEvent(const I2C_Module *I2Cx)Returns the last I2Cx Event.
name I2C_GetLastEvent.
fun Returns the last I2Cx Event.
param I2Cx :
- I2C1
- I2C2
return The last event
note Users can define their own events.
If the user decides to check only the general communication flags (ignore error flags),
when an error occurs, you can use error interrupts to handle error events.
FlagStatus I2C_GetFlag(const I2C_Module *I2Cx, uint32_t I2C_flag)Checks whether the specified I2C flag is set or not.
name I2C_GetFlag.
fun Checks whether the specified I2C flag is set or not.
param I2Cx :
- I2C1
- I2C2
param I2C_flag :
- I2C_FLAG_DUALFLAG Dual flag (Slave mode)
- I2C_FLAG_SMBHADDR SMBus host header (Slave mode)
- I2C_FLAG_SMBDADDR SMBus default header (Slave mode)
- I2C_FLAG_GCALLADDR General call header flag (Slave mode)
- I2C_FLAG_TRF Transmitter/Receiver flag
- I2C_FLAG_BUSY Bus busy flag
- I2C_FLAG_MSMODE Master/Slave flag
- I2C_FLAG_SMBALERT SMBus Alert flag
- I2C_FLAG_TIMEOUT Timeout
- I2C_FLAG_PECERR PEC error in reception flag
- I2C_FLAG_OVERRUN Overrun/Underrun flag (Slave mode)
- I2C_FLAG_ACKFAIL Acknowledge failure flag
- I2C_FLAG_ARLOST Arbitration lost flag (Master mode)
- I2C_FLAG_BUSERR Bus error flag
- I2C_FLAG_TXDATE Data register empty flag (Transmitter)
- I2C_FLAG_RXDATNE Data register not empty (Receiver) flag
- I2C_FLAG_STOPF Stop detection flag (Slave mode)
- I2C_FLAG_ADDR10F 10-bit header sent flag (Master mode)
- I2C_FLAG_BYTEF Byte transfer finished flag
- I2C_FLAG_ADDRF Address sent flag (Master mode)
Address matched flag (Slave mode)
- I2C_FLAG_STARTBF Start bit flag (Master mode)
return SET or RESET
note When this function is called, the status register is accessed, so other flags may be cleared.
You can call I2C_GetFlag() in I2Cx_ER_IRQHandler() to determine which error occurred.
void I2C_ClrFlag(I2C_Module *I2Cx, uint32_t I2C_flag)Clears the I2Cx’s flags.
name I2C_ClrFlag.
fun Clears the I2Cx's flags.
param I2Cx :
- I2C1
- I2C2
param I2C_flag :
- I2C_FLAG_SMBALERT SMBus Alert flag
- I2C_FLAG_TIMEOUT Timeout
- I2C_FLAG_PECERR PEC error in reception flag
- I2C_FLAG_OVERRUN Overrun/Underrun flag (Slave mode)
- I2C_FLAG_ACKFAIL Acknowledge failure flag
- I2C_FLAG_ARLOST Arbitration lost flag (Master mode)
- I2C_FLAG_BUSERR Bus error flag
note Software can read this bit or clear it by writing' 0', and writing' 1' has no effect on this bit.
The other flag bits are cleared in the following sequence:
- STOPF (STOP detection) is cleared by software sequence: a read operation
to I2C_STS1 register followed by a write operation to I2C_CTRL1 register (I2C_Enable()
to re-enable the I2C peripheral).
- ADDR10F (10-bit header sent) is cleared by software sequence: a read
operation to I2C_STS1 followed by writing the second byte of the address in DAT register.
- BYTEF (Byte Transfer Finished) is cleared by software sequence: a read
operation to I2C_STS1 register followed by a read/write to I2C_DAT register.
- ADDRF (Address sent) is cleared by software sequence: a read operation to
I2C_STS1 register followed by a read operation to I2C_STS2 register.
- STARTBF (Start Bit) is cleared software sequence: a read operation to I2C_STS1
register followed by a write operation to I2C_DAT register.
return none
INTStatus I2C_GetIntStatus(const I2C_Module *I2Cx, uint32_t Interrupt)Checks whether the specified I2C interrupt has occurred or not.
name I2C_GetIntStatus.
fun Checks whether the specified I2C interrupt has occurred or not.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Interrupt (The input parameters must be the following values):
- I2C_INT_SMBALERT SMBus Alert flag
- I2C_INT_TIMEOUT Timeout
- I2C_INT_PECERR PEC error in reception flag
- I2C_INT_OVERRUN Overrun/Underrun flag (Slave mode)
- I2C_INT_ACKFAIL Acknowledge failure flag
- I2C_INT_ARLOST Arbitration lost flag (Master mode)
- I2C_INT_BUSERR Bus error flag
- I2C_INT_TXDATE Data register empty flag (Transmitter)
- I2C_INT_RXDATNE Data register not empty (Receiver) flag
- I2C_INT_STOPF Stop detection flag (Slave mode)
- I2C_INT_ADDR10F 10-bit header sent flag (Master mode)
- I2C_INT_BYTEF Byte transfer finished flag
- I2C_INT_ADDRF Address sent flag (Master mode)
Address matched flag (Slave mode)
- I2C_INT_STARTBF Start bit flag (Master mode)
return SET or RESET
note You can call I2C_GetIntStatus() in I2Cx_ER_IRQHandler() to determine which error occurred.
void I2C_ClrIntPendingBit(I2C_Module *I2Cx, uint32_t Interrupt)Clears the I2Cx’s interrupt statu bits.
name I2C_ClrIntPendingBit.
fun Clears the I2Cx's interrupt statu bits.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param Interrupt (The input parameters must be the following values):
- I2C_INT_SMBALERT SMBus Alert flag
- I2C_INT_TIMEOUT Timeout
- I2C_INT_PECERR PEC error in reception flag
- I2C_INT_OVERRUN Overrun/Underrun flag (Slave mode)
- I2C_INT_ACKFAIL Acknowledge failure flag
- I2C_INT_ARLOST Arbitration lost flag (Master mode)
- I2C_INT_BUSERR Bus error flag
return none
note Software can read this bit or clear it by writing' 0', and writing' 1' has no effect on this bit.
The other flag bits are cleared in the following sequence:
- STOPF (STOP detection) is cleared by software sequence: a read operation
to I2C_STS1 register followed by a write operation to I2C_CTRL1 register (I2C_Enable()
to re-enable the I2C peripheral).
- ADDR10F (10-bit header sent) is cleared by software sequence: a read
operation to I2C_STS1 followed by writing the second byte of the address in DAT register.
- BYTEF (Byte Transfer Finished) is cleared by software sequence: a read
operation to I2C_STS1 register followed by a read/write to I2C_DAT register.
- ADDRF (Address sent) is cleared by software sequence: a read operation to
I2C_STS1 register followed by a read operation to I2C_STS2 register.
- STARTBF (Start Bit) is cleared software sequence: a read operation to I2C_STS1
register followed by a write operation to I2C_DAT register.
void I2C_SetTimeoutLowLimit(I2C_Module *I2Cx, uint32_t limit)timeout low threshold selection.
name I2C_SetTimeoutLowLimit.
fun timeout low threshold selection.
param I2Cx :
- I2C1
- I2C2
param limit:
- I2C_TIMEOUT_LOWLIMIT_25MS
- I2C_TIMEOUT_LOWLIMIT_100MS
- I2C_TIMEOUT_LOWLIMIT_1S
- I2C_TIMEOUT_LOWLIMIT_4S
return none
void I2C_SetTimeoutHighLimit(I2C_Module *I2Cx, uint32_t limit)timeout high threshold selection.
name I2C_SetTimeoutHighLimit.
fun timeout high threshold selection.
param I2Cx :
- I2C1
- I2C2
param limit:
- I2C_TIMEOUT_HIGHLIMIT_256US
- I2C_TIMEOUT_HIGHLIMIT_512US
- I2C_TIMEOUT_HIGHLIMIT_1MS
- I2C_TIMEOUT_HIGHLIMIT_128US
return none
void I2C_ConfigTimeoutInt(I2C_Module *I2Cx, uint32_t I2C_IT, FunctionalState Cmd)Enables the specified I2C timeout interrupts.
name I2C_ConfigTimeoutInt.
fun Enables the specified I2C timeout interrupts.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param I2C_IT (The input parameters must be the following values):
- I2C_TIMEOUT_INT_SCLLOW SCL low timeout error interrupt
- I2C_TIMEOUT_INT_SCLHIGH SCL high timeout error interrupt
- I2C_TIMEOUT_INT_SDALOW SDA low timeout error interrupt
param Cmd:
- ENABLE
- DISABLE
return none
void I2C_EnableTimeoutDetect(I2C_Module *I2Cx, uint32_t timeout_type, FunctionalState Cmd)Enables the specified I2C timeout detection function .
name I2C_EnableTimeoutDetect.
fun Enables the specified I2C timeout detection function .
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param timeout_type (The input parameters must be the following values):
- I2C_TIMEOUT_LOW low timeout function enable
- I2C_TIMEOUT_HIGH high timeout function enable
param Cmd:
- ENABLE
- DISABLE
note This function can be disabled in I2C mode when an timeout error is not required.
return none
FlagStatus I2C_GetTimeoutFlag(const I2C_Module *I2Cx, uint32_t I2C_flag)Checks whether the specified I2C timeout flag is set or not.
name I2C_GetTimeoutFlag.
fun Checks whether the specified I2C timeout flag is set or not.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param I2C_flag (The input parameters must be the following values):
- I2C_TIMEOUT_FLAG_SCLLOW SCL low timeout flag
- I2C_TIMEOUT_FLAG_SCLHIGH SCL high timeout flag
- I2C_TIMEOUT_FLAG_SDALOW SDA low timeout flag
return SET or RESET
void I2C_ClrTimeoutFlag(I2C_Module *I2Cx, uint32_t I2C_flag)Clears the I2Cx’s pending flags.
name I2C_ClrTimeoutFlag.
fun Clears the I2Cx's pending flags.
param I2Cx (The input parameters must be the following values):
- I2C1
- I2C2
param I2C_flag (The input parameters must be the following values):
- I2C_TIMEOUT_FLAG_SCLLOW SCL low timeout flag
- I2C_TIMEOUT_FLAG_SCLHIGH SCL high timeout flag
- I2C_TIMEOUT_FLAG_SDALOW SDA low timeout flag
return none
5.11.3 Demo descriptions
This section describes the 10 I2C examples: EEPROM_DMA, EEPROM_Int and EEPROM_Polling drive the same EEPROM driver with DMA, interrupts and polling; I2C_10bit demonstrates a 10-bit address master/slave test; I2C_Master and I2C_Master_Int demonstrate polled and interrupt master transfer; I2C_Slave and I2C_Slave_Int demonstrate polled and interrupt slave transfer; PMBUS_Master and PMBUS_Slave demonstrate PMBus master/slave communication with PEC. The master/slave examples are board-to-board tests.
5.11.3.1 EEPROM_DMA DMA page read/write
Example directory: projects/n32g41x_EVAL/examples/I2C/EEPROM_DMA/
Enable the I2C, GPIO and DMA clocks→Configure PA4 and PA5 as alternate-function open-drain→Initialize I2C1 as a 100 kHz master→Set up DMA_CH6 for transmit and DMA_CH7 for receive→Write 256 bytes by page→Read back and compare
| Item | Source analysis |
|---|---|
| Function | Writes and reads back whole pages of the AT24C02 EEPROM with DMA to verify the I2C1 master plus DMA loop. |
| Source files | src/main.c: main;src/i2c_eeprom.c, inc/i2c_eeprom.h: EEPROM driver, I2C and DMA configuration;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt handlers;readme.txt: function and test steps;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0xA0;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;DMA_CH6 ( DMA_REMAP_I2C1_TX) for transmit and DMA_CH7 (DMA_REMAP_I2C1_RX) for receive, both with peripheral address I2C1->DAT, byte transfer, normal mode. |
| Program and data path | main calls I2C_EE_Init and fills tx_buf, writes 256 bytes from 0x00 page by page with I2C_EE_WriteBuffer, reads them back with I2C_EE_ReadBuffer and compares with Buffercmp;on a page write the DMA moves memory data into I2C1->DAT, on a page read the DMA moves I2C1->DAT into memory. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_ConfigAck, I2C_EnableDMA, I2C_EnableDmaLastSend, I2C_GetFlag, DMA_Init, DMA_RequestRemap |
| Verification | Connect the EEPROM SCL and SDA to PA4 and PA5, with USART TX on PA9 at 115200 baud; after reset the USART prints This is a I2C EEPROM DMA demo and the compare result. |
| Use note | Transmit and receive use one DMA channel each, and the DMA_RequestRemap mapping macros must match the channels one to one;before the last received byte enable I2C_EnableDmaLastSend so the hardware generates the NACK and stop condition automatically;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example first initializes I2C1 and enables the DMA clock, then the page-write and read-buffer functions use DMA for transmit and receive.
Data path: I2C_EE_Init configures I2C1 and enables the DMA clock → I2C_EE_PageWrite moves pBuffer into I2Cx->DAT with DMA_CH6 → main calls I2C_EE_WriteBuffer to write 256 bytes by page → I2C_EE_ReadBuffer moves I2Cx->DAT into pBuffer with DMA_CH7 → Buffercmp compares the result
1. Initialize I2C1 and enable the DMA clock. The I2C is first de-initialized and the I2C_InitParam is filled: the bus mode is I2C, a 7-bit address, own address 0xff and the speed from I2C_Speed; the last line enables the DMA clock on the AHB for the later DMA_CH6 and DMA_CH7. Porting to another peripheral instance means changing I2Cx and I2C_Speed together.
/** I2C periphral configuration */
I2C_DeInit(I2Cx);
I2C_InitStruct(&I2C_InitStructure);
I2C_InitStructure.BusMode = I2C_BUSMODE_I2C;
I2C_InitStructure.FmDutyCycle = I2C_FMDUTYCYCLE_2;
I2C_InitStructure.OwnAddr1 = 0xff;
I2C_InitStructure.AckEnable = I2C_ACKEN;
I2C_InitStructure.AddrMode = I2C_ADDR_MODE_7BIT;
I2C_InitStructure.ClkSpeed = I2C_Speed;
I2C_Init(I2Cx, &I2C_InitStructure);
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA, ENABLE);
The DMA clock and the I2C clock are both required, so re-check them together when changing boards.
2. Configure the DMA channel used for transmit. The peripheral address points at I2Cx->DAT and the memory address at pBuffer, with the direction set to peripheral destination; BufSize is the number of bytes to write, the data width is byte and normal mode transfers once; DMA_RequestRemap maps DMA_CH6 to the I2C1 transmit request. The channel, mapping macro and data width must match the I2C transfer unit.
DMA_InitType DMA_InitStructure;
/** DMA initialization */
DMA_DeInit(DMA_CH6);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = (u32)&I2Cx->DAT; /// (u32)I2C1_DR_Address;
DMA_InitStructure.MemAddr = (u32)pBuffer; /// from function input parameter
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST; /// fixed for send function
DMA_InitStructure.BufSize = NumByteToWrite; /// from function input parameter
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE; // fixed
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE; /// fixed
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE; /// fixed
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte; /// fixed
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL; /// fixed
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH; /// up to user
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE; /// fixed
DMA_Init(DMA_CH6, &DMA_InitStructure);
DMA_RequestRemap(I2C_DMA_TX_Remap, DMA, DMA_CH6,ENABLE);
The receive direction instead uses DMA_DIR_PERIPH_SRC with the receive mapping macro; direction, channel and mapping must all correspond.
3. Enable the I2C DMA and finish after the byte transfer. The I2C DMA request is enabled first, then the DMA channel is opened; I2C_FLAG_BYTEF is polled until it is set, which means the last byte has left the data register and only then may the stop condition be generated and the DMA disabled. The finish order is to disable the DMA first, then generate the stop condition, and finally wait for the EEPROM internal write cycle.
I2C_EnableDMA(I2Cx, ENABLE);
DMA_EnableChannel(DMA_CH6, ENABLE);
/* Wait for transfer finished */
sEETimeout = sEE_LONG_TIMEOUT;
while (!I2C_GetFlag(I2Cx, I2C_FLAG_BYTEF))
{
if ((sEETimeout--) == 0)
{
sEE_TIMEOUT_UserCallback();
The stop condition must be generated only after BYTEF, otherwise the last byte is cut off.
/** Send STOP condition */
I2C_GenerateStop(I2Cx, ENABLE);
I2C_EnableDMA(I2Cx, DISABLE);
DMA_EnableChannel(DMA_CH6, DISABLE);
I2C_EE_WaitEepromStandbyState();
I2C_EE_WriteOnePageCompleted();
4. Configure the DMA channel used for receive. For a page read the direction is reversed to peripheral source and BufSize is the number of bytes to read; the other fields match the transmit case. When only one byte is read this branch is skipped and a single byte is read by polling.
if(NumByteToRead > 1)
{
DMA_InitType DMA_InitStructure;
/** DMA initialization */
DMA_DeInit(DMA_CH7);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.PeriphAddr = (u32)&I2Cx->DAT; /// (u32)I2C1_DR_Address;
DMA_InitStructure.MemAddr = (u32)pBuffer; /// from function input parameter
DMA_InitStructure.Direction = DMA_DIR_PERIPH_SRC; /// fixed for read function
DMA_InitStructure.BufSize = NumByteToRead; /// from function input parameter
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE; /// fixed
DMA_InitStructure.DMA_MemoryInc = DMA_MEM_INC_ENABLE; /// fixed
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;// fixed
DMA_InitStructure.MemDataSize = DMA_MemoryDataSize_Byte;// DMA_MemoryDataSize_Byte; /// fixed
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL; /// fixed
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH; /// up to user
DMA_InitStructure.Mem2Mem = DMA_M2M_DISABLE; /// fixed
DMA_Init(DMA_CH7, &DMA_InitStructure);
DMA_RequestRemap(I2C_DMA_RX_Remap, DMA, DMA_CH7,ENABLE);
}
The address phase before a page read is still done in software; the DMA only handles the data phase.
5. Finish the last byte of a master receive. When the length is greater than one, acknowledgement is enabled, ADDR is cleared, the I2C DMA and the last-DMA-transfer feature are enabled, and the receive channel is opened; the DMA completion flag DMA_FLAG_TC7 is polled, then the enable bits are cleared and the stop condition is generated. I2C_EnableDmaLastSend makes the hardware send the NACK before the last data, which is the key to a master receive.
I2C_ConfigAck(I2Cx, ENABLE);
(void)(I2Cx->STS1);
(void)(I2Cx->STS2);
I2C_EnableDMA(I2Cx, ENABLE);
I2C_EnableDmaLastSend(I2Cx, ENABLE);
DMA_EnableChannel(DMA_CH7, ENABLE);
sEETimeout = sEE_LONG_TIMEOUT;
while(!(DMA_GetFlagStatus(DMA_FLAG_TC7,DMA)))
{
if ((sEETimeout--) == 0)
The disable order is the reverse of the enable order: disable the DMA channel, then the I2C DMA, and generate the stop condition last.
I2C_EnableDMA(I2Cx, DISABLE);
DMA_EnableChannel(DMA_CH7, DISABLE);
I2C_EnableDmaLastSend(I2Cx, DISABLE);
I2C_GenerateStop(I2Cx, ENABLE);
Corresponding source files: src/i2c_eeprom.c, inc/i2c_eeprom.h
5.11.3.2 EEPROM_Int interrupt page read/write
Example directory: projects/n32g41x_EVAL/examples/I2C/EEPROM_Int/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz master→Configure and enable the I2C1 interrupt→Write 256 bytes by page→Advance the event state machine in the interrupt handler→Read back and compare
| Item | Source analysis |
|---|---|
| Function | Writes and reads back whole pages of the AT24C02 EEPROM through the I2C1 event, buffer and error interrupts, with the transfer advanced by an event state machine in the interrupt handler. |
| Source files | src/main.c: main;src/i2c_eeprom.c, inc/i2c_eeprom.h: EEPROM driver, I2C and NVIC configuration, I2C1_IRQHandler;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0xA0;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;interrupts enabled for event, buffer and error, with NVIC_PriorityGroup_2 and I2C1_IRQn preemption 0 and subpriority 1 and 0. |
| Program and data path | main calls I2C_EE_Init and fills tx_buf;I2C_EE_PageWrite only enables the acknowledgement and interrupt and generates the start condition, after which I2C1_IRQHandler moves data between EV5, EV6, EV7 and EV8;when the write finishes i2c_comm_state is set to COMM_DONE, and the read back is triggered by I2C_EE_ReadBuffer. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigAck, I2C_ConfigInt, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_SendData, I2C_RecvData, I2C_GetLastEvent, I2C_GetFlag, I2C_ClrFlag |
| Verification | Connect the EEPROM SCL and SDA to PA4 and PA5, with USART TX on PA9 at 115200 baud; after reset the USART prints This is a I2C EEPROM interrupt demo and the compare result. |
| Use note | In interrupt mode I2C_EE_PageWrite does not block;the state machine must be advanced by I2C1_IRQHandler;the error branch uses I2C_FLAG_ACKFAIL to tell an EEPROM write cycle from a real failure;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example hands all I2C1 transfer to the interrupt, and the main flow only starts it and waits for completion.
Data path: I2C_EE_Init configures I2C1 and the NVIC → I2C_EE_PageWrite enables the acknowledgement and interrupt and generates the start condition → I2C1_IRQHandler advances the transfer between EV5, EV6, EV7 and EV8 → when the write finishes i2c_comm_state is set to COMM_DONE → main compares tx_buf and rx_buf
1. Initialize I2C1 and configure the interrupt vector. I2C1 is first de-initialized and configured from I2C_InitParam, then the NVIC is set: under NVIC_PriorityGroup_2 the I2C1_IRQn preemption and subpriority are set to 0 and 1, and a second setting changes the subpriority to 0. The mapping between the handler and the vector is established here.
/** I2C periphral configuration */
I2C_DeInit(I2Cx);
I2C_InitStruct(&I2C_InitStructure);
I2C_InitStructure.BusMode = I2C_BUSMODE_I2C;
I2C_InitStructure.FmDutyCycle = I2C_FMDUTYCYCLE_2;
I2C_InitStructure.OwnAddr1 = 0xff;
I2C_InitStructure.AckEnable = I2C_ACKEN;
I2C_InitStructure.AddrMode = I2C_ADDR_MODE_7BIT;
I2C_InitStructure.ClkSpeed = I2C_Speed;
I2C_Init(I2Cx, &I2C_InitStructure);
NVIC_PriorityGroup_2 decides the split between preemption and subpriority bits, so re-check the values when changing the priority grouping.
/** I2C NVIC configuration */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = I2C1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
2. The page write only enables and starts. Unlike the polled version, the parameters are stored in static variables and the data to send is copied into SendBuf, then the acknowledgement and interrupt are enabled and the state machine is left to the interrupt; the function itself no longer polls events.
/** initialize static parameter */
MasterDirection = Transmitter;
i2c_comm_state = COMM_PRE;
/** initialize static parameter according to input parameter */
SlaveADDR = EEPROM_ADDRESS; /// this byte shoule be send by F/W (in loop or INTSTS way)
DeviceOffset = WriteAddr; /// this byte can be send by both F/W and DMA
OffsetDone = FALSE;
memcpy(SendBuf, pBuffer, NumByteToWrite);
BufCount = 0;
Int_NumByteToWrite = NumByteToWrite;
I2C_ConfigAck(I2Cx, ENABLE);
I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
The three interrupt bits cover the event, buffer and error interrupts; missing one drops the corresponding state.
3. The receive state machine in the interrupt handler. A master receive handles EV6 and EV7 separately: after the address matches it decides from the remaining byte count whether to disable the acknowledgement and pre-set the NACK; each received byte is stored in the buffer, and on the last byte the acknowledgement is disabled and the stop condition generated, while COMM_DONE is set when the buffer is full. The state machine relies on the Int_NumByteToRead counter, which must match the buffer size when porting.
/** Master Receiver events */
case I2C_EVT_MASTER_RXMODE_FLAG: /// EV6
if (Int_NumByteToRead == 1)
{
I2C_ConfigAck(I2C1, DISABLE);
I2C_GenerateStop(I2C1, ENABLE);
}
else if (Int_NumByteToRead == 2)
{
I2C1->CTRL1 |= I2C_NACK_POS_NEXT; /// set ACKPOS
I2C_ConfigAck(I2C1, DISABLE);
}
break;
case I2C_EVT_MASTER_DATA_RECVD_FLAG: /// EV7
case I2C_EVT_MASTER_DATA_RECVD_BSF_FLAG: // BUSY, MSMODE(Master) and Data register not empty, BSF(Byte transfer finished)flags.
*I2C_pBuffer = I2C_RecvData(I2C1);
I2C_pBuffer++;
Int_NumByteToRead--;
if (Int_NumByteToRead == 1)
{
/** Disable Acknowledgement */
I2C_ConfigAck(I2C1, DISABLE);
I2C_GenerateStop(I2C1, ENABLE);
}
if (Int_NumByteToRead == 0)
{
I2C_ConfigInt(I2C1, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, DISABLE);
i2c_comm_state = COMM_DONE;
}
break;
When the buffer is full the interrupt is disabled and COMM_DONE is set, which is how the main flow leaves its wait.
4. Error handling in the interrupt. The error branch reads I2C_FLAG_ACKFAIL: if it happens during the start phase the EEPROM is in its write cycle, so the start condition is generated again to retry; otherwise it is a real failure, so the stop condition is generated and COMM_EXIT is set, and finally the flag is cleared. Telling the two failures apart is key to making the interrupt transfer retryable.
/* --- Error handling --- */
if (I2C_GetFlag(I2C1, I2C_FLAG_ACKFAIL))
{
if (check_begin) /// EEPROM write busy
{
I2C_GenerateStart(I2C1, ENABLE);
}
else if (I2C1->STS2 & 0x01) /// real fail
{
I2C_GenerateStop(I2C1, ENABLE);
i2c_comm_state = COMM_EXIT;
}
I2C_ClrFlag(I2C1, I2C_FLAG_ACKFAIL);
}
Corresponding source files: src/i2c_eeprom.c, inc/i2c_eeprom.h
5.11.3.3 EEPROM_Polling polled page read/write
Example directory: projects/n32g41x_EVAL/examples/I2C/EEPROM_Polling/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz master→Write 256 bytes by page→Send byte by byte polling EV5, EV6 and EV8→Read back by polling→Compare the result
| Item | Source analysis |
|---|---|
| Function | Writes and reads back whole pages of the AT24C02 EEPROM by polling, waiting for each event with I2C_CheckEvent and a timeout counter. |
| Source files | src/main.c: main;src/i2c_eeprom.c, inc/i2c_eeprom.h: EEPROM driver, I2C configuration, I2C_EE_WaitEepromStandbyState;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0xA0;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;the I2C interrupt is not enabled and every event is polled in software. |
| Program and data path | main calls I2C_EE_Init and fills tx_buf, I2C_EE_WriteBuffer writes 256 bytes from 0x00 page by page, I2C_EE_ReadBuffer reads them back and Buffercmp compares;each byte in the page write waits for I2C_EVT_MASTER_DATA_SENDED. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_CheckEvent, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_SendData, I2C_RecvData, I2C_ConfigAck, I2C_GetFlag |
| Verification | Connect the EEPROM SCL and SDA to PA4 and PA5, with USART TX on PA9 at 115200 baud; after reset the USART prints This is a I2C EEPROM polling demo and the compare result. |
| Use note | The polled version is the most direct to read but waits on every event in software with timeout protection, so its real-time behaviour is poor; a single-byte read takes a different branch from a multi-byte read, and when NumByteToRead is 1 the multi-byte flow is not entered;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example advances the I2C1 transfer step by step by polling, judging progress from an event or flag at each stage.
Data path: I2C_EE_Init configures I2C1 → I2C_EE_PageWrite generates the start condition and waits for EV5 → sends the slave address and waits for EV6 → sends byte by byte and waits for EV8 → I2C_EE_ReadBuffer reads back → I2C_EE_WaitEepromStandbyState waits for the write cycle → main compares the result
1. Initialize I2C1. After the reset, I2C_InitParam is filled with the bus mode, fast-mode duty cycle, own address, acknowledgement, 7-bit address and speed, and I2C_Init is called. The polled version does not configure the NVIC and every event is read in software.
/** I2C periphral configuration */
I2C_DeInit(I2Cx);
I2C_InitStruct(&I2C_InitStructure);
I2C_InitStructure.BusMode = I2C_BUSMODE_I2C;
I2C_InitStructure.FmDutyCycle = I2C_FMDUTYCYCLE_2;
I2C_InitStructure.OwnAddr1 = 0xff;
I2C_InitStructure.AckEnable = I2C_ACKEN;
I2C_InitStructure.AddrMode = I2C_ADDR_MODE_7BIT;
I2C_InitStructure.ClkSpeed = I2C_Speed;
I2C_Init(I2Cx, &I2C_InitStructure);
2. Generate the start condition and wait for EV5. The page write generates the start condition first, then loops on I2C_CheckEvent waiting for the master-mode flag; the timeout counter is decremented each pass and the timeout callback is taken when it reaches zero. I2C_CheckEvent reads and clears the event flag as a side effect.
/** Send START condition */
I2C_GenerateStart(I2Cx, ENABLE);
/** Test on EV5 and clear it */
sEETimeout = sEE_LONG_TIMEOUT;
while (!I2C_CheckEvent(I2Cx, I2C_EVT_MASTER_MODE_FLAG))
{
if ((sEETimeout--) == 0)
3. Send byte by byte and wait for EV8. The body of the page write is a loop: each byte is sent with I2C_SendData, then I2C_EVT_MASTER_DATA_SENDED is awaited, meaning the data register is empty and the next byte can be written. The page is written whole only when NumByteToWrite matches the EEPROM page size.
/** While there is data to be written */
while (NumByteToWrite--)
{
/** Send the current byte */
I2C_SendData(I2Cx, *pBuffer);
/** Point to the next byte to be written */
pBuffer++;
/** Test on EV8 and clear it */
sEETimeout = sEE_LONG_TIMEOUT;
while (!I2C_CheckEvent(I2Cx, I2C_EVT_MASTER_DATA_SENDED))
{
if ((sEETimeout--) == 0)
4. The single-byte read branch. The read back first handles the one-byte case: the acknowledgement is disabled, STS1 and STS2 are read in turn to clear ADDR, and the stop condition is generated. A multi-byte read maintains the acknowledgement and NACK position on each byte, which differs from this branch.
/** While there is data to be read */
if (NumByteToRead == 1)
{
/** Disable Acknowledgement */
I2C_ConfigAck(I2Cx, DISABLE);
(void)(I2Cx->STS1); /// clear ADDR
(void)(I2Cx->STS2);
I2C_GenerateStop(I2Cx, ENABLE);
}
5. Wait for the EEPROM internal write cycle. After a page write the EEPROM enters its internal write cycle and does not acknowledge its address; I2C_EE_WaitEepromStandbyState repeatedly sends the slave address and reads STS1 to see whether it is acknowledged, until the EEPROM is ready; this logic writes and checks the address phase directly, so keep the polling limit when porting.
/** Send EEPROM address for write */
I2C_SendAddr7bit(I2Cx, EEPROM_ADDRESS, I2C_DIRECTION_SEND);
/** Wait for ADDR flag to be set (Slave acknowledged his address) */
sEETimeout = sEE_LONG_TIMEOUT;
do
{
/** Get the current value of the STS1 register */
tmpSR1 = I2Cx->STS1;
/** Update the timeout value and exit if it reach 0 */
if ((sEETimeout--) == 0)
Not acknowledging the address during the write cycle is normal; only exceeding the retry limit counts as a failure.
Corresponding source files: src/i2c_eeprom.c, inc/i2c_eeprom.h
5.11.3.4 I2C_10bit 10-bit address master/slave test
Example directory: projects/n32g41x_EVAL/examples/I2C/I2C_10bit/
Initialize I2C1 as master and I2C2 as slave with 10-bit addressing→Fill the transmit buffer→The master sends the start and 10-bit address→The slave matches the address and transfers→The interrupt handlers advance the events→Print the result over USART
| Item | Source analysis |
|---|---|
| Function | Uses I2C1 as master and I2C2 as slave on the same board to complete one round of transmit and receive with 10-bit addressing and compare the data. |
| Source files | src/main.c: main, i2c_master_init, i2c_slave_init, I2C1_IRQHandler, I2C2_IRQHandler;inc/main.h: pin, address and frame-header macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; master I2C1 address 0x230, SCL on PA4, SDA on PA5 (GPIO_AF7), slave I2C2 address 0x2A0, SCL on PB10, SDA on PB11 (GPIO_AF4);both ends use I2C_ADDR_MODE_10BIT, 100 kHz and GPIO_MODE_AF_OD;the frame headers 0xF0 and 0xF1 mark the transfer direction. |
| Program and data path | main calls i2c_master_init and i2c_slave_init, fills master_tx_buf, sets Comm_Flag and generates the start condition;I2C1_IRQHandler sends the frame header and the high byte of the 10-bit address on EV5 and the low address byte on I2C_EVT_MASTER_MODE_ADDRESS10_FLAG;I2C2_IRQHandler clears the counter and transfers data after the slave address matches. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigInt, I2C_Enable, I2C_GenerateStart, I2C_GenerateStop, I2C_SendData, I2C_RecvData, I2C_ConfigAck, I2C_GetLastEvent, I2C_GetFlag, I2C_ClrFlag |
| Verification | Connect the master I2C1 on PA4 and PA5 to the slave I2C2 on PB10 and PB11 with a common ground, with USART TX on PA9 at 115200 baud; after reset the USART prints this is a i2c(10bit) master/slave demo and the compare result. |
| Use note | A 10-bit address is sent in two parts, so the master must handle I2C_EVT_MASTER_MODE_ADDRESS10_FLAG and send the low address byte;the slave OwnAddr1 takes the full 10-bit address;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example initializes a master and a slave instance of the I2C, then completes one round of 10-bit address transfer in the interrupts.
Data path: i2c_master_init and i2c_slave_init configure I2C1 and I2C2 → main fills master_tx_buf and generates the start condition → I2C1_IRQHandler sends the 10-bit address in two parts and moves data → I2C2_IRQHandler transfers after the address matches → main compares the result
1. Initialize the master in 10-bit address mode. Besides the usual bus mode, speed and acknowledgement, the master sets AddrMode to I2C_ADDR_MODE_10BIT and OwnAddr1 to its own address 0x230; it then enables the event, buffer and error interrupts, configures the NVIC and enables the I2C. ClkSpeed is 100 kHz and must match at both ends.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_master);
i2cx_master.BusMode = I2C_BUSMODE_I2C;
i2cx_master.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_master.OwnAddr1 = I2C_MASTER_ADDR;
i2cx_master.AckEnable = I2C_ACKEN;
i2cx_master.AddrMode = I2C_ADDR_MODE_10BIT;
i2cx_master.ClkSpeed = 100000; //100K
I2C_Init(I2Cx, &i2cx_master);
// int enable
I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
NVIC_Configuration(1);
I2C_Enable(I2Cx, ENABLE);
2. Initialize the slave in 10-bit address mode. The slave I2C2 uses PB10 and PB11, takes the slave address 0x2A0 in OwnAddr1 and also uses 10-bit addressing; after the interrupt is enabled, NVIC_Configuration(2) sets up the I2C2 vector. The slave own address must match the master target address.
I2C_DeInit(Slave_I2Cx);
I2C_InitStruct(&i2c2_slave);
i2c2_slave.BusMode = I2C_BUSMODE_I2C;
i2c2_slave.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2c2_slave.OwnAddr1 = I2C_SLAVE_ADDR;
i2c2_slave.AckEnable = I2C_ACKEN;
i2c2_slave.AddrMode = I2C_ADDR_MODE_10BIT;
i2c2_slave.ClkSpeed = 100000; //100K
I2C_Init(Slave_I2Cx, &i2c2_slave);
// int enable
I2C_ConfigInt(Slave_I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
NVIC_Configuration(2);
I2C_Enable(Slave_I2Cx, ENABLE);
3. The master sends the 10-bit address in two parts. On EV5 the master sends the frame header byte carrying the top two address bits, then on I2C_EVT_MASTER_MODE_ADDRESS10_FLAG sends the low address byte; this is the key difference from 7-bit addressing.
case I2C_EVT_MASTER_MODE_FLAG: // EV5 start
if (flag_master_dir == TRANSMIT)
{
Memset(slave_rx_buf, 0, TEST_BUFFER_SIZE);
I2C_SendData(I2Cx, (I2C_FRAME_HEAD_TX | ((I2C_SLAVE_ADDR & 0x300) >> 7)));
tx_num = 0;
}
else if (flag_master_dir == RECEIVE)
{
Memset(master_rx_buf, 0, TEST_BUFFER_SIZE);
I2C_SendData(I2Cx, (I2C_FRAME_HEAD_RX | ((I2C_SLAVE_ADDR & 0x300) >> 7)));
rx_num = 0;
flag_master_dir = TRANSMIT;
}
break;
case I2C_EVT_MASTER_MODE_ADDRESS10_FLAG:
I2C_SendData(I2Cx, (I2C_SLAVE_ADDR & 0xFF));
break;
The low address byte and the top two bits form the full 10-bit address, so the macros and expressions must be ported together.
4. Generate the stop condition after transmit. When the transmit counter reaches the buffer length, the master generates the stop condition on the I2C_EVT_MASTER_DATA_SENDED event and sets the completion flag to tell the main flow to leave its wait.
case I2C_EVT_MASTER_DATA_SENDED: // 0x00070084.byte data send finish
// bit2 BSF (Byte transfer finished)
if (tx_num == TEST_BUFFER_SIZE)// data send finish
{
if (Comm_Flag == C_READY)
{
Comm_Flag = C_STOP_BIT;
I2C_GenerateStop(I2Cx, ENABLE);
flag_master_complete = 1;
}
}
break;
5. The slave prepares to transfer after the address matches. After receiving its own 10-bit address, the slave clears the receive counter on I2C_EVT_SLAVE_RECV_ADDR_MATCHED, and clears the transmit counter and preloads the first byte to send on I2C_EVT_SLAVE_SEND_ADDR_MATCHED; the remaining bytes are moved by the EV7 and EV8 events.
switch (last_event)
{
case I2C_EVT_SLAVE_RECV_ADDR_MATCHED:
rx_num = 0;
break;
case I2C_EVT_SLAVE_SEND_ADDR_MATCHED:
tx_num = 0;
Slave_I2Cx->DAT = slave_tx_buf[tx_num++];
break;
Corresponding source files: src/main.c, inc/main.h
5.11.3.5 I2C_Master polled master transfer
Example directory: projects/n32g41x_EVAL/examples/I2C/I2C_Master/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz master→Call the send function in a loop→Call the receive function→Compare the transmit and receive buffers→Repeat after a delay
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a master and repeatedly sends 200 bytes to the slave by polling and reads them back, comparing the buffers to verify the link. |
| Source files | src/main.c: main, i2c_master_init, i2c_master_send, i2c_master_recv, Buffercmp;inc/main.h: pin, address and buffer macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, master and slave address 0x30/0x10;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;a 200-byte buffer. |
| Program and data path | main calls i2c_master_init and fills tx_buf, then in an endless loop calls i2c_master_send(tx_buf, TEST_BUFFER_SIZE) first and i2c_master_recv(rx_buf, TEST_BUFFER_SIZE) next, compares and calls SysTick_Delay_Ms(50);the send and receive functions wait for EV5, EV6, EV8 step by step. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_Enable, I2C_CheckEvent, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_SendData, I2C_RecvData, I2C_ConfigAck, I2C_GetFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as master and I2C_Slave or I2C_Slave_Int as slave;USART TX on PA9 at 115200 baud; after reset the USART repeatedly prints this is a i2c master demo and write and read data are same, i2c master test pass. |
| Use note | The transmit and receive share one start condition, so ACKPOS must be reset before receiving;the branches for len equal to 1 or 2 handle the acknowledgement and stop condition differently;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example is built around two polled functions: i2c_master_send for transmit and i2c_master_recv for receive, which main calls repeatedly and compares.
Data path: i2c_master_init configures I2C1 → main fills tx_buf → i2c_master_send generates the start condition and sends byte by byte → i2c_master_recv generates the start condition again and reads back → Buffercmp compares → SysTick_Delay_Ms and repeat
1. Initialize the I2C1 master. The I2C_InitParam is filled after the reset with the bus mode, fast-mode duty cycle, master address, acknowledgement, 7-bit address and speed, then the I2C is enabled; the polled version does not involve the NVIC.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_master);
i2cx_master.BusMode = I2C_BUSMODE_I2C;
i2cx_master.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_master.OwnAddr1 = I2C_MASTER_ADDR;
i2cx_master.AckEnable = I2C_ACKEN;
i2cx_master.AddrMode = I2C_ADDR_MODE_7BIT;
i2cx_master.ClkSpeed = 100000; // 100K
I2C_Init(I2Cx, &i2cx_master);
I2C_Enable(I2Cx, ENABLE);
2. On transmit, wait for an idle bus and then generate the start condition. The send function polls I2C_FLAG_BUSY until the bus is idle, then generates the start condition when Comm_Flag allows and waits for EV5; it then sends the slave address and waits for EV6. The three steps cannot be reordered.
if (Comm_Flag == C_READY)
{
Comm_Flag = C_START_BIT;
I2C_GenerateStart(I2Cx, ENABLE);
}
I2CTimeout = I2CT_LONG_TIMEOUT;
while (!I2C_CheckEvent(I2Cx, I2C_EVT_MASTER_MODE_FLAG)) // EV5
{
if ((I2CTimeout--) == 0)
{
CommTimeOut_CallBack(MASTER_MODE);
3. Send byte by byte and wait for EV8. The body of the transmit is a loop driven by len; after each byte it waits for I2C_EVT_MASTER_DATA_SENDING, and after all data is out it waits once more for I2C_EVT_MASTER_DATA_SENDED to confirm the byte transfer finished.
Comm_Flag = C_READY;
// send data
while (len-- > 0)
{
I2C_SendData(I2Cx, *sendBufferPtr++);
I2CTimeout = I2CT_LONG_TIMEOUT;
while (!I2C_CheckEvent(I2Cx, I2C_EVT_MASTER_DATA_SENDING)) // EV8
{
if ((I2CTimeout--) == 0)
{
CommTimeOut_CallBack(MASTER_SENDING);
4. The single-byte receive branch. When len is 1 the receive function disables the acknowledgement, reads STS1 and STS2 to clear ADDR, generates the stop condition, and then waits for I2C_FLAG_RXDATNE to take the single byte; for length 2 or more it takes the other branch that sets the NACK position.
if (len == 1)
{
I2C_ConfigAck(I2Cx, DISABLE);
(void)(I2Cx->STS1); /// clear ADDR
(void)(I2Cx->STS2);
if (Comm_Flag == C_READY)
{
Comm_Flag = C_STOP_BIT;
I2C_GenerateStop(I2Cx, ENABLE);
}
I2CTimeout = I2CT_LONG_TIMEOUT;
while (!I2C_GetFlag(I2Cx, I2C_FLAG_RXDATNE))
{
if ((I2CTimeout--) == 0)
{
CommTimeOut_CallBack(MASTER_RECVD);
Corresponding source files: src/main.c, inc/main.h
5.11.3.6 I2C_Master_Int interrupt master transfer
Example directory: projects/n32g41x_EVAL/examples/I2C/I2C_Master_Int/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz master and configure the NVIC→Generate the start condition to begin transmit→Transfer in the interrupt handler by event→Wait for the completion flags→Compare the result
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a master and completes one round of 100-byte transmit and receive through the event, buffer and error interrupts, with the main flow only waiting for the completion flags. |
| Source files | src/main.c: main, NVIC_Configuration, i2c_master_init, I2Cx_IRQHandler;inc/main.h: pin, address and buffer macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, master and slave address 0x30/0x10;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;interrupts enabled for event, buffer and error, a 100-byte buffer. |
| Program and data path | main calls i2c_master_init and fills tx_buf, sets Comm_Flag and generates the start condition to begin transmit, waiting for flag_master_send_finish;it then resets ACKPOS, enables the acknowledgement and begins receive, waiting for flag_master_recv_finish;I2Cx_IRQHandler moves data between EV5, EV6, EV7 and EV8. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigInt, I2C_Enable, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_SendData, I2C_RecvData, I2C_ConfigAck, I2C_GetLastEvent, I2C_ClrFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as master and I2C_Slave or I2C_Slave_Int as slave;USART TX on PA9 at 115200 baud; after reset the USART prints This is a i2c master int demo! and i2c master int test pass!. |
| Use note | The transmit and receive directions are told apart by flag_master_send_finish and flag_master_recv_finish;ACKPOS is reset with I2C_NACK_POS_CURRENT before receiving;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example completes the transfer in the interrupt, so the main flow only waits for the completion flags between the two starts.
Data path: NVIC_Configuration sets up the I2C vector → i2c_master_init configures I2C1 and the interrupts → main generates the start condition to begin transmit → I2Cx_IRQHandler transfers through EV5 to EV8 → the transmit and receive completion flags are set → Buffercmp compares the result
1. Configure the I2C interrupt vector. NVIC_Configuration sets the I2Cx_IRQn preemption priority to 4 and the subpriority to 1, then sets them once more to preemption 3 and subpriority 1, enabling the channel both times.
NVIC_InitStructure.NVIC_IRQChannel = I2Cx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = I2Cx_IRQn; // test err
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
2. Initialize I2C1 and enable the interrupts. After de-initializing and configuring I2C1, the event, buffer and error interrupts are enabled, and the NVIC is set up and the I2C enabled. The interrupt enables and the configuration order inside i2c_master_init must match the event assumptions of the interrupt handler.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_master);
i2cx_master.BusMode = I2C_BUSMODE_I2C;
i2cx_master.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_master.OwnAddr1 = I2C_MASTER_ADDR;
i2cx_master.AckEnable = I2C_ACKEN;
i2cx_master.AddrMode = I2C_ADDR_MODE_7BIT;
i2cx_master.ClkSpeed = 100000; // 100000 100K
I2C_Init(I2Cx, &i2cx_master);
// int enable
I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
NVIC_Configuration();
I2C_Enable(I2Cx, ENABLE);
3. The main flow begins transmit and waits for completion. main sets flag_trans_direct to 0 for the write direction, generates the start condition and waits for flag_master_send_finish; after the transmit it changes the read-back direction to 1, resets ACKPOS and enables the acknowledgement, then begins the receive.
log_init();
log_info("\nThis is a i2c master int demo!\r\n");
/* Initialize the I2C driver */
i2c_master_init();
/* Fill the buffer to send */
for (i = 0; i < TEST_BUFFER_SIZE; i++)
{
tx_buf[i] = i;
}
/* First write in the memory followed by a read of the written data */
/* Write data */
flag_trans_direct = 0;
if (Comm_Flag == C_READY)
{
Comm_Flag = C_START_BIT;
I2C_GenerateStart(I2Cx, ENABLE);
}
4. The interrupt handler dispatches the address by direction. On EV5 it sends the read or write address depending on flag_trans_direct; on EV6 it sends the first data byte immediately; during EV8 it keeps sending the remaining bytes, and after all are sent it generates the stop condition and sets flag_master_send_finish.
case I2C_EVT_MASTER_MODE_FLAG: // EV5 start
if (flag_trans_direct) // read
{
Memset(rx_buf, 0, TEST_BUFFER_SIZE); // clear recv buf, ready to recv data
I2C_SendAddr7bit(I2Cx, I2C_SLAVE_ADDR, I2C_DIRECTION_RECV);
rx_num = 0;
}
else // write
{
I2C_SendAddr7bit(I2Cx, I2C_SLAVE_ADDR, I2C_DIRECTION_SEND);
tx_num = 0;
}
break;
case I2C_EVT_MASTER_TXMODE_FLAG: // EV8 justafter EV6
Comm_Flag = C_READY;
I2C_SendData(I2Cx, tx_buf[tx_num++]);
break;
case I2C_EVT_MASTER_DATA_SENDING: // transmitting data
if (tx_num < TEST_BUFFER_SIZE)
{
I2C_SendData(I2Cx, tx_buf[tx_num++]);
}
break;
5. Receive events and error handling. Each received byte is stored in rx_buf; on the last byte the acknowledgement is disabled and the stop condition generated, and when the receive is full flag_master_recv_finish is set; the error branch clears the flag and generates the stop condition when I2C_FLAG_ACKFAIL is detected.
case I2C_EVT_MASTER_DATA_RECVD_FLAG: // one byte recved :EV7.//BUSY,MSL and RXDATNE flags.
// case I2C_EVT_MASTER_DATA_RECVD_BSF_FLAG: // BUSY, MSMODE(Master) and Data register not empty, BSF(Byte transfer finished)flags.
rx_buf[rx_num++] = I2C_RecvData(I2Cx);
if (rx_num == (TEST_BUFFER_SIZE - 1))
{
I2C_ConfigAck(I2Cx, DISABLE); // Disable I2Cx acknowledgement.
if (Comm_Flag == C_READY)
{
Comm_Flag = C_STOP_BIT;
I2C_GenerateStop(I2Cx, ENABLE); // Send I2Cx STOP Condition.
}
}
else if (rx_num == TEST_BUFFER_SIZE)
{
flag_master_recv_finish = 1;
}
break;
Corresponding source files: src/main.c, inc/main.h
5.11.3.7 I2C_Slave polled slave transfer
Example directory: projects/n32g41x_EVAL/examples/I2C/I2C_Slave/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz slave→Wait in a loop for the slave receive address→Receive one frame by polling→Send the same frame by polling→Clear the buffer and repeat
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a slave, polls until the master addresses it, receives a frame and sends the same frame back to verify the slave side. |
| Source files | src/main.c: main, i2c_slave_init, i2c_slave_send, i2c_slave_recv;inc/main.h: pin, address and buffer macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0x10;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;a 256-byte buffer. |
| Program and data path | main calls i2c_slave_init, then in an endless loop calls i2c_slave_recv(data_buf, TEST_BUFFER_SIZE) first, which waits for I2C_EVT_SLAVE_RECV_ADDR_MATCHED and returns after the stop condition, and i2c_slave_send(data_buf, TEST_BUFFER_SIZE) next, which waits for I2C_EVT_SLAVE_SEND_ADDR_MATCHED and sends the data back. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_Enable, I2C_CheckEvent, I2C_GetLastEvent, I2C_SendData, I2C_RecvData, I2C_ClrFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as slave and I2C_Master or I2C_Master_Int as master;USART TX on PA9 at 115200 baud; after reset the USART repeatedly prints this is a i2c slave test demo and i2c slave test pass. |
| Use note | Both slave transmit and receive start from an address-match event, and i2c_slave_send and i2c_slave_recv each return only after polling to the stop condition;on an ACK loss or stop condition during transmit the code clears I2C_FLAG_ACKFAIL and exits;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example implements the slave transfer with two polled functions, and the main flow receives first and sends next to form a loopback.
Data path: i2c_slave_init configures the I2C1 slave → main enters an endless loop → i2c_slave_recv waits for the receive address match and fills one frame → i2c_slave_send waits for the send address match and sends it back → clear data_buf and repeat
1. Initialize the I2C1 slave. After the reset, I2C_InitParam is filled with the bus mode, fast-mode duty cycle, slave own address 0x10, acknowledgement, 7-bit address and speed, then the I2C is enabled; the slave address must match the master target address.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_slave);
i2cx_slave.BusMode = I2C_BUSMODE_I2C;
i2cx_slave.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_slave.OwnAddr1 = I2C_SLAVE_ADDR;
i2cx_slave.AckEnable = I2C_ACKEN;
i2cx_slave.AddrMode = I2C_ADDR_MODE_7BIT;
i2cx_slave.ClkSpeed = 100000; // 100000 100K
I2C_Init(I2Cx, &i2cx_slave);
I2C_Enable(I2Cx, ENABLE);
2. The slave transmit sends the first byte before matching. i2c_slave_send first enables the I2C and sends the first byte, then in the loop reads the last event: on I2C_EVT_SLAVE_DATA_SENDED it sends the next byte; on an ACK loss or stop condition it clears the flag and exits; a bus error takes the timeout callback.
I2C_Enable(I2Cx, ENABLE);
I2C_SendData(I2Cx, *sendBufferPtr++);
while (1)
{
lastevent = I2C_GetLastEvent(I2Cx);
if (lastevent == I2C_EVT_SLAVE_DATA_SENDED)
{
I2C_SendData(I2Cx, *sendBufferPtr++);
I2CTimeout = I2CT_LONG_TIMEOUT;
}
else if ((lastevent == I2C_EVT_SLAVE_ACK_MISS) || (lastevent == I2C_EVT_SLAVE_STOP_RECVD))
{
I2C_ClrFlag(I2Cx, I2C_FLAG_ACKFAIL);
I2C_Enable(I2Cx, ENABLE);
break;
}
3. The slave receive accumulates data by event. i2c_slave_recv waits for I2C_EVT_SLAVE_DATA_RECVD to take each byte and count it, and on I2C_EVT_SLAVE_STOP_RECVD it treats the frame as finished and leaves the loop; bus errors and timeouts are handled the same way.
I2C_Enable(I2Cx, ENABLE);
while (1)
{
lastevent = I2C_GetLastEvent(I2Cx);
if (lastevent == I2C_EVT_SLAVE_DATA_RECVD)
{
*data++ = I2C_RecvData(I2Cx);
rxDataNum++;
I2CTimeout = I2CT_LONG_TIMEOUT;
}
else if (lastevent == I2C_EVT_SLAVE_STOP_RECVD)
{
I2C_Enable(I2Cx, ENABLE);
break;
}
else if ((lastevent & 0x00000400) == 0x00000400)
{
CommTimeOut_CallBack(SLAVE_BUSERR);
4. The main flow receives first and sends next to form a loopback. After initializing, main enters an endless loop, receives one frame, sends it back unchanged, clears data_buf and prints a success message; a failure at either step breaks out of the inner loop to start again.
log_init();
log_info("\nthis is a i2c slave test demo\r\n");
/* Initialize the I2C EEPROM driver ----------------------------------------*/
i2c_slave_init();
Corresponding source files: src/main.c, inc/main.h
5.11.3.8 I2C_Slave_Int interrupt slave transfer
Example directory: projects/n32g41x_EVAL/examples/I2C/I2C_Slave_Int/
Enable the I2C and GPIO clocks and configure PA4 and PA5→Initialize I2C1 as a 100 kHz slave and configure the NVIC→Wait for the receive completion flag→Transfer in the interrupt handler by event→Wait for the send completion flag→Print the result
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a slave and uses the event interrupt to receive and send back one frame of 100 bytes, with the main flow only waiting for the two completion flags. |
| Source files | src/main.c: main, NVIC_Configuration, i2c_slave_init, I2Cx_IRQHandler;inc/main.h: pin, address and buffer macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0x10;SCL on PA4, SDA on PA5, with GPIO_AF7, GPIO_MODE_AF_OD, pull-up;interrupts enabled for event, buffer and error, a 100-byte buffer. |
| Program and data path | main calls i2c_slave_init and waits for flag_slave_recv_finish, then clears it and waits for flag_slave_send_finish;I2Cx_IRQHandler moves data between the slave address match, data transfer and stop condition events, and sets the send completion flag on an ACK loss. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigInt, I2C_Enable, I2C_GetLastEvent, I2C_SendData, I2C_RecvData, I2C_ClrFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as slave and I2C_Master or I2C_Master_Int as master;USART TX on PA9 at 115200 baud; after reset the USART prints this is a i2c slave int demo and recv finish, tx finish. |
| Use note | The slave receive and send-back share the same set of events, told apart by flag_slave_recv_finish and flag_slave_send_finish;on an ACK loss flag_slave_send_finish is set depending on whether rxDataNum is 0;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example puts both the slave receive and send into the interrupt, and the main flow waits for each phase with two flags.
Data path: NVIC_Configuration sets up the I2C vector → i2c_slave_init configures I2C1 and the interrupts → I2Cx_IRQHandler transfers data after the address match → the stop condition sets flag_slave_recv_finish → an ACK loss sets flag_slave_send_finish → main prints the result
1. Configure the I2C interrupt vector. As in the interrupt master example, NVIC_Configuration sets the I2Cx_IRQn priority twice, with preemption priority 4 and 3 and subpriority 1 both times.
NVIC_InitStructure.NVIC_IRQChannel = I2Cx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = I2Cx_IRQn; // test err
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
2. Initialize the I2C1 slave and enable the interrupts. After de-initializing and configuring I2C1, the event, buffer and error interrupts are enabled, the NVIC is set up and the I2C enabled; the slave address 0x10 must match the master target address.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_slave);
i2cx_slave.BusMode = I2C_BUSMODE_I2C;
i2cx_slave.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_slave.OwnAddr1 = I2C_SLAVE_ADDR;
i2cx_slave.AckEnable = I2C_ACKEN;
i2cx_slave.AddrMode = I2C_ADDR_MODE_7BIT;
i2cx_slave.ClkSpeed = 100000; // 100000 100K
I2C_Init(I2Cx, &i2cx_slave);
// int enable
I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
NVIC_Configuration();
I2C_Enable(I2Cx, ENABLE);
3. The interrupt handler separates the receive and send events. In the slave-mode branch: a receive address match clears the receive counter, a send address match clears the send counter, the data-sending and data-sent events write a byte from data_buf into DAT, the data-received event stores DAT into data_buf, and the stop condition sets flag_slave_recv_finish.
case I2C_EVT_SLAVE_RECV_ADDR_MATCHED:
// clear flag,ready to receive data
rxDataNum = 0;
break;
case I2C_EVT_SLAVE_SEND_ADDR_MATCHED:
rxDataNum = 0;
break;
case I2C_EVT_SLAVE_DATA_SENDING:
I2Cx->DAT = data_buf[rxDataNum++];
break;
case I2C_EVT_SLAVE_DATA_SENDED:
I2Cx->DAT = data_buf[rxDataNum++];
break;
case I2C_EVT_SLAVE_DATA_RECVD:
data_buf[rxDataNum++] = I2Cx->DAT;
break;
case I2C_EVT_SLAVE_STOP_RECVD:
I2C_Enable(I2Cx, ENABLE);
if(rxDataNum != 0)
{
flag_slave_recv_finish = 1; // The STOPF bit is not set after a NACK reception
}
break;
4. The main flow waits for the receive and send to complete. main first waits for flag_slave_recv_finish and prints the receive length, then clears it and waits for flag_slave_send_finish to print the send length; the two flags are set in the interrupt, so the main flow advances phase by phase.
log_init();
log_info("\nthis is a i2c slave int demo\r\n");
/* Initialize the I2C slave driver */
i2c_slave_init();
I2CTimeout = I2CT_LONG_TIMEOUT * 1000;
// recive data
while (flag_slave_recv_finish == 0) // wait for recv data finish
{
if ((I2CTimeout--) == 0)
CommTimeOut_CallBack(SLAVE_UNKNOW);
}
log_info("recv finish,recv len = %d\r\n", rxDataNum);
flag_slave_recv_finish = 0;
5. ACK loss handling in the interrupt. When the error branch detects I2C_EVT_SLAVE_ACK_MISS it clears I2C_FLAG_ACKFAIL; if data has already been sent this is the NACK of the last byte and flag_slave_send_finish is set, otherwise it is treated as an ordinary send failure.
/* --- Error handling --- */
last_event = I2C_GetLastEvent(I2Cx);
if(last_event & I2C_EVT_SLAVE_ACK_MISS)
{
I2C_ClrFlag(I2Cx, I2C_FLAG_ACKFAIL);
if(rxDataNum != 0) //slave send the last data and recv NACK
{
flag_slave_send_finish = 1;
}
else //not the last data recv nack, send fail
{
}
}
Corresponding source files: src/main.c, inc/main.h
5.11.3.9 PMBUS_Master PMBus master
Example directory: projects/n32g41x_EVAL/examples/I2C/PMBUS_Master/
Configure the PMBus control and alert pins→Initialize I2C1 as master and configure the interrupt→Pack the transmit buffer by command group→Generate the start condition and transfer→Compute and compare the PEC→Print the result of each command test
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a PMBus master and executes read-byte, read-word, write-byte and write-word transactions against the slave by command index, computing and checking the PEC byte when PEC is enabled. |
| Source files | src/main.c: main and the command tests;src/PMBusMaster.c, inc/PMBusMaster.h: PMBus command packing and CRC8;src/I2CMaster.c, inc/I2CMaster.h: I2CMaster_Transmit and I2Cx_IRQHandler;inc/PMBus.h, inc/main.h: command definitions and pin macros;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, master and slave address 0x30/0x10;SCL on PA4, SDA on PA5 ( GPIO_AF7), PMBus control and alert pins on PA7 and PA8;interrupts enabled for event, buffer and error; PEC polynomial CRC8_POLY, initial value CRC8_INIT_REM. |
| Program and data path | main calls PMBusMaster_Init and then reads STATUS_TEMPERATURE, OPERATION, STATUS_WORD and VOUT_COMMAND, and writes STORE_DEFAULT_CODE, OPERATION and VOUT_COMMAND;each PMBusMaster call fills PMBusMaster_TransmitBuffer by command group, transfers through I2CMaster_Transmit and, when PEC is enabled, computes the CRC8 with PMBusMaster_Crc8MakeBitwise and compares it with the slave PEC. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigInt, I2C_Enable, I2C_GenerateStart, I2C_GenerateStop, I2C_SendAddr7bit, I2C_SendData, I2C_RecvData, I2C_ConfigAck, I2C_GetLastEvent, I2C_ClrFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as master and PMBUS_Slave as slave;USART TX on PA9 at 115200 baud; after reset the USART prints the read values of each command and pmbus master int test pass, which can be checked against the slave USART log. |
| Use note | With PEC enabled, the CRC input stream is assembled from the command group after the transfer and then passed to the CRC8 function; a read byte or read word writes the command first and then reads data with a repeated start, told apart by PMBusMaster_CommandGroup;the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example splits a PMBus transaction into command packing, I2C transfer and PEC checking, and the main flow calls it by command index.
Data path: PMBusMaster_Init configures the pins and I2C1 → PMBusMaster packs PMBusMaster_TransmitBuffer by command group → I2CMaster_Transmit transfers through I2Cx_IRQHandler → with PEC enabled PMBusMaster_Crc8MakeBitwise computes the CRC8 → main collects the result of each command test
1. Configure the PMBus pins and initialize the master. PMBusMaster_Init configures the control and alert lines as alternate-function push-pull, then calls i2c_master_init to finish the I2C1 initialization; the pin numbers and ports come from the macros in inc/PMBus.h.
// Control Line functionality GPIO0
GPIO_InitType pmbus_gpio;
pmbus_gpio.Pin = PMBUS_CONTRL_PIN ;
pmbus_gpio.GPIO_Pull = GPIO_PULL_UP;
pmbus_gpio.GPIO_Alternate = GPIO_NO_AF;
pmbus_gpio.GPIO_Mode = GPIO_MODE_AF_PP;
pmbus_gpio.GPIO_Slew_Rate = GPIO_SLEW_RATE_SLOW;
GPIO_InitPeripheral(PMBUS_CONTRL_GPIO, &pmbus_gpio);
pmbus_gpio.Pin = PMBUS_ALERT_PIN;
pmbus_gpio.GPIO_Pull = GPIO_PULL_UP;
pmbus_gpio.GPIO_Alternate = GPIO_NO_AF;
pmbus_gpio.GPIO_Mode = GPIO_MODE_AF_PP;
pmbus_gpio.GPIO_Slew_Rate = GPIO_SLEW_RATE_SLOW;
GPIO_InitPeripheral(PMBUS_ALERT_GPIO, &pmbus_gpio);
i2c_master_init();
2. Dispatch the transfer by command group. PMBusMaster enters a different branch from PMBusMaster_CommandGroup: a read byte sends one command byte and reads one byte, a read word reads two bytes, and a write byte or write word packs the command plus data; every group transfers through I2CMaster_Transmit.
switch(PMBusMaster_CommandGroup)
{
#if !PEC
case READBYTE: // read byte
PMBusMaster_RxCount = 1;
while (I2CMaster_NotReady());
I2CMaster_Transmit(1,PMBusMaster_TransmitBuffer,1,PMBusMaster_ReceiveBuffer);
break;
case READWORD: // read word
PMBusMaster_RxCount = 2;
while (I2CMaster_NotReady());
I2CMaster_Transmit(1,PMBusMaster_TransmitBuffer,2,PMBusMaster_ReceiveBuffer);
break;
3. CRC8 computation for PEC. With PEC enabled, PMBusMaster_Crc8MakeBitwise computes the CRC8 bit by bit over the command stream with the given initial value and polynomial: it takes the first byte as the initial value, shifts in each byte, pads eight zeros at the end and returns the low byte. The result is the PEC byte used for transmission or checking.
uint8_t PMBusMaster_Crc8MakeBitwise(uint8_t PMBusMaster_CRC, uint8_t PMBusMaster_Poly, uint8_t *PMBusMaster_Pmsg, uint16_t PMBusMaster_MsgSize)
{
uint16_t i, j, carry;
uint8_t msg;
msg &= 0x00FF;
}
}
// The previous loop computes the CRC of the input bit stream. To this,
// 8 trailing zeros are padded and the CRC of the resultant value is
// computed. This gives the final CRC of the input bit stream.
for(j = 0 ; j < 8 ; j++)
{
carry = PMBusMaster_CRC & 0x80;
PMBusMaster_CRC <<= 1;
if(carry) PMBusMaster_CRC ^= PMBusMaster_Poly;
}
The initial value, polynomial and byte count must match the slave exactly, or the PEC check will always fail.
4. The interrupt finishes after the transfer. I2Cx_IRQHandler distinguishes the command groups after EV8: a read byte or read word re-enables the acknowledgement, resets ACKPOS and generates a repeated start after the command is sent, while the other commands generate the stop condition directly and set the transmit completion flag.
case I2C_EVT_MASTER_DATA_SENDED: // byte data send finish
// bit2 BSF (Byte transfer finished)
if (tx_num == I2CMaster_TransmitLen) // data send finish
{
if (Comm_Flag == C_READY)
{
if((PMBusMaster_CommandGroup == 0/*READBYTE*/) || (PMBusMaster_CommandGroup == 2/*READWORD*/))
{
I2C_ConfigAck(I2Cx, ENABLE);
Comm_Flag = C_START_BIT;
flag_trans_direct = 1;
I2Cx->CTRL1 &= I2C_NACK_POS_CURRENT; // reset ACKPOS
I2C_GenerateStart(I2Cx, ENABLE);
}
else
{
Comm_Flag = C_STOP_BIT;
I2C_GenerateStop(I2Cx, ENABLE);
}
flag_master_send_finish = 1;
}
}
break;
5. The main flow arranges the command tests. main calls PMBusMaster in turn to read the temperature, operation, status word and output voltage commands, then writes several commands and reads them back for checking, and finally collects test_status1 to test_status7 to print the overall result.
log_init();
log_info("\nThis is a pmbus master int demo!\r\n");
/* Initialize the I2C driver */
PMBusMaster_Init();
//reads
log_info("pmbus read test\r\n");
log_info("pmbus master read temperature test!\r\n");
Corresponding source files: src/main.c, src/PMBusMaster.c, src/I2CMaster.c, inc/PMBus.h
5.11.3.10 PMBUS_Slave PMBus slave
Example directory: projects/n32g41x_EVAL/examples/I2C/PMBUS_Slave/
Initialize I2C1 as slave and configure the interrupt→Build the PMBus command look-up table→The main loop waits for the receive to complete→Decode the command and prepare the reply data→Transfer in the interrupt handler→Check the CRC8 when PEC is enabled
| Item | Source analysis |
|---|---|
| Function | I2C1 acts as a PMBus slave, decodes the command sent by the master with a command look-up table, prepares the reply data by command group and checks the CRC8 when PEC is enabled. |
| Source files | src/main.c: main;src/PMBusSlave.c, inc/PMBusSlave.h: command table, decode, transfer state machine and CRC8;inc/PMBus.h, inc/main.h: command definitions and pin macros;src/n32g41x_it.c, inc/n32g41x_it.h: interrupt code;readme.txt: function and notes;MDK-ARM/, EWARM/: Keil and IAR projects. |
| Key configuration | Clock source HSI plus PLL, system clock 80 MHz for N32G412 and 96 MHz for N32G415; I2C1 at 100 kHz, I2C_ADDR_MODE_7BIT, slave address 0x10;SCL on PA4, SDA on PA5 ( GPIO_AF7), PMBus control and alert pins on PA7 and PA8;interrupts enabled for event, buffer and error; command table PMBus_Commands[120];PEC polynomial CRC8_POLY, initial value CRC8_INIT_REM. |
| Program and data path | main calls PMBusSlave_Init and then calls PMBusSlave in an endless loop;when the receive completion flag is set PMBusSlave_ProcessMasterWriteCommand decodes the command, and on a master read PMBusSlave_ProcessMasterReadCommand fills PMBusSlave_TransmitBuffer from the table;I2Cx_IRQHandler moves data between the slave events and clears the flags on the stop condition. |
| Main APIs | I2C_InitStruct, I2C_Init, I2C_ConfigInt, I2C_Enable, I2C_GetLastEvent, I2C_SendData, I2C_RecvData, I2C_GetIntStatus, I2C_ClrIntPendingBit, I2C_ClrFlag |
| Verification | Connect SCL, SDA and GND of two boards, with this project as slave and PMBUS_Master as master;USART TX on PA9 at 115200 baud; after reset the slave answers the master commands, and the master USART log should be checked to confirm the transfer and PEC result. |
| Use note | The slave address must match the master target address; the command table splits the command groups by index range and index 0 is the dummy byte used to probe whether the slave is present; with PEC enabled, check the CRC8 after the transfer with the same initial value and polynomial; the development environment is KEIL MDK-ARM V5.34 or IAR EWARM 8.50.1. |
Core-code walkthrough and porting
The example implements the PMBus slave with a command look-up table and an interrupt state machine, and the main loop only decodes after the receive completes.
Data path: PMBusSlave_Init configures the pins and I2C1 → main enters an endless loop and calls PMBusSlave → I2Cx_IRQHandler moves data between the slave events → the receive completion calls PMBusSlave_ProcessMasterWriteCommand → the command is decoded from PMBus_Commands → with PEC enabled PMBusSlave_Crc8MakeBitwise checks the CRC8
1. Command table and linear search. PMBusSlave_DecodeCommand declares the command-group variable and then walks the 120-entry PMBus_Commands to find the entry equal to the received command, remembering its index in PMBusSlave_Index; index 0 is the dummy byte that probes whether the slave is present.
uint8_t PMBusSlave_CommandGroup;
for(PMBusSlave_Index = 0; PMBusSlave_Index < 120; PMBusSlave_Index++)
{
if(PMBus_Commands[PMBusSlave_Index] == PMBusSlave_RxCommand)
break;
}
The table size and the index it returns are the only basis for decoding which commands the slave supports.
2. Initialize as a slave and enable the interrupts. i2c_slave_init de-initializes and configures I2C1 with I2C_SLAVE_ADDR as the slave address, then enables the event, buffer and error interrupts, sets up the NVIC and enables the I2C. The mapping between the vector and I2Cx_IRQHandler is established here.
I2C_DeInit(I2Cx);
I2C_InitStruct(&i2cx_slave);
i2cx_slave.BusMode = I2C_BUSMODE_I2C;
i2cx_slave.FmDutyCycle = I2C_FMDUTYCYCLE_2;
i2cx_slave.OwnAddr1 = I2C_SLAVE_ADDR;
i2cx_slave.AckEnable = I2C_ACKEN;
i2cx_slave.AddrMode = I2C_ADDR_MODE_7BIT;
i2cx_slave.ClkSpeed = 100000; // 100000 100K
I2C_Init(I2Cx, &i2cx_slave);
// int enable
I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
NVIC_Configuration();
I2C_Enable(I2Cx, ENABLE);
3. Map the index to a command group. PMBusSlave_DecodeCommand then decides the command group from the range the index falls in: 0 is the dummy byte, 1 to 12 is a read byte, 13 to 39 is a read word, and so on, with an illegal index entering an endless loop. The command group decides how many bytes to reply.
if(PMBusSlave_Index == 0)//dummy byte to check if slave is present
PMBusSlave_DummyCommand = 1;
if(PMBusSlave_Index > 0 && PMBusSlave_Index < 13) //read byte from slave device
PMBusSlave_CommandGroup = READBYTE;
if(PMBusSlave_Index > 12 && PMBusSlave_Index < 40) //read word from slave device
PMBusSlave_CommandGroup = READWORD;
if(PMBusSlave_Index > 39 && PMBusSlave_Index < 44) //write byte to slave device
PMBusSlave_CommandGroup = WRITEBYTE;
4. The interrupt handler drives the slave transfer. In the slave-mode branch: a receive address match clears the receive counter, the data-received event stores bytes into PMBusSlave_ReceiveBuffer, a send address match clears the transmit counter and calls PMBusSlave_ProcessMasterReadCommand to prepare the data, the data-sending and data-sent events write buffer bytes into DAT, and the stop condition sets flag_slave_recv_finish and clears the flags.
if ((last_event & I2C_ROLE_MASTER) != I2C_ROLE_MASTER) // MSMODE = 0:I2C slave mode
{
switch (last_event)
{
case I2C_EVT_SLAVE_RECV_ADDR_MATCHED: //Master has sent the slave address(w) and the slave prepares to receive data from the master
// clear flag,ready to receive data
i2c_rcv_cnt = 0;
break;
case I2C_EVT_SLAVE_DATA_RECVD:
PMBusSlave_ReceiveBuffer[i2c_rcv_cnt++] = I2C_RecvData(I2Cx);
break;
case I2C_EVT_SLAVE_SEND_ADDR_MATCHED: //Master has sent the slave address(R) and the slave prepares to send data to the master
i2c_transmit_cnt = 0;
pmbus_tx_num = 0;
PMBusSlave_ProcessMasterReadCommand();
break;
5. The main loop decodes a write command when the receive completes. When flag_slave_recv_finish is set, PMBusSlave clears the flag and calls PMBusSlave_ProcessMasterWriteCommand to handle a write command; main only initializes and calls the function repeatedly. This keeps the time-consuming command handling in the main loop while the interrupt only moves data.
void PMBusSlave(void)
{
if(flag_slave_recv_finish)
{
flag_slave_recv_finish = 0;
PMBusSlave_ProcessMasterWriteCommand(); // The STOPF bit is not set after a NACK reception
}
}
Corresponding source files: src/PMBusSlave.c, inc/PMBusSlave.h, inc/PMBus.h
5.11.4 Notes and troubleshooting
| Scenario | Check & action | Source |
|---|---|---|
| No acknowledgement during the master address phase | The user manual states that when the slave address is not acknowledged the ACKFAIL bit of STS1 is set, the slave releases the bus in hardware and the master must generate a stop condition and retry;for an EEPROM page write, an address NACK during the internal write cycle is normal, so wait for the device to be ready before resending. |
User manual |
| The bus is stuck after an error | The user manual states that on a BUSERR bus error the master does not release the bus and software must generate a stop condition so the slave discards its data and ends the transfer, while on arbitration loss the hardware releases the bus and returns to the slave state automatically. | User manual |
| Data is lost after clock stretching is disabled | The user manual states that OVERRUN can only occur when clock stretching is disabled (NOEXTEND=1), when the slave does not read the previous byte in time and a new byte overwrites it; with DMA do not enable BUFINTEN, to avoid the buffer interrupt conflicting with the DMA. |
User manual |
| The PEC check fails | The user manual states that PEC uses the CRC-8 polynomial C(x)=x8+x2+x+1, the PEC bit is sent before the acknowledge pulse and is invalidated on arbitration loss; the master and slave must use the same initial value, polynomial and sequence of bytes. |
User manual |
| Timeout detection does not fire or fires wrongly | The user manual states that the SCL low time can be 25 ms, 100 ms, 1 s or 4 s, the SCL high time can be 256 us, 512 us, 1 ms or 128 us, and there is also an SDA low timeout; a slave timeout resets the communication and releases the bus, while a master timeout generates a stop condition automatically; on SMBus the low timeout is 10 ms for a master and 25 ms for a slave. |
User manual |
| The digital or analog filter setting has no effect | The user manual states that the analog filter is on by default and can be set to 5, 15, 25 or 35 ns, and the digital filter is configured by the SDADFW/SCLDFW bits; check whether clock stretching is enabled and whether the per-byte DATACKTC acknowledgement is used with clock stretching on. |
User manual |
| Writes to the clock control register have no effect | The user manual states that the clock control register can only be set while the I2C is disabled, and the minimum value in standard mode and the fast-mode duty-cycle limit differ, so set the values from the manual before enabling the peripheral. | User manual |
The errata sheet V1.0.0 (2026.6.30) for the N32G412 and N32G415 series lists only a single A-version device row with an empty errata link column and no I2C-related entries.