m24c02芯片手册可以查看时序图
起始和停止信号
写一个字节:
主发送起始-》 CPU 芯片内部的I2C 控制器(片上外设主设备)-》发送起始信号-》发送设备地址-》EEPROM(从设备)返回回应信号-》主发字节起始地址-》从回应-》主发数据-》从回应-》主发送停止。
EEPROM256个字节,需要字节地址进行指定,这个字节地址是一个字节。
为什么STM32 软件模拟I2C需要delay延迟:
因为:EEPROM感知SCL时间有最低要求Tchcl,手册中规定Tchcl需要4us。
当前通讯速率100k,一个周期1/100k = 1/100ms = 10us,一个高电平和一个低电平整合是一个周期。
soft_i2c.h文件
#ifndef __SOFT_I2C_H__
#define __SOFT_I2C_H__#include "stm32f10x.h"
#include "delay.h"#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~ GPIO_ODR_ODR10)#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)#define SDA_READ (GPIOB->IDR & GPIO_IDR_IDR11)// 1. 为了方便代码编写 我们创建一个枚举类型
typedef enum{ACK,NOACK
} ACK_RESULT;void I2C_Init(void);void I2C_Start(void);void I2C_SendByte(uint8_t byte);uint8_t I2C_ReceiveByte(void);ACK_RESULT I2C_Wait4ACK(void);void I2C_Stop(void);// acknowledge
void I2C_ACK(void);void I2C_NOACK(void);#endif /* __SOFT_I2C_H__ */
soft_i2c.c文件
#include "soft_i2c.h"// 软件的I2C初始化 其实是对GPIO的初始化
void I2C_Init(void){// 1. 放时钟 PB组RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// new * 上电之后 应该默认是高阻态 表示空闲// GPIOB->ODR |= GPIO_ODR_ODR10;// GPIOB->ODR |= GPIO_ODR_ODR11;SCL_HIGH;SDA_HIGH;// 2. PB10 PB11 通用开漏模式 输出速度无脑最高GPIOB->CRH |= GPIO_CRH_MODE10;GPIOB->CRH &= ~ GPIO_CRH_CNF10_1;GPIOB->CRH |= GPIO_CRH_CNF10_0;GPIOB->CRH |= GPIO_CRH_MODE11;GPIOB->CRH &= ~ GPIO_CRH_CNF11_1;GPIOB->CRH |= GPIO_CRH_CNF11_0;
}// 1. 在I2C开始通讯前
// SDA 和 SCL 两个线 应该是什么状态?// 约定!每一个函数后 都必须维护好状态
// 除了 STOP SCL必须为低 SDA必须为高
void I2C_Start(void){// 0. 先确保 两根线都是高电平SDA_HIGH;SCL_HIGH;// 1. 做个延时 防止王八(E2PROM)反应不过来Delay_us(5);SDA_LOW;Delay_us(5);// 2.为了防止误采样 必须要保证 结束时的SCL处于低电平SCL_LOW; // 1.防止误采样 始终占用时钟线SDA_HIGH; // 保证SDA是高电平 这样别的设备可以占用// 3. 保证时钟线为低电平的时间足够长 也是为了照顾王八。Delay_us(5);
}void I2C_Stop(void){// 1. 已经约定好了 其他函数必须以SDA_HIGH结束 SDA_LOW;SCL_HIGH;Delay_us(5);SDA_HIGH;Delay_us(5);
}void I2C_SendByte(uint8_t byte){// 1. 函数第一次进来的时候 初始状态for (uint8_t i = 0; i < 8; i++){// 1. 0x80 是 1000 0000掩码,相当于取出8个bit中的最高位if(byte & 0x80){// 要发一个逻辑1SDA_HIGH;}else {SDA_LOW;}// 2. 时钟拉高,让E2Prom采样SCL_HIGH;Delay_us(5);// 3. 时钟拉低 并留足时间,SDA_HIGH是为了让函数执行完后,能够保持约定的状态。SCL_LOW;SDA_HIGH;Delay_us(5);// 4. 数组左移1位 准备发送下一个bitbyte = byte << 1;}// 5. 确保每一个函数执行完后,都是SDA为HIGH SCL为LOW的状态。
}ACK_RESULT I2C_Wait4ACK(void){ACK_RESULT ack = NOACK;// 0. 给出第9个节拍 并给足从设备反应 ACK 的时间SCL_HIGH;Delay_us(5);// 1. 读取SDA的值 判断到底有没有ACKif(SDA_READ){ack = NOACK;}else{ack = ACK;}// 2. 后摇 给足时钟的低电平时间。SCL_LOW;Delay_us(5);// 3. 返回ACK状态return ack;
}uint8_t I2C_ReceiveByte(void){// 0. 先准备一个容器 去装8个bituint8_t byte = 0;// 1. 循环8次for (uint8_t i = 0; i < 8; i++){// 2. 先把时钟拉高 保持一定时间 让EEPROM发bitSCL_HIGH;Delay_us(5);// 3. 左移写前面 让它先空左移一次byte <<= 1;// 4. 读取SDA状态,如果是1就置1。if(SDA_READ){byte |= 1;}// 5. 维持一段时间时钟低电平 让EEPROM换数据SCL_LOW;Delay_us(5);}// 6. 最后的状态: SCL还是低,SDA被EEPROM自动释放 那就是高了。return byte;
}// 0. 现在是STM32负责响应
void I2C_ACK(void){// 1. 在时钟线拉高之前 将SDA拉低 并让信号维持一段时间,确保EEPROM反应过来SDA_LOW;SCL_HIGH;Delay_us(5);// 2. 时钟线拉低之后 再将SDA释放 否则的话可能会导致 EEPROM误采样。SCL_LOW;SDA_HIGH;Delay_us(5);
}void I2C_NOACK(void){// 1. 因为约定好了SDA一定是高电平 所以 不用鸟SDASCL_HIGH;Delay_us(5);SCL_LOW;Delay_us(5);
}