一、存储器介绍
1、电子密码存储概述
单片机的电子密码存储是一种将密码信息以电子形式存储在单片机内部的技术。它通常用于需要保护敏感信息或限制访问权限的应用程序,如安全系统、门禁系统、电子锁等。
电子密码存储可以通过多种方式实现,以下是其中一种常见的概述:
(1)存储器选择:选择适合存储密码的内部或外部存储器。内部存储器通常是非易失性存储器(如闪存),可以在断电情况下保持数据。外部存储器可以是EEPROM(可擦写可编程只读存储器)或其他非易失性存储器(FLASH)。
(2)加密算法:为了增加密码的安全性,可以使用加密算法对密码进行加密。常见的加密算法包括DES(数据加密标准)、AES(高级加密标准)等。密码存储之前,将密码使用加密算法进行加密,然后再存储在存储器中。
(3)存储密码:将经过加密的密码存储在选择的存储器中。可以根据具体需求选择存储密码所需的存储空间大小。
(4)访问控制:为了安全地访问存储的密码,通常需要实现访问控制机制。这可以包括密码验证、访问权限设置等。例如,在门禁系统中,用户可能需要输入正确的密码才能获得访问权限。
(5)密码修改:为了方便用户,通常需要提供一种方式允许用户修改已存储的密码。这可以通过输入当前密码和新密码来实现。
总之,单片机的电子密码存储是通过选择适当的存储器、加密算法和访问控制机制,将密码以电子形式存储在单片机内部的技术。这种技术提供了一种安全可靠的方法来存储和管理密码信息。
2、EEPROM存储器和Flash存储器区别
EEPROM存储器和Flash存储器都属于电可擦除存储器,但由于内部的构成它们有着以下几点区别:
擦写次数:EEPROM存储器能擦写10万次以上,而FLASH存储器一般为1万次以下。
存储容量:在体积(芯片封装大小)相等情况下FLASH存储器的容量大。
适用范围:EEPROM存储器常用于存储经常修改的一些参数(如用户的一些设定值),而FLASH存储器则一般用于存储程序或者一些只读性数据。
通信协议:EEPROM存储器使用IIC通信协议,FLASH存储器使用SPI通信协议。
二、AT24Cxx存储芯片
1、AT24Cxx芯片概述
AT24Cxx是Atmel公司生产的低功耗CMOS型EEPROM存储芯片,采用I2C总线方式进行数据读写,可工作于标准模式、快速模式和高速模式。主要特点是可读可写,掉电数据不丢失。目前常用的有24C02、24C04、24C08、24C16、24C32。
2、AT24C04芯片功能
AT24C04内含4K bit,即512字节存储空间,分成32页,每页16个字节。并且AT24C04内部把512字节的存储空间分为2部分,每部分256Byte。AT24C04芯片采用I2C总线方式进行数据读写,可工作于标准模式、快速模式和高速模式。AT24Cxx的写周期约为5ms,也就是在写操作后5ms才能正常读出数据。
3、AT24C04芯片管脚描述
图AT24Cxx芯片功能管脚
表
管脚编号 | 管脚名称 | 功能管脚说明 |
1 | A0 | 空管脚 |
2 | A1 | 地址输入脚。地址输入脚根据硬件原理图的连接确定,如果管脚接电源,则为1, 接地则为0。 |
3 | A2 | |
5 | SCL | I2C通讯接口的串行数据线 |
6 | SDA | I2C通讯接口的串行时钟线 |
7 | WP | 为读写保护管脚,低电平时,可以对整个AT24C04器件的512个字节进行读写操作; 高电平后,器件前256个地址的数据被保护,只能读,不可写入,后256个字节数据 可进行读写操作。 |
4、8 | GND、VCC | 芯片电源负极和电源正极 |
三、AT24C04存储芯片应用
1、AT24C04地址寻址
(1)芯片器件寻址
AT24C04的芯片地址为1010,其地址控制格式为1010A2A1P0R/W。其中A2、A1为可编程地址选择位(A0为空引脚),数据空间由P0位决定,当P0位为“0”时,将对AT24C04的0~255空间数据进行操作;当P0位为“1”时,将对AT24C04的256~511空间数据进行操作。A2、A1引脚接高、低电平得到确定的两位编码加上P0位的数据与芯片地址(1010)形成7位器件的地址码,在7位地址编码最后1位加上数据方向为则构成了AT24C04芯片器件寻址的控制字节(如图12.11所示)。
操作0-255数据空间器件地址 + 写方向 = 10100000 == 0xA0
操作0-255数据空间器件地址 + 读方向 = 10100000 == 0xA1
AT24CXX控制字节格式
(2)片内子地址寻址
芯片片内寻址可对内部512Byte中的任意1个字节进行读/写操作,其寻址范围为00~1FF,共512个寻址单位。但在操作数据寻址的时候需要注意,由于AT24C04芯片分为2个数据区,使用P0为加以区别,当P0 = 0时,操作的是0~255的地址单元,当P0 = 1时,操作的是256~511的地址单元。
2、AT24C04芯片时序操作
(1)单字节写操作时序
单字节写操作要求在发送起始命令和从器件地址信息(R/W位清零)给从器件后,等待从器件产生应答信号,主器件发送1个字节子地址,即存储器内部地址,因AT24C04容量为512字节,所以子地址为0~512(需要注意P0位的参数,如果P0位参数为0,则无法操作256~511的地址)。主器件在收到从器件的另一个应答信号后,在接下来的时钟周期里主机发送8位数据,EEPROM再次应答,并在主器件产生停止信号后开始内部数据的擦写。在内部擦写过程中EEPROM不再应答主器件的任何请求。
AT24CXX单字节操作时序
起始条件 --> 器件地址 + 写方向 -->接收AT24C04的应答 --> 就是8位的数据(代表要写到这个AT24C04的哪个位置)-->接收AT24C04的应答 --> 发送8位的数据--> 接收AT24C04的应答-->停止条件
(2)页写操作时序
AT24C04提供32个16字节的页空间,用页写AT24C04可以一次写入16个字节数据,页写操作初始化与字节写相同,但在EEPROM接受到1字节数据后单片机并不发送停止条件,而是继续发送15字节数据,在每接受一字节数据,EEPROM发送一位应答。在发送完8字节数据后,单片机需发送停止信号以终止操作。
在进行页写操作时,存储器的子地址低三位会自动增一,但由于高位并不自增,所以在子地址加到页空间边界,即写入16字节后,下一字节会自动写入该页空间的第一字节,覆盖之前的数据,并且如果连续写入数据的地址超过256,地址将从0地址重新开始,从0地址开始的数据将覆盖之前的数据。AT42C04在接收到n+1字节数据和主器件发送的停止信号后启动内部写周期将数据写到数据区,所有接收的数据在一个写周期内写入EEPROM芯片内部。
AT24CXX页写操作时序
起始条件 --> 器件地址 + 写方向 -->接收AT24C04的应答 --> 就是8位的数据(代表要写到这个AT24C04的哪个位置)-->接收AT24C04的应答 --> 不断发送8位的数据(不能超过当前这一页的地址,否则会回到开头覆盖写入)--> 接收AT24C04的应答-->停止条件
(3)单字节读操作时序
主器件首先通过发送起始信号,接着发送从器件地址,在接收到应答后,再发送想读取的字节数据的地址执行一个伪写操作。在AT24C04应答之后,主器件重新发送发送起始信号和从器件地址(此时的器件地址最低位为1,表示读操作),此时子地址指向的为之前定义的值,AT24C04响应并发送应答信号,然后输出所要求的一个 8 位字节数据,主器件在接收到数据后发送非应答信号,并产生停止条件。
AT24CXX单字节读操作时序
起始条件 --> 器件地址 + 写方向 -->接收AT24C04的应答 --> 就是8位的数据(代表要读到这个AT24C04的哪个位置)--> 接收AT24C04的应答 -->起始条件 --> 器件地址 + 读方向 -->接收AT24C04的应答 -->接收AT24c04发送过来的数据 -->读完了回非应答信号 --> 停止条件
(4)连续读操作时序
连续读操作初始部分与单字节操作相同,在AT24C04发送完一个8 位字节数据后,主器件产生一个应答信号来响应,告知AT24C04主器件要求更多的数据。对应每个主机产生的应答信号,AT24C04将发送一个 8 位数据字节,当主器件发送非应答信号并发送停止位时结束此读操作。读操作可以连续进行,EEPROM内部地址会自动增加,这样整个寄存器区域在可在一个读操作内全部读出。当读取的字节超过最大地址时,地址计数器将翻转到0并继续输出数据字节。
AT24CXX连续读操作时序
起始条件 --> 器件地址 + 写方向 -->接收AT24C04的应答 --> 就是8位的数据(代表要读到这个AT24C04的哪个位置)-->接收AT24C04的应答 -->起始条件 --> 器件地址 + 读方向 -->接收AT24C04的应答 -->不断接收AT24c04发送过来的数据 -->直到回非应答信号才结束这次的接收 --> 停止条件
四、实例
#include "iic.h"
/****************************
函数功能:初始化IIC总线的IO口
函数形参:void
函数返回值:void
函数说明:
PA8 -- SCL -- 推挽输出
PC9 -- SDA
1.输入输出模式切换
SDA线既可以接收数据也可以发送数据
输入模式的时候不能输出数据
输出模式的时候可以读取数据
2.开漏输出
开漏输出:只能输出低电平
如果输出高电平就是断开输出电路
****************************/
void Iic_PortInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;//定义了一个结构体变量
//1. 打开GPIOB的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
//2. 配置GPIO口功能
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//配置输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//配置为推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;//选择8号管脚
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//无上下拉
GPIO_InitStruct.GPIO_Speed = GPIO_Low_Speed;//低速
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;//配置为开漏
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//选择9号管脚
GPIO_Init(GPIOC, &GPIO_InitStruct);
}
/****************************
函数功能:起始条件
函数形参:void
函数返回值:void
函数说明:在时钟线高电平期间,数据线产生下降沿
****************************/
void IIC_Start(void)
{
SDA_H;
SCL_H;
Delay_Us(2);//起始条件的建立时间
SDA_L;
Delay_Us(2);//起始条件的保持时间
SCL_L;//保证周期完整
}
/****************************
函数功能:停止条件
函数形参:void
函数返回值:void
函数说明:在时钟线高电平期间,数据线产生上升沿
****************************/
void IIC_Stop(void)
{
SDA_L;
SCL_H;
Delay_Us(2);//停止条件的建立时间
SDA_H;
Delay_Us(2);//停止和启动条件之间的总线空闲时间
}
/****************************
函数功能:发送应答位
函数形参:u8 ack
函数返回值:void
函数说明:0:代表应答
1:代表非应答
****************************/
void Send_Ack(u8 ack)
{
SCL_L;//发送方准备发送数据
if(ack)
{
SDA_H;
}
else
{
SDA_L;
}
Delay_Us(2);//发送数据所需要的时间
SCL_H;
Delay_Us(2);//接收数据所需要的时间
SCL_L;
Delay_Us(2);//保证一个完整的周期
}
/****************************
函数功能:接收应答位
函数形参:void
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Receive_Ack(void)
{
u8 ack = 0;
SDA_H;//断开输出电路
SCL_L;//发送方准备发送数据
Delay_Us(2);//发送数据所需要的时间
SCL_H;
if(SDA)
{
ack = 1;
}
Delay_Us(2);//接收数据所需要的时间
SCL_L;
Delay_Us(2);//保证一个完整的周期
return ack;
}
/****************************
函数功能:发送一个字节并接收一个应答位
函数形参:u8 data
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Send_Byte_Receive_Ack(u8 data)
{
u8 i,ack = 0;
for(i = 0; i < 8; i++)
{
SCL_L;//发送方准备发送数据
//1100 0011
//0100 0000
if(data & 0x80 >> i)
{
SDA_H;
}
else
SDA_L;
Delay_Us(2);//发送数据所需要的时间
SCL_H;
Delay_Us(2);//接收数据所需要的时间
}
ack = Receive_Ack();
return ack;
}
/****************************
函数功能:接收一个字节并发送一个应答位
函数形参:u8 data
函数返回值:u8
函数说明:
0:代表应答
1:代表非应答
****************************/
u8 Receive_Byte_Send_Ack(u8 ack)
{
u8 i = 0;
u8 data = 0;
for(i = 0; i < 8; i++)
{
SCL_L;//发送方准备发送数据
Delay_Us(2);//发送数据所需要的时间
SDA_H;//断开输出电路
SCL_H;
data <<= 1;//空出最低位
if(SDA)
{
data |= 1;
}
Delay_Us(2);//接收数据所需要的时间
}
Send_Ack(ack);
return data;
}
#include "at24c04.h"
/****************************
函数功能:页写
函数形参:u8 addr---写入的地址
u8 *str---写入的数据
u8 num--写入的个数
函数返回值:u8
函数说明:
****************************/
u8 AT24C04_PAGE_WRITE(u8 addr,u8 *str, u8 num)
{
IIC_Start();
if(Send_Byte_Receive_Ack(AT24C04_WRITEADDRESS))//先发器件地址写方向
{
IIC_Stop();
return 1;
}
if(Send_Byte_Receive_Ack(addr))//发送写入的地址
{
IIC_Stop();
return 2;
}
while(num--)//不断发送数据
{
if(Send_Byte_Receive_Ack(*str))
{
IIC_Stop();
return 3;
}
str++;
}
IIC_Stop();
Delay_ms(5);
return 0;
}
/****************************
函数功能:读数据
函数形参:u8 addr---写入的地址
u8 *str---写入的数据
u8 num--写入的个数
函数返回值:u8
函数说明:
****************************/
u8 AT24C04_READ(u8 addr,u8 *str, u8 num)
{
IIC_Start();
if(Send_Byte_Receive_Ack(AT24C04_WRITEADDRESS))//先发器件地址写方向
{
IIC_Stop();
return 4;
}
if(Send_Byte_Receive_Ack(addr))//发送写入的地址
{
IIC_Stop();
return 5;
}
IIC_Start();//起始条件
if(Send_Byte_Receive_Ack(AT24C04_READADDRESS))//发送读的地址
{
IIC_Stop();
return 6;
}
num = num - 1;
while(num--)
{
*str = Receive_Byte_Send_Ack(0);//前面的数据都发应答
str++;
}
*str = Receive_Byte_Send_Ack(1);//后面的数据发非应答
IIC_Stop();
return 0;
}
#ifndef __IIC_H_
#define __IIC_H_
#include "stm32f4xx.h"
#include "delay.h"
#define SCL_H GPIO_SetBits(GPIOA,GPIO_Pin_8)
#define SCL_L GPIO_ResetBits(GPIOA,GPIO_Pin_8)
#define SDA_H GPIO_SetBits(GPIOC,GPIO_Pin_9)
#define SDA_L GPIO_ResetBits(GPIOC,GPIO_Pin_9)
#define SDA GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9)
void Iic_PortInit(void);
void IIC_Start(void);
void IIC_Stop(void);
u8 Send_Byte_Receive_Ack(u8 data);
u8 Receive_Byte_Send_Ack(u8 ack);
#endif
#ifndef __AT24C04_H_
#define __AT24C04_H_
#include "stm32f4xx.h"
#include "iic.h"
#define AT24C04_WRITEADDRESS 0XA0
#define AT24C04_READADDRESS 0XA1
u8 AT24C04_READ(u8 addr,u8 *str, u8 num);
u8 AT24C04_PAGE_WRITE(u8 addr,u8 *str, u8 num);
#endif