昨天的帖子:8H1K17的I2C无法对主机做出应答 - QSPI/3组SPI/I2S/I2C,一线制温湿度传感器 国芯技术交流网站 - AI32位8051交流社区
继昨天I2C从机无应答,我使用了两块8H1K17,一块做主机,一块做从机,使用ISP中的例程,后用逻辑分析仪抓包,发现主从机通讯正常,从机可以应答。
于是我保留从机的板子,换成另外一个MCU作为主机,,情况依旧是从机无应答,下面是我的代码:
- /**
- ******************************************************************************
- * @file I2C_Soft_M.c
- * @author zhouq
- * @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
- * @brief I2C GPIO初始化
- * @param None
- * @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
- * @brief I2C初始化
- * @param None
- * @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
- * @brief I2C起始信号
- * @param None
- * @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
- * @brief I2C停止信号
- * @param None
- * @retval None
- */
- static void I2C_Stop(void)
- {
- //SCL为高电平,SDA的上升沿为I2C停止信号
- SDA_CLR;
- SCL_SET;
-
- Public.Delay_us(20);
- SDA_SET;
- }
-
- /*
- * @name I2C_Write_Byte
- * @brief I2C写字节
- * @param WR_Byte -> 待写入数据
- * @retval ACK_Value_t -> 从机应答值
- */
- ACK_Value_t I2C_Write_Byte(uint8_t WR_Byte)
- {
- uint8_t i;
- ACK_Value_t ack_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
- * @brief I2C读字节
- * @param ACK_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;
- }
-
- /**
- * @brief I2C主机写一帧数据
- * @param dev_addr: 从机地址
- * @param reg_addr: 寄存器地址
- * @param send_data: 数据缓冲区指针
- * @param length: 数据长度
- * @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[i]);
- if (ack != I2C_ACK) {
- I2C_Stop();
- return ERROR_Data; // 失败
- }
- }
-
- I2C_Stop(); // 发送停止信号
- return HAL_OK; // 成功
- }
-
- /**
- * @brief I2C主机读一帧数据
- * @param dev_addr: 从机地址
- * @param reg_addr: 寄存器地址
- * @param recv_data: 数据缓冲区指针,用于存储读取的数据
- * @param length: 要读取的数据长度
- * @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[i] = I2C_Read_Byte(I2C_NACK);
- } else {
- // 其他字节发送ACK
- recv_data[i] = I2C_Read_Byte(I2C_ACK);
- }
- }
-
- I2C_Stop(); // 发送停止信号
- return HAL_OK; // 成功
- }
-
- /********************************************************
- End Of File
- ********************************************************/
复制代码
我的代码我看不出问题,另外,不知道作为从机,对I2C通讯的速率是否有要求,如果有,得是多少?目前我的速率是小于400K的
|