STM32--IIC

一、IIC简介

左边位串口,右边位IIC

SDL数据传输主机可以向从机传送数据,从机也可以向主机传送,SCL作为时钟线,仅仅可以由主机发出,核心作用是 "同步通信节奏

时钟信号(比如 I2C 的 SCL)和数据信号(比如 I2C 的 SDA)是通信总线里的 "分工搭档",核心区别在作用、特性、内容这三点:

  1. 作用不同

    • 时钟信号:是 "节拍器",负责同步通信节奏,让收发双方按统一的时间规则操作(比如 "什么时候读数据、什么时候发数据")。

    • 数据信号:是 "内容载体",负责传递实际要通信的信息(比如地址、指令、数值)。

  2. 特性不同

    • 时钟信号:通常是固定频率的脉冲(比如 I2C 的 SCL 是周期性高低电平),节奏由主设备控制。

    • 数据信号:电平随传输的内容变化(比如要发 "1" 就输出高电平,发 "0" 就输出低电平),内容由通信需求决定。

  3. 内容属性不同

    • 时钟信号:本身不包含业务信息,只是 "控制时序的工具"。

    • 数据信号:承载的是实际要交互的业务信息(比如传感器的测量值、芯片的配置指令)。

简单说:时钟是 "通信节奏的指挥",数据是 "要传递的消息本身"。

  1. 实现双向通信 + 线与逻辑

SDA 需要同时支持 "主设备发数据" 和 "从设备回数据 / 应答",开漏输出的高阻态特性(输出 1 时 = 断开引脚),让 SDA 既能输出低电平,又能直接检测总线上的电平(相当于输入模式),不用频繁切换 IO 模式。

线与逻辑(多个设备共享总线时,只要有一个拉低 SDA,总线就是低电平;所有设备释放时,上拉电阻拉成高电平)是 I2C "多主设备仲裁""从设备应答" 的基础。

  1. 避免短路 / 硬件损坏

如果 SDA 用推挽输出(同时能主动输出高 / 低电平),当主设备输出高电平、从设备拉低 SDA 应答时,会形成 "电源→引脚→地" 的直流通路,导致电流过大、烧毁芯片。

  1. 兼容不同电压设备

开漏输出的高电平由外部上拉电阻提供,能同时连接 3.3V 和 5V 设备(比如主设备是 3.3V MCU,从设备是 5V 传感器),不用额外加电平转换电路。

而 SCL 可以用推挽输出(因为主设备完全控制 SCL,从设备不主动拉低),但实际工程中也常和 SDA 一样用开漏 + 上拉(兼容多主设备场景)。

二、IIC协议

低电平采集,高电平读取

发送0xA1表示需要主机读取从机的数据

三、EEPROM

1、程序移植

将LED文件复制,重命名为IIC

添加myiic.c文件

先将头文件修改

2、修改头文件(maiic.h)

使用宏定义方便移植

复制代码
#ifndef _MYIIC_H
#define _MYIIC_H
#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define IIC_SCL_GPIO_PORT                  GPIOA
#define IIC_SCL_GPIO_PIN                   GPIO_PIN_2
#define IIC_SCL_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)             /* PA口时钟使能 */

#define IIC_SDA_GPIO_PORT                  GPIOA
#define IIC_SDA_GPIO_PIN                   GPIO_PIN_3
#define IIC_SDA_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)  


/******************************************************************************************/
/* LED端口定义 */
#define IIC_SCL(x)   do{ x ? \
                      HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN , GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN , GPIO_PIN_RESET); \
                  }while(0)      /* LED0翻转 */

#define IIC_SDA(x)   do{ x ? \
                      HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN , GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN , GPIO_PIN_RESET); \
                  }while(0)      /* LED1翻转 */

#define IIC_READ__SDA(x)  HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN)   //读取SDA电平

#endif

3、IIC引脚初始化及程序编写(maiic.c)

复制代码
#include "myiic.h"
#include "./SYSTEM/delay/delay.h"

/**
 * @brief       I2C总线初始化函数
 * @note        初始化SCL和SDA引脚为GPIO输出模式,SCL推挽输出,SDA开漏输出,默认上拉
 *              初始化完成后,SCL和SDA均置为高电平,进入I2C空闲状态
 * @param       无
 * @retval      无
 */
void iic_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;  // GPIO初始化结构体
    
    IIC_SCL_GPIO_CLK_ENABLE();          /* 使能SCL引脚对应的GPIO端口时钟 */
    IIC_SDA_GPIO_CLK_ENABLE();          /* 使能SDA引脚对应的GPIO端口时钟 */

    /* 配置SCL引脚:推挽输出 + 上拉 */
    gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;        /* 指定SCL引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;    /* 推挽输出模式(I2C时钟线为主机控制,可用推挽) */
    gpio_init_struct.Pull = GPIO_PULLUP;            /* 上拉电阻使能,保证空闲时为高电平 */ 
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);  /* 初始化SCL引脚 */
	
    /* 配置SDA引脚:开漏输出 + 上拉 */
    gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;        /* 指定SDA引脚 */
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;    /* 开漏输出模式(I2C数据线需要双向通信,必须开漏) */
    gpio_init_struct.Pull = GPIO_PULLUP;            /* 上拉电阻使能,保证空闲时为高电平 */ 
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);  /* 初始化SDA引脚 */
	  
    IIC_SCL(1);  /* SCL置高,空闲状态 */
    IIC_SDA(1);  /* SDA置高,空闲状态 */
}

/**
 * @brief       I2C总线延时函数
 * @note        用于保证I2C通信时序的稳定性,延时2us(根据实际系统主频调整)
 * @param       无
 * @retval      无
 */
void iic_delay(void)
{
    delay_us(2);  /* 调用微秒级延时函数,延时2us */
}

/**
 * @brief       发送I2C起始信号
 * @note        起始信号时序:SCL高电平时,SDA从高电平拉低
 * @param       无
 * @retval      无
 */
void iic_start(void)
{
    IIC_SCL(1);  /* 先将SCL置高 */
    IIC_SDA(1);  /* 先将SDA置高,保证总线处于空闲状态 */
    iic_delay(); /* 延时稳定电平 */
    IIC_SDA(0);  /* SCL高电平时,SDA拉低,产生起始信号 */
    iic_delay(); /* 延时保证信号稳定 */
    IIC_SCL(0);  /* 拉低SCL,准备发送/接收数据 */
    iic_delay(); /* 延时稳定 */
}

/**
 * @brief       发送I2C停止信号
 * @note        停止信号时序:SCL高电平时,SDA从低电平拉高
 * @param       无
 * @retval      无
 */
void iic_stop(void)
{
    IIC_SDA(0);  /* 先将SDA置低 */
    iic_delay(); /* 延时稳定电平 */
    IIC_SCL(1);  /* 拉高SCL */
    iic_delay(); /* 延时保证信号稳定 */
    IIC_SDA(1);  /* SCL高电平时,SDA拉高,产生停止信号 */
    iic_delay(); /* 延时稳定 */
}

/**
 * @brief       发送I2C应答信号(ACK)
 * @note        应答信号时序:SCL高电平时,SDA保持低电平
 * @param       无
 * @retval      无
 */
void iic_ack(void)
{
    IIC_SDA(0);  /* SDA置低,表示应答 */
    iic_delay(); /* 延时稳定电平 */
    IIC_SCL(1);  /* 拉高SCL,让从机检测应答信号 */
    iic_delay(); /* 延时保证从机检测到 */
    IIC_SCL(0);  /* 拉低SCL,结束应答 */
    iic_delay(); /* 延时稳定 */
    IIC_SDA(1);  /* 释放SDA总线 */
    iic_delay(); /* 延时稳定 */
}

/**
 * @brief       发送I2C非应答信号(NACK)
 * @note        非应答信号时序:SCL高电平时,SDA保持高电平
 * @param       无
 * @retval      无
 */
void iic_nack(void)
{
    IIC_SDA(1);  /* SDA置高,表示非应答 */
    iic_delay(); /* 延时稳定电平 */
    IIC_SCL(1);  /* 拉高SCL,让从机检测非应答信号 */
    iic_delay(); /* 延时保证从机检测到 */
    IIC_SCL(0);  /* 拉低SCL,结束非应答 */
    iic_delay(); /* 延时稳定 */
}

/**
 * @brief       等待从机的应答信号
 * @note        主机释放SDA,等待从机拉低SDA产生应答,超时则返回错误
 * @param       无
 * @retval      0: 接收到应答(ACK)  1: 未接收到应答/超时(NACK)
 */
uint8_t iic_wait_ack()
{
    uint8_t rack = 0;     /* 应答状态标志:0=成功,1=失败 */
    uint8_t waittime = 0; /* 超时计数变量 */
    
    IIC_SDA(1);           /* 主机释放SDA总线,由从机控制 */
    iic_delay();          /* 延时稳定电平 */
    IIC_SCL(1);           /* 拉高SCL,等待从机应答 */
    iic_delay();          /* 延时稳定 */
    
    /* 循环检测SDA电平,判断是否收到应答 */
    while(IIC_READ_SDA)   /* 若SDA为高,表示未收到应答,进入循环等待 */
    {
        waittime++;       /* 超时计数+1 */
        if(waittime > 250)/* 超时判断(可根据实际系统调整) */
        {
            iic_stop();   /* 超时后发送停止信号,终止通信 */
            rack = 1;     /* 标记应答失败 */
        }
        break;            /* 注:此处break会导致仅检测1次,建议移到if内部 */
    }
    
    IIC_SCL(0);  /* 拉低SCL,结束应答检测 */
    iic_delay(); /* 延时稳定 */
    return rack; /* 返回应答状态 */
}

/**
 * @brief       向I2C总线发送1个字节数据
 * @note        数据高位先行,逐位发送,SCL时钟同步
 * @param       data: 要发送的8位数据
 * @retval      无
 */
void iic_send_byte(uint8_t data)
{
    /* 循环发送8位数据(I2C为8位通信) */
    for(uint8_t i = 0; i < 8; i++)
    {
        /* 发送当前最高位:(data & 0X80)取出最高位,右移7位转为0/1 */
        IIC_SDA((data & 0X80) >> 7);
        iic_delay();       /* 延时稳定电平 */
        IIC_SCL(1);        /* 拉高SCL,让从机读取当前位 */
        iic_delay();       /* 延时保证从机读取到 */
        IIC_SCL(0);        /* 拉低SCL,准备发送下一位 */
        iic_delay();       /* 延时稳定 */
        data <<= 1;        /* 数据左移1位,将次高位变为最高位 */
    }
    IIC_SDA(1);  /* 发送完成后释放SDA总线 */
    iic_delay(); /* 延时稳定 */
}

/**
 * @brief       从I2C总线读取1个字节数据
 * @note        数据高位先行,逐位读取,读取完成后发送ACK/NACK
 * @param       ack: 读取完成后是否发送应答  0: 发送NACK  1: 发送ACK
 * @retval      读取到的8位数据
 */
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t receive = 0;  /* 存储读取到的字节数据,初始化为0 */
    
    /* 循环读取8位数据(I2C为8位通信) */
    for(uint8_t i = 0; i < 8; i++)
    {
        receive <<= 1;    /* 数据左移1位,空出最低位,准备接收新位 */
        IIC_SDA(1);       /* 主机释放SDA总线,由从机控制 */
        iic_delay();      /* 延时稳定电平 */
        IIC_SCL(1);       /* 拉高SCL,从机输出当前位数据到SDA */
        iic_delay();      /* 延时保证数据稳定 */
        
        /* 读取SDA当前电平:
         * - 若SDA为高电平(1),将receive最低位设为1
         * - 若SDA为低电平(0),receive最低位保持0(左移后默认0)
         */
        if(IIC_READ_SDA)
        {
            receive |= 1;
        }
        
        IIC_SCL(0);       /* 拉低SCL,从机准备下一位数据 */
        iic_delay();      /* 延时稳定 */
    }
    
    /* 根据参数决定发送ACK或NACK */
    if(!ack)
    {
        iic_nack(); /* 发送非应答,表示后续无数据读取 */
    }
    else
    {
        iic_ack();  /* 发送应答,表示继续读取下一个字节 */
    }
    
    return receive; /* 返回读取到的完整字节数据 */
}

注释:上述代码由IIC协议编写,注释用AI生成

在头文件中进行声明

四、实现24C02读写函数

头文件"atc_24c02.h"

复制代码
#ifndef _ATC_24C02_H
#define _ATC_24C02_H
#include "./SYSTEM/sys/sys.h"
#include "myiic.h"
 
 
void atk_24c02_init(void);
void atk_24cxx_write_one_byte(uint16_t addr, uint8_t data);
uint8_t atk_24cxx_read_one_byte(uint16_t addr);


#endif

源文件"atc_24c02.c"

复制代码
/**
 ****************************************************************************************************
 * @file        led.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2020-04-17
 * @brief       LED 驱动代码
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 MiniSTM32 V4开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 * 修改说明
 * V1.0 20200417
 * 第一次发布
 *
 ****************************************************************************************************
 */
 
#include "myiic.h"
#include "atc_24c02.h"
#include "./SYSTEM/delay/delay.h"

/**
 * @brief       初始化LED相关IO口, 并使能时钟
 * @param       无
 * @retval      无
 */
void atk_24c02_init(void)
{
     iic_init();
}


void atk_24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
    iic_start();        /* 发送起始信号 */
    iic_send_byte(0XA0);/* 发送器件 0XA0 */
    iic_wait_ack();
    iic_send_byte(addr);/* 发送地址 */
    iic_wait_ack();
    iic_send_byte(data);/* 发送1字节 */
    iic_wait_ack();
    iic_stop();         /* 产生一个停止条件 */
    delay_ms(10);       /* EEPROM 写入比较慢 */
}



uint8_t atk_24cxx_read_one_byte(uint16_t addr)
{
    uint8_t temp = 0;
    iic_start();                     /* 发送起始信号 */
    iic_send_byte(0XA0);             /* 发送器件 0XA0 */
    iic_wait_ack();                  /* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(addr);             /* 发送低位地址 */
    iic_wait_ack();                  /* 等待ACK,此时地址发送完成了 */
    iic_start();                     /* 重新发送起始信号 */
    iic_send_byte(0XA1);             /* 进入接收模式,IIC规定最低位是0,表示读取 */
    iic_wait_ack();                  /* 每次发送完一个字节,都要等待ACK */
    temp = iic_read_byte(0);         /* 接收一个字节数据 */
    iic_stop();                      /* 产生一个停止条件 */
    return temp;
}
相关推荐
松涛和鸣6 小时前
55、ARM与IMX6ULL入门
c语言·arm开发·数据库·单片机·sqlite·html
代码游侠6 小时前
学习笔笔记——ARM 嵌入式系统与内核架构
arm开发·笔记·嵌入式硬件·学习·架构
国科安芯6 小时前
AS32X601的I2C模块操作EEPROM详解
stm32·单片机·嵌入式硬件·架构·安全威胁分析·安全性测试
xu_wenming7 小时前
沁恒MCU 和乐鑫ESP32MCU选型对比
单片机·嵌入式硬件
蓁蓁啊7 小时前
解决 GCC 工具链自动链接 libg.a 导致的链接失败问题
linux·服务器·前端·arm开发·嵌入式硬件
超级码农ProMax7 小时前
stm32——使用位图自定义内存池
stm32·单片机·嵌入式硬件
vsropy8 小时前
基于HAL库的STM32工程模板
stm32·单片机·嵌入式硬件
LCMICRO-133108477468 小时前
长芯微LD8574完全P2P替代PCF8574,主要用于扩展通用输入输出端口(GPIO)
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发
时空自由民.9 小时前
I3C协议介绍
汇编·stm32·单片机