ercircle 发表于 2025-3-18 18:47:31

zhouq 发表于 2025-3-18 18:21
按照你数据手册从机中断的代码例程,当我发送完设备地址后(读寄存器),此时给I2CTXD赋值,等我再发寄存器 ...

赋值完这个时候按我理解SDA线控制权应该在从机,从机回数据用呀。你上传下你测试用的主从机代码和电路图么

zhouq 发表于 2025-3-18 18:50:52

ercircle 发表于 2025-3-18 18:47
赋值完这个时候按我理解SDA线控制权应该在从机,从机回数据用呀。你上传下你测试用的主从机代码和电路图 ...

I2C规范中,当主机读从机数据时,SDA控制权需要转交给从机。回帖中有我写寄存器的代码截图

zhouq 发表于 2025-3-18 18:53:16

ercircle 发表于 2025-3-18 18:11
图里不是收到主机事件,将数据放到寄存器了嘛,放了SDA引脚应该就有反应了。




“图里不是收到主机事件,将数据放到寄存器了嘛,放了SDA引脚应该就有反应了。”
“放了SDA引脚应该就有反应了”我猜测也是这样,数据放到了SDA后,主机再发时钟信号,此时STC从机就会在SDA引脚动作。

ercircle 发表于 2025-3-18 19:41:14

zhouq 发表于 2025-3-18 17:45
主机发送完设备地址,寄存器地址,之后再发送起始信号,然后呢?从机通过什么方式把数据放到SDA引脚上?
...
只有这一张截图吗?操作SCL是主机代码啊,你的从机代码呢?

“等我再发寄存器地址(此时主机在发SCL)时,从机就会和主机抢SDA线的控制权”
这句话又是什么意思呢,从机发什么寄存器地址,从机直接发数据么,主机此时发SCL和SDA线收数据同时进行。

神农鼎 发表于 2025-3-18 21:10:25

1, I/O 设置成开漏,外部加10K 上拉电阻,或提前打开内部上拉电阻
2,这有现成的参考程序


#include "reg51.h"
#include "intrins.h"

sfr   P_SW2   =   0xba;

#define I2CCFG      (*(unsigned char volatile xdata *)0xfe80)
#define I2CMSCR   (*(unsigned char volatile xdata *)0xfe81)
#define I2CMSST   (*(unsigned char volatile xdata *)0xfe82)
#define I2CSLCR   (*(unsigned char volatile xdata *)0xfe83)
#define I2CSLST   (*(unsigned char volatile xdata *)0xfe84)
#define I2CSLADR    (*(unsigned char volatile xdata *)0xfe85)
#define I2CTXD      (*(unsigned char volatile xdata *)0xfe86)
#define I2CRXD      (*(unsigned char volatile xdata *)0xfe87)

sfr   P1M1    =   0x91;
sfr   P1M0    =   0x92;
sfr   P0M1    =   0x93;
sfr   P0M0    =   0x94;
sfr   P2M1    =   0x95;
sfr   P2M0    =   0x96;
sfr   P3M1    =   0xb1;
sfr   P3M0    =   0xb2;
sfr   P4M1    =   0xb3;
sfr   P4M0    =   0xb4;
sfr   P5M1    =   0xc9;
sfr   P5M0    =   0xca;

sbit    SDA   =   P1^4;
sbit    SCL   =   P1^5;

bit isda;                                       //设备地址标志
bit isma;                                       //存储地址标志
unsigned char addr;
unsigned char pdata buffer;

void I2C_Isr() interrupt 24
{
    _push_(P_SW2);
    P_SW2 |= 0x80;

    if (I2CSLST & 0x40)
    {
      I2CSLST &= ~0x40;                     //处理START事件
    }
    else if (I2CSLST & 0x20)
    {
      I2CSLST &= ~0x20;                     //处理RECV事件
      if (isda)
      {
            isda = 0;                           //处理RECV事件(RECV DEVICE ADDR)
      }
      else if (isma)
      {
            isma = 0;                           //处理RECV事件(RECV MEMORY ADDR)
            addr = I2CRXD;
            I2CTXD = buffer;
      }
      else
      {
            buffer = I2CRXD;            //处理RECV事件(RECV DATA)
      }
    }
    else if (I2CSLST & 0x10)
    {
      I2CSLST &= ~0x10;                     //处理SEND事件
      if (I2CSLST & 0x02)
      {
            I2CTXD = 0xff;                      //接收到NAK则停止读取数据
      }
      else
      {
            I2CTXD = buffer[++addr];            //接收到ACK则继续读取数据
      }
    }
    else if (I2CSLST & 0x08)
    {
      I2CSLST &= ~0x08;                     //处理STOP事件
      isda = 1;
      isma = 1;
    }

    _pop_(P_SW2);
}

void main()
{
    P0M0 = 0x00;
    P0M1 = 0x00;
    P1M0 = 0x00;
    P1M1 = 0x00;
    P2M0 = 0x00;
    P2M1 = 0x00;
    P3M0 = 0x00;
    P3M1 = 0x00;
    P4M0 = 0x00;
    P4M1 = 0x00;
    P5M0 = 0x00;
    P5M1 = 0x00;

    P_SW2 = 0x80;

    I2CCFG = 0x81;                              //使能I2C从机模式
    I2CSLADR = 0x5a;                            //设置从机设备地址寄存器I2CSLADR=0101_1010B
                                                //即I2CSLADR=010_1101B,MA=0B。
                                                //由于MA为0,主机发送的的设备地址必须与
                                                //I2CSLADR相同才能访问此I2C从机设备。
                                                //主机若需要写数据则要发送5AH(0101_1010B)
                                                //主机若需要读数据则要发送5BH(0101_1011B)
    I2CSLST = 0x00;
    I2CSLCR = 0x78;                           //使能从机模式中断
    EA = 1;

    isda = 1;                                 //用户变量初始化
    isma = 1;
    addr = 0;
    I2CTXD = buffer;

    while (1);
}


zhouq 发表于 2025-3-19 09:46:31

ercircle 发表于 2025-3-18 19:41
只有这一张截图吗?操作SCL是主机代码啊,你的从机代码呢?

“等我再发寄存器地址(此时主机在发SCL)时 ...

这是我的从机代码

zhouq 发表于 2025-3-19 09:47:24

这是我的从机代码,大家看下有无问题
/* Includes ------------------------------------------------------------------*/
#include "HeaderInc.h"

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

#define ADDR_IAP_17A   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 = User_I2C_ADDR;                        

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

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

/*
        * @name   I2C_INTR()
        * @briefI2C中断函数
        * @param参数说明
        * @retval None      
*/
void I2C_INTR() interrupt 24
{               
        //收到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_17A)
                        {
                        }
                }//写寄存器
                else if(I2C_S.ReadWriteFlag == FALSE)
                {
                        if(I2C_S.RegAddr == ADDR_IAP_17A)
                        {
                                //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_17A)
                        {
                                //I2CTXD = 0;
                        }
                       
                }
    }//从机收到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;
    }
}

ercircle 发表于 2025-3-19 12:02:47

zhouq 发表于 2025-3-19 09:47
这是我的从机代码,大家看下有无问题

还是不完整,用的中断模式,没见开I2CSLCR中断。
你用例程能跑通吗?
另外sda,scl用逻辑分析仪抓包没有,什么现象呢?

zhouq 发表于 2025-3-19 13:40:00

ercircle 发表于 2025-3-19 12:02
还是不完整,用的中断模式,没见开I2CSLCR中断。
你用例程能跑通吗?
另外sda,scl用逻辑分析仪抓包没有 ...

在别的文件配置了:
        //I2C从机模式的各种中断使能
        I2CSLCR = 0x78;

还没开始验证,我猜测芯片例程应该没问题,I2CRXT被赋值后,接收到SCL时钟信号就会把数据发出去。我在STC8G1K17A上成功实现过I2C功能,但只有设备地址,没有寄存器地址的读写

ercircle 发表于 2025-3-19 13:46:37

zhouq 发表于 2025-3-19 13:40
在别的文件配置了:
        //I2C从机模式的各种中断使能
        I2CSLCR = 0x78;


注意I2CRXD 是收,I2CTXD才是发送寄存器。
页: 1 [2] 3
查看完整版本: 第1篇_8H1K17 I2C从机如何/何时发送ACK/NACK