i2c.h
#ifndef __I2C_H
#define __I2C_H
#include "stm32f10x.h"
#include "delay.h"
// 宏定义
#define ACK 0
#define NACK 1
// 控制SCL,SDA的输出高低电平
#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 READ_SDA (GPIOB->IDR & GPIO_ODR_ODR11)
// 定义操作的基本延迟,数据的传输速率为100Kb/s,可以算出时钟周期为1/100000 = 10us,所以设置10微秒延迟
#define I2C_DELAY Delay_us(10)
// 初始化
void I2C_Init(void);
// 发出起始信号
void I2C_Start(void);
// 发出停止信号
void I2C_Stop(void);
// 主机发出应答信号
void I2C_Ack(void);
// 主机发出非应答信号
void I2C_Nack(void);
// 主机等待从设备发来应答信号
uint8_t I2C_Wait4Ack(void);
// 主机发送一个字节的数据
void I2C_SendByte(uint8_t byte);
// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_ReadByte(void);
#endif
i2c.c
#include "i2c.h"
// 初始化
void I2C_Init(void)
{
// 1.配置时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// 2.GPIO工作模式配置:通用开漏输出 CNF-01,MODE-11
GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
}
// 发出起始信号
void I2C_Start(void)
{
// 1.SCL拉高,SDA拉高
SCL_HIGH;
SDA_HIGH;
I2C_DELAY;
// 2.SCL保持不变,SDA拉低
SDA_LOW;
I2C_DELAY;
}
// 发出停止信号
void I2C_Stop(void)
{
// 1.SCL拉高,SDA拉低
SCL_HIGH;
SDA_LOW;
I2C_DELAY;
// 2.SCL保持不变,SDA拉高
SDA_HIGH;
I2C_DELAY;
}
// 主机发出应答信号
void I2C_Ack(void)
{
// 1.SCL拉低,SDA拉低,准备发出应答信号
SCL_LOW;
SDA_LOW;
I2C_DELAY;
// 2.SCL拉高,SDA保持不变,发出信号
SCL_HIGH;
I2C_DELAY;
// 3.SCL拉低,SDA保持不变,结束数据线信号采样
SCL_LOW;
I2C_DELAY;
// 4.SCL保持不变,SDA拉高,释放数据总线
SDA_HIGH;
I2C_DELAY;
}
// 主机发出非应答信号
void I2C_Nack(void)
{
// 1.SCL拉低,SDA拉低,准备发出应答信号
SCL_LOW;
SDA_HIGH;
I2C_DELAY;
// 2.SCL拉高,SDA保持不变,发出信号
SCL_HIGH;
I2C_DELAY;
// 3.SCL拉低,SDA保持不变,结束数据线信号采样
SCL_LOW;
I2C_DELAY;
}
// 主机等待从设备发来应答信号
uint8_t I2C_Wait4Ack(void)
{
// 1.SCL拉低,SDA拉高,释放数据总线
SCL_LOW;
SDA_HIGH;
I2C_DELAY;
// 2.SCL拉高,开始数据采样
SCL_HIGH;
I2C_DELAY;
// 3.读取SDA数据线上的电平
uint16_t ack = READ_SDA;
// 4.SCL拉低,结束数据采样
SCL_LOW;
I2C_DELAY;
return ack ? NACK : ACK; // ack = 0,ACK; ack != 0,NACK; 这么判断是因为ack的数据是用GPIOB->IDR & GPIO_ODR_ODR11与出来的,所以得到的数据不是1,所以要判断是否为0
}
// 主机发送一个字节的数据
void I2C_SendByte(uint8_t byte)
{
for (uint8_t i = 0; i < 8; i++)
{
// 1.SCL,SDA都拉低,等待数据翻转
SCL_LOW;
SDA_LOW;
I2C_DELAY;
// 2.取字节的最高位,向SDA写入数据
if (byte & 0x80)
{
SDA_HIGH;
}
else
{
SDA_LOW;
}
I2C_DELAY;
// 3.SCL拉高,数据写入完成
SCL_HIGH;
I2C_DELAY;
// 4.SCL拉低,采样结束
SCL_LOW;
I2C_DELAY;
// 5.左移一位
byte <<= 1;
}
}
// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_ReadByte(void)
{
// 定义一个变量,用来保存接收的数据
uint8_t data = 0;
// 循环处理每一位
for (uint8_t i = 0; i < 8; i++)
{
// 1. SCL拉低,等待数据翻转
SCL_LOW;
I2C_DELAY;
// 2. SCL拉高,开始采样
SCL_HIGH;
I2C_DELAY;
// 3. 数据采样,读取SDA
data <<= 1; // 先做左移,新存入的位永远在最低位
if (READ_SDA)
{
data |= 0x01; // 先存入最低位,然后每次都左移1位
}
// 4. SCL拉低,结束采样
SCL_LOW;
I2C_DELAY;
}
return data;
}
m24c02.h
#ifndef __M24C02_H
#define __M24C02_H
#include "i2c.h"
// 宏定义
#define W_ADDR 0xA0
#define R_ADDR 0xA1
// 初始化
void M24C02_Init(void);
// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
// 从EEPROM读取一个字节
uint8_t M24C02_ReadByte(uint8_t inneraddr);
// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size);
// 连续读取多个字节
void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size);
#endif
m24c02.c
#include "m24c02.h"
// 初始化
void M24C02_Init(void)
{
I2C_Init();
}
// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{
// 1.发送起始信号
I2C_Start();
// 2.发送写地址
I2C_SendByte(W_ADDR);
// 3.等待EEPROM应答
uint8_t ack = I2C_Wait4Ack();
if (ack == ACK)
{
// 4.发送内部地址
I2C_SendByte(inneraddr);
// 5.等待EEPROM应答
I2C_Wait4Ack();
// 6.发送具体数据
I2C_SendByte(byte);
// 7.等待EEPROM应答
I2C_Wait4Ack();
// 8.发送停止信号
I2C_Stop();
}
// 延迟等待写入周期结束
Delay_ms(5);
}
// 从EEPROM读取一个字节
uint8_t M24C02_ReadByte(uint8_t inneraddr)
{
// 1. 发出开始信号
I2C_Start();
// 2. 发送写地址(假写)
I2C_SendByte(W_ADDR);
// 3. 等待EEPROM应答
I2C_Wait4Ack();
// 4. 发送内部地址
I2C_SendByte(inneraddr);
// 5. 等待应答
I2C_Wait4Ack();
// 6. 发出开始信号
I2C_Start();
// 7. 发送读地址(真读)
I2C_SendByte(R_ADDR);
// 8. 等待EEPROM应答
I2C_Wait4Ack();
// 9. 读取一个字节
uint8_t byte = I2C_ReadByte();
// 10. 发送一个非应答
I2C_Nack();
// 11. 发出一个停止信号
I2C_Stop();
return byte;
}
// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size)
{
// 1.发送起始信号
I2C_Start();
// 2.发送写地址
I2C_SendByte(W_ADDR);
// 3.等待EEPROM应答
uint8_t ack = I2C_Wait4Ack();
if (ack == ACK)
{
// 4.发送内部地址
I2C_SendByte(inneraddr);
// 5.等待EEPROM应答
I2C_Wait4Ack();
// 利用循环不停发送数据
for (uint8_t i = 0; i < size; i++)
{
// 6.发送具体数据
I2C_SendByte(bytes[i]);
// 7.等待EEPROM应答
I2C_Wait4Ack();
}
// 8.发送停止信号
I2C_Stop();
}
// 延迟等待写入周期结束
Delay_ms(5);
}
// 连续读取多个字节
void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size)
{
// 1.发送起始信号
I2C_Start();
// 2.发送写地址
I2C_SendByte(W_ADDR);
// 3.等待EEPROM应答
I2C_Wait4Ack();
// 4.发送内部地址
I2C_SendByte(inneraddr);
// 5.等待EEPROM应答
I2C_Wait4Ack();
// 6.发出开始信号
I2C_Start();
// 7.发送读地址
I2C_SendByte(R_ADDR);
// 8.等待EEPROM应答
I2C_Wait4Ack();
// 利用循环不停读取数据
for (uint8_t i = 0; i < size; i++)
{
// 9.读取一个字节
buffer[i] = I2C_ReadByte();
// 10.发送一个应答或非应答
if (i < size - 1)
{
I2C_Ack();
}
else
{
I2C_Nack();
}
}
// 11.发送停止信号
I2C_Stop();
}
main.c
#include "usart.h"
#include "m24c02.h"
#include <string.h>
int main(void)
{
// 1.初始化
USART_Init();
M24C02_Init();
USART_SendString("你好\r\n", 6);
// 2.向EEPROM依次写入单个字符
M24C02_WriteByte(0x00, 'a');
M24C02_WriteByte(0x00, 'b');
M24C02_WriteByte(0x00, 'c');
// 3.读取字符
uint8_t byte1 = M24C02_ReadByte(0x00);
uint8_t byte2 = M24C02_ReadByte(0x01);
uint8_t byte3 = M24C02_ReadByte(0x02);
// 4.串口输出打印
printf("byte1 = %c\t byte2 = %c\t byte3 = %c\n", byte1, byte2, byte3);
// 5.向EEPROM写入多个字符
M24C02_WriteBytes(0x00, "123456", 6);
// 6.读取多个字符
uint8_t buffer[100] = {0};
M24C02_ReadBytes(0x00, buffer, 6);
// 7.串口输出打印
printf("buffer = %s\n", buffer);
// 8.测试超出16个字节的写入
// 清零缓冲区
memset(buffer, 0, sizeof(buffer));
M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
M24C02_ReadBytes(0x00, buffer, 21);
printf("buffer = %s\n", buffer);
while (1)
{
}
}