You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F401xx双机I2C主从通信Proteus仿真及代码问题排查

STM32F401xx I2C主从通信仿真故障排查指南

问题描述(翻译自英文提问)

在Proteus中使用两块STM32F401xx MCU搭建I2C主从通信系统:一块作为主设备(Master),另一块作为从设备(Slave)。主设备代码采用MicroC Pro For Arm编写,寄存器级I2C驱动代码如下:

#include <stdint.h>
#include <stdio.h>
uint8_t temp=0;
void begin(uint8_t addr);
void end(void);
void Write (uint8_t dat);
void main() {
       // Enable the I2C CLOCK and GPIO CLOCK
          RCC_APB1ENR |= (1<<21);  // enable I2C CLOCK
          RCC_AHB1ENR |= (1<<1);  // Enable GPIOB CLOCK

          // Configure the I2C PINs for ALternate Functions
          //PB8 and PB9 are connected to I2C1_SCL and I2C1_SDA
          GPIOB_MODER |= (2<<16) | (2<<18);

          GPIOB_OTYPER |= (1<<8) | (1<<9);
          GPIOB_OSPEEDR |= (3<<16) | (3<<18);
          GPIOB_PUPDR |= (1<<16) | (1<<18);
          GPIOB_AFRH |= (4<<0) | (4<<4);
          begin(0x08);
          Write(0x01);
          end();
}

void begin(uint8_t addr){
          //join I2C as a master mode
         //Generate a start condition
            I2C1_CR1 |= (1<<10);  // Enable the ACK
            I2C1_CR1 |= (1<<8);  // Generate START
            while (!(I2C1_SR1 & (1<<0)));  // Wait for SB bit to set to 1

           //Send the Slave Address to the DR Register
            I2C1_DR = addr;  //  send the address
            while (!(I2C1_SR1 & (1<<1)));  //
            temp = I2C1_SR1 | I2C1_SR2;  // read SR1 and SR2 to clear the ADDR bit

            // Reset the I2C
            I2C1_CR1 |= (1<<15); //I2C Peripheral under reset state -->page.493 manual
            I2C1_CR1 &= ~(1<<15); //I2C Peripheral not under reset  -->page.493 manual
            // Program the peripheral input clock in I2C_CR2 Register in order to generate correct timings
            I2C1_CR2 |= (42 <<0);  // PCLK1 FREQUENCY in MHz
            I2C1_CCR = 210<<0;  // Configure the clock control registers
            I2C1_TRISE = 43;  // Configure the rise time register
            I2C1_CR1 |= (1<<0);  // Enable I2C
}
 void end(void)  {

           I2C1_CR1 |= (1<<9);//stop generation
            // Wait until the STOP condition is complete
           while (I2C1_SR2 & (1<<0)); //Cleared by hardware after detecting a Stop condition on the bus
           // Clear the STOP bit
           I2C1_CR1 &= ~(1<<9);
  }

    void Write (uint8_t dat)
  {
          while (!(I2C1_SR1 & (1<<7)));  // wait for TXE bit to set
              I2C1_DR = dat   ;  // wait for BTF bit to set
          while (!(I2C1_SR1 & (1<<2))); //waiting while BTF=0 but when BTF=1; Data byte transfer succeeded

  }

从设备代码如下:

#include <stdint.h>
#include <stdio.h>
uint8_t temp=0;
uint8_t state =0x00;
void begin(uint8_t addr);
void end(void);
uint8_t Read ();
void main() {
          RCC_APB1ENR |= (1<<21);  // enable I2C CLOCK -
          RCC_AHB1ENR |= (1<<1);  // Enable GPIOB CLOCK
          RCC_AHB1ENR |= (1<<0);  // Enable GPIOA CLOCK
          GPIOA_MODER |=(1<<0); //set pin 0 as output
          // Configure the I2C PINs for ALternate Functions
          //PB8 and PB9 are connected to I2C1_SCL and I2C1_SDA
          GPIOB_MODER |= (2<<16) | (2<<18);

          GPIOB_OTYPER |= (1<<8) | (1<<9);
          GPIOB_OSPEEDR |= (3<<16) | (3<<18);
          GPIOB_PUPDR |= (1<<16) | (1<<18);
          GPIOB_AFRH |= (4<<0) | (4<<4);
          begin(0x08);
          state =  Read();
          end();
        if (state == 0x01){
            GPIOA_ODR |=(1<<0);//set 1 for pin 0
         }
}
void begin(uint8_t addr){
            //join I2C bus as a slave mode
            I2C1_CR2 |= (42<<0);  // PCLK1 FREQUENCY in MHz
            I2C1_OAR1 = addr;//own address interface
            I2C1_CR1 |= (1<<0);  // Enable I2C
            I2C1_CR1 |= (1<<10);  // Enable the ACK ,indicate that a byte is received
}
 void end(void)  {
           I2C1_CR1 |= (1<<9);//stop generation
            // Wait until the STOP condition is complete
           while (I2C1_SR2 & (1<<0)); //Cleared by hardware after detecting a Stop condition on the bus

           // Clear the STOP bit
           I2C1_CR1 &= ~(1<<9);
  }
   uint8_t Read (){
            uint8_t receivedData = 0;
            I2C1_CR1 &= ~(1<<10);  // clear the ACK bit
            temp = I2C1_SR1 | I2C1_SR2;  // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
            I2C1_CR1 |= (1<<9);  // Stop I2C
            while (!(I2C1_SR1 & (1<<6)));  // wait for RxNE to set
            receivedData = I2C1_DR; // Read the data from the DATA REGISTER
        return receivedData;
     }

参考STM32F401xx数据手册和参考手册编写了上述驱动,但仿真时LED始终未点亮,需排查代码问题并了解正确的Proteus仿真操作步骤。Proteus连接示意图说明:两块STM32F401xx的PB8(SCL)、PB9(SDA)分别相连,从设备PA0串联LED及限流电阻到GND,两块芯片均接3.3V电源与GND。


一、代码核心问题排查

1. 主设备代码错误

  • I2C初始化顺序完全颠倒:begin函数中先发送起始信号、从地址,再重置I2C并配置时钟参数,违反I2C初始化流程。正确顺序应为:重置I2C → 配置时钟参数 → 使能I2C → 发送起始信号 → 发送从地址。
  • 从地址格式错误:I2C的8位设备地址由7位地址+1位读写位组成,主设备写操作需将7位地址左移1位(读写位设为0)。原代码直接传入0x08,应改为0x08 << 1(即0x10)。
  • 重置操作时机错误:I2C重置应在配置时钟参数前执行,而非发送地址后。

修正后主设备begin函数:

void begin(uint8_t addr){
    // 重置I2C外设
    I2C1_CR1 |= (1<<15);
    I2C1_CR1 &= ~(1<<15);
    
    // 配置时钟参数
    I2C1_CR2 |= (42 << 0);  // PCLK1频率为42MHz
    I2C1_CCR = 210 << 0;    // 标准模式100kHz(CCR = PCLK1/(2*SCL频率) = 42000/(2*100)=210)
    I2C1_TRISE = 43;        // TRISE = PCLK1频率(MHz) + 1 = 42+1=43
    I2C1_CR1 |= (1<<10);    // 使能ACK
    I2C1_CR1 |= (1<<0);     // 使能I2C
    
    // 发送起始信号
    I2C1_CR1 |= (1<<8);
    while (!(I2C1_SR1 & (1<<0)));  // 等待SB位置位(起始信号发送完成)
    
    // 发送带读写位的从地址(写操作,最低位为0)
    I2C1_DR = (addr << 1) | 0;
    while (!(I2C1_SR1 & (1<<1)));  // 等待ADDR位置位(地址匹配成功)
    temp = I2C1_SR1 | I2C1_SR2;    // 读取SR1和SR2清除ADDR标志
}

2. 从设备代码错误

  • 从地址寄存器(OAR1)配置缺失关键位:STM32F4的I2C_OAR1寄存器第15位必须置1,否则从地址不生效;7位地址需左移1位存入OAR1的bit7-1位。原代码I2C1_OAR1 = addr应改为I2C1_OAR1 = (addr << 1) | (1 << 15)。
  • Read函数逻辑错误:从设备不应主动发送停止信号,停止信号由主设备发起;且需先等待地址匹配(ADDR置位)再读取数据,原代码直接清ACK、发停止,导致无法正确接收数据。
  • 无地址匹配等待循环:从设备main函数中begin后直接调用Read,未等待主设备发起通信,需在Read中加入等待ADDR置位的循环。

修正后从设备begin和Read函数:

void begin(uint8_t addr){
    I2C1_CR2 |= (42 << 0);  // PCLK1频率为42MHz
    // 配置7位从地址,必须置位OAR1的bit15
    I2C1_OAR1 = (addr << 1) | (1 << 15);
    I2C1_CR1 |= (1<<10);    // 使能ACK
    I2C1_CR1 |= (1<<0);     // 使能I2C
}

uint8_t Read (){
    uint8_t receivedData = 0;
    // 等待地址匹配(ADDR位置位)
    while (!(I2C1_SR1 & (1<<1)));
    temp = I2C1_SR1 | I2C1_SR2;  // 读取SR1和SR2清除ADDR标志
    
    // 等待接收数据就绪(RxNE位置位)
    while (!(I2C1_SR1 & (1<<6)));
    receivedData = I2C1_DR;
    
    // 等待主设备发送停止信号(STOPF位置位)
    while (!(I2C1_SR1 & (1<<4)));
    temp = I2C1_SR1;  // 读取SR1清除STOPF标志
    return receivedData;
}

3. 通用代码问题

需确保RCC_APB1ENR、I2C1_CR1等寄存器已通过头文件或宏定义正确映射,避免编译错误。


二、Proteus正确仿真操作步骤

  1. 原理图搭建

    • 放置两块STM32F401xx芯片,分别配置为主、从设备。
    • I2C总线连接:主设备PB8(SCL)与从设备PB8相连,主设备PB9(SDA)与从设备PB9相连;总线需串联10kΩ上拉电阻到3.3V(I2C总线必须上拉,否则无法正常通信)。
    • 从设备PA0串联220Ω限流电阻后连接LED,LED另一端接GND。
    • 给两块芯片分别连接3.3V电源和GND。
  2. 代码编译与hex文件生成

    • 使用MicroC Pro For Arm分别编译主、从设备代码,生成对应的hex文件。
    • 编译时需选择正确的芯片型号(STM32F401xx),并配置时钟树,确保PCLK1为42MHz(与代码中I2C1_CR2的配置一致)。
  3. Proteus仿真配置

    • 分别给两块STM32芯片加载对应的hex文件:双击芯片 → 打开属性窗口 → 选择Program File为对应hex文件。
    • 配置芯片系统时钟:设置为84MHz(STM32F401默认最大时钟,确保PCLK1为42MHz)。
    • 启动仿真:点击运行按钮,观察LED状态。
  4. 调试技巧

    • 使用Proteus的逻辑分析仪监测SCL、SDA信号,查看起始信号、地址帧、数据帧是否正常。
    • 若通信无响应,优先检查总线是否上拉、芯片电源是否正常、寄存器配置是否正确。

内容的提问来源于stack exchange,提问作者Ahm2025

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 09:55:54