代码
I2C.h
#ifndef __I2C_H
#define __I2C_H
#include "stm32f10x.h"
#include "delay.h"
// 宏定义
#define OK 0
#define FAIL 1
// 初始化
void I2C_Init(void);
// 发出起始信号
uint8_t I2C_Start(void);
// 设置发出停止信号
void I2C_Stop(void);
// 主机设置使能应答信号
void I2C_Ack(void);
// 主机设置使能非应答信号
void I2C_Nack(void);
// 主机发送设备地址,并等待应答
uint8_t I2C_SendAddr(uint8_t addr);
// 主机发送一个字节的数据(写入),并等待应答
uint8_t I2C_SendByte(uint8_t byte);
// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_ReadByte(void);
#endif
I2C.c
1. I2C模式设置+I2C使能
void I2C_Init(void)
{
// 1.配置时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
// 2.GPIO工作模式配置:服用开漏输出 CNF-11,MODE-11
GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11 |
GPIO_CRH_CNF10 | GPIO_CRH_CNF11);
// 3.I2C2配置
// 3.1 硬件工作模式
I2C2->CR1 &= ~I2C_CR1_SMBUS;
I2C2->CCR &= ~I2C_CCR_FS;
// 3.2 选择输入的时钟频率
I2C2->CR2 |= 36;
// 3.3 配置CCR,对应数据传输速率100kb/s,SCL高电平时间为5us
I2C2->CCR |= 180;
// 3.4 配置TRISE,SCL上升沿最大时钟周期数 + 1
I2C2->TRISE |= 37;
// 3.5 使能I2C2模块
I2C2->CR1 |= I2C_CR1_PE;
}2. 产生起始和终止条件
// 发出起始信号
uint8_t I2C_Start(void)
{
// 产生一个起始信号
I2C2->CR1 |= I2C_CR1_START;
// 引入一个超时时间
uint16_t timeout = 0xFFFF;
// 等待信号发出
while ((I2C1->SR1 & I2C_SR1_SB) == 0 && timeout)
{
timeout--;
}
return timeout ? OK : FAIL;
}
// 设置接收完成之后发出停止信号
void I2C_Stop(void)
{
I2C2->CR1 |= I2C_CR1_STOP;
}3. 产生应答信号和非应答信号
// 主机设置使能应答信号
void I2C_Ack(void)
{
I2C2->CR1 |= I2C_CR1_ACK;
}
// 主机设置使能非应答信号
void I2C_Nack(void)
{
I2C2->CR1 &= ~I2C_CR1_ACK;
}4. 收发数据
// 主机发送设备地址,并等待应答
uint8_t I2C_SendAddr(uint8_t addr)
{
// 直接讲要发送的地址发送给到DR
I2C2->DR = addr;
// 等待应答
uint16_t timeout = 0xFFFF;
while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout)
{
timeout--;
}
// 访问SR2,清除ADDR标志位
if (timeout)
{
I2C2->SR2;
}
return timeout ? OK : FAIL;
}
// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_SendByte(uint8_t byte)
{
// 1. 等待DR为空,上一个字节数据发送完毕
uint16_t timeout = 0xFFFF;
while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout)
{
timeout--;
}
// 2. 讲要发送的字节放入DR
I2C2->DR = byte;
// 3. 等待应答
timeout = 0xFFFF;
while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout)
{
timeout--;
}
/*
BTF的清除条件为:对数据寄存器的读或写操作,
或者在传输中发送一个其实或者停止条件后,
或者PE=0时,硬件自动清零该位,
所以根据各操作的流程来判断,这个清零操作不必要
*/
return timeout ? OK : FAIL;
}
uint8_t I2C_ReadByte(void)
{
// 1. 等待DR为满
uint16_t timeout = 0xFFFF;
while ((I2C2->SR1 & I2C_SR1_RXNE) == 0 && timeout)
{
timeout--;
}
// 2. 将收到的字节返回
return timeout ? I2C2->DR : FAIL;
}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_SendAddr(W_ADDR);
// 4.发送内部地址
I2C_SendByte(inneraddr);
// 5.发送具体数据
I2C_SendByte(byte);
// 6.发送停止信号
I2C_Stop();
// 延迟等待写入周期结束
Delay_ms(5);
}
// 从EEPROM读取一个字节
uint8_t M24C02_ReadByte(uint8_t inneraddr)
{
// 1. 发出开始信号
I2C_Start();
// 2. 发送写地址(假写)
I2C_SendAddr(W_ADDR);
// 3. 发送内部地址
I2C_SendByte(inneraddr);
// 4. 发出开始信号
I2C_Start();
// 5. 发送读地址(真读)
I2C_SendAddr(R_ADDR);
// 6. 设置发送一个非应答
I2C_Nack();
// 7. 设置发出一个停止信号
I2C_Stop();
// 8. 读取一个字节
uint8_t byte = I2C_ReadByte();
return byte;
}
// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t size)
{
// 1.发送起始信号
I2C_Start();
// 2.发送写地址
I2C_SendAddr(W_ADDR);
// 4.发送内部地址
I2C_SendByte(inneraddr);
// 利用循环不停发送数据
for (uint8_t i = 0; i < size; i++)
{
// 4.发送具体数据
I2C_SendByte(bytes[i]);
}
// 5.发送停止信号
I2C_Stop();
// 延迟等待写入周期结束
Delay_ms(5);
}
// 连续读取多个字节
void M24C02_ReadBytes(uint8_t inneraddr, uint8_t *buffer, uint8_t size)
{
// 1.发送起始信号
I2C_Start();
// 2.发送写地址
I2C_SendAddr(W_ADDR);
// 3.发送内部地址
I2C_SendByte(inneraddr);
// 4.发出开始信号
I2C_Start();
// 5.发送读地址
I2C_SendAddr(R_ADDR);
// 利用循环不停读取数据
for (uint8_t i = 0; i < size; i++)
{
// 6.设置应答或非应答
if (i < size - 1)
{
I2C_Ack();
}
else
{
I2C_Nack();
// 7. 设置发出停止信号
I2C_Stop();
}
// 8.读取一个字节
buffer[i] = I2C_ReadByte();
}
}
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. 写入多个字符
M24C02_WriteBytes(0x00, "abcdef", 6);
// 6. 读取多个字符
uint8_t buffer[] = {0};
M24C02_WriteBytes(0x00, buffer, 6);
// 7. 串口打印
print("buffer = %s\n", buffer);
// 8. 测试超出16个字节的写入
// 清零缓冲区
memset(buffer, 0, sizeof(buffer));
M24C02_WriteBytes(0x00, "12345678490abcdefghijk", 21);
M24C02_ReadBytes(0x00, buffer, 21);
print("buffer = %s\n", buffer);
while (1)
{
}
}
寄存器
控制寄存器

SMBUS

PE
置1让I2C模块使能

START
置1时,如果在主模式下
CR1会重复发出起始信号,由于可能有其他设备占用总线,它会一直重复发出信号直到总线空闲时接管总线后即可清零
从模式则只有在总线空闲时才会发出起始信号
是否成功发出起始信号,可以在状态寄存器的SB位中得知

STOP
在字节传输结束后或当前起始条件发出后产生停止条件

ACK
在接受完一个字节之后产生应答



时钟控制寄存器

F/S
F:fast
S:standard

CCR
TPCLK1指当前的时钟周期,为1/36μs
数据的传输速率为100kb/s,所以I2C的Thigh+Tlow = 1/100k(10μs),所以Thigh,Tlow应为5μs
Thigh/low = 5μs,TPCLK1 = 1/36,得出CCR = 5*36=180
如果提供了8Mhz频率的FREQR时钟,则CRR需要写入40
Thigh和Tlow时长是稳定时长和上升下降过程时长之和
所以Thigh+Tlow就是一个周期

TRISE寄存器

TRISE
标准模式中最大允许的SCL上升时间为1μs
时钟周期为1/36μs
TRISE的值表示我们的上升沿部分可以占时钟周期的多少个周期
1/36 * 36 = 1
因此TRISE最大可以设置为36,最后预留1个周期
及设置为36 + 1 = 37

状态寄存器

BTF:beat transform finishi 一个表示传输是否完成的标志位
ADDR:判断读写地址是否被成功发送
STOPF:从设备模式检测是否接收到停止信号的标志位
SB

TxE

RxNE

数据寄存器

DR
