ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

零基础STM32入门教程--12-I2C 串行通迅

零基础STM32入门教程--12-I2C 串行通迅 专栏STM32F103入门教程环境Keil‑MDK STM32F103 标准库3.5版本一、I2CI2C 全称 Inter‑Integrated Circuit集成电路总线由 NXP原飞利浦在 1980 年代开发只需要 SDA数据线 和 SCL时钟线 两根信号线即可完成通信。广泛用于单片机连接各类低速外设传感器、EEPROM、OLED、MPU6050、RTC 时钟芯片等。I2C 的主要特点两线制只有 SDA 和 SCL节省引脚和 PCB 布线空间。同步通信数据传输由主设备产生的 SCL 时钟同步属于同步串行通信。半双工SDA 一根线承担收发某一时刻只能单向传输。主从式通信每个通信周期由主设备发起从设备被动响应支持一主多从、多主机系统。地址寻址每个从设备有唯一地址主设备通过发送从设备地址来选择通信对象标准地址为 7 位扩展地址为 10 位。速率等级标准模式100 kbit/s兼容性最好调试优先用这个。快速模式400 kbit/sI2C 不适合长距离走线主要用于板内芯片之间通信。核心通信机制主从模式主机 (Master)一般是单片机产生 SCL 时钟、发起通信。从机 (Slave)传感器、EEPROM 等外设每个从机拥有唯一 7bit 设备地址主机发送地址选中总线上要对话的设备其余设备忽略总线数据。注意代码中地址经常需要左移 1 位最低位作为读 / 写控制位。时序信号起始信号 STARTSCL 为高时SDA 由高变低通信开始。停止信号 STOPSCL 为高时SDA 由低变高结束通信。应答无应答 ACK/NACK每传输完 1 字节8bit高位先发接收方在第 9 个时钟拉低 SDA ACK 应答成功保持高电平 NACK代表无应答、设备未响应。通信流程主机发送起始 → 发送从机地址 读写位 → 等待从机 ACK → 收发数据每字节后等待 ACK → 发送停止信号结束。两种实现方式单片机硬件 I2C片上外设硬件控制器由硬件产生时序CPU 负担小。软件模拟 I2C普通 GPIO 引脚软件模拟 SCL/SDA 时序。优点兼容性极强不受硬件外设 bug 影响缺点占用 CPU 时间不适合 RTOS 高抢占场景。二、AT24C02AT24C02 是I2C 接口的串行 EEPROM非常常用的掉电非易失存储器单片机项目用来保存参数、配置、掉电需要保存的数据阈值、计数、设备地址等。02 代表容量2Kbit 256 字节地址范围0‑255。1、基础参数容量2Kbit256 Bytes接口标准 I2C支持 100kHz /400kHz供电1.8V‑5.5V3.3V、5V 单片机都可以直接用写入寿命10 万次擦写数据保存时间100 年器件 I2C 从机地址0b1010 A2 A1 A0A2/A1/A0 是芯片硬件引脚用来修改设备地址一条 I2C 总线最多挂载 8 片 AT24C02。引脚全部悬空 / 接 GND 时7 位地址0x50。⚠️代码注意I2C 发送时地址需要左移一位所以写地址是0xA0读地址0xA1。2、引脚定义A0、A1、A2地址引脚接 GND/VCC修改芯片 I2C 设备地址。SCLI2C 时钟SDAI2C 数据WP写保护引脚WP 接 GND可以正常读写WP 接 VCC写保护开启只能读不能写入防止误擦除数据。VCC电源GND地SDA、SCL 必须外接 上拉电阻STM32F103 硬件 I2C 必须外部上拉。3、读写逻辑***字节写单字节写入) ***I2C 起始 → 发送器件写地址 (0xA0) → 发送芯片内部存储地址 (0~255) → 发送要写入的数据 → STOP 停止。⚠️写完之后必须等待写入周期 AT24C02 内部烧录需要时间典型 5ms。写完立刻读会读取失败。可以延时 5ms或者轮询 I2C ACK 等待芯片内部烧写完成。随机读指定地址读I2C 起始发送器件写地址 0xA0发送要读取的内部地址重新发起起始发送器件读地址 0xA1读取 1 字节数据主机发送 NACKSTOP 结束。4、工程高频坑点STM32F103 调试必看写完忘记等待 5ms 写周期写完马上读读出来乱码。I2C 地址忘记左移一直 NACK 无应答。AT24C02 7bit 地址 0x50I2C 发送写地址要 0xA0。WP 引脚接 VCC开启写保护写不进去读正常。STM32F103 硬件 I2C 本身存在卡死也可以通过检查超时复位I2C总线等方法或推荐软件模拟 I2C 驱动 AT24C02。不要频繁循环擦写同一个地址10 万次寿命高频循环写会把芯片写坏。5、典型使用场景保存设备配置参数阈值、波特率、设备编号6、同系列型号快速区分AT24C01128 字节AT24C02256 字节最常用AT24C04512 字节AT24C081KAT24C162K三、接线AT24C02 三个地址线A0,A1,A2全部接地。对应设备地址的bit1,bit2,bit3全为0。SCL和SDA模块已经自带上拉电阻否则要加接。WP接到GND,数据才能写进去。STM32F103C8T6 --PB6 ----------AT24C02 —SCLSTM32F103C8T6 --PB7 ----------AT24C02 —SDA四、工程与程序编写4.1工程复制复制UART工程文件夹为I2C文件夹UART.uvprojx复制为I2C.uvprojx。4.2 添加驱动文件1.Hardware文件夹下面新建I2c和At24c02两个文件夹。2.工程添加i2c_sim.h,i2c_sim.c,at24c02.h,at24c02.c文件。i2c_sim.h#ifndef__I2C_SIM_H#define__I2C_SIM_H#includestm32f10x.h// 引脚定义 PB6 SCL PB7 SDA#defineI2C_SCL_PINGPIO_Pin_6#defineI2C_SDA_PINGPIO_Pin_7#defineI2C_GPIO_PORTGPIOBvoidi2c_sim_init(void);voidi2c_sim_start(void);voidi2c_sim_stop(void);uint8_ti2c_sim_write_byte(uint8_tdat);uint8_ti2c_sim_read_byte(uint8_tack);#endifi2c_sim.c#includei2c_sim.h#includedelay.h/* 简单微延时根据系统主频72MHz微调延时保证I2C时序(100K) Cortex-M3for 循环每次迭代 i 自增、比较跳转一次循环大约 3~4 个 CPU 周期 循环 100 次 周期数 ≈ 100 × 4 400 个 CPU 时钟周期 CPU 时钟 72MHz1 个周期 1/72M ≈13.889ns 总时间 ≈ 400 ×13.889ns ≈ 5.55μs 不开编译优化(level 0)i2c_delay() 大概 5~6μs 标准 100kHz I2CSCL 周期 10μs高低电平最小建立 / 保持时间典型要求 tSU;STA起始建立 ≥4.7μs tHD;STA起始保持 ≥4.0μs tLOW SCL 低电平 ≥4.7μs tHIGH SCL 高电平 ≥4.0μs */staticvoidi2c_delay(void){uint32_ti;for(i0;i100;i);}//初始化GPIOvoidi2c_sim_init(void){GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitStruct.GPIO_PinI2C_SCL_PIN|I2C_SDA_PIN;GPIO_InitStruct.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(I2C_GPIO_PORT,GPIO_InitStruct);//总线空闲 SCL1 SDA1GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);}// I2C_SDA_PIN 设置为推挽输出voidsda_out(void){GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitStruct.GPIO_PinI2C_SDA_PIN;GPIO_InitStruct.GPIO_ModeGPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(I2C_GPIO_PORT,GPIO_InitStruct);}//I2C_SDA_PIN 浮空输入voidsda_in(void){GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitStruct.GPIO_PinI2C_SDA_PIN;GPIO_InitStruct.GPIO_ModeGPIO_Mode_IN_FLOATING;//浮空输入GPIO_InitStruct.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(I2C_GPIO_PORT,GPIO_InitStruct);}//I2C起始信号SCL高SDA拉低voidi2c_sim_start(void){sda_out();GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//SDA1SCL1 总线空闲状态i2c_delay();GPIO_ResetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//SDA 拉低 → 完成起始信号边沿告诉所有从机通信要开始了i2c_delay();GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//把时钟线拉低为主机输出第 1 位数据做准备//***SCL 高的时候SDA 不能随便变化否则会误触发起始 / 停止***}//I2C停止信号SCL高SDA拉高voidi2c_sim_stop(void){sda_out();GPIO_ResetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//SDA0GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//SCL1i2c_delay();GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//SDA1i2c_delay();}//写1字节返回ACK(0应答1NACK无应答)uint8_ti2c_sim_write_byte(uint8_tdat){uint8_ti;uint8_tack;sda_out();for(i0;i8;i)//一个字节8位{GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//先拉低SCL,为后面的发送SDA的位数据作准备i2c_delay();if(dat0x80)//先发送高位{GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//置高}else{GPIO_ResetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//置低}dat1;i2c_delay();GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//SCL 拉高 SDA数据有效i2c_delay();}//释放SDA读取应答切输入GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//把时钟线拉到低电平准备进入 ACK 阶段sda_in();//重点切输入读取从机应答 SCL 高电平期间不允许随便切换 SDA 输入输出i2c_delay();GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//SCL拉高这就是ACK的第9个时钟i2c_delay();if(GPIO_ReadInputDataBit(I2C_GPIO_PORT,I2C_SDA_PIN))//读取从机应答ack1;//NACKelseack0;//ACKGPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//次拉低SCL结束ACK时序sda_out();//读完切回输出returnack;}/* 写 1bit先输出 bit再左移 读 1bit先左移再存入 bit *///读1字节: ack0 发送ACK继续读ack1发送NACK结束读取uint8_ti2c_sim_read_byte(uint8_tack){uint8_ti;uint8_tdat0;GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);sda_in();for(i0;i8;i)//先读到的是高位{dat1;//左移1位放在前面读一个字节只需求移位7位,放在最后面时读bit0时就不能再移位了。GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);i2c_delay();GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);i2c_delay();if(GPIO_ReadInputDataBit(I2C_GPIO_PORT,I2C_SDA_PIN)){dat|0x01;}}//准备发送应答位GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);sda_out();if(ack){GPIO_SetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//NACK 不应答}else{GPIO_ResetBits(I2C_GPIO_PORT,I2C_SDA_PIN);//ACK}i2c_delay();GPIO_SetBits(I2C_GPIO_PORT,I2C_SCL_PIN);//第9个时钟ACK/NACK 有效i2c_delay();GPIO_ResetBits(I2C_GPIO_PORT,I2C_SCL_PIN);returndat;}at24c02.h c#ifndef__AT24C02_H#define__AT24C02_H#includei2c_sim.h#includedelay.h/* 器件地址字节的第 8 位最低 bit0是读写选择位 - 该位 1发起读操作 - 该位 0发起写操作 I2C 标准bit0 → R/W 位。 */#defineAT24C02_ADDR_WR0xA0//1010 000 0#defineAT24C02_ADDR_RD0xA1//1010 000 1voidat24c02_write_byte(uint8_taddr,uint8_tdata);uint8_tat24c02_read_byte(uint8_taddr);voidat24c02_test(void);#endifat24c02.c#includeat24c02.h#includestdio.h//写单个字节voidat24c02_write_byte(uint8_taddr,uint8_tdata){uint8_tack;i2c_sim_start();acki2c_sim_write_byte(AT24C02_ADDR_WR);if(ack){i2c_sim_stop();printf(at24c02_write_byte ack 0:%d ,ack);return;}acki2c_sim_write_byte(addr);if(ack){i2c_sim_stop();printf(at24c02_write_byte ack 1:%d ,ack);return;}acki2c_sim_write_byte(data);if(ack){i2c_sim_stop();printf(at24c02_write_byte ack 2:%d ,ack);return;}i2c_sim_stop();delay_ms(10);//AT24C02内部烧录等待时间必须}//读单个字节uint8_tat24c02_read_byte(uint8_taddr){uint8_tres;i2c_sim_start();//起始信号i2c_sim_write_byte(AT24C02_ADDR_WR);//从机地址写数据i2c_sim_write_byte(addr);//要读对应数据的地址i2c_sim_start();//起始信号i2c_sim_write_byte(AT24C02_ADDR_RD);//从机地址读数据resi2c_sim_read_byte(1);//NACK结束i2c_sim_stop();returnres;}voidat24c02_test(void){uint8_ti,data;printf(写之前读 \r\n);for(i0;i10;i){dataat24c02_read_byte(i);printf(add[%d]: %d \r\n,i,data);}for(i0;i10;i){datai10;at24c02_write_byte(i,data);printf(写入 add[%d]: %d \r\n,i,data);}printf(写之后读 \r\n);for(i0;i10;i){dataat24c02_read_byte(i);printf(add[%d]: %d \r\n,i,data);}}AT24Cxx 型号和地址型号容量页大小地址位数器件地址格式 (7位R/W)A2 A1 A0 引脚作用内存地址发送AT24C01128 Byte8 Byte8位1010 A2 A1 A0全部用于片选1字节0x00~0x7FAT24C02256 Byte8 Byte8位1010 A2 A1 A0全部用于片选1字节0x00~0xFFAT24C04512 Byte16 Byte9位1010 A2 A1 P0A2/A1 片选A0NC1字节P0地址 bit8AT24C081K Byte16 Byte10位1010 A2 P1 P0A2 片选A1/A0NC1字节P1/P0地址 bit9/8AT24C162K Byte16 Byte11位1010 P2 P1 P0全部 NC内部做地址高位1字节P2/P1/P0地址 bit10/9/8AT24C02 随机读 1 字节流程步骤操作发送方说明①START主机SCL 高电平时SDA 下降沿②发送设备地址 0xA0主机0xA0 0b1010 0000R/W0写模式③ACK从机AT24C02 拉低 SDA 应答④发送内存地址Word Address主机比如0x20告诉从机读哪个位置⑤ACK从机从机应答地址已接收⑥Repeated START (Sr)主机不释放总线直接重新开始⑦发送设备地址 0xA1主机0xA1 0b1010 0001R/W1读模式⚡⑧ACK从机从机准备发送数据⑨读取 1 字节数据从机→主机从机发送指定地址的数据MSB 先⑩NACK主机主机不拉低SDA 不再读了⑪STOP主机SCL 高电平时SDA 上升沿结束通信AT24C022Kbit → 256 字节存储空间存储地址范围 0x00 ~ 0xFFAT24C02 器件地址写地址0xA0读地址0xA1A2/A1/A0 全部接地的情况随机读读取任意指定存储地址不是顺序接着上一次读。I2C 随机读核心关键点先「写寄存器地址」发送起始 EEPROM 写地址 ACK 要读的存储单元地址 ACK不发停止信号发送重复起始信号 Repeated Start发送 EEPROM 读地址读取从机 ACK读取 1 字节数据读完主机输出 NACK发送停止信号 Stop 结束通信⚠重点随机读不能发 Stop再发 Start必须用重复起始信号。如果发 StopEEPROM 会丢失内部地址指针。AT24C02 随机写 1 字节流程步骤操作发送方说明①START主机SCL 高电平时SDA 下降沿②发送设备地址 0xA0主机0xA0 0b1010 0000R/W 0写模式③ACK从机AT24C02 拉低 SDA 应答④发送内存地址Word Address主机8 位地址如0x20指定写入位置⑤ACK从机地址已接收⑥发送 1 字节数据主机要写入的数据如0x55MSB 先⑦ACK从机数据已接收⑧STOP主机SCL 高电平时SDA 上升沿结束通信i2c_sim_start() I2C 起始信号 Starti2c_sim_write_byte(0xA0) 发送 EEPROM写器件地址判断 ACK如果 NACK直接 Stop 退出通信失败i2c_sim_write_byte(mem_addr) 发送要写入的存储地址0‑0xFF判断 ACKi2c_sim_write_byte(write_data) 发送需要写入的数据字节判断 ACKi2c_sim_stop() 发送停止信号 StopStop 之后 EEPROM 才启动内部页烧写⏳延时 5~10ms等待 EEPROM 内部编程完成这一步非常容易漏掉重点Stop 信号才触发 EEPROM 烧写没有 Stop 不会保存数据Stop 之后必须等待烧写时间期间 EEPROM 不响应 I2C此时去读 / 写会收到 NACK随机写不需要重复起始信号。main.c#includestm32f10x.h#includeled.h#includedelay.h#includekey.h#includeusart.h#includeexti.h#includetime.h#includestdio.h#includei2c_sim.h#includeat24c02.hintmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 设置中断优先级分组4led_init();delay_init();usart1_init(115200);i2c_sim_init();at24c02_test();while(1){}}4.3 添加文件包含路径4. 4 程序的编译和烧录测试结束写入和读出是相同才正确!模拟I2C 程序错误也能读写要读写测试判断.资料下载参考资料网盘 链接 : https://pan.baidu.com/s/1BrH0JbUY6z7j7gAihTa4Dw提取码: qnws本节课完整工程例程网盘链接: https://pan.baidu.com/s/1rl6EIqi9m6kQ1JFjuSqINg提取码: bumc
返回列表