zhouq 发表于 2025-3-26 19:29:42

第3篇_两块8H1K17用I2C通讯,成功

昨天的帖子:8H1K17的I2C无法对主机做出应答 - QSPI/3组SPI/I2S/I2C,一线制温湿度传感器 国芯技术交流网站 - AI32位8051交流社区

继昨天I2C从机无应答,我使用了两块8H1K17,一块做主机,一块做从机,使用ISP中的例程,后用逻辑分析仪抓包,发现主从机通讯正常,从机可以应答。

于是我保留从机的板子,换成另外一个MCU作为主机,,情况依旧是从机无应答,下面是我的代码:
/**
******************************************************************************
* @file    I2C_Soft_M.c
* @authorzhouq
* @Date    2024/11/11
* @brief   I2C通信驱动文件,实现软件模拟I2C主机功能
* @Description
******************************************************************************
* @attention
******************************************************************************
*/

/* Includes ------------------------------------------------------------------*/
#include "HeaderInc.h"

/* Private define-------------------------------------------------------------*/
//引脚定义
#define GPIO_I2C_SCL    GPIO_Pin_13//PA13
#define GPIO_I2C_SDA    GPIO_Pin_14//PA14

//置位与清零SCL管脚
#define SCL_SET    GPIOA_SetBits( GPIO_I2C_SCL )
#define SCL_CLR    GPIOA_ResetBits( GPIO_I2C_SCL )
//置位与清零SDA管脚
#define SDA_SET    GPIOA_SetBits( GPIO_I2C_SDA )
#define SDA_CLR    GPIOA_ResetBits( GPIO_I2C_SDA )

//读SDA管脚状态
#define READ_SDA   GPIOA_ReadInPortPin( GPIO_I2C_SDA )

/* Private variables----------------------------------------------------------*/
static void               I2C_GPIO_Init(void);          //I2C GPIO模式初始化
static void               I2C_Init(void);               //I2C初始化
static void               I2C_Start(void);            //I2C起始信号
static void               I2C_Stop(void);               //I2C停止信号
static ACK_Value_t      I2C_Write_Byte(uint8_t);      //I2C写字节
static uint8_t            I2C_Read_Byte (ACK_Value_t);//I2C读字节

static DeviceStatus_t I2C_Write_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *send_data, uint16_t length);
static DeviceStatus_t I2C_Read_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *recv_data, uint16_t length);

/* Public variables-----------------------------------------------------------*/
I2C_Soft_M_t I2C_Soft_M =
{
      .Init = I2C_Init,

      .Write_Frame = I2C_Write_Frame,
      .Read_Frame = I2C_Read_Frame
};

/* Private function prototypes------------------------------------------------*/
/*
      * @name   I2C_GPIO_Init
      * @briefI2C GPIO初始化
      * @paramNone
      * @retval None      
*/
void I2C_GPIO_Init(void)
{
      //将GPIO配置为开漏输出模式
      GPIOA_ModeCfg( GPIO_I2C_SCL,GPIO_ModeOut_OP_8mA );
      GPIOA_ModeCfg( GPIO_I2C_SDA,GPIO_ModeOut_OP_8mA );
      
}

/*
      * @name   I2C_Init
      * @briefI2C初始化
      * @paramNone
      * @retval None      
*/
static void I2C_Init(void)
{
    //SPI和I2C共用引脚
    R8_SPI0_CTRL_MOD = 0x02;//复位SPI0模式配置寄存器,先失能SPI功能

    I2C_GPIO_Init();

      SCL_SET;
      SDA_SET;
}

/*
      * @name   I2C_Start
      * @briefI2C起始信号
      * @paramNone
      * @retval None      
*/
static void I2C_Start(void)
{
      //SCL为高电平,SDA的下降沿为I2C起始信号
      SDA_SET;
      SCL_SET;
      Public.Delay_us(10);
      
      SDA_CLR;
      Public.Delay_us(20);
      
      //将SCL拉低,以免下一位是SDA出现上升沿,导致总线出现停止信号
      //SCL为高电平,SDA出现上升沿视为停止信号
      SCL_CLR;
      Public.Delay_us(10);
}

/*
      * @name   I2C_Stop
      * @briefI2C停止信号
      * @paramNone
      * @retval None      
*/
static void I2C_Stop(void)
{
      //SCL为高电平,SDA的上升沿为I2C停止信号
      SDA_CLR;
      SCL_SET;
      
      Public.Delay_us(20);
      SDA_SET;
}

/*
      * @name   I2C_Write_Byte
      * @briefI2C写字节
      * @paramWR_Byte -> 待写入数据
      * @retval ACK_Value_t -> 从机应答值
*/
ACK_Value_t I2C_Write_Byte(uint8_t WR_Byte)
{
      uint8_t i;
      ACK_Value_tack_respond;
      
      //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
      //数据按8位传输,高位在前,利用for循环逐个接收
      for(i=0;i<8;i++)
      {
                //SCL清零,主机SDA准备数据
                SCL_CLR;
                Public.Delay_us(10);
               
                //MSB,先发送高位
                //待发送的数据循环左移,和BIT7比较
                if((WR_Byte&BIT7) == BIT7)
                {
                        SDA_SET;
                }
                else
                {
                        SDA_CLR;
                }
                Public.Delay_us(10);
                //SCL置高,传输数据
                SCL_SET;
                Public.Delay_us(20);
               
                //准备发送下一比特位
                WR_Byte <<= 1;
      }
      
      //主机在SCL的高电平读取应答位,所以要先拉低SCL
      SCL_CLR;
      
      Public.Delay_us(2);
      //释放SDA,等待从机拉高/拉低SDA
      //如果主机不释放SDA,如果上一位数据是0,那么主机将占据SDA,从机没办法发送SDA=1的情况
      SDA_SET;
      //等待SDA被从机操作
      Public.Delay_us(20);
      //主机在SCL的高电平读取应答位,拉低SCL后再将其拉高
      SCL_SET;
    // 读取从机应答时的超时处理
    Timer1.Timeout = 0;
    while (READ_SDA != 0) // 等待从机应答拉低
    {
      if (Timer1.Timeout >= TIMER1_500mS)// 设置超时时间为500mS
      {
            SCL_CLR; //从机应答超时,主机继续占用总线
            return ERROR_TIMEOUT; // 返回超时错误
      }
    }
      //从机应答后,主机继续占用总线
      SCL_CLR;
      Public.Delay_us(10);

    // 返回从机的应答信号
    return (READ_SDA == 0) ? I2C_ACK : I2C_NACK; // 返回ACK或NACK
}

/*
      * @name   I2C_Read_Byte
      * @briefI2C读字节
      * @paramACK_Value -> 主机发送的ACK/NACK
      * @retval 从机返回主机要读的数据

*/
static uint8_t I2C_Read_Byte(ACK_Value_t ACK_Value)
{
      uint8_t read_byte = 0,i;
               
      //接收前,主机先确保释放SDA,避免干扰从机的数据发送
      SDA_SET;
      
      //SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
      //数据按8位传输,高位在前,利用for循环逐个接收
      for(i=0;i<8;i++)
      {
                //准备接收下一比特位
                read_byte <<= 1;//read_byte初始化时为0,第一次循环时,左移一位还是0
               
                //SCL清零,从机SDA准备数据
                SCL_CLR;
                Public.Delay_us(20);
               
                //SCL置高,获取数据
                SCL_SET;
                Public.Delay_us(20);

      // 读取从机发送的数据,接收的数据放在最低位
      if (READ_SDA) {
            read_byte |= 1; // 如果READ_SDA为高电平,则将最低位设置为1
      }//如果是低电平,则继续保持为0,不用操作
      }
      
      //SCL清零,主机准备应答信号
      SCL_CLR;
      Public.Delay_us(10);
      
      //主机发送应答信号
      if(ACK_Value == I2C_ACK)//应答,让从机继续发数据
      {
                SDA_CLR;
      }
      else//非应答,停止数据传输
      {
                SDA_SET;
    }
      Public.Delay_us(10);
      
      //置起SCL,将SDA发送出去
      SCL_SET;
      Public.Delay_us(20);
      
      //Note:
    //释放SDA数据线
      //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACK信号
      SCL_CLR;
    SDA_SET;
      Public.Delay_us(10);

      //返回数据
      return read_byte;
}

/**
* @briefI2C主机写一帧数据
* @paramdev_addr: 从机地址
* @paramreg_addr: 寄存器地址
* @paramsend_data: 数据缓冲区指针
* @paramlength: 数据长度
* @retval 0: 成功, 1: 失败
*/
static DeviceStatus_t I2C_Write_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *send_data, uint16_t length)
{
    uint16_t i;
    ACK_Value_t ack;

    I2C_Start(); // 发送起始信号

    // 发送从机地址和写位
    ack = I2C_Write_Byte(dev_addr & 0xFE);
    if (ack != I2C_ACK) {
      I2C_Stop();
      return ERROR_Data; // 失败
    }

      // 发送寄存器地址
    ack = I2C_Write_Byte(reg_addr);
    if (ack != I2C_ACK) {
      I2C_Stop();
      return ERROR_Data; // 失败
    }

    // 发送数据
    for (i = 0; i < length; i++) {
      ack = I2C_Write_Byte(send_data);
      if (ack != I2C_ACK) {
            I2C_Stop();
            return ERROR_Data; // 失败
      }
    }

    I2C_Stop(); // 发送停止信号
    return HAL_OK; // 成功
}

/**
* @briefI2C主机读一帧数据
* @paramdev_addr: 从机地址
* @paramreg_addr: 寄存器地址
* @paramrecv_data: 数据缓冲区指针,用于存储读取的数据
* @paramlength: 要读取的数据长度
* @retval 0: 成功, 1: 失败
*/
static DeviceStatus_t I2C_Read_Frame(uint8_t dev_addr, uint8_t reg_addr, uint8_t *recv_data, uint16_t length)
{
    uint16_t i;
    ACK_Value_t ack;

    I2C_Start(); // 发送起始信号

    // 发送从机地址和读位
    ack = I2C_Write_Byte(dev_addr | 0x01);
    if (ack != I2C_ACK) {
      I2C_Stop();
      return ERROR_Data; // 失败
    }

      // 发送寄存器地址
    ack = I2C_Write_Byte(reg_addr);
    if (ack != I2C_ACK) {
      I2C_Stop();
      return ERROR_Data; // 失败
    }

      I2C_Start(); // 重新启动I2C总线
    // 读取数据
    for (i = 0; i < length; i++) {
      if (i == length - 1) {
            // 最后一个字节发送NACK
            recv_data = I2C_Read_Byte(I2C_NACK);
      } else {
            // 其他字节发送ACK
            recv_data = I2C_Read_Byte(I2C_ACK);
      }
    }

    I2C_Stop(); // 发送停止信号
    return HAL_OK; // 成功
}

/********************************************************
End Of File
********************************************************/

我的代码我看不出问题,另外,不知道作为从机,对I2C通讯的速率是否有要求,如果有,得是多少?目前我的速率是小于400K的

DebugLab 发表于 2025-3-27 10:18:08

https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=16514&pid=155128
根据另一个帖子的逻辑分析仪截图猜测是主机推挽输出,从机无法拉低
但您的主机是其他厂商的单片机,还是软件模拟的,还用了库函数
其他厂商的MCU建议到其他地方寻求技术支持,本论坛仅讨论STC AI MCU,可以使用相关的MCU做主机
不熟悉你用的这个库函数,开漏应该是OD(Open Drain),推挽是PP(Push Pull),这个OP无法理解

如使用STC AI MCU,一主多从或单主单从,建议主机I2C打开内部4K上拉,从机开漏
不清楚你用的MCU是否支持内部4K上拉(建议1K~5K上拉),如无,需要外接上拉电阻
软件模拟I2C主机可以参考8051U实验箱例程:
AI8051U-DEMO-CODE-V1.2.zip\Ai8051U-32Bit\84-USB录放音声卡-TLV320AIC23B-内部36.864M-外接32768Hz晶振\src\TLV320AIC23.c


实验箱:
https://www.stcai.com/syx
AI8051U实验箱1.2:
https://www.stcaimcu.com/data/download/DemoCode/AI8051U-DEMO-CODE-V1.2.zip

zhouq 发表于 2025-3-27 14:19:07

DebugLab 发表于 2025-3-27 10:18
https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=16514&pid=155128
根据另一个帖子的 ...

好的,谢谢解答
页: [1]
查看完整版本: 第3篇_两块8H1K17用I2C通讯,成功