zhouq 发表于 2025-3-25 14:14:55

第2篇_8H1K17的I2C无法对主机做出应答(附带主从机代码)

我是用某单片机作为主机,读写8H1K17的I2C,结果是STC8H1K17无法应答主机发起的读设备地址:
1、8H1K17地址我设置为0x30

2、MA设置为0(为了调试,也曾设置为1,结果都是从机无法应答)

下图是我用逻辑分析仪抓取的主机读设备地址的信号


主机模拟I2C的代码:
/**
******************************************************************************
* @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      
*/
static 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)
{
      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(1);
      
      SDA_CLR;
      Public.Delay_us(5);
      
      //将SCL拉低,以免下一位是SDA出现上升沿,导致总线出现停止信号
      //SCL为高电平,SDA出现上升沿视为停止信号
      SCL_CLR;
      Public.Delay_us(1);
}

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

/*
      * @name   I2C_Write_Byte
      * @briefI2C写字节
      * @paramWR_Byte -> 待写入数据
      * @retval ACK_Value_t -> 从机应答值      
*/
static 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(1);
               
                //MSB,先发送高位
                //待发送的数据循环左移,和BIT7比较
                if((WR_Byte&BIT7) == BIT7)
                {
                        SDA_SET;
                }
                else
                {
                        SDA_CLR;
                }
                Public.Delay_us(1);
                //SCL置高,传输数据
                SCL_SET;
                Public.Delay_us(5);
               
                //准备发送下一比特位
                WR_Byte <<= 1;
      }
      
      //主机在SCL的高电平读取应答位,所以要先拉低SCL
      SCL_CLR;
      
      //释放SDA,等待从机拉高/拉低SDA
      //如果主机不释放SDA,如果上一位数据是0,那么主机将占据SDA,从机没办法发送SDA=1的情况
      SDA_SET;
      Public.Delay_us(1);
      
      //主机在SCL的高电平读取应答位,拉低SCL后再将其拉高
      SCL_SET;
      //等待SDA被从机操作
      Public.Delay_us(5);

    // 读取从机应答时的超时处理
    Timer1.Timeout = 0;
    while (READ_SDA != 0) // 等待从机应答拉低
    {
      if (Timer1.Timeout >= TIMER1_1S)// 设置超时时间为1S
      {
            SCL_CLR; //从机应答超时,主机继续占用总线
            return ERROR_TIMEOUT; // 返回超时错误
      }
    }      
      
      //从机应答后,主机继续占用总线
      SCL_CLR;
      Public.Delay_us(1);

    // 返回从机的应答信号
    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(5);
               
                //SCL置高,获取数据
                SCL_SET;
                Public.Delay_us(5);

                //接收从机发送的数据,接收的数据放在最低位
                read_byte |= READ_SDA;
      }
      
      //SCL清零,主机准备应答信号
      SCL_CLR;
      Public.Delay_us(1);
      
      //主机发送应答信号      
      if(ACK_Value == I2C_ACK)//应答,让从机继续发数据
      {
                SDA_CLR;
      }
      else//非应答,停止数据传输
      {
                SDA_SET;
    }
      Public.Delay_us(1);
      
      //置起SCL,将SDA发送出去
      SCL_SET;         
      Public.Delay_us(5);
      
      //Note:
    //释放SDA数据线
      //SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACK信号
      SCL_CLR;
    SDA_SET;         
      Public.Delay_us(1);

      //返回数据
      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
********************************************************/
主机发起的读操作
   uint8_t CHA_Value;
   CHA_Value=0x3C;
   CHA_Value=0x3B;

   I2C_Soft_M.Init();
   I2C_Soft_M.Read_Frame(0x30,ChA_ID,CHA_Value,2);

从机代码(STC8H1K17):
/* Includes ------------------------------------------------------------------*/
#include "HeaderInc.h"

/* Private define-------------------------------------------------------------*/
#define User_I2C_ADDR0x30          //从机地址为0x30,高7位,MA=0,MA=0表示设备地址必须与I2CSLADR相同
#define WRMask         0x01          //读写操作掩码

#define ADDR_IAP_STC   0x8A   //IAP升级芯片地址


//I2C配置寄存器(I2CCFG)
//使能I2C从机模式
//B7-B6 10
#define EnI2C_Slave    0x80
//使能I2C主机模式
//B7-B6 11
//#define EnI2C_Master   0xC0
//MSSPEED,I2C在主机模式下,MSSPEED设置才有效
//I2C总线速度=FOSC / 2 / (MSSPEED * 2 + 4),设置总线速度为400K,那么MSSPEED=13
//#define I2C_Speed      0x0D

//I2C 从机状态寄存器(I2CSLST)
#define I2C_S_BUSY       0x80          //从机状态位,0空闲,1忙,只读
#define I2C_S_STAIF      0x40          //收到START信号的中断请求位
#define I2C_S_RXIF       0x20          //从机收到1字节的数据的中断请求位
#define I2C_S_TXIF       0x10          //从机发完1字节的数据的中断请求位
#define I2C_S_STOIF      0x08          //从机收到STOP信号的中断请求位
#define I2C_S_ACKI       0x02          //从机接收到的ACK数据
#define I2C_S_ACKO       0x01          //从机准备要发送的ACK信号


/* Private variables----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      
//声明使用的函数
static void   I2C_S_Init(void);      //I2C初始化

/* Public variables-----------------------------------------------------------*/
//创建并初始化结构体
I2C_S_t idata I2C_S =
{
      FALSE,      /* DevAddrFlag - 初始化时未接收到设备地址,即未收到任何数据 */
      FALSE,      /* RegAddrFlag - 初始化时未接收到寄存器地址 */
      FALSE,            /* 读写标志位,TRUE 表示读操作,FALSE 表示写操作 */
      0,          /* DevAddr */
      0,          /* RegAddr */
      I2C_S_Init/* Init */
};


/*
      * @name   I2C_S_Init
      * @briefI2C初始化
      * @paramNone
      * @retval None      
*/
static void I2C_S_Init(void)
{
      //设置P14 P15为 I2C 功能
    P_SW2 |= 0x80;
      
    //设置从机设备地址寄存器I2CSLADR=0011_0000B
      //B0为MA,设置为0表示设备地址必须与I2CSLADR相同
      I2CSLADR = 0x30;                        

      //配置从机状态为空闲,并清零所有中断标志
      I2CSLST = 0x00;

    //使能I2C从机模式,此寄存器涉及到端口
      I2CCFG = EnI2C_Slave;
}

/*
      * @name   I2C_INTR()
      * @briefI2C中断函数
      * @param参数说明
      * @retval None      
*/
void I2C_INTR() interrupt 24
{               
      P_SW2 |= 0x80;
      
      //收到START信号
    if (I2CSLST & I2C_S_STAIF)
    {
      I2CSLST &= ~I2C_S_STAIF;//清零STAIF标志位
                        
    }//从机收到了1字节数据
    else if (I2CSLST & I2C_S_RXIF)
    {
      I2CSLST &= ~I2C_S_RXIF;//清零RXIF标志位
                        
                //接收设备地址
      if (I2C_S.DevAddrFlag == FALSE)//如果设备标志位为FALSE,表示这是收到的第1字节                     
      {      
                        I2C_S.DevAddr = I2CRXD;//接收设备地址
                        
                        //下面代码多余了,MA=0时设备地址只有和I2CSLADR相同才会处理数据,留着也无妨
                        //第1字节数据是设备地址数据
                        if((I2C_S.DevAddr & 0xFE) == User_I2C_ADDR)
                        {
                              I2C_S.DevAddrFlag = TRUE;//处理RECV事件(RECV DEVICE ADDR)      
                              if (I2C_S.DevAddr & WRMask) {
                                        I2C_S.ReadWriteFlag = TRUE;// 读操作
                              } else {
                                        I2C_S.ReadWriteFlag = FALSE;// 写操作
                              }      
                        }                        
      }//接收寄存器地址,已经收到过设备地址,且I2C_S.RegAddrFlag为 FALSE,则表示此次数据是寄存器地址
      else if ((I2C_S.DevAddrFlag == TRUE) && (I2C_S.RegAddrFlag == FALSE))                        
      {
            I2C_S.RegAddrFlag = TRUE;//处理RECV事件(RECV REG ADDR)      
                        I2C_S.RegAddr = I2CRXD;//接收寄存器地址
      }//读寄存器
                else if(I2C_S.ReadWriteFlag == TRUE)
                {
                        //当前只读四个通道的值
                        if (I2C_S.RegAddr == ChA_ID ||
                              I2C_S.RegAddr == ChB_ID ||
                              I2C_S.RegAddr == ChC_ID ||
                              I2C_S.RegAddr == ChD_ID)
                        {
                              ChxDetec.BitValue = ChxDetec.GetBitValue(I2C_S.RegAddr);
                              I2CTXD = (uint8_t)(ChxDetec.BitValue >> 8);//上传低位的ADC值
                        }
                        else if(I2C_S.RegAddr == ADDR_IAP_STC)
                        {
                        }
                }//写寄存器
                else if(I2C_S.ReadWriteFlag == FALSE)
                {
                        if(I2C_S.RegAddr == ADDR_IAP_STC)
                        {
                              //buffer = I2CRXD;
                        }
                }               
                              
    }//从机发送完了1字节数据
    else if (I2CSLST & I2C_S_TXIF)                                                                              
    {
      I2CSLST &= ~I2C_S_TXIF;//清零TXIF标志位
               
                if(I2CSLST & I2C_S_ACKI)//如果ACKI=1,说明主机NACK了
                {
                        //主机NACK停止发送数据
               
                }//接收到ACK
                else
                {
                        if (I2C_S.RegAddr == ChA_ID ||
                              I2C_S.RegAddr == ChB_ID ||
                              I2C_S.RegAddr == ChC_ID ||
                              I2C_S.RegAddr == ChD_ID)
                        {
                              ChxDetec.BitValue = ChxDetec.GetBitValue(I2C_S.RegAddr);
                              I2CTXD = (uint8_t)(ChxDetec.BitValue & 0x00FF);//上传低位的ADC值
                        }
                        else if(I2C_S.RegAddr == ADDR_IAP_STC)
                        {
                              //处理IAP升级的逻辑
                        }
                        
                }
    }//从机收到STOP信号
    else if (I2CSLST & I2C_S_STOIF)
    {
      I2CSLST &= ~I2C_S_STOIF;//清零STOIF标志位
                        
      I2C_S.DevAddrFlag = FALSE;//将设备地址标志位恢复默认值
      I2C_S.RegAddrFlag = FALSE;//将寄存器地址标志位恢复默认值
                I2C_S.ReadWriteFlag = FALSE;               
                I2C_S.DevAddr = 0;
                I2C_S.RegAddr = 0;
    }
      _pop_(P_SW2);
}

/********************************************************
End Of File
********************************************************/IO初始化和中断配置
static void GPIO_Init(void)
{
      //M1M0
      //0   0             准双向口
      //0   1             推挽输出
      //1   0             高阻输入
      //1   1             开漏
      
      //使用 P10 作为 PWM-1K 的输出引脚
      //设置P10为准双向口
      //P14 P15为I2C接口,配置开漏输出
      P1M1 = 0x30;
      P1M0 = 0x30;
      
      
      //P30 P31作为UART 设置为准双向口
      //P32 P33 P34 P35为ADC采样端口,设置为高阻输入
      P3M1 = 0x3C; //0011 1100
      P3M0 = 0x00; //0000 0000

}

/*
      * @name   INTR_Init
      * @brief中断初始化
      * @paramNone
      * @retval None      
*/
static void INTR_Init(void)
{      
      /* 定时器1用作串口波特率发生器
         定时器1中断设置 */
      ET1 = 0;                  //禁止定时器1中断
      TR1 = 1;                  //启动定时器1
      ES = 1;             //串行口开中断
      
      //定时器0中断设置
      ET0 = 1;                  //开启定时器0中断

      //I2C从机模式的各种中断使能
      I2CSLCR = 0x78;
      
      //开中断
      EA = 1;             //打开总中断
}我的STC8H1K17程序结构,在进入while(1)之前,会先调用GPIO_Init()和INTR_Init()配置IO和中断

我尝试过多种调试,但没能找到问题,可能是只缘身在此山中吧。

国芯人工智能 发表于 2025-3-25 14:18:00

针对您描述的STC8H1K17的I2C无法对主机做出应答的问题,以下是一些可能的原因及解决方案,供您参考:

1. 设备地址设置问题
您提到将STC8H1K17的地址设置为0x30,但请注意,I2C设备地址通常为7位,且需要在数据传输时左移一位,并在最低位添加读写位(0表示写,1表示读)。因此,如果您设置的是7位地址0x30,实际发送的地址应为0x60(写操作)或0x61(读操作)。
请确认您在主机发送地址时是否正确处理了地址位。

2. I2C时序问题
I2C通信对时序要求非常严格。如果时序不符合规范,可能导致从机无法正确响应。建议您检查以下时序参数:
起始条件:SCL为高电平时,SDA由高电平跳变到低电平。
停止条件:SCL为高电平时,SDA由低电平跳变到高电平。
ACK/NACK:在每个字节传输后,接收方应在第9个时钟周期内拉低SDA以表示ACK。
您可以使用逻辑分析仪或示波器检查这些时序是否符合I2C协议的要求。

3. I2C引脚配置
确保STC8H1K17的I2C引脚(SDA和SCL)已正确配置为开漏输出模式,并已启用内部上拉电阻或外部上拉电阻(通常为4.7kΩ)。
检查主机和从机的I2C引脚是否连接正确,且没有短路或断路的情况。

4. MA(多主机)模式设置
您提到将MA设置为0和1进行调试,但无论设置为哪种模式都无法应答。建议您检查以下几点:
如果MA设置为0,表示STC8H1K17作为从机工作,应确保主机发送的地址与从机地址匹配。
如果MA设置为1,表示STC8H1K17作为主机工作,此时它不会对主机的地址请求做出应答。
建议在调试阶段将MA设置为0,并确保主机发送的地址与从机地址一致。

5. 电源和地连接
确保主机和从机的电源和地连接良好,且电压稳定。电源噪声或地线问题可能导致I2C通信失败。

6. 代码检查
请检查主机模拟I2C的代码,确保起始条件、停止条件、ACK/NACK处理等逻辑正确实现。以下是示例代码片段:
   c
   void I2CStart(void) {
         SDAHIGH;
         SCLHIGH;
         delayus(5);
         SDALOW;
         delayus(5);
         SCLLOW;
   }

   void I2CStop(void) {
         SDALOW;
         SCLHIGH;
         delayus(5);
         SDAHIGH;
         delayus(5);
   }

   uint8t I2CReadACK(void) {
         SCLHIGH;
         delayus(5);
         uint8t ack = SDAREAD;
         SCLLOW;
         return ack;
   }

7. 逻辑分析仪数据
您提到使用逻辑分析仪抓取了信号,但未提供具体波形。建议您分析波形,确认以下几点:
起始条件是否正确。
主机发送的地址是否正确(包括读写位)。
从机是否在ACK周期内拉低了SDA。

通过以上步骤逐一排查,您应该能够找到STC8H1K17无法应答的原因并解决问题。如果问题仍然存在,建议进一步检查硬件连接或咨询STC的技术支持团队。

[本文内容由国芯人工智能辅助生成,仅供参考]

zhouq 发表于 2025-3-25 14:50:12

从机面对主机的读写,我需要自己手动设置ACK和NACK吗?

yao眼的光 发表于 2025-3-25 16:11:42

【新提醒】部分较难问题需要帮忙分析的,可跟贴放上原帖链接通知我 | 新问题另外开贴 - 第12页 - C语言,汇编语言,Proteus MCU软件仿真 国芯技术交流网站 - AI32位8051交流社区https://www.stcaimcu.com/forum.php?mod=viewthread&tid=12065&extra=&page=12关注下这个贴114楼

zhouq 发表于 2025-3-25 16:25:52

补充:
我另外还测试了官方例程,和我写的代码一样的情况
主机和从机的SDA SCL均配置为开漏输出,SDA SCL通过5.1K电阻上拉到3.3V

zhouq 发表于 2025-3-25 16:39:48

补充,已排除焊接不良、芯片损坏的情况

DebugLab 发表于 2025-3-25 17:32:29

使用官方I2C从机例程仍然不正常
建议先排查一下主机的问题
可以使用我写的这个程序下载到一箭双雕上扫描一下所有从机,看能不能扫描到:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=9938

zhouq 发表于 2025-3-25 17:57:41

DebugLab 发表于 2025-3-25 17:32
使用官方I2C从机例程仍然不正常
建议先排查一下主机的问题
可以使用我写的这个程序下载到一箭双雕上扫描一 ...

我没有您提到的“一箭双雕”,我主机的代码和逻辑分析仪抓取的信号都在帖子里

DebugLab 发表于 2025-3-27 10:17:22

该从机拉低的时候无法拉低,怀疑主机是推挽输出了

zhouq 发表于 2025-3-27 11:44:27

DebugLab 发表于 2025-3-27 10:17
该从机拉低的时候无法拉低,怀疑主机是推挽输出了

可以看我的配置,主机配置为开漏输出
页: [1]
查看完整版本: 第2篇_8H1K17的I2C无法对主机做出应答(附带主从机代码)