一、IIC基础知识
1. 串口通信与IIC通信
-
串口通信通常需要至少三条线(TX、RX和GND),而 I2C 总线仅需要两条信号线(SDA和SCL);
-
串口通信仅支持一对一通信,而 I2C 总线支持多机通信,允许单个主机与多个从机设备进行通信;
-
串口通信通常无应答机制,而 I2C 必须有应答机制;
-
串口通讯一般是异步通信,而 I2C 使用同步传输方式,数据在时钟信号(SCL)的控制下传输。
2. IIC总线介绍
I2C总线,全称Inter-Integrated Circuit(互连集成电路),是一种由Philips(现NXP半导体)公司在1980年
代初开发的同步 串行 半双工通信总线。
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。
IIC接上拉电阻,因此引脚配置成开漏输出(只可以输出低电平)。
2.1 工作原理
- 主从关系:主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。
- 数据传送:如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送。
如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。 - 时钟同步:SCL用于数据的时钟同步,确保主从设备之间的数据传输同步进行。
2.2 主要特点
- 硬件简单:I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,不需要特殊的接口电路。
- 多主机总线:I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏。
- 在线检测:I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试。
- 数据传输与地址设定:数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。
- 负载能力:由于线路中电容会影响总线传输速度,I2C总线的负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。
2.3 应用领域
I2C总线广泛应用于各种设备和应用领域,例如传感器、存储器(如EEPROM)、显示屏、温度传感器、实时时钟(RTC)、扩展IO芯片等。
3. IIC总线时序
3.1 起始和停止信号
当 SCL 为高电平时,SDA 产生下降沿,就是电平由高变低表示起始信号;
当 SCL 为高电平时,SDA 产生上升沿,就是电平由低变高表示停止信号;
3.2 数据有效性
在SCL的高电平期间,SDA是不允许变化的;而只有在时钟线SCL的低电平期间,SDA才能够出现变化;
3.3 响应和非响应
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答。
二、软件模拟IIC
EEPROM芯片最常用的通讯方式就是I2C协议。我们使用的芯片是AT24C02。
需求描述:
向EEPROM写入一段数据,再读取出来,最后发送到串口。
1. AT24C02简介
AT24C02是一款串行电可擦除编程只读存储器(EEPROM)。存储器可存储256个字节数据,分为32页,每页8字节,随机字寻址需要8位数据字地址。
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
VCC和GND: 电源线和地线
从上图可以看出,AT24C02设备地址如下,前四位固定为1010,低三位由A0~A2信号线的电平决定设备地址,这里都接的低电平。最后一位表示读写操作。
所以AT24C02的读地址为0xA1,写地址为0xA0。
2. 操作时序
2.1 写入一个字节时序
- 发送一个START信号
- 接着发送器件地址,器件地址的最后一位为数据的传输方向位,R/W,低电平0表示主机往从机写数据(W),1表示主机从从机读数据(R)。ACK应答,应答是从机发送给主机的应答,这里不用管。
- 传送数据的存储地址,即数据要写入的位置。同样ACK应答不用管。
- 传送要写入的数据。ACK应答不用管。
- 产生STOP信号。
写入数据时,EEPROM先写到缓冲区,再写入非易失性存储器,这个过程需要不超过5ms的时间。每一次页写或字节写结束后延时5ms确保完成写周期,否则时序会出错。
一次性写入多个字节,也叫页写入。每页只有8个字节,所以一次性最多只能写入8个字节。当一次性写入超过8个字节,继续写数据会从页的首地址写入覆盖原来的数据。
2.2 随机读取
- 产生START信号
- 传送器件地址(写操作),ACK应答
- 传送字地址,ACK应答
- 再次产生START信号
- 再传送一次器件地址(读操作),ACK应答
- 读取一个字节数据 ,读数据最后结束前无应答信号
- 产生STOP信号
这里先写入器件地址和字地址,而不写入实际数据,称为“假写”,其目的是加载数据字地址。事实上,EEPROM有一个内部数据字地址计数器。内部数据字地址计数器保持上次读或写操作访问的最后一个地址,并加一。只要芯片电源保持,该地址在操作之间保持有效。如果不进行假写,直接传送器件地址进行读操作,SDA数据线直接交给EEPROM控制,此时无法传送我们所需读取的地址。
IIC协议在读写数据时,总是要发送器件地址,这里需要注意的是,不是主机给从机发送地址,而是主机给地址总线上发送地址,挂IIC总线上的所有从机都能收到地址,如果发过来的地址和自己的地址匹配上了,从机就会给主机一个应答,这样就建立起来了一个通讯。
IIC协议就是通过地址不同来判断给哪个器件传送数据的,如果两个器件的地址完全一样,器件会产生应答,那么两个器件就通过竞争判断给谁通信了,有随机性。即IIC协议一次只能和一个设备/器件进行通讯。
除了随机读取,EEPROM的读操作还有当前地址读和顺序读。
2.3 当前地址读
很显然当前地址读没有假写操作,由于内部数据字地址计数器已经保持了上次读或写操作访问的最后地址+1,所以直接从该地址读取,就是所谓的当前地址读。
2.4 顺序读取
顺序读取就是单次读出多个字节。
顺序读取由当前地址读取或随机地址读取启动。在微控制器接收到数据字后,它会以确认响应。只要 EEPROM 接收到确认,它就会继续递增数据字地址并串行输出顺序数据字。当达到内存地址限制时,数据字地址将“回滚”,顺序读取将继续。
读操作中的地址“回滚”是从最后一个内存页的最后一个字节到第一个页的第一个字节。写操作中的地址“回滚”是从当前页的最后一个字节到同一页的第一个字节。
3.示例
3.1 i2c.h
#ifndef __I2C_H
#define __I2C_H#include "stm32f10x.h"
#include "delay.h"#define ACK 0
#define NACK 1// PB6 SCL
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR6)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR6)
// PB7 SDA
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR7)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR7)#define READ_SDA (GPIOB->IDR & GPIO_ODR_ODR7)void I2C_init(void);
void I2C_Start(void);
void I2C_Stop(void);
// 产生应答信号
void I2C_Ack(void);
// 产生非应答信号
void I2C_NAck(void);
// 等待应答
uint8_t I2C_WaitAck(void);
// 发送一个字节的数据
void I2C_SendByte(uint8_t byte);
// 读一个字节数据
uint8_t I2C_ReadByte(void);#endif
3.2 i2c.c
#include "i2c.h"#define I2C_delay Delay_us(10)void I2C_init(void)
{// PB6 SCL// PB7 SDA 设置开漏输出 MODE=11 CNF=01RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;GPIOB->CRL |= GPIO_CRL_MODE6;GPIOB->CRL |= GPIO_CRL_CNF6_0;GPIOB->CRL &= ~GPIO_CRL_CNF6_1;GPIOB->CRL |= GPIO_CRL_MODE7;GPIOB->CRL |= GPIO_CRL_CNF7_0;GPIOB->CRL &= ~GPIO_CRL_CNF7_1;
}void I2C_Start(void)
{// 拉高sda sclSDA_HIGH;SCL_HIGH;// 延时I2C_delay;// 拉低sdaSDA_LOW;// 延时I2C_delay;
}void I2C_Stop(void)
{// 拉低sda 拉高sclSDA_LOW;SCL_HIGH;// 延时I2C_delay;// 拉高sdaSDA_HIGH;// 延时I2C_delay;
}// 产生应答信号
void I2C_Ack(void)
{// 拉高sda 拉低sclSDA_HIGH;SCL_LOW;// 延时I2C_delay;// 拉低sdaSDA_LOW;// 延时I2C_delay;// 拉高sclSCL_HIGH;// 延时I2C_delay;// 拉低sclSCL_LOW;// 延时I2C_delay;// 拉高sdaSDA_HIGH;// 延时I2C_delay;
}// 产生非应答信号
void I2C_NAck(void)
{SDA_HIGH;SCL_LOW;I2C_delay;SCL_HIGH;I2C_delay;SCL_LOW;I2C_delay;
}// 等待应答
uint8_t I2C_WaitAck(void)
{uint8_t ack = ACK;// sda拉高 sda主动权交给对方SDA_HIGH;// scl拉低SCL_LOW;// 延时I2C_delay;// scl拉高SCL_HIGH;// 延时I2C_delay;// 读取sda电平if (READ_SDA){ack = NACK;}// scl拉低SCL_LOW;// 延时I2C_delay;return ack;
}// 发送一个字节的数据
void I2C_SendByte(uint8_t byte)
{for (uint8_t i = 0; i < 8; i++){// 拉低sda sclSDA_LOW;SCL_LOW;I2C_delay;// 向sda写数据 高字节优先if (byte & 0x80){SDA_HIGH;}else{SDA_LOW;}I2C_delay;// 拉高sclSCL_HIGH;I2C_delay;// 拉低sclSCL_LOW;I2C_delay;// 左移1位 为下一次发送做准备byte <<= 1;}
}// 读一个字节数据
uint8_t I2C_ReadByte(void)
{uint8_t data = 0;for (uint8_t i = 0; i < 8; i++){// 拉低sclSCL_LOW;I2C_delay;// 拉高sclSCL_HIGH;I2C_delay;// 读取sdadata <<= 1;if (READ_SDA){data |= 0x01;}// 拉低sclSCL_LOW;I2C_delay;}return data;
}
3.3 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H
#include "stm32f10x.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
3.4 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{I2C_init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 开始信号I2C_Start();// 发送写地址I2C_SendByte(ADDR_WRITE);// 等待响应I2C_WaitAck();// 发送内部地址I2C_SendByte(inneraddr);// 等待响应I2C_WaitAck();// 发送数据I2C_SendByte(byte);// 等待响应I2C_WaitAck();// 停止信号I2C_Stop();// 写周期Delay_ms(5);
}// 页写入 一次性写入多个字节
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 开始信号I2C_Start();// 发送写地址I2C_SendByte(ADDR_WRITE);// 等待响应I2C_WaitAck();// 发送内部地址I2C_SendByte(inneraddr);// 等待响应I2C_WaitAck();for (uint8_t i = 0; i < len; i++){// 发送数据I2C_SendByte(bytes[i]);// 等待响应I2C_WaitAck();}// 停止信号I2C_Stop();// 写周期Delay_ms(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{// 起始信号I2C_Start();// 发送写地址 假写I2C_SendByte(ADDR_WRITE);// 等待响应I2C_WaitAck();// 发送内部地址I2C_SendByte(inneraddr);// 等待响应I2C_WaitAck();// 起始信号I2C_Start();// 发送读地址 真读I2C_SendByte(ADDR_READ);// 等待响应I2C_WaitAck();// 读取一个字节uint8_t byte = I2C_ReadByte();// 给对方非应答I2C_NAck();// 停止信号I2C_Stop();return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 起始信号I2C_Start();// 发送写地址 假写I2C_SendByte(ADDR_WRITE);// 等待响应I2C_WaitAck();// 发送内部地址I2C_SendByte(inneraddr);// 等待响应I2C_WaitAck();// 起始信号I2C_Start();// 发送读地址 真读I2C_SendByte(ADDR_READ);// 等待响应I2C_WaitAck();for (uint8_t i = 0; i < len; i++){// 读取一个字节bytes[i] = I2C_ReadByte();if (i < len - 1){// 主动发送应答信号I2C_Ack();}else{// 最后一个字节发送非应答信号I2C_NAck();}}// 停止信号I2C_Stop();
}
3.5 main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "at24c02.h"
#include "string.h"int main(void)
{USART1_init();AT24C02_Init();printf("hello world!\r\n");AT24C02_WriteByte(0x00, 'a');AT24C02_WriteByte(0x01, 'b');AT24C02_WriteByte(0x02, 'c');uint8_t byte1 = AT24C02_ReadByte(0x00);uint8_t byte2 = AT24C02_ReadByte(0x01);uint8_t byte3 = AT24C02_ReadByte(0x02);printf("%c\r\n", byte1);printf("%c\r\n", byte2);printf("%c\r\n", byte3);AT24C02_WriteBytes(0x00, "123456", 6);uint8_t buff[16] = {0};AT24C02_ReadBytes(0x00, buff, 6);printf("%s\r\n", buff);// 页写 最多一次性写入8个字节 超过覆盖掉前面数据AT24C02_WriteBytes(0x00, "123456abc", 9);memset(buff, 0, sizeof(buff));AT24C02_ReadBytes(0x00, buff, 9);printf("%s\r\n", buff);while (1){}
}
三、硬件实现IIC
STM32的 I2C 外设可用作通讯的主机及从机,支持100Kbit/s和400Kbit/s的速率,支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。
它的I2C外设还支持 SMBus2.0协议,SMBus协议与I2C类似。
1. 寄存器实现
1.1 i2c.h
#ifndef __I2C_H
#define __I2C_H#include "stm32f10x.h"
#include "delay.h"#define OK 1
#define FAIL 0void 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);
// 读一个字节数据
uint8_t I2C_ReadByte(void);#endif
1.2 i2c.c
#include "i2c.h"// #define I2C_delay Delay_us(10)void I2C_init(void)
{// 1.开启时钟// 1.1 GPIO时钟RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 1.2 i2c硬件时钟RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;// 2. 设置工作模式// PB6 SCL// PB7 SDA 设置复用开漏输出 MODE=11 CNF=11GPIOB->CRL |= GPIO_CRL_MODE6;GPIOB->CRL |= GPIO_CRL_CNF6;GPIOB->CRL |= GPIO_CRL_MODE7;GPIOB->CRL |= GPIO_CRL_CNF7;// 3. 设置i2c// 3.1 配置硬件工作模式I2C1->CR1 &= ~I2C_CR1_SMBUS;// 3.2 配置给i2c设备提供的时钟频率 36MHZI2C1->CR2 |= 36;// 3.3 设置标准模式/快速模式I2C1->CCR &= ~I2C_CCR_FS;// 3.4 配置时钟控制分频系数// Thigh=CCR * Tcplk1// ccr = Thigh/=Tcplk1 = 5us / (1/36)us = 180I2C1->CCR |= 180;// 3.5 时钟信号的上升沿// 时钟频率是36MHz则 写入:1 /(1/36) + 1 = 37// 计算的是最大上升沿时间/时钟周期 + 1I2C1->TRISE |= 37;// 4. 使能i2cI2C1->CR1 |= I2C_CR1_PE;
}uint8_t I2C_Start(void)
{// 重复产生起始条件// SDA数据线开始可能会被占用 所以这里需要重复产生I2C1->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)
{// 产生终止条件I2C1->CR1 |= I2C_CR1_STOP;
}// 产生应答信号
void I2C_Ack(void)
{I2C1->CR1 |= I2C_CR1_ACK;
}// 产生非应答信号
void I2C_NAck(void)
{I2C1->CR1 &= ~I2C_CR1_ACK;
}// 发送设备地址
uint8_t I2C_SendAddr(uint8_t addr)
{I2C1->DR = addr;uint16_t timeout = 0xffff;while ((I2C1->SR1 & I2C_SR1_ADDR) == 0 && timeout){timeout--;}// 读取SR1寄存器后,对SR2寄存器的读操作将清除该位I2C1->SR2;return timeout ? OK : FAIL;
}// 发送一个字节的数据
uint8_t I2C_SendByte(uint8_t byte)
{uint16_t timeout = 0xffff;// 判断数据寄存器是否为空while ((I2C1->SR1 & I2C_SR1_TXE) == 0 && timeout){timeout--;}// 要发送的数据写到寄存器I2C1->DR = byte;// 判断字节是否发送结束timeout = 0xffff;while ((I2C1->SR1 & I2C_SR1_BTF) == 0 && timeout){timeout--;}return timeout ? OK : FAIL;
}// 读一个字节数据
uint8_t I2C_ReadByte(void)
{uint16_t timeout = 0xffff;// 判断数据寄存器是否为非空while ((I2C1->SR1 & I2C_SR1_RXNE) == 0 && timeout){timeout--;}// 数据寄存器的值返回uint8_t data = timeout ? I2C1->DR : 0;return data;
}
下面代码截取了i2c.c的一部分代码,用于配置CCR和TRISE寄存器的相应位。
// 3.4 配置时钟控制分频系数// Thigh=CCR * Tcplk1// ccr = Thigh/=Tcplk1 = 5us / (1/36)us = 180I2C1->CCR |= 180;// 3.5 时钟信号的上升沿// 时钟频率是36MHz则 写入:1 /(1/36) + 1 = 37// 计算的是最大上升沿时间/时钟周期 + 1I2C1->TRISE |= 37;
如何根据需求配置?
1.2.1 CCR相应位配置
T(high) 为SCL上升时间加SCL高电平时间
仿照上面来自手册的截图的示例来计算:
在标准模式下,产生100KHZ的SCL的频率,对应周期10us,T(high) =T(low) =5us。
FREQR选择36MHZ,则T(PCLK1) = 1/36M = (1/36)us
那么相应CCR位写入5us/ (1/36)us = 180
1.2.2 TRISE相应位配置
100KHz(标准i2c)的时候要求最大上升沿不超过1us
仿照示例来计算:
时钟(FREQ[5:0])选择36MHZ 则写入1us / (1/36 us) +1 = 37
1.3 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H
#include "stm32f10x.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
1.4 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{I2C_init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 开始信号I2C_Start();// 发送写地址I2C_SendAddr(ADDR_WRITE);// 发送内部地址I2C_SendByte(inneraddr);// 发送数据I2C_SendByte(byte);// 停止信号I2C_Stop();// 写周期Delay_ms(5);
}// 页写入 一次性写入多个字节
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 开始信号I2C_Start();// 发送写地址I2C_SendAddr(ADDR_WRITE);// 发送内部地址I2C_SendByte(inneraddr);for (uint8_t i = 0; i < len; i++){// 发送数据I2C_SendByte(bytes[i]);}// 停止信号I2C_Stop();// 写周期Delay_ms(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{// 起始信号I2C_Start();// 发送写地址 假写I2C_SendAddr(ADDR_WRITE);// 发送内部地址I2C_SendByte(inneraddr);// 起始信号I2C_Start();// 发送读地址 真读I2C_SendAddr(ADDR_READ);// 设置非应答I2C_NAck();// 设置在接收下一个字节后发出停止信号I2C_Stop();// 读取一个字节uint8_t byte = I2C_ReadByte();return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{// 起始信号I2C_Start();// 发送写地址 假写I2C_SendAddr(ADDR_WRITE);// 发送内部地址I2C_SendByte(inneraddr);// 起始信号I2C_Start();// 发送读地址 真读I2C_SendAddr(ADDR_READ);for (uint8_t i = 0; i < len; i++){if (i < len - 1){// 设置发送应答信号I2C_Ack();}else{// 设置最后一个字节发送非应答信号I2C_NAck();// 设置停止信号I2C_Stop();}// 读取一个字节bytes[i] = I2C_ReadByte();}
}
在上面代码中设置非应答和停止信号要在读取字节之前
// 设置非应答I2C_NAck();// 设置在接收下一个字节后发出停止信号I2C_Stop();// 读取一个字节uint8_t byte = I2C_ReadByte();
因为在手册中对主接收器有下面说明:
1.5 main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "at24c02.h"
#include "string.h"int main(void)
{USART1_init();AT24C02_Init();printf("hello world!\r\n");AT24C02_WriteByte(0x00, 'a');AT24C02_WriteByte(0x01, 'b');AT24C02_WriteByte(0x02, 'c');uint8_t byte1 = AT24C02_ReadByte(0x00);uint8_t byte2 = AT24C02_ReadByte(0x01);uint8_t byte3 = AT24C02_ReadByte(0x02);printf("%c\r\n", byte1);printf("%c\r\n", byte2);printf("%c\r\n", byte3);AT24C02_WriteBytes(0x00, "123456", 6);uint8_t buff[16] = {0};AT24C02_ReadBytes(0x00, buff, 6);printf("%s\r\n", buff);// 页写 最多一次性写入8个字节 超过覆盖掉前面数据AT24C02_WriteBytes(0x00, "123456abc", 9);memset(buff, 0, sizeof(buff));AT24C02_ReadBytes(0x00, buff, 9);printf("%s\r\n", buff);while (1){}
}
2. HAL库实现
使用STM32CubeMX配置,开启I2C1,默认配置。同时需要开启UART1,因为我们需要使用串口打印。
在usart.c中重写fputc,方便后面使用printf
#include "stdio.h"
int fputc(int ch, FILE *file)
{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}
向工程中添加at24c02.h、at24c02.c文件
2.1 at24c02.h
#ifndef __AT24C02_H
#define __AT24C02_H#include "stm32f1xx_hal.h"#define ADDR_READ 0xA1
#define ADDR_WRITE 0xA0void AT24C02_Init(void);
void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte);
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);
uint8_t AT24C02_ReadByte(uint8_t inneraddr);
void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len);#endif
2.2 at24c02.c
#include "at24c02.h"
#include "i2c.h"void AT24C02_Init(void)
{MX_I2C1_Init();
}void AT24C02_WriteByte(uint8_t inneraddr, uint8_t byte)
{// 向设备指定内存地址写入数据// MemAddSize:内存地址大小HAL_I2C_Mem_Write(&hi2c1, ADDR_WRITE, inneraddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);// 写周期HAL_Delay(5);
}// 页写入 一次性写入多个字节
void AT24C02_WriteBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{HAL_I2C_Mem_Write(&hi2c1, ADDR_WRITE, inneraddr, I2C_MEMADD_SIZE_8BIT, bytes, len, 1000);// 写周期HAL_Delay(5);
}uint8_t AT24C02_ReadByte(uint8_t inneraddr)
{uint8_t byte;HAL_I2C_Mem_Read(&hi2c1, ADDR_READ, inneraddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);return byte;
}void AT24C02_ReadBytes(uint8_t inneraddr, uint8_t *bytes, uint8_t len)
{HAL_I2C_Mem_Read(&hi2c1, ADDR_READ, inneraddr, I2C_MEMADD_SIZE_8BIT, bytes, len, 1000);
}