STM32G030k6t6使用IO模拟I2C与SH367305通信
我在使用IO模拟I2C与SH367305通信总是出现各种,搞了我三天,才能成功。我得记录一下,不然怎么甘心。
SH367305的官方文件,我觉得是写的不完整而且容易让人陷入误区。比如如下面的几点:
- 读和写都有CRC校验。
那么请问,这两个CRC哪一个是主机发送,哪一个是从机发的?(当然强的人,一眼就知道写是主机发CRC,读是从机发CRC,但我却搞了半天,惭愧!)。

- 读过程,需要发送两次从机地址那么请问“这两个从机地址的最后一位都是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);
}