1 EEPROM及其背景知识
1.1 EEPROM
(1)一些概念:ROM、RAM、PROM(可编程ROM)、EPROM(可擦除ROM)、EEPROM(电可擦除ROM)。
(2)为什么需要EEPROM(单片机内部的ROM只能在程序下载时进行擦除和改写,但是程序运行本身是不能改写的。单片机内部的RAM中的数据程序运行时可以改,但是掉电就丢失了。有时候我们有一些数据要存在系统中,要求掉电不丢失,而且程序还要能改。所以内部ROM和RAM都不行。这时候系统中就需要一块EEPROM)。
(3)EEPROM(按照功能命名)和flash(按照工艺进行命名)的区别与联系,EEPROM可能是用flash工艺做出来的(flash可以做成EEPROM)。
(4)EEPROM存在系统中的2种形式:内置在单片机内部,外部扩展。
1.2 EEPROM如何编程
(1)I2C接口底层时序。
(2)器件定义的寄存器读写时序。
2 原理图和数据手册
2.1 原理图和接线确定
(1)关键性引脚定义及其接法(SCL对应P2.1,SDA对应P2.0)。
(2)接线。
2.2 数据手册浏览
(1)芯片基本信息。
(2)I2C从地址确定。
(3)I2C低层时序:起始信号、停止信号、发送字节、读取字节。
2.3 I2C总结
(1)主CPU和其附属芯片之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要。
(2)三根线:GND、SCL、SDA,串行、电平式。
(3)总线式结构,可以一对多,总线上可以挂上百个器件,用从地址来区分。
(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应。
(5)通信速率一般(kbps级别),不适合语音、视频等信息类型。
3 I2C低层时序图和程序
3.1 起始信号和结束信号
(1)起始信号:SCL保持高时,SDA有一个从高到低(下降沿)。
(2)结束信号:SCL保持高时,SDA有一个从低到高(上升沿)。
3.2 I2C发送一个字节
(1)I2C发送和接收字节时,都是从高位开始的。
3.3 应答位处理
3.4 I2C接收一个字节
概念:释放总线。在51单片机中,SDA=1就是释放总线;在其他更高级的单片机(譬如STM32等)这里的处理还会有点不一样。为什么SDA=1就是释放总线,是因为当51单片机把引脚拉高时,从设备可以选择再把这个引脚拉高或者拉低;但是当51单片机把这个引脚拉低(接地)后,从设备再也没办法把这个引脚拉高了。
4 EEPROM读写测试
4.1 24C02读写高层时序
(1)从器件的地址是由从器件自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片数据手册来确定。
(2)同一个I2C网络中只有一个主设备,但是从设备可以有多个。这多个从设备的从地址不能相同(硬件设计工程师必须保证这一点。因为从地址是不能通过软件设定的)。
(3)通过分析原理图和24C02的地址定义,可以得出:
从设备地址是:读地址:0xa1,写地址:0xa0
4.2 写时序
4.3 读时序
4.4 工程建立与文件导入
…
4.5 加入串口输出代码
…
4.6 测试EEPROM
…
4.7 程序问题解决
(1)通过调试发现程序跑飞了,经过检查发现uart中没有关中断。
(2)读出内容不对,怀疑是EEPROM经不起快速的连续读写,所以在读和写之间加入20ms的delay,测试后发现读写正确了。
(3)定义了局部变量没有初始化,程序中直接去通过串口输出,结果导致程序出现错误。
自己写代码中出现的问题:1.引脚定义错误。2.发送数据和接收数据时移位运算出错。
4.8 代码
i2c.h
#ifndef __I2C_H_
#define __I2C_H_
#include
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//--定义使用的IO口--//
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;
//--声明全局变量--//
void I2C_Delay10us();
void I2C_Start();
void I2C_Stop();
uchar I2C_SendByte(uchar dat, uchar ack);
uchar I2C_ReadByte();
#endif
i2c.c
#include "i2c.h"
/*******************************************************************************
* 函 数 名 : Delay1us()
* 函数功能 : 延时
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void I2C_Delay10us()
{
uchar a, b;
for(b=1; b>0; b--)
{
for(a=2; a>0; a--);
}
}
/*******************************************************************************
* 函 数 名 : I2C_Start()
* 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
* 输 入 : 无
* 输 出 : 无
* 备 注 : 起始之后I2C_SDA和I2C_SCL都为0
*******************************************************************************/
void I2C_Start()
{
I2C_SDA = 1;
I2C_Delay10us();
I2C_SCL = 1;
I2C_Delay10us();//建立时间是I2C_SDA保持时间>4.7us
I2C_SDA = 0;
I2C_Delay10us();//保持时间是>4us
I2C_SCL = 0;
I2C_Delay10us();
}
/*******************************************************************************
* 函 数 名 : I2C_Stop()
* 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
* 输 入 : 无
* 输 出 : 无
* 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
*******************************************************************************/
void I2C_Stop()
{
I2C_SDA = 0;
I2C_Delay10us();
I2C_SCL = 1;
I2C_Delay10us();//建立时间大于4.7us
I2C_SDA = 1;
I2C_Delay10us();
}
/*******************************************************************************
* 函 数 名 : I2cSendByte(uchar num)
* 函数功能 : 通过I2C发送一个字节。在I2C_SCL时钟信号高电平期间,
* * 保持发送信号I2C_SDA保持稳定
* 输 入 : num ,ack
* 输 出 : 0或1。发送成功返回1,发送失败返回0
* 备 注 : 发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0
*******************************************************************************/
uchar I2C_SendByte(uchar dat, uchar ack)
{
uchar a = 0,b = 0;//最大255,一个机器周期为1us,最大延时255us。
// 为了保证时序正确,这里应该加一句 SCL = 0;
for(a=0; a<8; a++)//要发送8位,从最高位开始
{
I2C_SDA = dat >> 7; //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
dat = dat << 1;
I2C_Delay10us();
I2C_SCL = 1;
I2C_Delay10us();//建立时间>4.7us
I2C_SCL = 0;
I2C_Delay10us();//时间大于4us
}
I2C_SDA = 1; // 主设备释放SDA线给从设备去操作
I2C_Delay10us();
I2C_SCL = 1; // 主设备开始了第9个周期
while(I2C_SDA && (ack == 1))//等待应答,也就是等待从设备把I2C_SDA拉低
{
b++;
if(b > 200) //如果超过200us没有应答发送失败,或者为非应答,表示接收结束
{
I2C_SCL = 0;
I2C_Delay10us();
return 0;
}
}
I2C_SCL = 0;
I2C_Delay10us();
return 1;
}
/*******************************************************************************
* 函 数 名 : I2cReadByte()
* 函数功能 : 使用I2c读取一个字节
* 输 入 : 无
* 输 出 : dat
* 备 注 : 接收完一个字节I2C_SCL=0
*******************************************************************************/
uchar I2C_ReadByte()
{
uchar a = 0,dat = 0;
I2C_SDA = 1; //起始和发送一个字节之后I2C_SCL都是0
I2C_Delay10us();
// 按道理这里应该有一个SCL = 0的
for(a=0; a<8; a++)//接收8个字节
{
I2C_SCL = 1; // 通知从设备我要开始读了,可以放1bit数据到SDA了
I2C_Delay10us();
dat <<= 1; // 读取的时候是高位在前的
dat |= I2C_SDA;
I2C_Delay10us();
I2C_SCL = 0; // 拉低,为下一个bit的周期做准备
I2C_Delay10us();
}
return dat;
}
at24c02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void At24c02Write(unsigned char addr,unsigned char dat);
unsigned char At24c02Read(unsigned char addr);
#endif
at24c02.c
#include "at24c02.h"
#include "i2c.h"
/*******************************************************************************
* 函 数 名 : void At24c02Write(unsigned char addr,unsigned char dat)
* 函数功能 : 往24c02的一个地址写入一个数据
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void At24c02Write(unsigned char addr,unsigned char dat)
{
I2C_Start();
I2C_SendByte(0xa0, 1);//发送写器件地址
I2C_SendByte(addr, 1);//发送要写入内存地址
I2C_SendByte(dat, 0); //发送数据
I2C_Stop();
}
/*******************************************************************************
* 函 数 名 : unsigned char At24c02Read(unsigned char addr)
* 函数功能 : 读取24c02的一个地址的一个数据
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
unsigned char At24c02Read(unsigned char addr)
{
unsigned char num;
I2C_Start();
I2C_SendByte(0xa0, 1); //发送写器件地址
I2C_SendByte(addr, 1); //发送要读取的地址
I2C_Start();
I2C_SendByte(0xa1, 1); //发送读器件地址
num=I2C_ReadByte(); //读取数据
I2C_Stop();
return num;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
main.c
#include
#include "at24c02.h"
#include "uart.h"
void delay20ms(void) //误差 -0.000000000005us
{
unsigned char a,b,c;
for(c=1;c>0;c--)
for(b=222;b>0;b--)
for(a=40;a>0;a--);
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
unsigned char i;
unsigned char addr;
unsigned char src_data[] = "()ab#cde!fg1234567";
unsigned char buf[8] = "ABCDEFGH";
uart_init();
/*
for (i=0; i<128; i++)
{
uart_send_byte(i);
}
while (1);
*/
// 先随便找一堆数据,譬如"abcdefg1234567-_-*&%@/\"
// 把这些写入EEPROM的特定地址中
// 然后读EEROM的这些地址,读出后通过串口打印出来看是不是我们写入的
uart_send_byte('%');
addr = 0;
for (i=0; i<8; i++)
{
At24c02Write(addr, src_data[i]);
delay20ms();
addr++;
}
for (i=0; i<8; i++)
{
uart_send_byte(buf[i]);
}
for (i=0; i<20; i++)
{
uart_send_byte('-');
}
// 读出测试
addr = 0;
for (i=0; i<8; i++)
{
buf[i] = At24c02Read(addr);
delay20ms();
addr++;
}
for (i=0; i<8; i++)
{
uart_send_byte(buf[i]);
}
while (1);
// 进一步测试
// 先写入一些特定内容,然后关机断电;然后改代码为读出并打印显示看内容
}
上一篇:I2C总线EEPROM实现
下一篇:标志寄存器及其标志位
推荐阅读最新更新时间:2024-03-16 16:08