一、IIC基础知识

1. 串口通信与IIC通信

  1. 串口通信通常需要至少三条线(TX、RX和GND),而 I2C 总线仅需要两条信号线(SDA和SCL);

  2. 串口通信仅支持一对一通信,而 I2C 总线支持多机通信,允许单个主机与多个从机设备进行通信;

  3. 串口通信通常无应答机制,而 I2C 必须有应答机制;

  4. 串口通讯一般是异步通信,而 I2C 使用同步传输方式,数据在时钟信号(SCL)的控制下传输。

2. IIC总线介绍

I2C总线,全称Inter-Integrated Circuit(互连集成电路),是一种由Philips(现NXP半导体)公司在1980年
代初开发的同步 串行 半双工通信总线。

在这里插入图片描述

IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。

IIC接上拉电阻,因此引脚配置成开漏输出(只可以输出低电平)。

2.1 工作原理

  1. 主从关系:主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。
  2. 数据传送:如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送。
    如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。
  3. 时钟同步:SCL用于数据的时钟同步,确保主从设备之间的数据传输同步进行。

2.2 主要特点

  1. 硬件简单:I2C总线只需要一根数据线和一根时钟线两根线,总线接口已经集成在芯片内部,不需要特殊的接口电路。
  2. 多主机总线:I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏。
  3. 在线检测:I2C总线可以通过外部连线进行在线检测,便于系统故障诊断和调试。
  4. 数据传输与地址设定:数据传输和地址设定由软件设定,非常灵活。总线上的器件增加和删除不影响其他器件正常工作。
  5. 负载能力:由于线路中电容会影响总线传输速度,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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90735.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90735.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/90735.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宝塔 php支持sqlserver

PDOException: SQLSTATE[IMSSP]: This extension requires the Microsoft ODBC Driver for SQL Server to communicate with SQL Server.错误原因这是 PHP 试图连接 SQL Server 数据库&#xff0c;但缺少必要的 ODBC 驱动支持 导致的。具体来说&#xff1a;你使用的是 PDO_SQLS…

day02-数组part02

一、长度最小的子数组&#xff08;滑动窗口&#xff09; leetcode 209 长度最小子数组 这道题的核心思想就是使用滑动窗口&#xff0c;滑动窗口三板斧&#xff1a; 初始位置i滑动窗口长度j-i1结束位置j 我们在写代码时是通过for循环来控制结束位置j&#xff0c;而初始位置i…

天爱验证码深度解析:从原理到实战,构建 Web 安全新防线

在网络安全日益严峻的当下&#xff0c;验证码作为抵御自动化攻击的重要屏障&#xff0c;其性能与可靠性直接关系到系统的安全稳定。天爱验证码&#xff08;TIANAI CAPTCHA&#xff09;作为国内优秀的开源行为验证码解决方案&#xff0c;凭借独特的技术优势&#xff0c;在电商、…

软考(软件设计师)软件工程-软件质量,软件测试,McCabe圈复杂度

软件质量 ISO/IEC 9126 是软件工程领域的经典质量模型&#xff0c;于1991年首次发布&#xff0c;2001年更新后成为软件产品质量评估的国际标准。其核心贡献是将抽象的“质量”概念分解为可度量、可管理的特性体系。以下是深度解析&#xff08;2023年行业实践视角&#xff09;&a…

CentOS7环境安装包部署并配置MySQL5.7

卸载MySQL卸载MySQL5.71、关闭MySQL5.7服务service mysqld stop2、查看MySQL安装rpm -qa|grep -i mysqlmysql-community-libs-5.7.35-1.el7.x86_64mysql-community-libs-compat-5.7.35-1.el7.x86_64mysql-community-common-5.7.35-1.el7.x86_64mysql57-community-release-el7-1…

1-Git安装配置与远程仓库使用

Git安装配置与远程仓库使用 1. Git 下载与安装 ① 进入Git 官网 https://git-scm.com/ ② 选择合适系统版本下载&#xff0c;本文以windows为例进行下载 当前最新版本为 2.50.1 &#xff0c;浏览器默认下载很慢&#xff0c;用迅雷比较快 ③ 安装Git 我安装在D盘 等待完…

开源“具身大脑” 实现不同机器人群体协作-RoboBrain

开源“具身大脑” 实现不同机器人群体协作-RoboBrain 具身大小脑协作框架RoboOS与开源具身大脑RoboBrain&#xff0c;实现跨场景多任务轻量化快速部署与跨本体协作&#xff0c;推动单机智能迈向群体智能&#xff0c;为构建具身智能开源统一生态加速场景应用提供底层技术支持。支…

【笔记】训练步骤代码解析

目录 config参数配置 setup_dirs创建训练文件夹 load_data加载数据 build_model创建模型 train训练 记录一下训练代码中不理解的地方 config参数配置 config {data_root: r"D:\project\megnetometer\datasets\WISDM_ar_latest\organized_dataset",train_dir: t…

Java填充Word模板

文章目录前言一、设置word模板普通字段列表字段复选框二、代码1. 引入POM2. 模板放入项目3.代码实体类工具类三、测试四、运行结果五、注意事项前言 最近有个Java填充Word模板的需求&#xff0c;包括文本&#xff0c;列表和复选框勾选&#xff0c;写一个工具类&#xff0c;以此…

【MYSQL8】springboot项目,开启ssl证书安全连接

文章目录一、开启ssl证书1、msysql部署时默认开启ssl证书2、配置文件3、创建用户并指定ssl二、添加Java信任库1、使用 keytool 导入证书2、验证证书是否已导入三、修改连接配置一、开启ssl证书 1、msysql部署时默认开启ssl证书 可通过命令查看&#xff1a; SHOW VARIABLES L…

Telegraf vs. Logstash:实时数据处理架构中的关键组件对比

在现代数据基础设施中&#xff0c;Telegraf 和 Logstash 是两种广泛使用的开源数据收集与处理工具&#xff0c;但它们在设计目标、应用场景和架构角色上存在显著差异。本文将从实时数据处理架构、时序数据库集成、消息代理支持等方面对比两者的核心功能&#xff0c;并结合实际应…

Vue Vue-route (4)

Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue-route 编程式导航和几种路由 目录 编程式导航 详情组件 创建组件 设置路由 电影列表 传参 另一种方式 动态路由 命名路由 别名 总结 编程式导航 点击电影列表 跳转电影详情 详情组件 创建组件 在views中创…

存在两个cuda环境,在conda中切换到另一个

进入 openmmlab 环境 conda activate openmmlab 设置环境变量为 CUDA 12.4&#xff08;只影响当前 shell 会话&#xff09; export PATH/usr/local/cuda-12.4/bin:PATHexportLDLIBRARYPATH/usr/local/cuda−12.4/lib64:PATH export LD_LIBRARY_PATH/usr/local/cuda-12.4/lib64:…

Django 视图(View)

1. 视图简介 视图负责接收 web 请求并返回 web 响应。视图就是一个 python 函数,被定义在 views.py 中。响应可以是一张网页的 HTML 内容、一个重定向、一个 404 错误等等。响应处理过程如下图: 用户在浏览器中输入网址:www.demo.com/1/100Django 获取网址信息,去除域名和端…

HarmonyOS基础概念

一、OpenHarmony、HarmonyOS和Harmony NEXT区别OpenHarmony是由开放原子开源基金会&#xff08;OpenAtom Foundation&#xff09;孵化及运营的开源项目&#xff0c;开放原子开源基金会由华为、阿里、腾讯、百度、浪潮、招商银行、360等十家互联网企业共同发起组建。目标是面向全…

spark3 streaming 读kafka写es

1. 代码 package data_import import org.apache.spark.sql.{DataFrame, Row, SparkSession, SaveMode} import org.apache.spark.sql.types.{ArrayType, DoubleType, LongType, StringType, StructField, StructType, TimestampType} import org.apache.spark.sql.functions._…

【跟着PMP学习项目管理】每日一练 - 3

1、你是一个建筑项目的项目经理。电工已经开始铺设路线,此时客户带着一个变更请求来找你。他需要增加插座,你认为这会增加相关工作的成本。你要做的第一件事? A、拒绝做出变更,因为这会增加项目的成本并超出预算 B、参考项目管理计划,查看是否应当处理这个变更 C、查阅…

CentOS 安装 JDK+ NGINX+ Tomcat + Redis + MySQL搭建项目环境

目录第一步&#xff1a;安装JDK 1.8方法 1&#xff1a;安装 Oracle JDK 1.8方法 2&#xff1a;安装 OpenJDK 1.8第二步&#xff1a;使用yum安装NGINX第三步&#xff1a;安装Tomcat第四步&#xff1a;安装Redis第五步&#xff1a;安装MySQL第六步&#xff1a;MySQL版本兼容性问题…

如何设计一个登录管理系统:单点登录系统架构设计

关键词&#xff1a;如何设计一个登录管理系统、登录系统架构、用户认证、系统安全设计 &#x1f4cb; 目录 开篇&#xff1a;为什么登录系统这么重要&#xff1f;整体架构设计核心功能模块安全设计要点技术实现细节性能优化策略总结与展望 开篇&#xff1a;为什么登录系统这么…

论迹不论心

2025年7月11日&#xff0c;16~26℃&#xff0c;阴 紧急不紧急重要 备考ing 备课不重要 遇见&#xff1a;免费人格测试 | 16Personalities&#xff0c;下面是我的结果 INFJ分析与优化建议 User: Anonymous (隐藏) Created: 2025/7/11 23:38 Updated: 2025/7/11 23:43 Exported:…