Bootstrap

STM32学习笔记---IIC与AT24C02

目录

一、什么是IIC

1、I2C总线概念

2、主机与从机的关系

3、主机的作用

3.1 波特率的选择

3.2 数据通信的方向

3.3 数据的发送和接收

4、通信配置方式

二、如何配置IIC

1、IO口模拟通信时序

2、IO口模拟IIC时序

2.1  数据线的开漏类型

2.2 IIC的时序图

3、程序设计

三、具体使用IIC

1、AT24C02

1.1 内存分类

1.2 什么是AT24C02

1.3 内部结构框图

1.4 引脚说明

2、基于IIC基础协议对AT24C02进行读写操作

2.1 读写位

2.2 单字节写入 

 2.3 单字节读取

 2.4 页写

 2.5 连续读

2.6 连续页写 

3、具体使用

需求1:往AT24C02中存储结构体,并且读出

需求2:打印开机次数

需求3:存储流水灯速度,按键调控流水灯,调控的速度要存储到AT24C02中


一、什么是IIC

1、I2C总线概念

I2C总线是由Philips公司开发的一种双向二线制同步串行半双工总线。它只需要两根线(SCL、SDA)即可在连接于总线上的器件之间传送信息。

2、主机与从机的关系

主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

3、主机的作用

3.1 波特率的选择

3.2 数据通信的方向

X:数据传输的方向 0:主机发送数据 1:主机接收数据

3.3 数据的发送和接收

写数据:开始--->发送从机地址+写(0)--->主发从收--->结束

读数据:开始--->发送从机地址+读(1)--->从发主收--->结束

注意:无论是开始还是结束都是由主机决定的

4、通信配置方式

        配置IIC通信的方式一般有两种,一种是配置通信控制器中的寄存器,利用IIC通信控制器驱动IO口产生通信协议时序,这种方式的优点就是稳定;;另一种就是IO口模拟通信时序,主要根据时序图利用IO口输出高低电平模仿通信时序,从而可以达到模拟通信时序的效果,并且这种方式的优点就是移植性好。由于考虑到后续程序的移植,所以IIC通信一般会使用IO口模拟时序的配置方式。两种方式具体的区别如下:

而此篇使用的是IO口模拟通信时序来配置IIC通信,为了更好的移植。 

二、如何配置IIC

1、IO口模拟通信时序

如何看时序图

规则:

因为一个通信协议规则是由时钟线和数据线一起体现出来的,即规则应该是时钟线和数据线结合看。

时钟线的某个电平状态态此时数据线是什么状态?

        规则①: 时钟线为低电平,数据线呈现出交叉状态

                        时钟线为低电平,可以写数据

                        想要写入数据,就要先把时钟线拉低

        规则②:时钟线为高电平,数据线呈现出平行状态

                        时钟线为高电平,可以读数据

                        想要读数据,就要先把时钟线拉高

看时序图方法:

        ①分清时钟线和数据线

        ②数据线数据段---------平行和交叉

        ③将时钟线与数据线结合--数据段规则         

        时钟线什么电平的时候可以写数据,时钟线什么电平的时候可以读数据

        ④看空闲状态

        ⑤看其他(从左到右看)

2、IO口模拟IIC时序

2.1  数据线的开漏类型

时钟线:是芯片提供时钟源,所以时钟线管脚为通用输出(推挽)

数据线:既能输入,又能输出,配置双向的,所以数据线配置为开漏输出

IIC的数据线为什么要配置开漏? 怎么输出?如何切换到输入?

首先,因为在输入时,输出禁止;而推挽输出既可以输出高电平又可以输出低电平,无法在输入时禁止输出;而开漏输出只能输出低电平,如果想输出高电平时,可以借助IIC的外接上拉电阻输出高电平;如果想切换到输入,就让输出数据寄存器ODR置1,使得输出禁止,这时就可以输入了。

开漏输出的优势:

  1. 防止短路
  2. 实现线与逻辑

外接上拉电阻与内置上拉电阻的区别

一、结构区别

内置上拉电阻:这是芯片本身自带的电阻,一般在输入端口内部连接。其电阻值固定,常见的有1KΩ、10KΩ等,具体值取决于芯片的设计。

外接上拉电阻:需要通过外部引脚连接到芯片上。其电阻值和型号可以根据实际需要选择,具有更高的灵活性。

二、使用区别

内置上拉电阻

优点:可以直接在硬件设计中使用,无需连接外部元器件,节省引脚资源和PCB面积,提高设计的灵活性。同时,简化了电路设计,降低了开发难度,适用于大多数应用场合。

缺点:电阻值固定,无法根据电路要求进行变化。在一些特殊场合下,如高精度模拟信号处理,内置上拉电阻的参数可能会产生影响。

外接上拉电阻

优点:电阻值可以根据实际需求进行选取,提高了电路的灵活性和适用性。尤其是在需要高精度信号处理的场合下,外接上拉电阻更具优势。

缺点:需要占用更多的引脚资源和PCB面积。

2.2 IIC的时序图

时序帧: 空闲段+起始段+数据段+应答段+停止段

空闲:

时钟线为高电平,数据线为高电平

起始信号:

时钟线为高电平,数据线为下降沿

数据段:

时钟线为低电平,数据线交叉(可读)

时钟线为高电平,数据线平行(可写)

停止信号:

时钟线为高电平,数据线为上升沿

应答:

IIC通信每传输一个字节(8位)就要有一个应答/不应答信号产生

主机是如何接收应答的?

①当主机作为发送数据端:---------主机检测应答/不应答信号

从机有IIC硬件电路,所以接收数据/检测应答--->从机自动完成

②当主机作为接收数据端:---------主机发送应答/不应答信号

从机有IIC硬件电路,所以发送数据/检测应答--->从机自动完成

 

应答/不应答信号总结:

①主机发送数据端

  1. 主机发送数据  //写程序
  2. 从机接收数据  //自动
  3. 从机发送应答信号/不应答信号 //自动
  4. 主机检测发送应答信号/不应答信号 //写程序

所以:检测应答/不应答函数

②主机接收数据端

  1. 从机发送数据  //自动
  2. 主机接收数据  //写程序
  3. 主机发送应答信号/不应答信号 //写程序
  4. 从机检测发送应答信号/不应答信号 //自动

所以:发送应答/不应答函数

 数据帧格式:

一帧数据帧的格式--->起始位+数据位+应答位+停止位 

总结:

空闲信号:

        IIC所用iO口初始化函数

起始信号:

        IIC起始信号函数

数据传输段:

        IIC发送一个字节函数

        IIC接收一个字节函数

应答段:

        发送应答/不应答信号函数

        检测应答/不应答函数

停止信号:

        IIC停止信号函数

3、程序设计

IIC标准时序图:

AT24Cxx时序图:

①起始信号函数:

时钟线拉低//为了改变数据线

数据线拉高//为下降沿做准备

时钟线拉高//起始信号的条件

延时5ms

数据线拉低//产生起始信号

延时5ms

时钟线拉低//安全作用

/*
函数名: iic_star
函数功能:IIC起始信号函数
返回值:void
形参:void
函数说明: 
				  
*/
void iic_star(void)
{
	//时钟线拉低   //为了可以改变数据线
	IIC_SCL_L;
	//数据线拉高   //为下降沿做准备
	IIC_SDA_OUT_H;
	
	//时钟线拉高   //起始信号的条件
	IIC_SCL_H;
	timer6_delay_us(5);
	//数据线拉低   //产生起始信号
	IIC_SDA_OUT_L;
	timer6_delay_us(5);
	
	//时钟线拉低 //安全作用
	IIC_SCL_L;
}

②停止信号函数:

时钟线拉低//为了改变数据线

数据线拉低//为上升沿做准备

时钟线拉高//停止信号的条件

延时5ms

数据线拉高//产生停止信号

延时5ms

/*
函数名: iic_stop
函数功能:IIC停止信号函数
返回值:void
形参:void
函数说明: 
				  
*/
void iic_stop(void)
{
	//时钟线拉低   //为了可以改变数据线
	IIC_SCL_L;
	//数据线拉低   //为上升沿做准备
	IIC_SDA_OUT_L;
	
	//时钟线拉高   //停止信号的条件
	IIC_SCL_H;
	timer6_delay_us(5);
	//数据线拉高   //产生停止信号
	IIC_SDA_OUT_H;
	timer6_delay_us(5);
}

③发送应答/不应答函数:

时钟线拉低//为了改变数据线

延时3ms

数据线拉低/拉高

延时2ms

时钟线拉高//主机帮助从机拉高时钟线,从机可以读取此位数据位

延时5ms

时钟线拉低 //安全作用

/*
函数名: iic_send_ack
函数功能:IIC发送应答/不应答函数
返回值:void
形参:u8 ack
函数说明: 传入1  主机停止接收数据
		   传入0  主机继续接收数据
*/
void iic_send_ack(u8 ack)
{
		//时钟线拉低   		//为了可以改变数据线
		IIC_SCL_L;
	  timer6_delay_us(3);
		//不应答--数据线拉高  应答数据线拉低
	  ack ? (IIC_SDA_OUT_H) : (IIC_SDA_OUT_L); 
		timer6_delay_us(2);
	  //时钟线拉高   		//产生应答\不应答信号的条件
		IIC_SCL_H;
		timer6_delay_us(5);
		//时钟线拉低			 //安全作用
		IIC_SCL_L;
		
}

接收应答\不应答函数:

 /*切换到输入模式*/

时钟线拉低//为了改变数据线

数据线拉高//为了关闭输出通道

/*读取应答信号*/

时钟线拉低//主机帮助从机拉低时钟线,从机自动发送应答/不应答信号

延时5ms

时钟线拉高//主机读取数据线

延时5ms

读取数据线   //读取数据不需要时间

时钟线拉低  //安全作用

返回应答

/*
函数名: iic_rec_ack
函数功能:IIC检测应答\不应答函数
返回值:u8
形参:void
函数说明: 
	        返回0   主机接收到从机的应答
			返回1   主机接收到从机的不应答
*/
u8 iic_rec_ack(void)
{
	  u8 ack;
	/*将数据线切换到输入*/
	//时钟线拉低   		//为了可以改变数据线
		IIC_SCL_L;
	//数据线拉高      //为了关闭输出通道
	  IIC_SDA_OUT_H;
	
	/*读取应答信号*/
	//时钟线拉低      //主机帮助从机拉低时钟线,从机自动发送应答/不应答信号
		IIC_SCL_L;
		timer6_delay_us(5);
	//时钟线拉高
		IIC_SCL_H;
	  timer6_delay_us(5);
	//读取数据线---读取数据不需要时间
		if(IIC_SDA_IN)
		{
			ack = 1;//不应答
		}
		else 
		{
			ack = 0;//应答
		}
	//时钟线拉低			 //安全作用
		IIC_SCL_L;
	
	  return ack;
	
}

⑤发送一个字节函数: 

时钟线拉低//为了改变数据线

延时3ms

/*循环一位一位的发送*/

时钟线拉低//为了可以改变数据线

延时3ms

根据要发送的数据的对应为是0还是1,决定时钟线拉高还是拉低

延时2ms

时钟线拉高//主机帮助从机拉高时钟线,从机可以读取此位数据位

延时5ms

下一位数据位

时钟线拉低  //安全作用

/*
函数名: iic_send_byte
函数功能:IIC发送一字节函数
返回值:void
形参:u8 data
函数说明: 		   

*/
void iic_send_byte(u8 data)
{
	  u8 i;
	
	//发一位接一位
	 for(i = 0;i < 8; i++)
	{
		//时钟线拉低   		//为了可以改变数据线
		IIC_SCL_L;
		timer6_delay_us(3);
		if(data & 0x80)
		{
			//数据线拉高
			IIC_SDA_OUT_H;
		}
		else
		{
			//数据线拉低
			IIC_SDA_OUT_L;
		}
		timer6_delay_us(2);
		//时钟线拉高
		IIC_SCL_H;//主机帮助从机拉高时钟线,从机可以读取此位数据位
		timer6_delay_us(5);
		data = data << 1;//下一位数据
	}
		//时钟线拉低			 //安全作用
		IIC_SCL_L;
	
}

⑥接收一个字节函数:

 /*切换到输入模式*/

时钟线拉低//为了改变数据线

数据线拉高//为了关闭输出通道

 /*读取数据*/

//循环-->一位一位的读取

时钟线拉低///主机帮助从机拉低时钟线,从机自动数据

延时5ms

时钟线拉高//主机读取此位数据

延时5ms

左移一位数据

读取数据线

时钟线拉低//安全作用

返回数据

/*
函数名: iic_rec_byte
函数功能:IIC接收一字节函数
返回值:u8 
形参:void
函数说明: 		

*/
u8 iic_rec_byte(void)
{
	u8 i;
	u8 data;
	/*将数据线切换到输入*/
	//时钟线拉低   		//为了可以改变数据线
	IIC_SCL_L;
	//数据线拉高      //为了关闭输出通道
	IIC_SDA_OUT_H;
	
	/*读取数据*/
	for(i = 0;i < 8;i++)
	{
		//时钟线拉低    //主机帮助从机拉低时钟线,从机改变数据线
		IIC_SCL_L;
		timer6_delay_us(5);
		//时钟线拉高    //主机读取此位数据位
		IIC_SCL_H;
		timer6_delay_us(5);
		data = data << 1;//低位补0
		//读取数据线
		if(IIC_SDA_IN)
		{
			data |= 0x01;
		}
		
	}
	//时钟线拉低			 //安全作用
		IIC_SCL_L;
	
		return data;
	
	
	
}

三、具体使用IIC

使用IIC通信是基于EEPROM的存储器上的,故需要了解什么是AT24C02

EEPROM(Electrically Erasable Programmable Read-Only Memory)是指带电可擦可编程只读存储器,它是一种非易失性存储器,能够在系统断电后仍然保留数据。

1、AT24C02

1.1 内存分类

RAM与ROM是计算机中常见的存储器类型。RAM(Random Access Memory)是一种临时存储器,用于存储计算机正在运行的程序和数据。它具有快速的读写速度和随机访问的特点。相比之下,ROM(Read-Only Memory)是一种只读存储器,用于存储固定的程序指令和数据。ROM中的数据在计算机断电时不会丢失,因此被称为非易失性存储器。ROM可分为MASKROM(掩模只读存储器)、PROM(可编程只读存储器)、EPROM(可擦除可编程只读存储器)、EEPROM、FLASH。而最常用的ROM是EEPROM和FLASHEEPROM是一种电可擦除的可编程ROM,相对于EPROM,它不需要紫外线擦除,它是利用电压脉冲进行擦除和编程数据;而FLASH是一种基于EEPROM技术的存储器,它是通过块擦除方式进行修改,相比EEPROM更具灵活性和可编程 

1.2 什么是AT24C02

AT24C02就属于EEPROM中的一种存储芯片

AT24C02特点

①at24c02是一种EEPROM存储芯片

②工作电压范围 1.8~5.5v (工作电压范围宽)

③支持低功耗模式(待机)

④内置256*8bit(共2k)存储空间

内部存储格式为8字节一页,共32页,构成256字节空间绝对地址0~255

⑥硬件数据写保护

把数据写进去,可以通过写保护管脚接入相应的电平时候,只能读不能写

⑦内部写周期最大5ms

当写数据时候,他先将数据存在存储器中,把相应地址空间的数据清除,然后才会把数据写到对应地址中。要给5ms的时间(写一次所需的时间,最大写入8个字节也就是一页)

⑧可按字节写,也可以按页写

按字节写:对应的找到地址空间,写入数据

按页写  :一页8字节,可以自动写下一个字节但是不能自动换页

起始地址编号  和  内容

读是没有限定的可以从0地址空间读到255地址空间(自动地址递增)

⑨可擦写100万次。(注意:跟读没关系)

1.3 内部结构框图

数据传输到数据缓冲器里,需要5ms的时间再写入到存储器中 

1.4 引脚说明

Vcc      : 电源

GND     : 地

A0,A1,A2 : 器件地址位

此器件地址: 1 0 1 0 A2 A1 A0 R/W 前四位固定,后三位自由设置,最后一位为读写控制位

                        (1:主机读AT24的数据   0:主机写数据到AT24)

根据原理图可知:A2 A1 A0 接地  

所以器件地址为: 1 0 1 0 0 0 0 R/W      写:10100000(0xa0)     读:10100001(0xa1)   

                                                                 0xa0:写模式                   0xa1:读模式

SDA      : IIC通信的数据线

SCL      : IIC通信的时钟线

WP       : 写保护管脚,接地可以正常读写,高电平只能读

2、基于IIC底层协议对AT24C02进行读写操作

2.1 读写位

接下来STM32要对存储芯片的读写操作

写: 0                 10100000      0xa0

读: 1                 10100001      0xa1

                                                                                

2.2 单字节写入 

字地址:器件内部存储空间地址(0~255)

/***************************************
*函数名			:at24c02_write_byte
*函数功能		:对at24c02某个空间存储一个字节数据
*函数参数		:u8  inner_addr     要存储的内部地址
				  u8  data           要存储的数据
*函数返回值	:u8
*函数描述		:阶段性错误返回标志
				AT24C02_NO_ERR  0;   //应答  无错误
				AT24C02_ERR1   1;   //无应答
				AT24C02_ERR2   2;   //无应答
			    AT24C02_ERR3   3;   //无应答
****************************************/
u8 at24c02_write_byte(u8 inner_addr,u8 data)
{
	u8 ack;
	iic_star();
	iic_send_byte(ATC24C02_ADDR_W);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	iic_send_byte(inner_addr);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR2;
	}
	
	iic_send_byte(data);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR3;
	}
	
	iic_stop();
	
	//写周期
	timer6_delay_ms(5);
	
	return AT24C02_NO_ERR;
}
 2.3 单字节读取

/***************************************
*函数名			:at24c02_read_byte
*函数功能		:从at24c02某个空间读一个字节数据
*函数参数		:u8  inner_addr     要读取的内部地址
				  u8 *data           读取到的内容放的地址
*函数返回值	:u8
*函数描述		:阶段性错误返回标志
				AT24C02_NO_ERR  0;   //应答  无错误
				AT24C02_ERR1    1;   //无应答
				AT24C02_ERR2    2;   //无应答
				AT24C02_ERR3    3;   //无应答
****************************************/
u8 at24c02_read_byte(u8 inner_addr,u8 *data)
{
	u8 ack;
	iic_star();
	iic_send_byte(ATC24C02_ADDR_W);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	iic_send_byte(inner_addr);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR2;
	}
	
	iic_star();
	iic_send_byte(ATC24C02_ADDR_R);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	*data = iic_rec_byte();
	
	iic_send_ack(1);//停止接收应答
	iic_stop();
	
	return AT24C02_NO_ERR;
	
}
 2.4 页写

为什么不用IIC发送一字节函数连续写入数据?

首先IIC发送一字节函数每发一次字节有5ms的写入周期,如果连续写入多个数据后则会造成写周期时间累积,导致效率低;所以可以用页写进行操作,页写是每页写完才会进行一次写周期,并且页内是可以自动写下一个字节但是不能自动换页。

分析:

需要根据写入的起始地址和写入的字节数判断是否跨页

Inner/8==(inner + num_byte-1)/8  不跨页

/***************************************
函数名    :at24c02_write_page
函数功能  :对at24c02的存储空间页写操作       
函数参数  :u8 inner_addr    //起始空间地址
           u8 num_byte     //要写的字节数
		   u8 *str         //要写内容的起始地址
函数返回值:每次发送都会加测响应,通过返回值判断具体哪次发送出问题
			宏定义错误标志:
			AT24C02_NO_ERR 0;   //应答  无错误
			AT24C02_ERR1   1;   //无应答
			AT24C02_ERR2   2;   //无应答
			AT24C02_ERR3   3;   //无应答
			AT24C02_OVER   4;   //跨页操作错误返回
****************************************/
u8 at24c02_write_page(u8 inner_addr,u8 num_byte,u8 *str)
{
	u8 ack;
	if(inner_addr/8 != (inner_addr+num_byte-1)/8 )//跨页
	{
		return AT24C02_OVER;
	}
	/*跨页操作*/
	iic_star();
	iic_send_byte(ATC24C02_ADDR_W);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	iic_send_byte(inner_addr);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR2;
	}
	//循环发送
	while(num_byte)//abc
	{
		iic_send_byte(*str);
		ack = iic_rec_ack();
		if(ack == 1)
		{
			return AT24C02_ERR3;
		}
		num_byte--;
		str++;
	}
	
	iic_stop();
	
	//写周期
	timer6_delay_ms(5);
	
	return AT24C02_NO_ERR;
	
}
 2.5 连续读

/***************************************
函数名  :at24c02_read_bytes
函数功能:从at24c02的某个地址空间开始连续读多个字节
函数参数:u8  inner_addr     //器件内部存储空间地址  0~255 
			 u16  num_byte      //要读多少个字节
			 u8   *data          //将读到的数据写入此地址	  
        
返回值  :每次发送都会加测响应,通过返回值判断具体哪次发送出问题
		宏定义错误标志:
		AT24C02_NO_ERR 0;   //应答  无错误
		AT24C02_ERR1   1;   //无应答
		AT24C02_ERR2   2;   //无应答
		AT24C02_ERR3   3;   //无应答
		AT24C02_OVER   4;   //跨页操作错误返回
****************************************/
u8 at24c02_read_bytes(u8 inner_addr,u16 num_byte,u8 *data)
{
	u8 ack;
	iic_star();
	iic_send_byte(ATC24C02_ADDR_W);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	iic_send_byte(inner_addr);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR2;
	}
	
	iic_star();
	iic_send_byte(ATC24C02_ADDR_R);
	ack = iic_rec_ack();
	if(ack == 1)
	{
		iic_stop();
		return AT24C02_ERR1;
	}
	
	/*连续读操作*/
	while(num_byte)
	{
		*data = iic_rec_byte();
		if(num_byte == 1)
		{
			iic_send_ack(1);//停止接收应答
		}
		else
		{
			iic_send_ack(0);
		}
		num_byte--;
		data++;
	}
	
	iic_stop();
	
	return AT24C02_NO_ERR;
	
}
2.6 连续页写 

说明:

从任意地址位置,连续写多字节,不要考虑跨页限制

分析:

不能跨页的原因:

页内地址自动递增,页之间的地址是不能自动递增的, 7号地址不能自动递增到8号地址

跨页的关键:如何切换到下一页

思路:

/************************************************
*函数名			:at24c02_write_bytes
*函数功能		:对at20c02的存储空间开始连续写 可跨页
*函数参数		:u8  inner_addr      起始地址
				  u16 num_byte        要读的字节数
                  u8  *str            要写的数据的首地址
*函数返回值	:u8
*函数描述		:
*************************************************/
void at24c02_write_bytes(u8 inner_addr,u16 num_byte,u8 *str)
{
	u8 less_byte;
	
	while(1)
	{
		//计算本页还剩多少空间可写 less_byte = 8 - inner_addr % 8   
		less_byte = 8 - inner_addr % 8;
		//如果要写的内容不需要跨页 less_byte >= num_byte
		if(less_byte >= num_byte)
		{
			//调用发送页写函数(inner_addr,num_byte,str)
			at24c02_write_page(inner_addr,num_byte,str);
			break;
		}
		//如果要写的内容需要跨页 less_byte < num_byte
		else 
		{
			//调用发送页写函数把本页剩下的空间写完(inner_addr,less_byte,str)
			at24c02_write_page(inner_addr,less_byte,str);
			//计算出剩下多少个字节:num_byte = num_byte - less_byte
			num_byte = num_byte - less_byte;
			//下一页的首地址: inner_addr = inner_addr + less_byte
			inner_addr = inner_addr + less_byte;
			//剩余要写的内容数据的地址:str = str + less_byte
			str = str + less_byte;
		}
		
	}
	
	
	
}

3、具体使用

需求1:往AT24C02中存储结构体,并且读出

分析:

存储一本书的信息

typedef struct book
{
	
	u8 name[15];
	u8 writer[15];
	u8 number[15];
	u32 hot;
  u32 sc;
	float price;
}BK;
 

int main(void)
{
	
	u8 start_val;
	u8 set_flag = 0;
	BK send_book = {"西游记","吴承恩","W201955",0,30,55.5};
	BK rec_book;
	NVIC_SetPriorityGrouping(5); //设置优先级分组
	Usart1_init(115200);//串口初始化
	
	at24c02_init();
	
	at24c02_write_bytes(0,sizeof(send_book),(u8 *)&send_book);
	at24c02_read_bytes(0,sizeof(rec_book),(u8 *)&rec_book);
	

		
	while(1)
	{
		printf("name:%s writer:%s number:%s hot:%d sc:%d price:%.1f\r\n",rec_book.name,rec_book.writer,rec_book.number,rec_book.hot,rec_book.sc,rec_book.price);
		timer11_delay_ms(200);
	}
	
}

需求2:打印开机次数

分析:

获取上一次的开机次数(从AT24C02中读出来)

读出来的数据+1,再写到AT24C02中(下次用)

根据需求怎么分析?

首先根据分析可得,先读取上一次开机次数->次数+1->打印开机次数->往AT24C02中存储本次的开机次数;

发现问题:第一次开机之前就没存过开机次数

解决问题:分情况第一次开机与不是第一次开机

又发现问题:如何判断第一次开机

解决问题:一个空间作为第一次开机标志位

具体如下:

                                                                        比喻

每次开机都去读取某个空间-------->每次都去”到此一游”留名处

如果读到不是0xff(自己定),则是第一次开机------>无”到此一游”则证明没来过

并且将0xff写入到空间内,以便下次开机判断是否为第一次开机------>写上”到此一游”,下次再来则证明来过了

    at24c02_init();
	/**********************
	10号是开机标志位 11号是开机次数
	**********************/
	//一开机就读取10号空间,以便于判断是否为第一次开机
	at24c02_read_byte(10,&open_flag);
	
	//第一次开机
	if(open_flag != 0xff)
	{
		open_cnt = 1;
		printf("开机次数:%d\r\n",open_cnt);
		//往AT24C02中存储开机次数
		at24c02_write_byte(11,open_cnt);
		//往AT24C02中存储开机标志位
		open_flag = 0xff;
		at24c02_write_byte(10,open_flag);
	}
	
	//不是第一次开机
	else
	{
		//获取上一次开机的次数
		at24c02_read_byte(11,&open_cnt);
		//打印
		open_cnt++;
		printf("开机次数:%d\r\n",open_cnt);
		//往AT24C02中存储开机次数
		at24c02_write_byte(11,open_cnt);

	}
	

需求3:存储流水灯速度,按键调控流水灯,调控的速度要存储到AT24C02中

优化:

①按按键1存储数据

KEY1在没有开流水灯的时候,起到开启作用--->显示初始速度或者上次存储的速度

开流水灯后,起到保存当前速度作用--->确定保存速度并且显示

②调整到某个速度后,3s不做任何操作,速度自动保存

按下按键开始计时     

如何做到按下按键开始计时?

利用定时中断-->按按键并且到时间 才保存速度(一次)

    at24c02_init();
	/**********************
	12号空间是开机标志位 
	13号空间作为存储流水灯的速度
	**********************/
	//一开机就读取12号空间,以便于判断是否为第一次开机
	at24c02_read_byte(12,&open_flag);
	
	//第一次开机
	if(open_flag != 0xff)
	{
		//赋值流水灯速度
		speed = 5;
		//往AT24C02中存储流水灯速度
		at24c02_write_byte(13,speed);
		
		//往AT24C02中存储开机标志位
		open_flag = 0xff;
		at24c02_write_byte(12,open_flag);
	}
	
	//不是第一次开机
	else
	{
		//获取上一次开机流水灯的速度
		at24c02_read_byte(13,&speed);
	}
	
	printf("当前流水灯速度:%d\r\n",speed);
		
	while(1)
	{
		  keynum = Key_scan();
			switch(keynum)
			{
				case 1:timer7_flag = 0;at24c02_write_byte(13,speed);printf("当前流水灯速度:%d\r\n",speed);led_flag = 1;break;//按键1打开流水灯
				case 2:led_flag = 0;LED_OFF;break;//按键2关闭流水灯
				case 3:if(led_flag){Tim7_cnt[8] = 0;timer7_flag = 1;speed -= 1;if(speed < 1)speed = 5;}break;//按键3加速
				case 4:if(led_flag){Tim7_cnt[8] = 0;timer7_flag = 1;speed += 1;if(speed > 10)speed = 5;}break;//按键4减速
			}
			if(led_flag == 1)
			{
				LED_flash(speed);
			}


	}
/*
函数名:TIM7_IRQHandler
函数功能:基本定时器中断服务函数
返回值:void
形参:void
函数说明:

*/
u16 Tim7_cnt[10];
u8 RGB_flag = 0;
u8 speed;
u8 timer7_flag = 0;
void TIM7_IRQHandler(void)
{
	//清除中断标志位
	TIM7->SR &= ~(1<<0);
	//紧急事件
	Tim7_cnt[8]++;
	
	if(Tim7_cnt[8] >= 3000)
	{
		if(timer7_flag == 1)
		{
			//标志位清零
			timer7_flag = 0;
			//获取流水灯速度
			at24c02_write_byte(13,speed);
			printf("当前流水灯速度:%d\r\n",speed);
			Beep_ON;
		}
		Tim7_cnt[8] = 0;
	}
	if(Tim7_cnt[8] >= 300)
		Beep_OFF;
	
	
	
}

③利用结构体来管理数据

typedef struct ctrval

{

        u8 open_flag; //第一次开机标志

        u8 speed;     //存储流水灯速度

        u16 open_cont;//存储开机次数

}CTRVAL_t;

at24c02_init();
	/**************************************
	设置参数存放地址:1
	***************************************/
	//读取开机标志空间
	at24c02_read_bytes(1,sizeof(ctr_data),(u8 *)&ctr_data);
	
	//第一次开机
	if(ctr_data.open_flag != 0xff)
	{
		//赋值开机标志位
		ctr_data.open_flag = 0xff;
		
		//赋值开机次数
		ctr_data.open_cont = 1;
		printf("第%d次开机\r\n",ctr_data.open_cont);
		
		//赋值流水灯速度
		ctr_data.speed = 5;
		printf("当前的速度:%d\r\n",ctr_data.speed);
		
		//往AT24C02中储存结构体数据
		at24c02_write_bytes(1,sizeof(ctr_data),(u8 *)&ctr_data);
	}
	
	//不是第一次开机
	else
	{
		//开机次数自增
		ctr_data.open_cont++;
		printf("第%d次开机\r\n",ctr_data.open_cont);
		
		//往AT24C02中储存结构体数据
		at24c02_write_bytes(1,sizeof(ctr_data),(u8 *)&ctr_data);
	}
	
	printf("当前流水灯速度:%d\r\n",ctr_data.speed);
		
	while(1)
	{
		  keynum = Key_scan();
			switch(keynum)
			{
				case 1:timer7_flag = 0;at24c02_write_bytes(1,sizeof(ctr_data),(u8 *)&ctr_data);printf("当前流水灯速度:%d\r\n",ctr_data.speed);led_flag = 1;break;//按键1打开流水灯
				case 2:led_flag = 0;LED_OFF;break;//按键2关闭流水灯
				case 3:if(led_flag){Tim7_cnt[8] = 0;timer7_flag = 1;ctr_data.speed -= 1;if(ctr_data.speed < 1)ctr_data.speed = 5;}break;//按键3加速
				case 4:if(led_flag){Tim7_cnt[8] = 0;timer7_flag = 1;ctr_data.speed += 1;if(ctr_data.speed > 10)ctr_data.speed = 5;}break;//按键4减速
			}
			if(led_flag == 1)
			{
				LED_flash(ctr_data.speed);
			}


	}
	
}

;