stm32 iic通信

使用两块stm32进行模拟iic的主从机交互实验,遇上的问题是配置32的PB6和PB7管脚,也就是SCL和SDA,引脚设置复用开漏模式后想让主机发送一条报文,SCL电平无法跳变,但同时SDA电平是正常的,现在的情况是给主机的SCL引脚改成普通输出模式,从机接收依旧设置成复用模式,可以正常通信,报文可以正常发送,但SCL引脚设置成复用模式SCL电平在示波器上就是一条直线,SDA正常跳变。
目前想实现主机给从机发送数据后,从机返还数据,主机接收到从机发出的数据,并作判断数据内容,现在已经实现了从机接收主机发送的数据,并读取数据作判断,但从机无法发送报文,然后发现了SCL设置成复用模式无法正常发送报文,一开始实现主机数据给从机,从机作判断的时候,主机的SDA和SCL设置的是输出模式,从机的SDA和SCL是复用模式,现在想实现更多的功能,不知道是哪里出的问题? 从机是可以接收到主机的数据并进入中断的,但想让从机给主机返回数据却无法做到
以下是配置代码

void IIC_Init(void)
{            
  GPIO_InitTypeDef  GPIO_InitStructure;
  I2C_InitTypeDef     I2C_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
    
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    
  GPIO_InitStructure.GPIO_Pin =GPIO_Pin_6 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
    

  GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7 ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
    IIC_SCL=1;
    IIC_SDA=1;
    
  GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_I2C1); 
  GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_I2C1);

    
    I2C_DeInit(I2C1);
    I2C_InitStructure.I2C_Mode             = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle     = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress1     = 0x00;     //从机地址,一定要设置正确
    I2C_InitStructure.I2C_Ack             = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress= I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed     = 100000;
    I2C_Init(I2C1, &I2C_InitStructure);
    
    NVIC_InitStructure.NVIC_IRQChannel                   = I2C1_EV_IRQn;//事件中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

  NVIC_InitStructure.NVIC_IRQChannel = I2C1_ER_IRQn;//错误中断
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                 
  NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
    
  I2C_ITConfig(I2C1, I2C_IT_BUF | I2C_IT_EVT |I2C_IT_ERR, ENABLE);   
  I2C_Cmd(I2C1, ENABLE);     
 
}

根据你提供的代码,可以看出你使用了STM32的I2C模块来模拟IIC通信。在配置GPIO的时候,你将SCL和SDA引脚都设置为了复用模式,并且将SCL引脚设置为开漏输出模式。这一点是正确的,因为在IIC通信中,SCL和SDA引脚都需要设置为复用模式,并且SCL引脚需要设置为开漏模式,以便能够实现多主机共享总线的功能。

然而,你在测试过程中发现,当SCL引脚设置为复用模式时,SCL电平无法跳变,但SDA电平正常跳变。这可能是由于你没有正确设置I2C模块的时钟频率导致的。在你的代码中,你将I2C时钟频率设置为了100KHz,这是IIC通信中常用的时钟频率。但是,如果你的系统时钟频率不是100MHz,那么你需要根据系统时钟频率来计算出正确的I2C时钟频率在你的代码中,你使用了I2C模块的中断来处理IIC通信。你在配置中断的时候,将I2C1的事件中断和错误中断都使能了。这是正确的做法,因为I2C模块的事件中断可以用来处理完成IIC通信的各种事件,例如发送或接收数据完成、IIC总线错误等。而错误中断则可以用来处理一些错误事件,例如总线超时等。

在你的代码中,你使用了I2C_ITConfig函数来使能I2C的中断。这一点是正确的。但是,在你的代码中并没有看到你实现从机向主机发送数据的部分。如果你想实现从机向主机发送数据的功能,你需要在从机的中断处理函数中实现,具体的实现方式可以参考以下代码:

void I2C1_EV_IRQHandler(void)
{
    if (I2C_GetITStatus(I2C1, I2C_IT_RXNE))  // 判断是否接收到数据
    {
        uint8_t data = I2C_ReceiveData(I2C1); // 读取接收到的数据
        //在上面的代码中,我们在从机的事件中断处理函数`I2C1_EV_IRQHandler`中增加了处理从机向主机发送数据的代码。首先,我们使用`I2C_GetITStatus`函数判断是否接收到了数据。如果接收到了数据,我们使用`I2C_ReceiveData`函数读取接收到的数据。接下来,我们可以在这个函数中添加从机向主机发送数据的代码,例如使用`I2C_SendData`函数将数据发送出去。

另外,在你的代码中,你还需要在从机的中断处理函数中增加一些逻辑,以便处理主机向从机发送数据的情况。具体的实现方式可以参考以下代码:

void I2C1_EV_IRQHandler(void)
{
    if (I2C_GetITStatus(I2C1, I2C_IT_RXNE))  // 判断是否接收到数据
    {
        uint8_t data = I2C_ReceiveData(I2C1); // 读取接收到的数据
        // 处理从机向主机发送数据的代码
        // ...
    }
    else if (I2C_GetITStatus(I2C1, I2C_IT_ADDR)) //接上一部分:
    else if (I2C_GetITStatus(I2C1, I2C_IT_ADDR)) // 判断是否接收到地址帧
    {
        // 处理主机向从机发送数据的代码
        // ...
    }
    else if (I2C_GetITStatus(I2C1, I2C_IT_STOPF)) // 判断是否接收到停止位
    {
        I2C_ClearFlag(I2C1, I2C_FLAG_STOPF);  // 清除停止位标志
    }
    // ...
}

在上面的代码中,我们在从机的事件中断处理函数I2C1_EV_IRQHandler中增加了处理主机向从机发送数据的代码。首先,我们使用I2C_GetITStatus函数判断是否接收到了地址帧。如果接收到了地址帧,说明主机向从机发送了数据,我们可以在这个函数中添加从机接收数据的代码,例如使用I2C_ReceiveData函数读取接收到的数据。接下来,我们可以在这个函数中添加从机向主机发送数据的代码,例如使用I2C_SendData函数将数据发送出去。

另外,在你的代码中,我没有看到你使用I2C_GenerateSTART函数来发送起始位。在IIC通信中,通信开始时需要发送起始位,而结束时需要发送停止位。所以在从机中断处理函数中,你需要判断是否接收到了起始位和停止位,并在相应的情况下发送响应的信号。以下是一个示例代码,演示了如何在从机中断处理函数中处理主机发送的数据,并返回响应的数据:

void I2C1_EV_IRQHandler(void)
{
    if (I2C_GetITStatus(I2C1, I2C_IT_RXNE))  // 判断是否接收到数据
    {
        uint8_t data = I2C_ReceiveData(I2C1); // 读取接收到的数据
        // 处理从机向主机发送数据的代码
        // ...
    }
    else if (I2C_GetITStatus(I2C1, I2C_IT_ADDR)) // 判断是否接收到地址帧
    {
        uint8_t addr = I2C_ReceiveData(I2C1);  // 读取从机地址
        I2C_AcknowledgeConfig(I2C1, ENABLE);   // 使能应答位
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // 等待主机发送数据
        uint8_t data = I2C_ReceiveData(I2C1); // 读取主机发送的数据
        // 处理主机发送的数据,并准备发送响应数据
        // ...
        I2C_SendData(I2C1, response_data);    // 发送响应数据
    }
    else if (I2C_GetITStatus(I2C1, I2C_IT_STOPF)) // 判断是否接收到停止位
    {
        I2C_ClearFlag(I2C1, I2C_FLAG_STOPF);  // 清除停止位标志
    }
    // ...
}

在上面的代码中,我们在从机的事件中断处理函数I2C1_EV_IRQHandler中增加了处理主机向从机发送数据的代码。当从机接收到地址帧时,我们使用I2C_ReceiveData函数读取从机地址,并使用I2C_CheckEvent函数等待主机发送数据。然后,我们使用I2C_ReceiveData函数读取主机发送的数据,并进行相应的处理,例如计算响应数据。最后,我们使用I2C_SendData函数将响应数据发送给主机。

需要注意的是,在从机中断处理函数中,你需要注意时序的控制。例如,在发送响应数据之前,你需要等待主机发送完数据并释放总线。在从机接收数据时,你需要在最后一个字节前发送应答位,而在最后一个字节后不发送应答位。这些时序控制非常重要,否则会导致通信失败。

总之,在实现模拟IIC通信时,需要注意时序控制、时钟频率设置、中断处理函数的实现等方面。希望以上的解答可以帮助你解决问题,如果还有需要进一步的帮助,请随时提出。