[应用相关] stm32软件IIC

[复制链接]
186|0
观海 发表于 2025-8-12 13:49 | 显示全部楼层 |阅读模式
前言
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

您需要登录后才可以回帖 登录 | 注册

本版积分规则

160

主题

4412

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部