zhouq 发表于 2025-8-26 18:10:12

IAP_06_从机没办法发出NACK

我现在在使用STC8H1K17作为I2C从机,主机通过I2C给从机发送数据,有连续发送多个字节数据的需求,现在发现从机没办法在主机发完所有数据后发出NACK,全部都是ACK。

下面是逻辑分析仪抓包的图片:


写1个字节数据


写62个字节数据



我的代码:
void I2C_S_Init(void)是初始化代码
void I2C_PollingTask(void)是轮询接收数据
/*
    * @name   I2C_S_Init
    * @briefI2C初始化
    * @paramNone
    * @retval None      
*/
static void I2C_S_Init(void)
{
    //设置P14 P15为 I2C 功能
    P_SW2 |= 0x80;
   
    //使能I2C从机模式,此寄存器涉及到端口
    I2CCFG = EnI2C_Slave;
      
    //设置从机设备地址寄存器I2CSLADR=0011_0000B
    //B0为MA,设置为0表示设备地址必须与I2CSLADR相同
    I2CSLADR = User_I2C_ADDR;                        

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

    //禁止从机模式中断
    I2CSLCR = 0x00;
}

/*
    * @name   void I2C_PollingTask(void)
    * @briefI2C轮询任务
    * @param参数说明
    * @retval None      
*/
void I2C_PollingTask(void)
{
    uint8_t rx_data = 0;
    uint8_t cmd = 0;
   
    P_SW2 |= 0x80;
   
    //收到START信号
    if (I2CSLST & I2C_S_STAIF)
    {
      I2CSLST &= ~I2C_S_STAIF;//清零STAIF标志位
      // 注意:不在这里重置IAP缓冲区索引,因为I2C可以使用重复起始信号连续发送数据
            
    }//从机收到了1字节数据
    else if (I2CSLST & I2C_S_RXIF)
    {
      I2CSLST &= ~I2C_S_RXIF;//清零RXIF标志位
      rx_data = I2CRXD;
            
      //处理设备地址
      if (I2C_S.DevAddrFlag == FALSE)//如果设备标志位为FALSE,表示这是收到的第1字节                     
      {   
            I2C_S.DevAddr = rx_data;//接收设备地址
            
            //下面代码多余了,MA=0时设备地址只有和I2CSLADR相同才会处理数据,留着也无妨
            //第1字节数据是设备地址数据
            if((I2C_S.DevAddr & 0xFE) == User_I2C_ADDR)
            {
                I2C_S.DevAddrFlag = TRUE;//处理RECV事件(RECV DEVICE ADDR)   
                I2C_S.ReadWriteFlag = (I2C_S.DevAddr & WRMask) ? TRUE : FALSE;//TRUE表示读操作,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 = rx_data;
            // 如果是IAP命令,重置相关变量
            if (I2C_S.RegAddr == ADDR_IAP_STC) {
                gIAP_Buff_Index = 0;
            }
      }//读寄存器
      else if(I2C_S.ReadWriteFlag == TRUE)
      {
            switch (I2C_S.RegAddr)
            {
                case ADDR_IAP_STATUS:
                  I2CTXD = gIAP_Status;
                  gIAP_Status = HAL_BUSY;
                  break;
                case ADDR_BIN_VERSION:
                  I2CTXD = 0x00;
                  break;
                default:
                  break;
            }      
      }//写寄存器
      else if (I2C_S.ReadWriteFlag == FALSE)   
      {
            if (I2C_S.RegAddr == ADDR_IAP_STC)
            {
                // 保存数据到 buffer(统一入口)
                gIAP_Buff = rx_data;
      
                if (gIAP_Buff_Index == 0) // 第1字节 = 命令
                {
                  cmd = rx_data;
      
                  if (cmd == CMD_IAP_PROM)
                  {
                        gIAP_Cmd_Recved = HAL_LOCKED; // 开始接收数据
                        //I2CSLST &= ~I2C_S_ACKO;       // ACK
                  }
                  else
                  {
                        gIAP_Cmd_Recved = HAL_UNLOCKED; // 单字节命令
                        //I2CSLST |= I2C_S_ACKO;          // NACK
                  }
                }
                else if (cmd == CMD_IAP_PROM) // 后续数据
                {
                  if (gIAP_Buff_Index < (IAP_BUFFER_SIZE - 1))
                  {
                        //I2CSLST &= ~I2C_S_ACKO; // 继续 ACK
                  }
                  else
                  {
                        gIAP_Cmd_Recved = HAL_UNLOCKED; // buffer 满
                        //I2CSLST |= I2C_S_ACKO;          // 下一字节 NACK
                  }
                }
      
                // 统一自增 index
                gIAP_Buff_Index++;
            }
      }         
               
    }
   
    //从机发送完了1字节数据
    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 == ADDR_IAP_STC)
            {
                // 暂时没有要处理的内容
            }
            else if(I2C_S.RegAddr == ADDR_IAP_STATUS)
            {
                // 暂时没有要处理的内容
            }
            else if(I2C_S.RegAddr == ADDR_BIN_VERSION)
            {
                //读取固件版本号,ISP阶段固件版本固定为0
                I2CTXD = 0x00;
            }
            
      }
    }
   
    //从机收到STOP信号
    if (I2CSLST & I2C_S_STOIF)
    {
      I2CSLST &= ~I2C_S_STOIF;//清零STOIF标志位
               
      // 如果是IAP命令,且已经接收到数据,则标记命令已接收
      if(
            (I2C_S.RegAddr == ADDR_IAP_STC) &&
            ((cmd == CMD_IAP_UPGRADE) || (cmd == CMD_IAP_ERASE) || (cmd == CMD_IAP_END)) )
      {
            gIAP_Cmd_Recved = HAL_UNLOCKED;
            
      }//已接收完一包CMD_IAP_PROM
      if ((I2C_S.RegAddr == ADDR_IAP_STC) &&
            (gIAP_Buff_Index == IAP_BUFFER_SIZE))
      {
            gIAP_Cmd_Recved = HAL_UNLOCKED;
      }
            
      I2C_S.DevAddrFlag = FALSE;//将设备地址标志位恢复默认值
      I2C_S.RegAddrFlag = FALSE;//将寄存器地址标志位恢复默认值
      I2C_S.ReadWriteFlag = FALSE;      
      I2C_S.DevAddr = 0;
      I2C_S.RegAddr = 0;

      gIAP_Buff_Index = 0;
    }
}


国芯人工智能 发表于 2025-8-26 18:12:00

根据您提供的信息及代码,IAP06 从机无法在主机发送完所有数据后发出 NACK 的问题,可能与 I2C 从机模式的配置、中断处理逻辑或状态机控制有关。以下为专业分析及建议:

一、问题现象

您描述在使用 STC8H1K17 作为 I2C 从机时,无论发送一个字节还是多个字节(如 62 字节),从机始终返回 ACK,未能正确发出 NACK。此行为不符合 I2C 协议中“当从机接收完毕且不再需要数据时应发出 NACK”的规范。

二、可能原因分析

1. 从机未正确识别结束信号(STOP)
I2C 协议中,主机在发送完数据后会发出 STOP 信号,从机应在收到该信号后发出 NACK。若从机未检测到 STOP 信号,则可能持续等待数据,导致始终返回 ACK。
检查是否在 I2CPollingTask 中正确判断了 STOP 事件。
确保在接收到 STOP 后,从机进入空闲状态并释放总线。

2. I2CSLST 寄存器配置不当
I2CSLST 是 I2C 从机状态寄存器,用于指示当前从机状态。若未正确设置或清除该寄存器,可能导致状态机异常,从而影响 NACK 发送。
建议在初始化时将 I2CSLST 设置为 0x00,并在每次接收完成后重新清零。
在 I2CPollingTask 中检查 I2CSLST 的值,确认是否正常响应了 START 和 STOP 信号。

3. 中断标志未及时清除
若使用中断方式接收数据,需确保在每次中断处理后及时清除中断标志位(如 I2CIF)和状态标志(如 I2CSLST)。
若未清除中断标志,可能导致重复触发中断,造成状态混乱。
推荐在轮询任务中优先检查 I2CIF 标志,并在处理完成后手动清除。

4. 未处理多字节接收逻辑
在接收多个字节时,需在每个字节接收后判断是否为最后一个字节。如果是,则应发送 NACK;否则继续接收并发送 ACK。
当前代码中未明确体现此逻辑,可能导致所有字节均被 ACK。
建议引入计数器机制,记录已接收字节数,并在最后一位时发送 NACK。

三、优化建议

1. 完善 I2CPollingTask 逻辑

c
void I2CPollingTask(void)
{
    if (I2CIF) { // 检查 I2C 中断标志
      I2CIF = 0; // 清除中断标志

      if (I2CSLST & 0x01) { // 判断是否为 START 信号
            // 处理 START 信号
            I2CSLST &= ~0x01; // 清除 START 标志
            datacount = 0;
      }

      if (I2CSLST & 0x02) { // 判断是否为地址匹配成功
            // 地址匹配成功,准备接收数据
            I2CSLST &= ~0x02; // 清除地址匹配标志
      }

      if (I2CSLST & 0x04) { // 判断是否为数据接收完成
            // 数据接收完成,处理数据
            I2CSLST &= ~0x04; // 清除数据接收标志
            if (datacount == expectedlength - 1) {
                // 最后一个字节,发送 NACK
                I2CDAT = rxdata;
                I2CCON |= 0x10; // 设置 NACK
            } else {
                // 其他字节,发送 ACK
                I2CDAT = rxdata;
                I2CCON &= ~0x10; // 设置 ACK
            }
            datacount++;
      }

      if (I2CSLST & 0x08) { // 判断是否为 STOP 信号
            // 处理 STOP 信号,重置状态
            I2CSLST &= ~0x08; // 清除 STOP 标志
            datacount = 0;
      }
    }
}

2. 确保地址匹配与数据接收逻辑正确
在 I2CSLST 中,确保地址匹配(BIT 1)和数据接收(BIT 2)的处理顺序合理。
避免因地址匹配失败导致数据接收逻辑未执行。

3. 调试建议
使用逻辑分析仪进一步确认 STOP 信号是否被正确发出。
检查 I2C 时序是否符合标准,特别是 SCL 和 SDA 的上升/下降沿时间。
在代码中加入调试输出,打印 I2CSLST 的状态变化,便于定位问题。

四、结论

IAP06 从机无法发出 NACK 的问题,主要可能源于以下几点:
未正确识别 STOP 信号
I2CSLST 状态未正确更新或清除
未在最后一字节接收后设置 NACK
中断标志未及时处理

建议结合上述优化方案对 I2CPollingTask 进行完善,并通过逻辑分析仪进一步验证 I2C 通信过程,以确保从机能够按照协议正确响应主机请求。

如需进一步协助,请提供完整的 I2CPollingTask 代码片段,以便更精准地定位问题。

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

DebugLab 发表于 7 天前

I2CSLST.SLACKO要在这段时间内置1才能正确发送NAK

zhouq 发表于 6 天前

DebugLab 发表于 2025-8-27 00:43
I2CSLST.SLACKO要在这段时间内置1才能正确发送NAK

在我的轮询代码中,怎么在这个字节数据内对SLACKO置位1?
页: [1]
查看完整版本: IAP_06_从机没办法发出NACK