写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2023.04.23
- 一、元件:AT24C02——E2PROM存储器
- 二、51:I2C总线
- 三、51:AT24C02数据存储(I2C总线)
- 1. 编写`I2C总线`函数
- 2. 编写`AT24C02数据存储`函数
- 3. 编写`main`函数
一、元件:AT24C02——E2PROM存储器
- 存储器介绍
详细可看计算机三级嵌入式系统开发 知识点笔记
- AT24C02介绍
- 引脚及应用电路(SCL、SDA需要上拉电阻)
(此处写保护为高电平有效,但我用的是低电平有效的存储器)
- 内部结构框图
二、51:I2C总线
I2C
总线介绍
详细可看计算机三级嵌入式系统开发 知识点笔记
I2C
电路规范
上拉电阻
将一个不确定的信号(高或低电平),通过一个电阻与电源VCC相连,固定在高电平。
一般说法是上拉增大电流,下拉电阻是用来吸收电流。
上拉电阻阻值的选择原则:
1、从节约功耗及芯片的灌电流能力考虑应当足够大;电阻大,电流小。
2、从确保足够的驱动电流考虑应当足够小;电阻小,电流大。
3、对于高速电路,过大的上拉电阻可能边沿变平缓。
综合考虑以上三点,通常在 1K 到 10K 之间选取。对下拉电阻也是类似道理。
开漏输出(Open Drain Output)
常说的与推挽输出相对的就是开漏输出,对于开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。
-
I2C
时序结构
起始条件触发后,SCL
开始产生时钟信号
SDA
发送一个二进制,SCL
拉高一次
如果在SCL
低电平切换SDA
,不会有任何影响
和发送相同,不过拉高SCL
和放置SDA
的变成了从机
主机发送字节后,从机在下一时钟发送应答
主机接收字节后,主机在下一时钟发送应答 -
I2C
数据帧
第一个字节中前7位为地址,第8位为读取或写入(1是read
,0是write
),如果器件只有A0~A2
三个引脚,那么A3~A6
为固定位(查手册)
如果在SCL
低电平时切换SDA
,认为触发了起始条件或终止条件
三、51:AT24C02数据存储(I2C总线)
1. 编写I2C总线
函数
- 寻找
I2C
对应引脚,很明显是P20
和P21
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
- 编写I2C发送开始信号函数
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
- 将I2C总线的数据线(SDA)置为高电平(1)。
- 将I2C总线的时钟线(SCL)置为高电平(1)。
- 将I2C总线的数据线(SDA)置为低电平(0),表示发送开始信号。
- 将I2C总线的时钟线(SCL)置为低电平(0),完成发送开始信号。
通过执行这段代码,可以在I2C总线上产生一个开始信号,该信号用于启动I2C通信过程。开始信号的形式是先将数据线(SDA)保持高电平,然后将时钟线(SCL)从高电平变为低电平,表示I2C通信的起始。
- 编写I2C发送停止信号函数
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
- 将I2C总线的数据线(SDA)置为低电平(0)。
- 将I2C总线的时钟线(SCL)置为高电平(1)。
- 将I2C总线的数据线(SDA)置为高电平(1),表示发送停止信号。
通过执行这段代码,可以在I2C总线上产生一个停止信号,该信号用于结束I2C通信过程。停止信号的形式是先将数据线(SDA)变为低电平,然后将时钟线(SCL)从高电平变为低电平,最后将数据线(SDA)变为高电平,表示I2C通信的结束。停止信号通常用于表示一次完整的数据传输完成。
- 编写I2C发送一个字节数据函数
/**
* @brief I2C发送一个字节
* @param Byte 要发送的字节
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
- 函数接受一个无符号字符类型的参数
Byte
,表示要发送的字节数据。 - 进入一个循环,循环执行8次,每次发送一个bit的数据。
- 在每次循环中,通过按位与操作
(Byte & (0x80 >> i))
来获取要发送的bit的值。 - 将得到的bit值设置到I2C总线的数据线(SDA)上,通过将I2C_SDA变量赋值为该bit值。
- 将I2C总线的时钟线(SCL)置为高电平(1),表示准备发送数据。
- 然后将I2C总线的时钟线(SCL)置为低电平(0),完成一个bit的发送。
- 循环结束后,函数完成了一个字节数据的发送。
该函数用于通过I2C总线发送数据时,通过连续发送8个bit来传输一个字节数据。具体的工作流程是根据要发送的字节数据,按位设置I2C总线的数据线(SDA)的状态,并通过控制I2C总线的时钟线(SCL)来实现数据的发送。
- 编写I2C接收一个字节数据函数
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
- 首先声明了一个无符号字符类型的变量
i
和Byte
,并将Byte
初始化为0x00,用于存储接收到的字节数据。 - 将I2C总线的数据线(SDA)置为高电平(1),表示准备接收数据。
- 进入一个循环,循环执行8次,每次接收一个bit的数据。
- 在每次循环中,首先将I2C总线的时钟线(SCL)置为高电平(1),表示准备接收数据。
- 然后通过判断I2C总线的数据线(SDA)的状态,来确定接收到的bit是0还是1。如果SDA为高电平,则将对应的bit位置为1(Byte的对应bit位根据循环次数递减),如果SDA为低电平,则对应的bit位置为0。
- 最后,将I2C总线的时钟线(SCL)置为低电平(0),完成一个bit的接收。
- 循环结束后,函数返回接收到的一个字节数据(Byte)。
该函数用于从I2C总线接收数据时,通过连续接收8个bit来组成一个字节数据。具体的工作流程是根据I2C总线的时钟和数据线状态来逐位接收数据,并将其组装成一个完整的字节数据。
- 编写I2C发送应答信号函数
/**
* @brief I2C发送应答
* @param AckBit 应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
- 将I2C总线的数据线(SDA)设置为应答位(AckBit),其中0表示应答,1表示非应答。
- 将I2C总线的时钟线(SCL)置为高电平(1)。
- 将I2C总线的时钟线(SCL)置为低电平(0),完成应答信号的发送。
通过执行这段代码,可以在I2C总线上发送应答信号,用于确认接收到的数据或指示拒绝接收数据。应答信号通常在主设备向从设备发送数据后,从设备进行响应,以指示是否成功接收数据。
- 编写I2C接收应答信号函数
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
- 将I2C总线的数据线(SDA)置为高电平(1)。
- 将I2C总线的时钟线(SCL)置为高电平(1)。
- 将接收到的应答位(AckBit)设置为I2C总线的数据线(SDA)的状态。
- 将I2C总线的时钟线(SCL)置为低电平(0),完成应答信号的接收。
通过执行这段代码,可以在I2C总线上接收从设备发送的应答信号,并将其作为函数的返回值返回。应答信号通常由从设备发送给主设备,用于确认接收到的数据或指示拒绝接收数据。函数返回的应答位为0表示应答,为1表示非应答。
2. 编写AT24C02数据存储
函数
- 确定固定地址
因为器件只有E0~E2
三个引脚,那么E3~E6
为固定位(1010)
#define AT24C02_ADDRESS 0xA0
- 编写 向AT24C02芯片写入一个字节数据 的函数
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
- 参数WordAddress表示要写入字节的地址。
- 参数Data表示要写入的数据。
- 函数首先发送开始信号(I2C_Start()),然后发送AT24C02芯片的地址(AT24C02_ADDRESS)并接收应答信号(I2C_ReceiveAck())。
- 接下来,发送要写入的字节的地址(WordAddress)并接收应答信号。
- 最后,发送要写入的数据(Data)并接收应答信号。
- 最后发送停止信号(I2C_Stop())结束通信。
- 编写 从AT24C02芯片读取一个字节数据 的函数
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;
}
- 参数WordAddress表示要读取字节的地址。
- 函数首先发送开始信号(I2C_Start()),然后发送AT24C02芯片的地址(AT24C02_ADDRESS)并接收应答信号。
- 接下来,发送要读取的字节的地址(WordAddress)并接收应答信号。
- 然后再次发送开始信号,发送AT24C02芯片地址的读取位(AT24C02_ADDRESS | 0x01)并接收应答信号。
- 接下来,从AT24C02芯片读取一个字节数据(Data = I2C_ReceiveByte())。
- 最后发送非应答信号(I2C_SendAck(1))表示读取结束,并发送停止信号(I2C_Stop())结束通信。
- 函数返回读取到的数据。
3. 编写main
函数
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1) //K1按键,Num自增
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2) //K2按键,Num自减
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3) //K3按键,向AT24C02写入数据
{
AT24C02_WriteByte(0,Num%256);
Delay(5);
AT24C02_WriteByte(1,Num/256);
Delay(5);
LCD_ShowString(2,1,"Write OK");
Delay(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4) //K4按键,从AT24C02读取数据
{
Num=AT24C02_ReadByte(0);
Num|=AT24C02_ReadByte(1)<<8;
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK ");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}
main
函数通过按键控制一个数字变量Num
的增减以及与AT24C02 EEPROM
芯片的读写操作。
下面是代码的执行流程:
- 首先,调用
LCD_Init(
)函数初始化LCD显示器。 - 调用
LCD_ShowNum()
函数在LCD
上显示Num
的当前值。 - 进入一个无限循环,程序将不断检测按键状态。
- 使用Key()函数获取当前按下的按键编号,并将其存储在
KeyNum
变量中。 - 如果按下的是
K1
按键(KeyNum
等于1),则将Num自增,并使用LCD_ShowNum()
函数在LCD上显示更新后的Num的值。 - 如果按下的是
K2
按键(KeyNum
等于2),则将Num自减,并使用LCD_ShowNum()
函数在LCD上显示更新后的Num的值。 - 如果按下的是
K3
按键(KeyNum
等于3),则将Num的低8位写入AT24C02
的地址0,并将Num的高8位写入AT24C02
的地址1。然后,在LCD上显示"Write OK"消息,并进行延迟等待一段时间后清除显示。 - 如果按下的是
K4
按键(KeyNum
等于4),则从AT24C02
的地址0读取一个字节,并将其存储在Num
的低8位中,然后从地址1读取一个字节,并将其存储在Num的高8位中。最后,使用LCD_ShowNum()
函数在LCD上显示读取到的Num的值,并显示"Read OK"
消息,并进行延迟等待一段时间后清除显示。 - 回到步骤3,继续等待按键操作。
这段代码演示了如何使用按键控制一个变量的增减,并将该变量的值写入和从EEPROM
芯片读取。