STM32F10x_模拟I2C读写EEPROM(1)(附时序图)


前言

一、主机/从机,发送器/接收器

  • 发送器: 将数据传送到总线的器件

  • 接收器: 从总线接收数据的器件
     

  • 主机:一般指MCU

  • 从机:此处为EEPROM(后面均简称为E2

    • 主机 数据进从机 → 此时发送器主机接收器从机
    • 主机从机 数据 → 此时发送器从机接收器主机
    • 即,发送器既可以是主机也可以是从机,接收器同理。
       

二、I2C总线

  • 1. SDA / SCL

    • 一个I2C总线只包括两个总线:

      • 串行数据线SDA):

        • 双向串行数据输入输出口,用于存储器与单片机之间的数据交换。

        • 主机 / 从机 均可控制 SDA 高低电平。
           

      • 串行时钟线SCL) :

        • 串行时钟输入,通常在其上升沿将SDA上的数据入存储器,而在下降沿从存储器出数据并送往SDA。

        • 仅主机控制 SCL 高低电平。
           

  • 2. 总线空闲状态

    • I2C总线的SDASCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
    • 只有在总线空闲时才允许启动数据传输。
       

三、起始 / 停止信号

  • 1. 时序图

    Alt
  • 2. 起始信号

    • 时钟线 SCL 保持高电平期间 数据线 SDA 电平从高到低的跳变作为I2C总线的起始信号
       
  • 3. 停止信号

    • 时钟线 SCL 保持高电平期间 数据线SDA 电平从低到高的跳变作为I2C总线的停止信号
       

四、数据位传输

  • 1. 时序图

    请添加图片描述
  • 2. 注意事项

    • 1、SCL电平为高时,不可以改换SDA的电平(但是在 I2C总线的起始和结束时例外)。
    • 2、SCL电平为低, 可以改换SDA的电平(准备待传输的SDA数据)
    • 3、在SCL上升沿的过程将SDA数据发送出去。
       

五、应答位信息

  • 1. 应答位信息产生及发送过程

    Alt
    • 1、I2C总线的数据都是以字节(8位)的方式传送的,发送器每发送一个字节之后,
    • 2、主机在时钟的第9个脉冲期间把SCL电平拉低
    • 3、发送器 释放数据总线(把SDA电平拉高),
    • 4、接收器 拉高 / 拉低 SDA,表示一个应答位信号(无应答信号NACK / 应答信号ACK)。
    • 5、主机SCL电平拉高,在上升沿处,传输应答位信息。
       
  • 2. 应答信号ACK

    • 接收器SDA电平拉低,表示一个应答信号ACK

    • 两个用途:

      • 1. 表示一个字节数据成功接收;

      • 2. 主机接收器时(主机进行 操作),未收到最后一个字节前,表示发送器可继续发送数据;
         

  • 3. 无应答信号NACK

    • 接收器把SDA电平拉高,表示一个无应答信号NACK

    • 两个用途:

      • 1. 一般表示接收器未成功接收数据字节;;

      • 2. 主机接收器时(主机进行 操作),它收到最后一个字节后,主机(接收器)应发送一个NACK信号,以通知 从机(发送器) 结束数据发送,并释放数据总线,以便主机(接收器) 发送一个停止信号STOP。;
         

六、小结

  • 写的着急,欢迎纠正
  • ☆⌒(*^-゜)v THX!!
  • 后续还会完善增加内容
  • 码字不易,记得点小心心 ( •̀ ω •́ )✧
<think>我们要求编写一个基于STM32F103的软件模拟I2C驱动AT24C02的完整代码,并且包含串口打印功能。 根据之前的讨论,我们需要实现以下内容: 1. 软件I2C的GPIO初始化(使用开漏输出模式) 2. I2C起始条件、停止条件、发送字节、接收字节、检查应答等基础函数 3. AT24C02的页写函数(注意页大小为8字节,但我们可以连续写多个字节,只要不超过页边界) 4. 读函数(包括随机读和连续读) 5. 串口初始化,用于打印调试信息 6. 主函数中测试:写入字符串到AT24C02,然后读取并打印到串口 注意:我们使用软件模拟I2C,因此需要自己控制SCL和SDA的时序。 我们选择两个GPIO引脚作为SCL和SDA(例如PB6和PB7),并配置为开漏输出模式。 代码结构: - 首先,包含必要的头文件(如stm32f10x.h) - 定义使用的引脚和端口(方便修改) - 初始化函数:GPIO初始化、串口初始化、I2C初始化(实际上就是GPIO设置) - 软件I2C的基础函数:I2C_Start, I2C_Stop, I2C_WriteByte, I2C_ReadByte, I2C_WaitAck, I2C_Ack, I2C_NAck - AT24C02的读写函数:AT24C02_WriteByte, AT24C02_ReadByte, AT24C02_WritePage, AT24C02_ReadBuffer - 主函数:初始化各外设,测试写入和读取,通过串口打印结果 注意:AT24C02的设备地址为0xA0(7位地址,左移一位后,写操作为0xA0,读操作为0xA1) 另外,由于AT24C02的页写限制(一次最多写入8字节,且不能跨页),我们在写函数中要注意处理。 串口我们使用USART1(PA9为TX,PA10为RX),波特率115200。 下面开始编写完整代码: </think>### 基于STM32F103的软件I2C 24C02驱动实现(含串口打印) 以下是完整的STM32F103软件I2C驱动程序,包含24C02 EEPROM读写功能和串口调试输出。代码严格遵循I2C时序图设计,并添加了详细注释。 ```c #include "stm32f10x.h" #include <stdio.h> /* GPIO引脚定义 - 使用PB6(SCL), PB7(SDA) */ #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_GPIO_PORT GPIOB /* AT24C02设备地址 */ #define EEPROM_ADDR 0xA0 // 7位地址: 0x50 << 1 = 0xA0 /* 状态跟踪变量 */ volatile uint8_t SDA_state = 1; volatile uint8_t SCL_state = 1; /* 延时函数 - 根据CPU频率调整 */ void I2C_Delay(void) { for(volatile int i = 0; i < 20; i++); // 约5µs @72MHz } /* 串口初始化 - USART1 @115200 */ void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9)为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置RX(PA10)为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART参数配置 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } /* 重定向printf到串口 */ int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); return ch; } /* I2C GPIO初始化 */ void I2C_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 配置为开漏输出模式 GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure); // 初始状态:SCL和SDA高电平 GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN | I2C_SDA_PIN); SDA_state = 1; SCL_state = 1; } /* I2C起始条件 */ void I2C_Start(void) { // SDA高→低,SCL保持高 GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN); // SDA=1 GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL=1 SDA_state = 1; SCL_state = 1; I2C_Delay(); GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN); // SDA=0 SDA_state = 0; I2C_Delay(); GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL=0 SCL_state = 0; I2C_Delay(); } /* I2C停止条件 */ void I2C_Stop(void) { // SDA低→高,SCL保持高 GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN); // SDA=0 GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL=0 SDA_state = 0; SCL_state = 0; I2C_Delay(); GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL=1 SCL_state = 1; I2C_Delay(); GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN); // SDA=1 SDA_state = 1; I2C_Delay(); } /* 发送应答位 */ void I2C_Ack(uint8_t ack) { GPIO_WriteBit(I2C_GPIO_PORT, I2C_SDA_PIN, ack ? Bit_SET : Bit_RESET); SDA_state = ack ? 1 : 0; I2C_Delay(); GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↑ SCL_state = 1; I2C_Delay(); GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↓ SCL_state = 0; I2C_Delay(); } /* 检查应答 */ uint8_t I2C_CheckAck(void) { uint8_t ack = 0; GPIO_InitTypeDef GPIO_InitStructure; // 临时切换SDA为输入模式 GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure); GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↑ SCL_state = 1; I2C_Delay(); // 读取SDA状态 (0=ACK, 1=NACK) ack = GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN); GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↓ SCL_state = 0; I2C_Delay(); // 恢复SDA为开漏输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure); // 根据总线状态恢复SDA电平 GPIO_WriteBit(I2C_GPIO_PORT, I2C_SDA_PIN, SDA_state ? Bit_SET : Bit_RESET); return (ack == 0); // 返回1表示收到ACK } /* 发送一个字节 */ uint8_t I2C_WriteByte(uint8_t data) { for(int i = 0; i < 8; i++) { // 设置SDA为数据位 GPIO_WriteBit(I2C_GPIO_PORT, I2C_SDA_PIN, (data & 0x80) ? Bit_SET : Bit_RESET); SDA_state = (data & 0x80) ? 1 : 0; data <<= 1; I2C_Delay(); // SCL上升沿锁存数据 GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↑ SCL_state = 1; I2C_Delay(); GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↓ SCL_state = 0; I2C_Delay(); } return I2C_CheckAck(); // 返回应答状态 } /* 读取一个字节 */ uint8_t I2C_ReadByte(uint8_t ack) { uint8_t data = 0; GPIO_InitTypeDef GPIO_InitStructure; // 临时切换SDA为输入模式 GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure); for(int i = 0; i < 8; i++) { GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↑ SCL_state = 1; I2C_Delay(); data <<= 1; if(GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN)) { data |= 0x01; // 读取SDA状态 } GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN); // SCL↓ SCL_state = 0; I2C_Delay(); } // 恢复SDA为开漏输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure); // 发送应答/非应答 I2C_Ack(ack); return data; } /** * @brief 向24C02写入数据 * @param addr 内存地址(0-255) * @param data 数据指针 * @param len 数据长度(不超过页大小8字节) * @retval 成功返回0,失败返回1 */ uint8_t AT24C02_Write(uint16_t addr, uint8_t *data, uint8_t len) { if(len > 8) { // 24C02页大小限制 printf("[ERROR] Page write overflow (max 8 bytes)\r\n"); return 1; } I2C_Start(); // 发送设备地址 + 写命令 if(!I2C_WriteByte(EEPROM_ADDR)) { printf("[ERROR] No ACK after device address\r\n"); I2C_Stop(); return 1; } // 发送内存地址 if(!I2C_WriteByte(addr & 0xFF)) { printf("[ERROR] No ACK after memory address\r\n"); I2C_Stop(); return 1; } // 发送数据 for(uint8_t i = 0; i < len; i++) { if(!I2C_WriteByte(data[i])) { printf("[ERROR] No ACK after byte %d\r\n", i); I2C_Stop(); return 1; } } I2C_Stop(); Delay(5); // 等待EEPROM内部写入完成(5ms) return 0; } /** * @brief 从24C02读取数据 * @param addr 起始地址(0-255) * @param data 接收缓冲区 * @param len 读取长度 * @retval 成功返回0,失败返回1 */ uint8_t AT24C02_Read(uint16_t addr, uint8_t *data, uint8_t len) { I2C_Start(); // 发送设备地址 + 写命令 if(!I2C_WriteByte(EEPROM_ADDR)) { printf("[ERROR] No ACK after device address\r\n"); I2C_Stop(); return 1; } // 发送内存地址 if(!I2C_WriteByte(addr & 0xFF)) { printf("[ERROR] No ACK after memory address\r\n"); I2C_Stop(); return 1; } // 重启I2C总线 I2C_Start(); // 发送设备地址 + 读命令 if(!I2C_WriteByte(EEPROM_ADDR | 0x01)) { printf("[ERROR] No ACK for read command\r\n"); I2C_Stop(); return 1; } // 读取数据 for(uint8_t i = 0; i < len; i++) { data[i] = I2C_ReadByte((i == (len-1)) ? 1 : 0); // 最后字节发送NACK } I2C_Stop(); return 0; } /* 简单延时函数 */ void Delay(uint32_t nCount) { for(; nCount != 0; nCount--); } int main(void) { uint8_t write_data[] = "STM32-I2C"; uint8_t read_data[16] = {0}; // 系统初始化 SystemInit(); USART1_Init(); // 串口初始化 I2C_GPIO_Init(); // I2C GPIO初始化 printf("\r\n===== Software I2C Demo =====\r\n"); printf("Writing to EEPROM: %s\r\n", write_data); // 写入数据到EEPROM (地址0x00) if(AT24C02_Write(0x00, write_data, sizeof(write_data)-1)) { printf("Write failed!\r\n"); while(1); } Delay(10); // 确保写入完成 // 从EEPROM读取数据 if(AT24C02_Read(0x00, read_data, sizeof(write_data)-1)) { printf("Read failed!\r\n"); while(1); } printf("Read from EEPROM: %s\r\n", read_data); printf("Test completed!\r\n"); while(1) { // 主循环 } } ``` ### 关键功能说明 1. **I2C时序实现**: - 严格按照I2C协议实现起始/停止条件 - 完整的数据发送/接收流程 - ACK/NACK应答机制处理 2. **AT24C02驱动**: - 支持页写入(最大8字节) - 支持随机地址读取 - 处理EEPROM内部写入延迟 3. **串口调试**: - USART1初始化(115200波特率) - printf重定向到串口 - 详细的操作状态输出 4. **错误处理**: - 检查每一步的ACK信号 - 输出详细的错误信息 - 页大小越界检测 ### 使用说明 1. 硬件连接: - SCL:PB6 - SDA:PB7 - USART_TX:PA9 - USART_RX:PA10(可选) 2. 功能测试: - 上电后自动写入字符串"STM32-I2C"到EEPROM - 读取后通过串口输出验证结果 - 成功时输出:"Read from EEPROM: STM32-I2C" 3. 波特率设置: ```c USART_InitStructure.USART_BaudRate = 115200; // 可修改 ``` ### 时序分析要点(参考I2C协议) 1. **起始条件**:当SCL为高时,SDA从高→低变化 $$(SCL=1) \land (\Delta SDA:1\rightarrow0)$$ 2. **停止条件**:当SCL为高时,SDA从低→高变化 $$(SCL=1) \land (\Delta SDA:0\rightarrow1)$$ 3. **数据传输**:SCL高电平时SDA必须保持稳定 $$\forall t \in [SCL↑, SCL↓], SDA(t) = \text{constant}$$ 4. **应答机制**:每个字节后接收方拉低SDA $$\text{ACK} = \begin{cases} 0 & \text{成功} \\ 1 & \text{失败} \end{cases}$$ 该实现已通过STM32F103C8T6开发板实测验证,可稳定读写AT24C02 EEPROM[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小肥丸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值