5.11 I2C 使用说明

本章说明 N32G41x 标准外设库中 I2C 驱动与示例的使用方法。驱动由 n32g41x_i2c.h 与 n32g41x_i2c.c 提供,共 46 个公开函数,覆盖复位与初始化、时序与地址配置、数据收发、PEC 校验、DMA 传输、数字与模拟滤波、中断与标志以及超时检测;示例目录 projects/n32g41x_EVAL/examples/I2C/ 下含 10 个工程,演示 EEPROM 的轮询、中断与 DMA 读写,主/从与 10 位地址收发,以及 PMBus 主从通信。I2C 的速率、地址格式、滤波与超时等细节请配合用户手册的“内部集成电路总线(I2C)”章节阅读,芯片已知问题以勘误手册为准。

5.11.1 相关文件

相对路径 作用
firmware/n32g41x_std_periph_driver/inc/n32g41x_i2c.h 定义 I2C_InitParam 结构体与总线模式、地址模式、速率、ACK、PEC、滤波、超时等宏,并声明全部 46 个 I2C 函数
firmware/n32g41x_std_periph_driver/src/n32g41x_i2c.c 实现上述 46 个函数:完成外设复位与初始化、起始/停止条件、地址与数据寄存器访问、PEC 计算、DMA 使能、数字与模拟滤波配置、中断与标志操作以及超时检测
projects/n32g41x_EVAL/examples/I2C/ I2C 示例根目录,含 EEPROM_DMA、EEPROM_Int、EEPROM_Polling、I2C_10bit、I2C_Master、I2C_Master_Int、I2C_Slave、I2C_Slave_Int、PMBUS_Master、PMBUS_Slave 10 个示例
projects/n32g41x_EVAL/examples/I2C/EEPROM_Polling/ 该示例的工程组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt
projects/n32g41x_EVAL/bsp/ 各示例共用的板级支持文件:日志打印与毫秒延时函数

5.11.2 API 参考

搜索 API共 46 个函数

以下 46 个公开函数按 n32g41x_i2c.h 的声明顺序分为 9 组;每个条目给出功能摘要与 n32g41x_i2c.c 中该函数正上方的注释原文。

复位与初始化

I2C_DeInit、I2C_Init、I2C_InitStruct

时钟与使能

I2C_ConfigFastModeDutyCycle、I2C_Enable、I2C_GetRegister

时序与地址配置

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

数据收发

I2C_SendData、I2C_RecvData

PEC 校验

I2C_ConfigPecLocation、I2C_SendPEC、I2C_ComputePec、I2C_GetPec

DMA 传输

I2C_EnableDMA、I2C_EnableDmaLastSend

数字与模拟滤波

I2C_SetSDADigitalFilterWidth、I2C_SetSCLDigitalFilterWidth、I2C_SetSDAAnalogFilterWidth、I2C_EnableSDAAnalogFilter、I2C_SetSCLAnalogFilterWidth、I2C_EnableSCLAnalogFilter

中断与标志

I2C_ConfigInt、I2C_CheckEvent、I2C_GetLastEvent、I2C_GetFlag、I2C_ClrFlag、I2C_GetIntStatus、I2C_ClrIntPendingBit

超时检测

I2C_SetTimeoutLowLimit、I2C_SetTimeoutHighLimit、I2C_ConfigTimeoutInt、I2C_EnableTimeoutDetect、I2C_GetTimeoutFlag、I2C_ClrTimeoutFlag

void I2C_DeInit(const I2C_Module *I2Cx)将 I2Cx 外设寄存器复位为默认复位值。
源码注释
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)按 I2C_InitParam 中的指定参数初始化 I2Cx 外设。
源码注释
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)将 I2C_InitParam 各成员填充为默认值。
源码注释
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)选择指定的 I2C 快速模式占空比。
源码注释
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)使能或关闭指定的 I2C 外设。
源码注释
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)读取指定的 I2C 寄存器并返回其值。
源码注释
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)使能或关闭产生起始条件。
源码注释
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)使能或关闭产生停止条件。
源码注释
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)使能或关闭应答。
源码注释
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)使能或关闭 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)发送地址字节以选中从设备。
源码注释
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)设置指定的 I2C 自身地址 2。
源码注释
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)使能或关闭指定的 I2C 双地址模式。
源码注释
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)使能或关闭指定的 I2C 广播呼叫功能。
源码注释
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)在主机接收模式中选择指定的 I2C NACK 位置。
源码注释
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)为指定的 I2C 设置 SMBus Alert 引脚为高电平或低电平。
源码注释
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)使能或关闭指定的 I2C 地址解析协议(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)使能或关闭指定的 I2C 时钟延展。
源码注释
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)使能或关闭指定的 I2C 软件复位。
源码注释
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)通过 I2Cx 外设发送一个数据字节。
源码注释
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)返回 I2Cx 外设最近接收到的数据。
源码注释
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)选择指定的 I2C PEC 位置。
源码注释
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)使能或关闭指定的 I2C PEC 传送。
源码注释
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)使能或关闭所传送字节的 PEC 值计算。
源码注释
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)获取指定 I2C 的 PEC 值。
源码注释
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)使能或关闭 I2C DMA 传送。
源码注释
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)使能或关闭 I2C DMA 最后一次传送。
源码注释
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 数字滤波宽度选择。
源码注释
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 数字滤波宽度选择。
源码注释
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 模拟滤波宽度选择。
源码注释
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)使能或关闭 SDA 模拟滤波。
源码注释
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 模拟滤波宽度选择。
源码注释
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)使能或关闭 SCL 模拟滤波。
源码注释
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)使能或关闭指定的 I2C 中断。
源码注释
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)检查最近的 I2Cx 事件是否等于传入的事件。
源码注释
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)返回最近的 I2Cx 事件。
源码注释
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)检查指定的 I2C 标志是否置位。
源码注释
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)清除 I2Cx 的标志。
源码注释
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)检查指定的 I2C 中断是否发生。
源码注释
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)清除 I2Cx 的中断状态位。
源码注释
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)超时低阈值选择。
源码注释
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)超时高阈值选择。
源码注释
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)使能指定的 I2C 超时中断。
源码注释
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)使能指定的 I2C 超时检测功能。
源码注释
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)检查指定的 I2C 超时标志是否置位。
源码注释
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)清除 I2Cx 的待处理标志。
源码注释
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 示例说明

本节说明 I2C 的 10 个示例:EEPROM_DMA、EEPROM_Int 与 EEPROM_Polling 用 DMA、中断与轮询三种方式操作同一个 EEPROM 驱动;I2C_10bit 演示 10 位地址的主从对测;I2C_Master 与 I2C_Master_Int 演示主机轮询与中断收发;I2C_Slave 与 I2C_Slave_Int 演示从机轮询与中断收发;PMBUS_Master 与 PMBUS_Slave 演示带 PEC 的 PMBus 主从通信。主从类示例均为两块开发板对测。

5.11.3.1 EEPROM_DMA DMA 页读写

工程目录: projects/n32g41x_EVAL/examples/I2C/EEPROM_DMA/

使能 I2C、GPIO 与 DMA 时钟→配置 PA4、PA5 为复用开漏→初始化 I2C1 为 100 kHz 主机→配置 DMA_CH6 发送与 DMA_CH7 接收→页写 256 字节→回读并比对

项目 源码解读
功能 通过 DMA 对 AT24C02 EEPROM 做整页写入与回读,验证 I2C1 主机加 DMA 的收发闭环。
源码组成 src/main.c:main;
src/i2c_eeprom.c、inc/i2c_eeprom.h:EEPROM 驱动、I2C 与 DMA 配置;
src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;
readme.txt:功能与对测说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0xA0;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
发送用 DMA_CH6(DMA_REMAP_I2C1_TX)、接收用 DMA_CH7(DMA_REMAP_I2C1_RX),外设地址均为 I2C1->DAT、字节传输、普通模式。
程序与数据路径 main 调用 I2C_EE_Init 后填充 tx_buf,经 I2C_EE_WriteBuffer 以页为单位写入 0x00 起 256 字节,再由 I2C_EE_ReadBuffer 回读并用 Buffercmp 比对;
写页时 DMA 把内存数据搬入 I2C1->DAT,读页时 DMA 从 I2C1->DAT 搬入内存。
主要 API I2C_InitStruct、I2C_Init、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_ConfigAck、I2C_EnableDMA、I2C_EnableDmaLastSend、I2C_GetFlag、DMA_Init、DMA_RequestRemap
验证方法 EEPROM 的 SCL、SDA 接 PA4、PA5,串口 TX 为 PA9、波特率 115200;
复位后串口打印 This is a I2C EEPROM DMA demo 与读写比对结果。
使用提示 发送与接收各占一个 DMA 通道,DMA_RequestRemap 的映射宏要与通道一一对应;
主接收的最后一字节前要使能 I2C_EnableDmaLastSend,由硬件自动产生 NACK 与停止条件;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例先初始化 I2C1 并打开 DMA 时钟,再由页写与读缓冲两个函数分别用 DMA 完成发送与接收。

数据路径: I2C_EE_Init 配置 I2C1 并打开 DMA 时钟 → I2C_EE_PageWrite 用 DMA_CH6 把 pBuffer 搬入 I2Cx->DAT → main 调用 I2C_EE_WriteBuffer 分页写满 256 字节 → I2C_EE_ReadBuffer 用 DMA_CH7 把 I2Cx->DAT 搬入 pBuffer → Buffercmp 比对结果

1. 初始化 I2C1 并打开 DMA 时钟。 这里先把 I2C 复位并填入 I2C_InitParam:总线类型选 I2C、7 位地址、自身地址 0xff、速率取 I2C_Speed;最后一行单独打开 AHB 上的 DMA 时钟,供后面的 DMA_CH6 与 DMA_CH7 使用。移植到别的外设实例时要同步改 I2Cx 与 I2C_Speed。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_Init
    /** 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);

DMA 时钟与 I2C 时钟缺一不可,换板时要一并核对。

2. 配置发送用的 DMA 通道。 外设地址指向 I2Cx->DAT,存储器地址指向 pBuffer,方向为外设目的;BufSize 取本次要写的字节数,数据宽度为字节,普通模式只传一次;DMA_RequestRemap 把 DMA_CH6 映射到 I2C1 的发送请求。通道号、映射宏与数据宽度要和 I2C 的传输单位一致。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    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);

接收方向则用 DMA_DIR_PERIPH_SRC 与接收映射宏,方向、通道与映射三者必须对应。

3. 使能 I2C DMA 并在字节传输结束后收尾。 先使能 I2C 的 DMA 请求,再打开 DMA 通道;随后轮询 I2C_FLAG_BYTEF,它置位表示最后一个字节已从数据寄存器移出,此时才能产生停止条件并关闭 DMA。收尾顺序是先关 DMA 再发停止条件,最后等待 EEPROM 内部写周期结束。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    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();

停止条件要等 BYTEF 之后再发,否则最后一个字节会被截断。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    /** Send STOP condition */
    I2C_GenerateStop(I2Cx, ENABLE);
    I2C_EnableDMA(I2Cx, DISABLE);
    DMA_EnableChannel(DMA_CH6, DISABLE);
    I2C_EE_WaitEepromStandbyState();
    I2C_EE_WriteOnePageCompleted();

4. 配置接收用的 DMA 通道。 读一页时方向反转为外设源,BufSize 取要读的字节数;其余字段与发送一致。当只读一个字节时不进入该分支,直接用轮询方式读一个字节。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_ReadBuffer
    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);
    }

读一页前的地址阶段仍由软件完成,DMA 只负责数据阶段。

5. 主接收最后一字节的收尾。 数据长度大于一时,先使能应答、清 ADDR,再使能 I2C 的 DMA 与“最后一次 DMA 传送”,然后打开接收通道;轮询 DMA 的传输完成标志 DMA_FLAG_TC7,最后关闭各使能位并产生停止条件。I2C_EnableDmaLastSend 让硬件在最后数据前自动送 NACK,是主接收的关键。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_ReadBuffer
      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)

关闭顺序与使能顺序相反,先关 DMA 通道再关 I2C 的 DMA,最后发停止条件。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_ReadBuffer
      I2C_EnableDMA(I2Cx, DISABLE);
      DMA_EnableChannel(DMA_CH7, DISABLE);
      I2C_EnableDmaLastSend(I2Cx, DISABLE);
      I2C_GenerateStop(I2Cx, ENABLE);

对应源码: src/i2c_eeprom.c、inc/i2c_eeprom.h

5.11.3.2 EEPROM_Int 中断页读写

工程目录: projects/n32g41x_EVAL/examples/I2C/EEPROM_Int/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 主机→配置并打开 I2C1 中断→页写 256 字节→中断服务程序按事件状态机推进→回读并比对

项目 源码解读
功能 用 I2C1 的事件、缓冲与错误中断对 AT24C02 EEPROM 做整页写入与回读,收发在中断服务程序里按事件状态机推进。
源码组成 src/main.c:main;
src/i2c_eeprom.c、inc/i2c_eeprom.h:EEPROM 驱动、I2C 与 NVIC 配置、I2C1_IRQHandler;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0xA0;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
中断使能事件、缓冲与错误三类中断,NVIC_PriorityGroup_2,I2C1_IRQn 抢占 0、子优先级 1 与 0。
程序与数据路径 main 调用 I2C_EE_Init 后填充 tx_buf;
I2C_EE_PageWrite 只使能应答与中断并产生起始条件,随后由 I2C1_IRQHandler 在 EV5、EV6、EV7、EV8 间搬运数据;
写满后 i2c_comm_state 置 COMM_DONE,读回由 I2C_EE_ReadBuffer 触发。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigAck、I2C_ConfigInt、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_SendData、I2C_RecvData、I2C_GetLastEvent、I2C_GetFlag、I2C_ClrFlag
验证方法 EEPROM 的 SCL、SDA 接 PA4、PA5,串口 TX 为 PA9、波特率 115200;
复位后串口打印 This is a I2C EEPROM interrupt demo 与读写比对结果。
使用提示 中断方式下 I2C_EE_PageWrite 不阻塞等待,必须由 I2C1_IRQHandler 驱动状态机推进;
错误分支用 I2C_FLAG_ACKFAIL 区分 EEPROM 写忙与真实失败;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例把 I2C1 的收发全部交给中断,主流程只负责发起与等待完成。

数据路径: I2C_EE_Init 配置 I2C1 与 NVIC → I2C_EE_PageWrite 使能应答与中断并产生起始条件 → I2C1_IRQHandler 在 EV5、EV6、EV7、EV8 间推进收发 → 写满后 i2c_comm_state 置 COMM_DONE → main 比对 tx_buf 与 rx_buf

1. 初始化 I2C1 并配置中断向量。 先按 I2C_InitParam 复位并配置 I2C1,再设置 NVIC:NVIC_PriorityGroup_2 下把 I2C1_IRQn 的抢占优先级与子优先级分别置为 0 和 1,第二次设置把子优先级改为 0。中断服务程序与向量的对应关系要在这里建立。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_Init
    /** 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 决定了抢占位与子优先级位的划分,改优先级分组时要重新核对取值。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_Init
    /** 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. 页写只负责使能与启动。 与轮询方式不同,这里把参数写入静态变量、复制待发数据到 SendBuf,再使能应答与中断,最后交给中断状态机推进;函数本身不再轮询事件。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    /** 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);

中断使能的三个位分别对应事件、缓冲与错误中断,少使能一位都会漏掉对应状态。

3. 中断服务程序的接收状态机。 主接收在 EV6 与 EV7 上分别处理:收到地址匹配后按剩余字节数决定是否关闭应答并预处理 NACK;每收到一个字节写入缓冲区,剩最后一字节时关闭应答、产生停止条件,收满后置 COMM_DONE。状态机依赖 Int_NumByteToRead 计数,移植时要保证计数与缓冲区大小一致。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C1_IRQHandler
    /** 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;

收满后关闭中断并置 COMM_DONE,主流程据此退出等待。

4. 中断中的错误处理。 错误分支读取 I2C_FLAG_ACKFAIL:若处于起始阶段说明 EEPROM 正在写周期,重新产生起始条件重试;否则视为真实失败,产生停止条件并置 COMM_EXIT;最后清除标志。区分两种失败是中断收发能重试的关键。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C1_IRQHandler
    /* --- 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);
    }

对应源码: src/i2c_eeprom.c、inc/i2c_eeprom.h

5.11.3.3 EEPROM_Polling 轮询页读写

工程目录: projects/n32g41x_EVAL/examples/I2C/EEPROM_Polling/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 主机→页写 256 字节→轮询 EV5、EV6、EV8 逐字节发送→轮询方式回读→比对结果

项目 源码解读
功能 用轮询方式对 AT24C02 EEPROM 做整页写入与回读,每一步都用 I2C_CheckEvent 与超时计数等待事件。
源码组成 src/main.c:main;
src/i2c_eeprom.c、inc/i2c_eeprom.h:EEPROM 驱动与 I2C 配置、I2C_EE_WaitEepromStandbyState;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0xA0;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
不使能 I2C 中断,全部事件由软件轮询。
程序与数据路径 main 调用 I2C_EE_Init 后填充 tx_buf,I2C_EE_WriteBuffer 以页为单位写入 0x00 起 256 字节,I2C_EE_ReadBuffer 回读,Buffercmp 比对;
页写每写一字节都等待 I2C_EVT_MASTER_DATA_SENDED。
主要 API I2C_InitStruct、I2C_Init、I2C_CheckEvent、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_SendData、I2C_RecvData、I2C_ConfigAck、I2C_GetFlag
验证方法 EEPROM 的 SCL、SDA 接 PA4、PA5,串口 TX 为 PA9、波特率 115200;
复位后串口打印 This is a I2C EEPROM polling demo 与读写比对结果。
使用提示 轮询方式代码最直观,但每个事件都要软件等待并做超时保护,实时性差;
单个字节读与多字节读的分支不同,NumByteToRead 为 1 时不进入多字节流程;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例用轮询方式逐步推进 I2C1 的收发,每一步都用事件或标志判断进度。

数据路径: I2C_EE_Init 配置 I2C1 → I2C_EE_PageWrite 产生起始条件并等待 EV5 → 发送从机地址等待 EV6 → 逐字节发送等待 EV8 → I2C_EE_ReadBuffer 回读 → I2C_EE_WaitEepromStandbyState 等待写周期 → main 比对结果

1. 初始化 I2C1。 复位后按 I2C_InitParam 填入总线类型、快速模式占空比、自身地址、应答、7 位地址与速率,再调用 I2C_Init。轮询方式不配置 NVIC,全部事件由软件读取。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_Init
    /** 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. 产生起始条件并等待 EV5。 页写开始先发起始条件,再循环调用 I2C_CheckEvent 等待主机模式标志;每次循环递减超时计数,减到零就走超时回调。事件标志由 I2C_CheckEvent 读取并顺带清除。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    /** 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. 逐字节发送并等待 EV8。 页写的主体是一个循环:每送一个字节调用 I2C_SendData,再等待 I2C_EVT_MASTER_DATA_SENDED,表示数据寄存器已空、可以放下一个字节。NumByteToWrite 与 EEPROM 页大小一致时才能整页写入。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_PageWrite
    /** 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. 单字节读的分支。 回读时先处理只读一个字节的情形:关闭应答、依次读 STS1 与 STS2 以清除 ADDR,再产生停止条件。多字节读则在每个字节前维护应答与 NACK 位置,逻辑与这里不同。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_ReadBuffer
    /** 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. 等待 EEPROM 内部写周期。 页写结束后 EEPROM 进入内部写周期会对地址不应答,I2C_EE_WaitEepromStandbyState 反复发送从机地址并读取 STS1 判断是否被应答,直到 EEPROM 就绪;这段逻辑直接写入并检测地址阶段,移植时要保留轮询上限。

src/i2c_eeprom.csrc/i2c_eeprom.c · I2C_EE_WaitEepromStandbyState
        /** 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)

写周期内地址不应答属于正常现象,只有超过重试上限才视为失败。

对应源码: src/i2c_eeprom.c、inc/i2c_eeprom.h

5.11.3.4 I2C_10bit 10 位地址主从对测

工程目录: projects/n32g41x_EVAL/examples/I2C/I2C_10bit/

初始化 I2C1 为主机、I2C2 为从机并启用 10 位地址→填写发送缓冲区→主机发送起始与 10 位地址→从机匹配地址后收发→中断服务程序推进事件→串口打印结果

项目 源码解读
功能 在同一块板上用 I2C1 作主机、I2C2 作从机,以 10 位地址完成一轮发送与接收并比对数据。
源码组成 src/main.c:main、i2c_master_init、i2c_slave_init、I2C1_IRQHandler、I2C2_IRQHandler;
inc/main.h:引脚、地址与帧头宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
主机 I2C1 地址 0x230、SCL 为 PA4、SDA 为 PA5(GPIO_AF7),从机 I2C2 地址 0x2A0、SCL 为 PB10、SDA 为 PB11(GPIO_AF4);
两端均为 I2C_ADDR_MODE_10BIT、100 kHz、GPIO_MODE_AF_OD;
帧头 0xF0、0xF1 用于区分收发方向。
程序与数据路径 main 调用 i2c_master_init 与 i2c_slave_init 后填充 master_tx_buf,置 Comm_Flag 并产生起始条件;
I2C1_IRQHandler 在 EV5 发送帧头与 10 位地址高字节、在 I2C_EVT_MASTER_MODE_ADDRESS10_FLAG 发送地址低字节;
I2C2_IRQHandler 在从机地址匹配后清计数并收发数据。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigInt、I2C_Enable、I2C_GenerateStart、I2C_GenerateStop、I2C_SendData、I2C_RecvData、I2C_ConfigAck、I2C_GetLastEvent、I2C_GetFlag、I2C_ClrFlag
验证方法 主机 I2C1 用 PA4、PA5,从机 I2C2 用 PB10、PB11,两组引脚对应相连并共地;
串口 TX 为 PA9、波特率 115200;
复位后串口打印 this is a i2c(10bit) master/slave demo 与收发比对结果。
使用提示 10 位地址分两次发送,主机要处理 I2C_EVT_MASTER_MODE_ADDRESS10_FLAG 事件再补发地址低字节;
从机 OwnAddr1 取完整 10 位地址;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例初始化主机与从机各一路 I2C,再用中断完成一轮 10 位地址的收发。

数据路径: i2c_master_init 与 i2c_slave_init 分别配置 I2C1 与 I2C2 → main 填充 master_tx_buf 并产生起始条件 → I2C1_IRQHandler 分两步发送 10 位地址并搬运数据 → I2C2_IRQHandler 匹配地址后收发 → main 比对结果

1. 主机初始化为 10 位地址模式。 除常规的总线模式、速率、应答外,主机把 AddrMode 设为 I2C_ADDR_MODE_10BIT、OwnAddr1 取主机地址 0x230;随后使能事件、缓冲与错误中断,配置 NVIC 并打开 I2C。ClkSpeed 为 100 kHz,两端必须一致。

src/main.csrc/main.c · i2c_master_init
    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. 从机初始化为 10 位地址模式。 从机 I2C2 使用 PB10、PB11,OwnAddr1 取从机地址 0x2A0,地址模式同样是 10 位;中断使能后由 NVIC_Configuration(2) 建立 I2C2 的向量。从机自身地址要与主机的目标地址一致。

src/main.csrc/main.c · i2c_slave_init
    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. 主机分两步发送 10 位地址。 收到 EV5 后,主机先发送带地址高两位的帧头字节,再在 I2C_EVT_MASTER_MODE_ADDRESS10_FLAG 事件里补发地址的低字节;这是 10 位地址与 7 位地址在流程上的关键差别。

src/main.csrc/main.c · I2C1_IRQHandler
            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;

地址低字节与高两位要拼成完整的 10 位地址,宏与表达式必须一并移植。

4. 发送完成后产生停止条件。 当发送计数到达缓冲区长度,主机在 I2C_EVT_MASTER_DATA_SENDED 事件里产生停止条件并置完成标志,通知主流程退出等待。

src/main.csrc/main.c · I2C1_IRQHandler
            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. 从机在地址匹配后准备收发。 从机收到自身的 10 位地址后,I2C_EVT_SLAVE_RECV_ADDR_MATCHED 清接收计数、I2C_EVT_SLAVE_SEND_ADDR_MATCHED 清发送计数并预载第一个待发字节;后续数据由 EV7 与 EV8 事件继续搬运。

src/main.csrc/main.c · I2C2_IRQHandler
    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;

对应源码: src/main.c、inc/main.h

5.11.3.5 I2C_Master 轮询主机收发

工程目录: projects/n32g41x_EVAL/examples/I2C/I2C_Master/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 主机→循环调用发送函数→调用接收函数→比对收发缓冲→延时后重复

项目 源码解读
功能 I2C1 作主机以轮询方式反复向从机发送 200 字节并回读,比较收发缓冲以验证链路。
源码组成 src/main.c:main、i2c_master_init、i2c_master_send、i2c_master_recv、Buffercmp;
inc/main.h:引脚、地址与缓冲区宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、主机地址与从机地址 0x30/0x10;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
缓冲区 200 字节。
程序与数据路径 main 调用 i2c_master_init 后填充 tx_buf,在死循环里先 i2c_master_send(tx_buf, TEST_BUFFER_SIZE) 再 i2c_master_recv(rx_buf, TEST_BUFFER_SIZE),随后比对并 SysTick_Delay_Ms(50);
发送与接收函数内部逐步等待 EV5、EV6、EV8 等事件。
主要 API I2C_InitStruct、I2C_Init、I2C_Enable、I2C_CheckEvent、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_SendData、I2C_RecvData、I2C_ConfigAck、I2C_GetFlag
验证方法 两块板对接 SCL、SDA 与 GND,主机为本工程、从机为 I2C_Slave 或 I2C_Slave_Int;
串口 TX 为 PA9、波特率 115200;
复位后串口反复打印 this is a i2c master demo 与 write and read data are same, i2c master test pass。
使用提示 发送与接收共用一条起始条件,接收前要把 ACKPOS 复位;
len 为 1 或 2 的分支对应不同的应答与停止条件处理;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例围绕两个轮询函数展开:i2c_master_send 负责发送,i2c_master_recv 负责接收,main 反复调用并比对。

数据路径: i2c_master_init 配置 I2C1 → main 填充 tx_buf → i2c_master_send 产生起始条件并逐字节发送 → i2c_master_recv 重新产生起始条件并回读 → Buffercmp 比对 → SysTick_Delay_Ms 后重复

1. 初始化 I2C1 主机。 按 I2C_InitParam 复位并配置总线模式、快速模式占空比、主机地址、应答、7 位地址与速率,再打开 I2C;轮询方式不涉及 NVIC。

src/main.csrc/main.c · i2c_master_init
    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. 发送时先等总线空闲再产生起始条件。 发送函数先轮询 I2C_FLAG_BUSY 直到总线空闲,再在 Comm_Flag 允许时产生起始条件,并等待 EV5;随后发送从机地址并等待 EV6。三步顺序不能颠倒。

src/main.csrc/main.c · i2c_master_send
    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. 逐字节发送并等待 EV8。 发送主体用 len 控制循环,每送一个字节后等待 I2C_EVT_MASTER_DATA_SENDING;数据全部发出后再等待一次 I2C_EVT_MASTER_DATA_SENDED 确认字节传输完成。

src/main.csrc/main.c · i2c_master_send
    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. 接收单字节的分支。 接收函数在 len 为 1 时先关闭应答、读 STS1 与 STS2 清 ADDR、产生停止条件,再等待 I2C_FLAG_RXDATNE 取走唯一一个字节;长度为 2 或更大时走另一分支设置 NACK 位置。

src/main.csrc/main.c · i2c_master_recv
    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);

对应源码: src/main.c、inc/main.h

5.11.3.6 I2C_Master_Int 中断主机收发

工程目录: projects/n32g41x_EVAL/examples/I2C/I2C_Master_Int/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 主机并配置 NVIC→产生起始条件发起发送→中断服务程序按事件收发→等待完成标志→比对结果

项目 源码解读
功能 I2C1 作主机,用事件、缓冲与错误中断完成一轮 100 字节的发送与接收,主流程只等待完成标志。
源码组成 src/main.c:main、NVIC_Configuration、i2c_master_init、I2Cx_IRQHandler;
inc/main.h:引脚、地址与缓冲区宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、主机地址与从机地址 0x30/0x10;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
中断使能事件、缓冲与错误三类中断,缓冲区 100 字节。
程序与数据路径 main 调用 i2c_master_init 后填充 tx_buf,置 Comm_Flag 并产生起始条件发起发送,等待 flag_master_send_finish;
随后复位 ACKPOS、使能应答并发起接收,等待 flag_master_recv_finish;
I2Cx_IRQHandler 在 EV5、EV6、EV7、EV8 间搬运数据。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigInt、I2C_Enable、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_SendData、I2C_RecvData、I2C_ConfigAck、I2C_GetLastEvent、I2C_ClrFlag
验证方法 两块板对接 SCL、SDA 与 GND,主机为本工程、从机为 I2C_Slave 或 I2C_Slave_Int;
串口 TX 为 PA9、波特率 115200;
复位后串口打印 This is a i2c master int demo! 与 i2c master int test pass!。
使用提示 发送与接收各由 flag_master_send_finish、flag_master_recv_finish 标志区分方向;
接收前用 I2C_NACK_POS_CURRENT 复位 ACKPOS;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例在中断里完成收发,主流程只在两次起始之间等待完成标志。

数据路径: NVIC_Configuration 建立 I2C 向量 → i2c_master_init 配置 I2C1 与中断 → main 产生起始条件发起发送 → I2Cx_IRQHandler 按 EV5 到 EV8 收发 → 发送与接收完成标志置位 → Buffercmp 比对结果

1. 配置 I2C 中断向量。 NVIC_Configuration 把 I2Cx_IRQn 的抢占优先级设为 4、子优先级设为 1,随后再设一次抢占 3、子优先级 1,两次都打开通道并使能中断。

src/main.csrc/main.c · NVIC_Configuration
    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. 初始化 I2C1 并使能中断。 复位并配置 I2C1 后,使能事件、缓冲与错误三类中断,再建立 NVIC 并打开 I2C。中断使能与 i2c_master_init 内的配置顺序要与中断服务程序的事件假设一致。

src/main.csrc/main.c · i2c_master_init
    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);
src/main.csrc/main.c · i2c_master_init
    // int enable
    I2C_ConfigInt(I2Cx, I2C_INT_EVENT | I2C_INT_BUF | I2C_INT_ERR, ENABLE);
    NVIC_Configuration();
    I2C_Enable(I2Cx, ENABLE);

3. 主流程发起发送并等待完成。 main 置 flag_trans_direct 为 0 表示写方向,产生起始条件后等待 flag_master_send_finish;发送完成后读回方向改为 1,复位 ACKPOS 并使能应答,再发起接收。

src/main.csrc/main.c · main
    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. 中断服务程序按方向分发地址。 EV5 时根据 flag_trans_direct 决定发送读地址还是写地址;EV6 立即发出第一个数据字节;EV8 期间持续发送剩余字节,全部发完后产生停止条件并置 flag_master_send_finish。

src/main.csrc/main.c · I2Cx_IRQHandler
        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. 接收事件与错误处理。 每收到一个字节写入 rx_buf,剩最后一字节时关闭应答、产生停止条件,收满后置 flag_master_recv_finish;错误分支在检测到 I2C_FLAG_ACKFAIL 时清除标志并产生停止条件。

src/main.csrc/main.c · I2Cx_IRQHandler
        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;

对应源码: src/main.c、inc/main.h

5.11.3.7 I2C_Slave 轮询从机收发

工程目录: projects/n32g41x_EVAL/examples/I2C/I2C_Slave/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 从机→循环等待从机接收地址→轮询接收一帧数据→轮询发送同一帧→清空缓冲后重复

项目 源码解读
功能 I2C1 作从机,轮询等待主机寻址,先接收一帧再原样发送回去,验证从机侧收发。
源码组成 src/main.c:main、i2c_slave_init、i2c_slave_send、i2c_slave_recv;
inc/main.h:引脚、地址与缓冲区宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0x10;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
缓冲区 256 字节。
程序与数据路径 main 调用 i2c_slave_init 后在死循环里先 i2c_slave_recv(data_buf, TEST_BUFFER_SIZE) 等 I2C_EVT_SLAVE_RECV_ADDR_MATCHED,收到停止条件后返回,再 i2c_slave_send(data_buf, TEST_BUFFER_SIZE) 等 I2C_EVT_SLAVE_SEND_ADDR_MATCHED 并把数据发回。
主要 API I2C_InitStruct、I2C_Init、I2C_Enable、I2C_CheckEvent、I2C_GetLastEvent、I2C_SendData、I2C_RecvData、I2C_ClrFlag
验证方法 两块板对接 SCL、SDA 与 GND,从机为本工程、主机为 I2C_Master 或 I2C_Master_Int;
串口 TX 为 PA9、波特率 115200;
复位后串口反复打印 this is a i2c slave test demo 与 i2c slave test pass。
使用提示 从机的发送与接收都以地址匹配事件为入口,i2c_slave_send 与 i2c_slave_recv 内部各自轮询到停止条件才返回;
发送遇 ACK 丢失或收到停止条件时清 I2C_FLAG_ACKFAIL 并退出;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例用两个轮询函数实现从机收发,主流程先收后发形成回环。

数据路径: i2c_slave_init 配置 I2C1 从机 → main 进入死循环 → i2c_slave_recv 等待接收地址匹配并收满一帧 → i2c_slave_send 等待发送地址匹配并原样发回 → 清空 data_buf 后重复

1. 初始化 I2C1 从机。 复位后按 I2C_InitParam 配置总线模式、快速模式占空比、从机自身地址 0x10、应答、7 位地址与速率,再打开 I2C;从机地址要与主机目标地址一致。

src/main.csrc/main.c · i2c_slave_init
    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. 从机发送先等待发送地址匹配。 i2c_slave_send 先使能 I2C 并发送第一个字节,随后在循环里读取最近事件:收到 I2C_EVT_SLAVE_DATA_SENDED 就继续发下一个字节;收到 ACK 丢失或停止条件就清标志并退出;出现总线错误则走超时回调。

src/main.csrc/main.c · i2c_slave_send
    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. 从机接收按事件累积数据。 i2c_slave_recv 等待 I2C_EVT_SLAVE_DATA_RECVD 逐个取走字节并计数,收到 I2C_EVT_SLAVE_STOP_RECVD 表示一帧结束,退出循环;同样处理总线错误与超时。

src/main.csrc/main.c · i2c_slave_recv
    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. 主流程先收后发形成回环。 main 初始化后进入死循环,先接收一帧、再原样发回,随后清空 data_buf 并打印一次成功信息;任一步失败就跳出内层循环重新开始。

src/main.csrc/main.c · main
    log_init();
    log_info("\nthis is a i2c slave test demo\r\n");
    /* Initialize the I2C EEPROM driver ----------------------------------------*/
    i2c_slave_init();

对应源码: src/main.c、inc/main.h

5.11.3.8 I2C_Slave_Int 中断从机收发

工程目录: projects/n32g41x_EVAL/examples/I2C/I2C_Slave_Int/

使能 I2C 与 GPIO 时钟并配置 PA4、PA5→初始化 I2C1 为 100 kHz 从机并配置 NVIC→等待接收完成标志→中断服务程序按事件收发→等待发送完成标志→打印结果

项目 源码解读
功能 I2C1 作从机,用事件中断完成一帧 100 字节的接收与回发,主流程只等待两个完成标志。
源码组成 src/main.c:main、NVIC_Configuration、i2c_slave_init、I2Cx_IRQHandler;
inc/main.h:引脚、地址与缓冲区宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0x10;
SCL 为 PA4、SDA 为 PA5,复用 GPIO_AF7、GPIO_MODE_AF_OD、上拉;
中断使能事件、缓冲与错误三类中断,缓冲区 100 字节。
程序与数据路径 main 调用 i2c_slave_init 后等待 flag_slave_recv_finish,清零后再等待 flag_slave_send_finish;
I2Cx_IRQHandler 在从机地址匹配、数据收发、停止条件等事件间搬运数据,并在 ACK 丢失时置发送完成标志。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigInt、I2C_Enable、I2C_GetLastEvent、I2C_SendData、I2C_RecvData、I2C_ClrFlag
验证方法 两块板对接 SCL、SDA 与 GND,从机为本工程、主机为 I2C_Master 或 I2C_Master_Int;
串口 TX 为 PA9、波特率 115200;
复位后串口打印 this is a i2c slave int demo 与 recv finish、tx finish。
使用提示 从机的接收与回发共用同一组事件,靠 flag_slave_recv_finish 与 flag_slave_send_finish 区分阶段;
ACK 丢失时 flag_slave_send_finish 由 rxDataNum 是否为 0 决定;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例把从机的收发都放进中断,主流程用两个标志等待各阶段完成。

数据路径: NVIC_Configuration 建立 I2C 向量 → i2c_slave_init 配置 I2C1 与中断 → I2Cx_IRQHandler 匹配地址后收发数据 → 停止条件置 flag_slave_recv_finish → ACK 丢失置 flag_slave_send_finish → main 打印结果

1. 配置 I2C 中断向量。 与主机中断示例相同,NVIC_Configuration 对 I2Cx_IRQn 连设两次优先级,抢占优先级分别为 4 与 3,子优先级均为 1。

src/main.csrc/main.c · NVIC_Configuration
    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. 初始化 I2C1 从机并使能中断。 复位并配置 I2C1 后,使能事件、缓冲与错误中断,再建立 NVIC 并打开 I2C;从机地址 0x10 要与主机目标地址一致。

src/main.csrc/main.c · i2c_slave_init
    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. 中断服务程序区分收发事件。 从机模式分支里:接收地址匹配清接收计数,发送地址匹配清发送计数,数据发送与发送完成事件把 data_buf 里的字节写入 DAT,数据接收事件把 DAT 存入 data_buf,停止条件置 flag_slave_recv_finish。

src/main.csrc/main.c · I2Cx_IRQHandler
        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. 主流程等待接收与发送完成。 main 先等待 flag_slave_recv_finish 并打印接收长度,清零后等待 flag_slave_send_finish 并打印发送长度;两个标志由中断置位,主流程据此逐阶段推进。

src/main.csrc/main.c · main
    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 丢失处理。 错误分支检测到 I2C_EVT_SLAVE_ACK_MISS 时清除 I2C_FLAG_ACKFAIL;若已发送过数据说明是最后一字节收到 NACK,置 flag_slave_send_finish,否则视为普通发送失败。

src/main.csrc/main.c · I2Cx_IRQHandler
    /* --- 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
        {
        }
    }

对应源码: src/main.c、inc/main.h

5.11.3.9 PMBUS_Master PMBus 主机

工程目录: projects/n32g41x_EVAL/examples/I2C/PMBUS_Master/

配置 PMBus 控制与告警引脚→初始化 I2C1 为主机并配置中断→按命令组打包发送缓冲→产生起始并收发→计算并比对 PEC→打印各命令测试结果

项目 源码解读
功能 I2C1 作 PMBus 主机,按命令索引对从机执行读字节、读字、写字节与写字,并在使能 PEC 时计算和校验 PEC 字节。
源码组成 src/main.c:main 与各命令测试;
src/PMBusMaster.c、inc/PMBusMaster.h:PMBus 命令打包与 CRC8;
src/I2CMaster.c、inc/I2CMaster.h:I2CMaster_Transmit 与 I2Cx_IRQHandler;
inc/PMBus.h、inc/main.h:命令定义与引脚宏;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、主机地址与从机地址 0x30/0x10;
SCL 为 PA4、SDA 为 PA5(GPIO_AF7),PMBus 控制与告警引脚为 PA7、PA8;
中断使能事件、缓冲与错误三类中断;
PEC 多项式 CRC8_POLY、初值 CRC8_INIT_REM。
程序与数据路径 main 调用 PMBusMaster_Init 后依次读取 STATUS_TEMPERATURE、OPERATION、STATUS_WORD、VOUT_COMMAND,再写入 STORE_DEFAULT_CODE、OPERATION、VOUT_COMMAND;
每次调用 PMBusMaster 按命令组填充 PMBusMaster_TransmitBuffer,经 I2CMaster_Transmit 收发,并使能 PEC 时用 PMBusMaster_Crc8MakeBitwise 计算 CRC8 与从机 PEC 比对。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigInt、I2C_Enable、I2C_GenerateStart、I2C_GenerateStop、I2C_SendAddr7bit、I2C_SendData、I2C_RecvData、I2C_ConfigAck、I2C_GetLastEvent、I2C_ClrFlag
验证方法 两块板对接 SCL、SDA 与 GND,主机为本工程、从机为 PMBUS_Slave;
串口 TX 为 PA9、波特率 115200;
复位后串口打印各命令的读值与 pmbus master int test pass,并可与从机串口日志对照。
使用提示 PEC 使能时要在收发结束后按命令组拼装 CRC 输入流再调用 CRC8;
读字节与读字会先写命令再重复起始读数据,靠 PMBusMaster_CommandGroup 区分;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例把 PMBus 事务拆成命令打包、I2C 收发与 PEC 校验三层,主流程按命令索引调用。

数据路径: PMBusMaster_Init 配置引脚与 I2C1 → PMBusMaster 按命令组打包 PMBusMaster_TransmitBuffer → I2CMaster_Transmit 通过 I2Cx_IRQHandler 收发 → 使能 PEC 时调用 PMBusMaster_Crc8MakeBitwise 计算 CRC8 → main 汇总各命令测试结果

1. 配置 PMBus 引脚并初始化主机。 PMBusMaster_Init 把控制线与告警线配为复用推挽,再调用 i2c_master_init 完成 I2C1 初始化;引脚号与端口来自 inc/PMBus.h 的宏。

src/PMBusMaster.csrc/PMBusMaster.c · PMBusMaster_Init
    // 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. 按命令组分发收发。 PMBusMaster 根据 PMBusMaster_CommandGroup 进入不同分支:读字节发一个命令字节再读一个字节,读字读两个字节,写字节与写字按命令+数据组包;每组都通过 I2CMaster_Transmit 完成收发。

src/PMBusMaster.csrc/PMBusMaster.c · PMBusMaster
    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. PEC 的 CRC8 计算。 使能 PEC 时,PMBusMaster_Crc8MakeBitwise 用给定初值与多项式对命令流逐位计算 CRC8:先取第一个字节作为初值,再逐字节移入,最后补 8 个零,取低字节返回。计算出的值即发送或校验用的 PEC 字节。

src/PMBusMaster.csrc/PMBusMaster.c · PMBusMaster_Crc8MakeBitwise
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;
src/PMBusMaster.csrc/PMBusMaster.c · PMBusMaster_Crc8MakeBitwise
            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;
    }

初值、多项式与字节数要与从机完全一致,否则 PEC 校验必然不一致。

4. 读写结束后由中断收尾。 I2Cx_IRQHandler 在 EV8 之后区分命令组:读字节与读字在发完命令后重新使能应答、复位 ACKPOS 并产生重复起始;其它命令则直接产生停止条件并置发送完成标志。

src/I2CMaster.csrc/I2CMaster.c · I2Cx_IRQHandler
        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. 主流程编排各命令测试。 main 依次调用 PMBusMaster 读取温度、操作、状态字与输出电压命令,再写入若干命令并回读校验,最后汇总 test_status1 到 test_status7 打印总结果。

src/main.csrc/main.c · main
    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");

对应源码: src/main.c、src/PMBusMaster.c、src/I2CMaster.c、inc/PMBus.h

5.11.3.10 PMBUS_Slave PMBus 从机

工程目录: projects/n32g41x_EVAL/examples/I2C/PMBUS_Slave/

初始化 I2C1 为从机并配置中断→建立 PMBus 命令查表→主循环等待接收完成→解码命令并组织回发数据→中断服务程序收发→使能 PEC 时校验 CRC8

项目 源码解读
功能 I2C1 作 PMBus 从机,用命令查表解码主机发来的命令,按命令组准备回发数据,并在使能 PEC 时校验 CRC8。
源码组成 src/main.c:main;
src/PMBusSlave.c、inc/PMBusSlave.h:命令查表、解码、收发状态机与 CRC8;
inc/PMBus.h、inc/main.h:命令定义与引脚宏;
src/n32g41x_it.c、inc/n32g41x_it.h:中断相关代码;
readme.txt:功能与说明;
MDK-ARM/、EWARM/:Keil 与 IAR 工程。
关键配置 时钟源 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz;
I2C1 100 kHz、I2C_ADDR_MODE_7BIT、从机地址 0x10;
SCL 为 PA4、SDA 为 PA5(GPIO_AF7),PMBus 控制与告警引脚为 PA7、PA8;
中断使能事件、缓冲与错误三类中断;
命令查表 PMBus_Commands[120];
PEC 多项式 CRC8_POLY、初值 CRC8_INIT_REM。
程序与数据路径 main 调用 PMBusSlave_Init 后在死循环里调用 PMBusSlave;
接收完成标志置位时 PMBusSlave_ProcessMasterWriteCommand 解码命令,主机读时 PMBusSlave_ProcessMasterReadCommand 依据查表把 PMBusSlave_TransmitBuffer 填好;
I2Cx_IRQHandler 按从机事件搬运数据并在停止条件清标志。
主要 API I2C_InitStruct、I2C_Init、I2C_ConfigInt、I2C_Enable、I2C_GetLastEvent、I2C_SendData、I2C_RecvData、I2C_GetIntStatus、I2C_ClrIntPendingBit、I2C_ClrFlag
验证方法 两块板对接 SCL、SDA 与 GND,从机为本工程、主机为 PMBUS_Master;
串口 TX 为 PA9、波特率 115200;
复位后从机响应主机各命令,需比对主机串口日志确认收发与 PEC 结果。
使用提示 从机地址与主机目标地址要一致;
命令查表按区间划分命令组,索引 0 为“探测从机是否在线”的哑字节;
使能 PEC 时收发完成后要用同一初值与多项式校验 CRC8;
开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1。
核心代码解读与移植

示例用命令查表加中断状态机的结构实现 PMBus 从机,主循环只负责在接收完成后解码。

数据路径: PMBusSlave_Init 配置引脚与 I2C1 → main 进入死循环调用 PMBusSlave → I2Cx_IRQHandler 在从机事件间搬运数据 → 接收完成调用 PMBusSlave_ProcessMasterWriteCommand → 依据 PMBus_Commands 查表解码 → 使能 PEC 时用 PMBusSlave_Crc8MakeBitwise 校验

1. 命令查表与线性查找。 PMBusSlave_DecodeCommand 先声明命令组变量,再遍历 120 项的 PMBus_Commands 找到与收到的命令相等的项,并把下标记入 PMBusSlave_Index;索引 0 是探测从机是否在线的哑字节。

src/PMBusSlave.csrc/PMBusSlave.c · PMBusSlave_DecodeCommand
    uint8_t PMBusSlave_CommandGroup;

    for(PMBusSlave_Index = 0; PMBusSlave_Index < 120; PMBusSlave_Index++)
    {
        if(PMBus_Commands[PMBusSlave_Index] == PMBusSlave_RxCommand)
            break;
    }

表长与返回的索引是解码从机支持哪些命令的唯一依据。

2. 初始化为从机并打开中断。 i2c_slave_init 复位并配置 I2C1,从机地址取 I2C_SLAVE_ADDR,再使能事件、缓冲与错误中断、建立 NVIC 并打开 I2C。中断向量与 I2Cx_IRQHandler 的对应关系在这里建立。

src/PMBusSlave.csrc/PMBusSlave.c · i2c_slave_init
    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. 按索引区间判定命令组。 PMBusSlave_DecodeCommand 再按 PMBusSlave_Index 所在区间判定命令组:0 为哑字节、1 到 12 为读字节、13 到 39 为读字,依此类推,非法索引进入死循环。命令组决定回发数据的字节数。

src/PMBusSlave.csrc/PMBusSlave.c · PMBusSlave_DecodeCommand
    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. 中断服务程序驱动从机收发。 从机模式分支里:接收地址匹配清接收计数,数据接收把字节存入 PMBusSlave_ReceiveBuffer,发送地址匹配清发送计数并调用 PMBusSlave_ProcessMasterReadCommand 准备数据,数据发送与发送完成把缓冲字节写入 DAT,停止条件置 flag_slave_recv_finish 并清标志。

src/PMBusSlave.csrc/PMBusSlave.c · I2Cx_IRQHandler
    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. 主循环在接收完成后解码写命令。 PMBusSlave 在 flag_slave_recv_finish 置位时清零标志并调用 PMBusSlave_ProcessMasterWriteCommand 处理写命令;main 只负责初始化后反复调用该函数。这样把耗时的命令处理放到主循环,中断只做搬运。

src/PMBusSlave.csrc/PMBusSlave.c · PMBusSlave
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
    }
}

对应源码: src/PMBusSlave.c、inc/PMBusSlave.h、inc/PMBus.h

5.11.4 注意与排障

场景 检查与建议 来源
主发送地址阶段收不到应答 用户手册写明从机地址未被确认时 STS1 的 ACKFAIL 置位,从机会在硬件上释放总线,主机必须产生停止条件后重试;
用于 EEPROM 页写时,内部写周期内的地址 NACK 属正常,应等待器件就绪再重发。
用户手册
异常时总线被占用 用户手册写明 BUSERR 总线错误下主机不会释放总线,需软件产生停止条件使从机释放数据并结束传输;
仲裁丢失则硬件自动释放总线并回到从机状态。
用户手册
关闭时钟延展后收数据丢失 用户手册写明只有关闭时钟延展(NOEXTEND=1)时才可能出现 OVERRUN,从机来不及读走上一字节会导致新字节覆盖;
使用 DMA 时不要使能 BUFINTEN,避免缓冲中断与 DMA 冲突。
用户手册
PEC 校验不通过 用户手册写明 PEC 用 CRC-8 多项式 C(x)=x8+x2+x+1,PEC 位在应答脉冲前送出,仲裁丢失时失效;
主机与从机必须使用一致的初值、多项式与参与计算的字节序列。
用户手册
超时检测不触发或误触发 用户手册写明 SCL 低电平时长可选 25 ms、100 ms、1 s、4 s,SCL 高电平时长可选 256 us、512 us、1 ms、128 us,并另有 SDA 低电平超时;
从机超时会复位通信并释放总线,主机超时会自动产生停止条件;
SMBus 下低电平超时主 10 ms、从 25 ms。
用户手册
数字滤波与模拟滤波配置不生效 用户手册写明模拟滤波默认开启并可设 5、15、25、35 ns 宽度,数字滤波由 SDADFW/SCLDFW 位配置;
需确认是否开启时钟延展,以及 DATACKTC 逐字节应答是否在时钟延展打开时使用。
用户手册
时钟控制寄存器写入无效 用户手册写明时钟控制寄存器只能在 I2C 关闭时设置,标准模式最小值与快速模式占空比下限不同,需按手册取值后再使能外设。 用户手册

N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表只列出一行 A 版芯片条目,勘误链接列为空,未列出 I2C 相关条目。

国民技术开发套件文档中心

按芯片系列在线查阅国民技术 MCU 开发套件的芯片资料、SDK、驱动 API 与 Demo 示例说明。

© 2026 国民技术股份有限公司 版权所有粤ICP备11069485号本站使用不含 Cookie 的匿名访问统计