第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和中断
我尝试过多种调试,但没能找到问题,可能是只缘身在此山中吧。
针对您描述的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的技术支持团队。
[本文内容由国芯人工智能辅助生成,仅供参考] 从机面对主机的读写,我需要自己手动设置ACK和NACK吗? 【新提醒】部分较难问题需要帮忙分析的,可跟贴放上原帖链接通知我 | 新问题另外开贴 - 第12页 - C语言,汇编语言,Proteus MCU软件仿真 国芯技术交流网站 - AI32位8051交流社区https://www.stcaimcu.com/forum.php?mod=viewthread&tid=12065&extra=&page=12关注下这个贴114楼 补充:
我另外还测试了官方例程,和我写的代码一样的情况
主机和从机的SDA SCL均配置为开漏输出,SDA SCL通过5.1K电阻上拉到3.3V 补充,已排除焊接不良、芯片损坏的情况 使用官方I2C从机例程仍然不正常
建议先排查一下主机的问题
可以使用我写的这个程序下载到一箭双雕上扫描一下所有从机,看能不能扫描到:
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=9938
DebugLab 发表于 2025-3-25 17:32
使用官方I2C从机例程仍然不正常
建议先排查一下主机的问题
可以使用我写的这个程序下载到一箭双雕上扫描一 ...
我没有您提到的“一箭双雕”,我主机的代码和逻辑分析仪抓取的信号都在帖子里 该从机拉低的时候无法拉低,怀疑主机是推挽输出了
DebugLab 发表于 2025-3-27 10:17
该从机拉低的时候无法拉低,怀疑主机是推挽输出了
可以看我的配置,主机配置为开漏输出
页:
[1]