第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的
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
DebugLab 发表于 2025-3-27 10:18
https://www.stcaimcu.com/forum.php?mod=redirect&goto=findpost&ptid=16514&pid=155128
根据另一个帖子的 ...
好的,谢谢解答
页:
[1]