前言
IIC(Inter-Integrated Circuit)是 IIC Bus 简称,主多从半双工总线型通讯协议。它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。本文主要介绍软件IIC的实现方法。
一、主要特性
IIC通过SDA和SCL两根信号线进行通信。
IIC通信协议支持多主多从架构,每个设备都有一个唯一的地址,主设备和从设备之间通过地址来进行通信。
IIC通信协议支持数据的双向传输,主设备可以向从设备发送数据,从设备也可以向主设备发送数据。
SDA和SCL都需要配置为上拉模式开漏输出。(如果使用推挽输出,当两个设备同时发送数据时,会导致信号冲突,严重会导致芯片短路)
IIC总线所有器件都具有自动应答功能,无论是发送和接收都会发送一个应答ACK或NACK信号。
需要全面了解IIC协议,可以查看官方文档

二、工作时序
2.1 启动信号和停止信号
在空闲情况下,SDA和SCL都是高电平:

/*******************************************************************************
* @brief IIC延时函数
* @param 无
* @retval 无
*/
static void iic_delay(void)
{
delay_us(2); /* 延时2us */
}
在开始正式代码前,需要先注意一下IIC延时函数,因为每台IIC协议的解析速率不一,所以为了保证通讯的稳定性,需要加入延时函数,延时的时间根据需要通讯的设备来确定。
::: table title=“常见频率” align=“center”

:::
2.1.1 启动信号
启动信号是由主设备发送给从设备的信号,用于通知从设备开始通信。启动信号的时序图如下:

由上图我们可以看到IIC的启动时序,我们也由此开始编写代码,通过代码可以看出,首先确定SDA和SCL都已经释放(已经拉高),然后delay防止主机速度过快,接着看是发送我们的起始信号,从图中我们看出,先将SDA从高拉到低,经过一定的时间周期以后,再将SCL拉低,由此IIC的起始信号就完成了。
/*******************************************************************************
* @brief IIC Start函数
* @param 无
* @retval 无
*/
void iic_start(void)
{
SDA(1);
SCL(1);
iic_delay();
SDA(0);
iic_delay();
SCL(0);
}
2.1.2 停止信号
停止信号是由主设备发送给从设备的信号,用于通知从设备结束通信。停止信号的时序图如下:

通过代码可以看到,首先将SDA拉低,然后将SCL拉高,最后将SDA拉高,这样就完成了IIC的停止信号。
/*******************************************************************************
* @brief IIC stop函数
* @param 无
* @retval 无
*/
void iic_stop(void)
{
SDA(0);
SCL(1);
iic_delay();
SDA(1);
}
2.2 应答信号

由于IIC在接收数据和发送数据都需要发送应答,所以我们在封装函数的时候需要考虑到这一点。
/*******************************************************************************
* @brief IIC 发送ACK
* @param ack: 0-发送ACK, 1-发送NACK
* @retval 无
*/
void iic_send_ack(uint8_t ack)
{
if (ack)
{
SDA(1); /* 发送NACK */
}
else
{
SDA(0); /* 发送ACK */
}
SCL(1);
iic_delay();
SCL(0);
}
/*******************************************************************************
* @brief IIC 接收ACK
* @param 无
* @retval 无
*/
uint8_t iic_recv_ack(void)
{
uint8_t ack = 0;
uint8_t waittime = 0;
SDA(1); /* 主机释放SDA */
iic_delay();
SCL(1); /* 拉高SCL */
iic_delay();
while(SDA_READ())
{
waittime++;
if(waittime > 250) /* 等待时间超过250us,则认为没有应答 */
{
iic_stop(); /* 停止IIC */
ack = 1; /* 返回NACK */
break;
}
}
SCL(0); /* 拉低SCL */
iic_delay();
return ack; /* 返回ACK状态 */
}
2.3 数据传输

/*******************************************************************************
* @brief IIC发送一字节
* @param 无
* @retval 无
*/
void iic_send_byte(uint8_t byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
SDA(byte & (0x80 >> i)); /* 先发送最高位 */
iic_delay();
SCL(1);
iic_delay();
SCL(0);
iic_delay();
}
SDA(1); /* 主机释放SDA */
iic_delay();
}
@tab 发送1byte
/*******************************************************************************
* @brief IIC接收一字节
* @param 无
* @retval 无
*/
uint8_t iic_recv_byte(uint8_t ack)
{
uint8_t recv = 0;
SDA(1); /* 主机释放SDA */
iic_delay();
SCL(1); /* 拉高SCL */
iic_delay();
for (uint8_t i = 0; i < 8; i++)
{
SCL(1); /* 拉高SCL */
iic_delay();
recv <<= 1; /* 左移一位 */
if (SDA_READ()) /* 读取SDA */
{
recv |= 0x01; /* 如果SDA为1,则设置最低位为1 */
}
SCL(0); /* 拉低SCL */
iic_delay();
}
iic_send_ack(ack); /* 发送ACK或NACK */
return recv;
}
三、完整代码
在移植完整代码时,对于stm平台,只需修改IIC.h中的引脚定义即可。如果是其他平台,还需根据平台的GPIO库函数修改iic_init(void)函数。
#include "./24C02_IIC.h"
/**************************************************************************************************
* @brief IIC初始化函数
* @param 无
* @retval 无
*/
void iic_init(void)
{
/* 使能SCL和SDA的时钟 */
SCL_GPIO_CLK_ENABLE();
SDA_GPIO_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_strcut;
gpio_init_strcut.Mode = GPIO_MODE_OUTPUT_OD;
gpio_init_strcut.Pin = SCL_GPIO_PIN;
gpio_init_strcut.Pull = GPIO_PULLUP;
gpio_init_strcut.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(SCL_GPIO_PORT, &gpio_init_strcut);
gpio_init_strcut.Pin = SDA_GPIO_PIN;
HAL_GPIO_Init(SDA_GPIO_PORT, &gpio_init_strcut);
SDA(1);
SCL(1);
}
/**************************************************************************************************
* @brief IIC延时函数
* @param 无
* @retval 无
*/
static void iic_delay(void)
{
delay_us(2); /* 延时2us */
}
/**************************************************************************************************
* @brief IIC Start函数
* @param 无
* @retval 无
*/
void iic_start(void)
{
SDA(1);
SCL(1);
iic_delay();
SDA(0);
iic_delay();
SCL(0);
}
/**************************************************************************************************
* @brief IIC stop函数
* @param 无
* @retval 无
*/
void iic_stop(void)
{
SDA(0);
SCL(1);
iic_delay();
SDA(1);
}
/**************************************************************************************************
* @brief IIC 发送ACK
* @param ack: 0-发送ACK, 1-发送NACK
* @retval 无
*/
void iic_send_ack(uint8_t ack)
{
if (ack)
{
SDA(1); /* 发送NACK */
}
else
{
SDA(0); /* 发送ACK */
}
SCL(1);
iic_delay();
SCL(0);
}
/**************************************************************************************************
* @brief IIC 接收ACK
* @param 无
* @retval 无
*/
uint8_t iic_recv_ack(void)
{
uint8_t ack = 0;
uint8_t waittime = 0;
SDA(1); /* 主机释放SDA */
iic_delay();
SCL(1); /* 拉高SCL */
iic_delay();
while(SDA_READ())
{
waittime++;
if(waittime > 250) /* 等待时间超过250us,则认为没有应答 */
{
iic_stop(); /* 停止IIC */
ack = 1; /* 返回NACK */
break;
}
}
SCL(0); /* 拉低SCL */
iic_delay();
return ack; /* 返回ACK状态 */
}
/**************************************************************************************************
* @brief IIC发送一字节
* @param 无
* @retval 无
*/
void iic_send_byte(uint8_t byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
SDA(byte & (0x80 >> i)); /* 先发送最高位 */
iic_delay();
SCL(1);
iic_delay();
SCL(0);
iic_delay();
}
SDA(1); /* 主机释放SDA */
iic_delay();
}
/**************************************************************************************************
* @brief IIC接收一字节
* @param 无
* @retval 无
*/
uint8_t iic_recv_byte(uint8_t ack)
{
uint8_t recv = 0;
SDA(1); /* 主机释放SDA */
iic_delay();
SCL(1); /* 拉高SCL */
iic_delay();
for (uint8_t i = 0; i < 8; i++)
{
SCL(1); /* 拉高SCL */
iic_delay();
recv <<= 1; /* 左移一位 */
if (SDA_READ()) /* 读取SDA */
{
recv |= 0x01; /* 如果SDA为1,则设置最低位为1 */
}
SCL(0); /* 拉低SCL */
iic_delay();
}
iic_send_ack(ack); /* 发送ACK或NACK */
return recv;
}
@tab IIC.h
#ifndef __24C02_IIC_h
#define __24C02_IIC_h
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
/******************************************************************************************/
/* 引脚 定义 */
#define SCL_GPIO_PORT GPIOB
#define SCL_GPIO_PIN GPIO_PIN_6
#define SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define SDA_GPIO_PORT GPIOB
#define SDA_GPIO_PIN GPIO_PIN_7
#define SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/******************************************************************************************/
/* IIC端口定义 */
#define SCL(x) do{ x ? \
HAL_GPIO_WritePin(SCL_GPIO_PORT, SCL_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(SCL_GPIO_PORT, SCL_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SCL */
#define SDA(x) do{ x ? \
HAL_GPIO_WritePin(SDA_GPIO_PORT, SDA_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(SDA_GPIO_PORT, SDA_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SDA */
#define SDA_READ() HAL_GPIO_ReadPin(SDA_GPIO_PORT, SDA_GPIO_PIN) /* 读取SDA */
/******************************************************************************************/
void iic_init(void);
void iic_start(void);
void iic_stop(void);
void iic_send_ack(uint8_t ack);
uint8_t iic_recv_ack(void);
void iic_send_byte(uint8_t byte);
uint8_t iic_recv_byte(uint8_t ack);
#endif
————————————————
版权声明:本文为CSDN博主「楠离啊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45535478/article/details/149952144
|