
(1)一些概念:ROM【只读存储器---硬盘】,RAM【随机访问存储器--内存】,PROM【可编程的ROM】,EPROM【可擦除ROM】,EEPROM【电可擦除ROM】

单片机内部的ROM只能在程序下载时进行擦除和改写,但是程序运行本身是不能改写的。
单片机内部的RAM中的数据程序运行时可以改,但是掉电就丢失了。
有时候我们需要有一些数据存在系统中,要求掉电不丢失,而且程序还要能改。所以内部ROM和RAM都不行。【这时候系统需要一块EEPROM】
1:内置在单片机内部
2:外部扩展

底层:CPU和I2C的接口
上层:器件时序




查看SCL和SDA无其他接线影响
SCL对应P2.1 SDA对应P2.0

立创商城_一站式电子元器件采购自营商城_现货元器件交易网-嘉立创电子商城


类似于一个主持人叫A说话,其他人就不可以说话,但是其他人可以听到主持人和A说话,但是不可以回应。 --广播式

主设备:51单片机---发送器
从设备:24Cxxx---接收器
每一个I2C都有从地址
起始信号:
发送字节:一般第一个是从设备的地址【因为我们在通话之前,要先发送要进行通话的地址,设备都与自己的地址是否相同,如果相同则响应;如果不同,则丢弃】
读取字节:
停止信号:


(1)主CPU和其附属芯片之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要
(2)三根线:SCL,SDA,GND,串行,电平式
(3)总线式结构:可以一对多,总线上可以挂上百个器件【一个主设备,多个从设备】,用【从地址】来区分--主设备不需要地址
(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应
(5)通信速率一般(kbps级别),不合适语音,视频等信息类型



从设备回复主设备,判断从设备是否得到数据。
可以设置是否要进行”应答信号“【可有可无】


SCL和SDA交互进行判断
(1)起始信号:SCL保持高时,SDA有一个从高到低(下降沿)
(2)结束信号:SCL保持高时,SDA有一个从低到高(上升沿)
- /*******************************************************************************
- * 函 数 名 : iic_start
- * 函数功能 : 产生IIC起始信号
- * 输 入 : 无
- * 输 出 : 无
- *******************************************************************************/
- void iic_start(void)
- {
- IIC_SDA=1;//如果把该条语句放在SCL后面,第二次读写会出现问题
- delay_10us(1);
- IIC_SCL=1;
- delay_10us(1);
- IIC_SDA=0; //当SCL为高电平时,SDA由高变为低,表示起始信号
- delay_10us(1);
- IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
- delay_10us(1);
- }
- /*******************************************************************************
- * 函 数 名 : iic_stop
- * 函数功能 : 产生IIC停止信号
- * 输 入 : 无
- * 输 出 : 无
- *******************************************************************************/
- void iic_stop(void)
- {
- IIC_SDA=0;//如果把该条语句放在SCL后面,第二次读写会出现问题
- delay_10us(1);
- IIC_SCL=1;
- delay_10us(1);
- IIC_SDA=1; //当SCL为高电平时,SDA由低变为高,表示结束信号
- delay_10us(1);
- }




- /*******************************************************************************
- * 函 数 名 : iic_write_byte
- * 函数功能 : IIC发送一个字节
- * 输 入 : dat:发送一个字节
- * 输 出 : 无
- *******************************************************************************/
- void iic_write_byte(u8 dat)
- {
- u8 i=0;
-
- //为了保证时序正确,这里要加上
- //当SDA将数据放好才可以将SCL置为高电平
- IIC_SCL=0;
- for(i=0;i<8;i++) //循环8次将一个字节传出,先传高再传低位
- {
- if((dat&0x80)>0)
- IIC_SDA=1;
- else
- IIC_SDA=0;
- dat<<=1; //将次高位移动到最高位
- delay_10us(1);
- IIC_SCL=1; //产生一个上升沿
- delay_10us(1);
- IIC_SCL=0; //产生一个下降沿
- delay_10us(1);
- }
- }

(1)I2C发送和接收字节时,都是从高位开始的

在接收完8位bit后,在第9个时间周期
应答处理:SDA变低【AT2402拉低】。
如果我们去检测,如果此时SDA为低电平,则表示已经被拉低,则表示已经响应到;如果SDA为高电平,则表示未能响应到。
- /*******************************************************************************
- * 函 数 名 : iic_ack
- * 函数功能 : 产生ACK应答
- * 输 入 : 无
- * 输 出 : 无
- *******************************************************************************/
- void iic_ack(void)
- {
- IIC_SCL=0;
- IIC_SDA=0; //SDA为低电平
- delay_10us(1);
- IIC_SCL=1; //将SCL拉高
- delay_10us(1);
- IIC_SCL=0; //在将SCL拉低
- }
- /*******************************************************************************
- * 函 数 名 : iic_nack
- * 函数功能 : 产生NACK非应答
- * 输 入 : 无
- * 输 出 : 无
- *******************************************************************************/
- void iic_nack(void)
- {
- IIC_SCL=0;
- IIC_SDA=1; //SDA为高电平
- delay_10us(1);
- IIC_SCL=1;
- delay_10us(1);
- IIC_SCL=0;
- }
- /*******************************************************************************
- * 函 数 名 : iic_wait_ack
- * 函数功能 : 等待应答信号到来
- * 输 入 : 无
- * 输 出 : 1,接收应答失败
- 0,接收应答成功
- *******************************************************************************/
- u8 iic_wait_ack(void)
- {
- u8 time_temp=0;
-
- IIC_SCL=1;
- delay_10us(1);
- while(IIC_SDA) //等待SDA为低电平
- {
- time_temp++;
- if(time_temp>100)//超时则强制结束IIC通信
- {
- iic_stop();
- return 1;
- }
- }
- IIC_SCL=0;
- return 0;
- }


在51单片机中,SDA=1就是释放总线【相当于主持人把话筒给嘉宾】;在其他更高级的单片机(比如STM32)这里的处理还会不一样。【因为拉高,则可以拉低(接地);但是拉低了,但是无法拉高】
为什么SDA=1就是释放总线??是因为当51单片机把引脚拉高时,从设备可以选择再把引脚拉高或者拉低;但是当51单片机把这个引脚拉低(接地)后,从设备也没有办法把这个引脚拉高了。
- unsigned char IIC_ReadByte()
- {
- unsigned char a=0,dat=0;
- //释放总线
- IIC_SDA=1; //起始和发送一个字节之后IIC_SCL都是0
- IIC_delay();
- //按道理来说这里应该有一个SCL=0的
- for(a=0;a<8;a++){
- IIC_SCL=1;//通知从设备我要开始读了,可以放bit数据到SDA了
- IIC_delay();
- dat<<=1; //读取的时候高位再前
- dat|=IIC_SDA;
- IIC_delay();
- IIC_SCL=0;// 拉低为下一个bit周期做准备
- ICC_delay();
- }
- return dat;
- }


(1)从器件的地址是由器件自身定义的,不同的从器件的地址的定义方式是不同的,要查具体的芯片数据手册来确定
(2)同一个I2C网络中只有一个主设备,但是从设备可以有多个。这多个从设备的地址不能相同。【硬件工程师必须保证这一点。因为从地址是不能通过软件设定的】
(3)A0,A1,A2----2的三次方=8【表示最多只能接8个EEPROM】
从CPU的角度来分析24C02的地址定义【如果不是从CPU角度看则得出结果不一样】
从设备地址是:读地址:0xa1
写地址:0xa0


start-send_byte(从地址)--send_byte(字节地址)---send_byte(写入数据)

- /*******************************************************************************
- * 函 数 名 : at24c02_write_one_byte
- * 函数功能 : 在AT24CXX指定地址写入一个数据
- * 输 入 : addr:写入数据的目的地址
- dat:要写入的数据
- * 输 出 : 无
- *******************************************************************************/
- void at24c02_write_one_byte(u8 addr,u8 dat)
- {
- iic_start();
- iic_write_byte(0XA0); //发送写命令,发送写器件地址
- iic_wait_ack(); //表示要接收应答
- iic_write_byte(addr); //发送写地址
- iic_wait_ack(); //表示要接收应答
- iic_write_byte(dat); //发送字节
- iic_wait_ack();
- iic_stop(); //产生一个停止条件
- delay_ms(10);
- }

- /*******************************************************************************
- * 函 数 名 : at24c02_write_one_byte
- * 函数功能 : 在AT24CXX指定地址写入一个数据
- * 输 入 : addr:写入数据的目的地址
- dat:要写入的数据
- * 输 出 : 无
- *******************************************************************************/
- void at24c02_write_one_byte(u8 addr,u8 dat[],u8 i)
- {
- u8 j;
- iic_start();
- iic_write_byte(0XA0); //发送写命令,发送写器件地址
- iic_wait_ack(); //表示要接收应答
- iic_write_byte(addr); //发送写地址
- iic_wait_ack(); //表示要接收应答
- for(j=0;j
- iic_write_byte(dat[i]); //发送字节
- iic_wait_ack();
- }
- iic_stop(); //产生一个停止条件
- delay_ms(10);
- }
3. 24C02读高层时序




- /*******************************************************************************
- * 函 数 名 : at24c02_read_one_byte
- * 函数功能 : 在AT24CXX指定地址读出一个数据
- * 输 入 : addr:开始读数的地址
- * 输 出 : 读到的数据
- *******************************************************************************/
- u8 at24c02_read_one_byte(u8 addr)
- {
- u8 temp=0;
- iic_start();
- iic_write_byte(0XA0); //发送写命令
- iic_wait_ack();
- iic_write_byte(addr); //发送写地址
- iic_wait_ack();
- iic_start();
- iic_write_byte(0XA1); //进入接收模式
- iic_wait_ack();
- temp=iic_read_byte(0); //读取字节
- iic_stop(); //产生一个停止条件
- return temp; //返回读取的数据
- }
4.复合格式
1.先发送在接收

2.字节写+随机读

4.加入串口输出代码
- /*******************************************************************************
- * 实验名 : EEPROM实验
- * 使用的IO :
- * 实验效果 : 按K1保存显示的数据,按K2读取上次保存的数据,按K3显示数据加一,
- *按K4显示数据清零。
- * 注意 :由于P3.2口跟红外线共用,所以做按键实验时为了不让红外线影响实验效果,最好把红外线先
- *取下来。
- *
- *********************************************************************************/
- #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++;
- }
-
- //先打印出buf
- //如果这里没有给buf初始化,则打印会出现问题
- 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);
-
- // 进一步测试
- // 先写入一些特定内容,然后关机断电;然后改代码为读出并打印显示看内容
-
- }
问题分析
(1)通过调试发现程序跑飞了,经检测发现uart中没有关中断
(2)读出内容不对,怀疑是EEPROM经不起快速的连续读写,所以在读和写之间加入20ms的delay,测试后发现读写正确了
(3)定义了局部变量没有初始化,程序中直接去通过串口输出,结果导致程序