一、IIC简介左边位串口右边位IICSDL数据传输主机可以向从机传送数据从机也可以向主机传送SCL作为时钟线仅仅可以由主机发出核心作用是 “同步通信节奏时钟信号比如 I2C 的 SCL和数据信号比如 I2C 的 SDA是通信总线里的 “分工搭档”核心区别在作用、特性、内容这三点作用不同时钟信号是 “节拍器”负责同步通信节奏让收发双方按统一的时间规则操作比如 “什么时候读数据、什么时候发数据”。数据信号是 “内容载体”负责传递实际要通信的信息比如地址、指令、数值。特性不同时钟信号通常是固定频率的脉冲比如 I2C 的 SCL 是周期性高低电平节奏由主设备控制。数据信号电平随传输的内容变化比如要发 “1” 就输出高电平发 “0” 就输出低电平内容由通信需求决定。内容属性不同时钟信号本身不包含业务信息只是 “控制时序的工具”。数据信号承载的是实际要交互的业务信息比如传感器的测量值、芯片的配置指令。简单说时钟是 “通信节奏的指挥”数据是 “要传递的消息本身”。1. 实现双向通信 线与逻辑SDA 需要同时支持 “主设备发数据” 和 “从设备回数据 / 应答”开漏输出的高阻态特性输出 1 时 断开引脚让 SDA 既能输出低电平又能直接检测总线上的电平相当于输入模式不用频繁切换 IO 模式。而线与逻辑多个设备共享总线时只要有一个拉低 SDA总线就是低电平所有设备释放时上拉电阻拉成高电平是 I2C “多主设备仲裁”“从设备应答” 的基础。2. 避免短路 / 硬件损坏如果 SDA 用推挽输出同时能主动输出高 / 低电平当主设备输出高电平、从设备拉低 SDA 应答时会形成 “电源→引脚→地” 的直流通路导致电流过大、烧毁芯片。3. 兼容不同电压设备开漏输出的高电平由外部上拉电阻提供能同时连接 3.3V 和 5V 设备比如主设备是 3.3V MCU从设备是 5V 传感器不用额外加电平转换电路。而 SCL 可以用推挽输出因为主设备完全控制 SCL从设备不主动拉低但实际工程中也常和 SDA 一样用开漏 上拉兼容多主设备场景。二、IIC协议低电平采集高电平读取发送0xA1表示需要主机读取从机的数据三、EEPROM1、程序移植将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电平 #endif3、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; }