STM32G030k6t6使用IO模拟I2C与SH367305通信。

发布于:2025-05-27 ⋅ 阅读:(132) ⋅ 点赞:(0)

STM32G030k6t6使用IO模拟I2C与SH367305通信

我在使用IO模拟I2C与SH367305通信总是出现各种,搞了我三天,才能成功。我得记录一下,不然怎么甘心。

SH367305的官方文件,我觉得是写的不完整而且容易让人陷入误区。比如如下面的几点:

  1. 读和写都有CRC校验。
    那么请问,这两个CRC哪一个是主机发送,哪一个是从机发的?(当然强的人,一眼就知道写是主机发CRC,读是从机发CRC,但我却搞了半天,惭愧!)。
    在这里插入图片描述
  2. 读过程,需要发送两次从机地址那么请问“这两个从机地址的最后一位都是1(读)吗?”
    这里是我觉得折磨人的地方,第一个是0(写),第二才是1(读)。但是官方文档这里却没有特别说明,难道是I2C本身就是这么规定的?乖乖,一查还真是I2C本身就是这么规定的,我去。只能说还是要仔细看官方的文档啊。惭愧!
    在这里插入图片描述
    官方的说明:在这里插入图片描述
    以下是我的代码。
/**
  * @brief  The slave sends data, and the host receives the data.
  * @retval int
  */
void I2C_SDA_IN_Config(void) 	
{  
	LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	GPIO_InitStruct.Pin = SIMULATE_I2C_SDA_PIN;
	GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
	GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
	GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
	LL_GPIO_Init(SIMULATE_I2C_PORT, &GPIO_InitStruct);
} 


/**
  * @brief  The host sends data, and the slave receives data.
  * @retval int
  */
void I2C_SDA_OUT_Config(void)
{
	LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

	GPIO_InitStruct.Pin = SIMULATE_I2C_SDA_PIN;
	GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
	GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
	GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
	LL_GPIO_Init(SIMULATE_I2C_PORT, &GPIO_InitStruct);
	
}


/**
  * @brief I2C的SDA输出高/低电平
  *
  * @param Data  :  电平状态.
  *
  * @retval None
  */
void I2C_SDA_Out(uint8_t Data)
{
	if(Data == 1)
	{
	    /* 输出高 */
		LL_GPIO_SetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SDA_PIN);		
	}else 
	{
	  	/* 输出低 */
		LL_GPIO_ResetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SDA_PIN); 	
	}
}


/**
  * @brief I2C的SCL输出高/低电平
  *
  * @param Data  :  电平状态.
  *
  * @retval None
  */
void I2C_SCL_Out(uint8_t Data)
{
	if(Data == 1)
	{
		/* 输出高 */
		LL_GPIO_SetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SCL_PIN);	
	}else 
	{
		/* 输出低 */
		LL_GPIO_ResetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SCL_PIN);
	}
}


/**
  * @brief I2C的读取SDA数据
  *
  * @param None.
  *
  * @retval None
  */
uint8_t I2C_READ_SDA(void)
{
	return LL_GPIO_IsInputPinSet(SIMULATE_I2C_PORT, SIMULATE_I2C_SDA_PIN);
}


/**
  * @brief I2C的读取SCL状态
  *
  * @param None.
  *
  * @retval None
  */
uint8_t I2C_READ_SCL(void)
{
	return LL_GPIO_IsInputPinSet(SIMULATE_I2C_PORT, SIMULATE_I2C_SCL_PIN);
}



/**
  * @brief 产生I2C起始信号
  *
  * @param None.
  *
  * @retval None
  */
void I2C_Start(void)
{

	
	I2C_SDA_OUT_Config();
	
	I2C_SCL_Out(1); 		// 将时钟线拉高

	Delay_us(DELAY_TIME);
	I2C_SDA_Out(1); 		// 数据线高电平

    Delay_us(DELAY_TIME);

    I2C_SDA_Out(0); 		// 在时钟线为高电平时,将数据线拉低,产生启动信号

    Delay_us(DELAY_TIME);
	I2C_SCL_Out(0); 		// 

	Delay_us(DELAY_TIME);
}


/**
  * @brief 产生I2C停止信号
  *
  * @param None.
  *
  * @retval None
  */
void I2C_Stop(void)
{
	I2C_SCL_Out(1); 
	Delay_us(DELAY_TIME);
	I2C_SDA_Out(0); 		// 确保数据线在时钟线拉高之前为低电平
	Delay_us(DELAY_TIME);
    I2C_SDA_Out(1); 		// 将数据线拉高,产生停止信号
    Delay_us(DELAY_TIME);
}


/**
  * @brief 发送ACK或NACK
  *
  * @param ack:  ack = 1发送NACK  或 ack = 0发送ACK信号
  *
  * @retval None
  */
void I2C_Send_ACK_or_NACK(uint8_t ack)
{
	I2C_SCL_Out(0);
	
	Delay_us(DELAY_TIME);
	
	I2C_SDA_Out(ack);
	Delay_us(DELAY_TIME);
	I2C_SCL_Out(1);
	Delay_us(DELAY_TIME);
	I2C_SCL_Out(0);

	
}


/**
  * @brief 等待应答信号到来
  *
  * @param None.
  *
  * @retval Write in results
  *          		STATUS_OK
  *          		STATUS_FAIL
  */
uint8_t I2C_Wait_Ack(void)
{
	uint16_t timeout = 0;
	uint16_t max_trycny = 250;
	uint8_t temp = 0;
	uint8_t ack_nack = I2C_SUCCESS;

	I2C_SDA_IN_Config();

	// 等待从机ACK(超时处理)
	temp = I2C_READ_SDA();
	while(temp)
	{
		timeout++;
		Delay_us(1);
		temp = I2C_READ_SDA();
		if(timeout > max_trycny)
		{
			ack_nack = I2C_NACK_RECEIVED;
			break;
		}
	}

	Delay_us(DELAY_TIME);
	I2C_SCL_Out(1);
	
	Delay_us(DELAY_TIME);
	temp = I2C_READ_SDA();
	if(temp)
	{
		ack_nack = I2C_NACK_RECEIVED;
	}else
	{	
		ack_nack = I2C_SUCCESS;
	}
	Delay_us(DELAY_TIME);
	I2C_SCL_Out(0);
	Delay_us(DELAY_TIME);

	I2C_SDA_OUT_Config();
	return ack_nack;
}

	
/**
  * @brief I2C发送一个字节
  *
  * @param Data 	:数据.
  *
  * @retval ack
  *          		STATUS_OK
  *          		STATUS_FAIL
  */
uint8_t I2C_Send_Byte(uint8_t data)
{     
	
	for (uint8_t i = 0; i < 8; i++)
	{
		I2C_SCL_Out(0);
		Delay_us(DELAY_TIME);
		if(data & 0x80)
		{
			I2C_SDA_Out(1);
		}else
		{
			I2C_SDA_Out(0);
		}
		data <<= 1;
		Delay_us(DELAY_TIME);
		I2C_SCL_Out(1);		
		Delay_us(DELAY_TIME);
	}

	I2C_SCL_Out(0);
	Delay_us(DELAY_TIME);

	I2C_SDA_Out(1);			// 释放SDA,让从机操作
	Delay_us(DELAY_TIME);
	
	return I2C_Wait_Ack();
}


/**
  * @brief 读1个字节, 
  *
  * @param None
  *
  * @retval data	: 接收结果
  */
uint8_t I2C_Receive_Byte(void)
{
	uint8_t data = 0;

	I2C_SDA_IN_Config();
	
	for (uint8_t i = 0; i < 8; i++)
	{
		I2C_SCL_Out(0);
		Delay_us(DELAY_TIME);
		I2C_SCL_Out(1);
		Delay_us(DELAY_TIME);
		data |= (I2C_READ_SDA() << (7 - i));
	}

	
	//读完一个字节,将SDA重新设置为输出
	I2C_SDA_OUT_Config();
	return data;
}


/**
  * @brief I2C传输写函数 
  *
  * @param [in] slave_address    	:   从机地址
  * @param [in] reg    				:   寄存器地址
  * @param [in] data    			:   数据指针
  * @param [in] length    			:   数据长度
  *
  * @retval 通信结果
  *              			I2C_SUCCESS
  * 					    I2C_FAILURE
  * 						I2C_NACK_RECEIVED
  * 						I2C_TIMEOU
  */
uint8_t I2C_Transfer_Write(uint8_t slave_address, uint8_t reg, uint8_t *data, uint8_t length)
{
	// 计算CRC8校验值(包括从机地址、寄存器地址和数据)
	uint8_t crc_data[length + 1];

	uint8_t crc = 0;
	
	crc_data[0] = slave_address;
	crc_data[1] = reg;
	crc_data[2] = *data;
	
	I2C_Start();
	
	// 发送从机地址和方向
	if(I2C_Send_Byte(slave_address | 0x00) != I2C_SUCCESS)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	// 发送寄存器地址
	if(I2C_Send_Byte(reg) != I2C_SUCCESS)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	// 写数据
	if(I2C_Send_Byte(*data) != I2C_SUCCESS)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}
	

	// 发送CRC校验
	crc = Caclute_Crc8(crc_data, 3);
	if(I2C_Send_Byte(crc) != I2C_SUCCESS)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	I2C_Stop();
	return I2C_SUCCESS;
}


/**
  * @brief I2C传输读函数 
  *
  * @param [in] slave_address    	:   从机地址
  * @param [in] reg    				:   寄存器地址
  * @param [in] data    			:   数据指针
  * @param [in] length    			:   数据长度
  *
  * @retval 通信结果
  *              			I2C_SUCCESS
  * 					    I2C_FAILURE
  * 						I2C_NACK_RECEIVED
  * 						I2C_TIMEOU
  */
uint8_t I2C_Transfer_Read(uint8_t slave_address, uint8_t reg, uint8_t *data, uint8_t length)
{
	uint8_t i = 0;
    uint8_t temp_data[length + 4];
	
	I2C_Start();
	
	// 发送从机地址和方向
	if(I2C_Send_Byte(slave_address) != I2C_SUCCESS)	// 这里依然是写
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	// 发送寄存器地址
	if (I2C_Send_Byte(reg) != I2C_SUCCESS)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}
	
	// 重新启动
	I2C_Start();

	// 发送从机地址和读方向
	if (I2C_Send_Byte(slave_address | 0x01) != I2C_SUCCESS)	// 这里才是读
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	temp_data[0] = slave_address;
	temp_data[1] = reg;
	temp_data[2] = slave_address|0x01;
	
	// 读取数据
	for(i = 0; i < length + 1; i++)
	{
		temp_data[3 + i] = I2C_Receive_Byte();
		if (i == length)
		{
			// 最后一个字节发送NACK
			I2C_Send_ACK_or_NACK(1);
		}else
		{
			// 中间字节发送ACK
			I2C_Send_ACK_or_NACK(0);
		}
	}

	// 检查CRC校验值
	uint8_t received_crc = temp_data[3 + length];
	
	uint8_t calculated_crc = Caclute_Crc8(temp_data, 3 + length);

	if (received_crc != calculated_crc)
	{
		I2C_Stop();
		return I2C_FAILURE;
	}

	// 数据有效,复制到用户缓冲区
	for (i = 0; i < length; i++)
	{
		data[i] = temp_data[i + 3];  // 跳过寄存器地址
	}

	I2C_Stop();
	return I2C_SUCCESS;
}


/**
  * @brief I2C传输函数 
  *
  * @param [in] slave_address    	:   从机地址
  * @param [in] reg    				:   寄存器地址
  * @param [in] data    			:   数据指针
  * @param [in] length    			:   数据长度
  * @param [in] read    			:   1(读取)
  *
  * @retval 通信结果
  *              			I2C_SUCCESS
  * 					    I2C_FAILURE
  * 						I2C_NACK_RECEIVED
  * 						I2C_TIMEOU
  */
uint8_t I2C_Transfer(uint8_t slave_address, uint8_t reg, uint8_t *data, uint8_t length, uint8_t read)
{
	uint8_t retry_cnt = 3;
	uint8_t res = 0;
	
	if(data == NULL)
	{
			I2C_Stop();
			return I2C_FAILURE; 
	}
	
	if(!read)
	{
		/* 发送数据(写操作) */
		while(retry_cnt)
		{
			res = I2C_Transfer_Write(slave_address, reg, data, length);
			if(res == I2C_SUCCESS)
			{
				break;
			}else
			{
				retry_cnt--;
			}
		}
	}else
	{
		/* 读取 */
		while(retry_cnt)
		{
			res = I2C_Transfer_Read(slave_address, reg, data, length);
			if(res == I2C_SUCCESS)
			{
				break;
			}else
			{
				retry_cnt--;
			}
		}
	}
	
	I2C_Stop();
	if(res != I2C_SUCCESS)
	{
		return I2C_FAILURE;
	}
	return I2C_SUCCESS;
}

 
/**
  * @brief I2C多字节写操作
  *
  * @param [in] slave_address    	:   从机地址
  * @param [in] reg    				:   寄存器地址
  * @param [in] data    			:   数据指针
  * @param [in] length    			:   数据长度
  *
  * @retval None
  */
uint8_t I2C_Write_Multi(uint8_t slave_address, uint8_t reg, uint8_t *data, uint8_t length)
{
	uint8_t result = I2C_Transfer(slave_address, reg, data, length, 0);
	if (result != I2C_SUCCESS)
	{
		// 处理错误
	}
	return result;
}


/**
  * @brief I2C多字节读操作
  *
  * @param [in] slave_address    	:   从机地址
  * @param [in] reg    				:   寄存器地址
  * @param [in] data    			:   数据指针
  * @param [in] length    			:   数据长度
  *
  * @retval 通信结果
  *              			I2C_SUCCESS
  * 					    I2C_FAILURE
  * 						I2C_NACK_RECEIVED
  * 						I2C_TIMEOU
  */
uint8_t I2C_Read_Multi(uint8_t slave_address, uint8_t reg, uint8_t *data, uint8_t length)
{
	uint8_t result = I2C_Transfer(slave_address, reg, data, length, 1);
	
	return result; 
}


/**
  * @brief  Initialize IO simulation I2C.
  * @retval int
  */
void Init_I2C_IO(void)
{
  LL_GPIO_InitTypeDef  GPIO_InitStruct = {0};
  /* -1- Enable GPIO Clock (to be able to program the configuration registers) */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);

  /* -2- Configure IO in output push-pull mode */
  GPIO_InitStruct.Pin = SIMULATE_I2C_SCL_PIN;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(SIMULATE_I2C_PORT, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = SIMULATE_I2C_SDA_PIN;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_MEDIUM;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(SIMULATE_I2C_PORT, &GPIO_InitStruct);

  LL_GPIO_SetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SCL_PIN);
  LL_GPIO_SetOutputPin(SIMULATE_I2C_PORT, SIMULATE_I2C_SDA_PIN);
}


网站公告


今日签到

点亮在社区的每一天
去签到