写在前面
这篇博客记录下我在开发板上使用AT24C02芯片的过程。
本次用AT24C02实现的是EEPROM的功能,可以实现断电记忆功能。
单片机是 STC12C5A60S2 平台是 普中HC6800-ES V2.0
24C02与单片机使用的是IIC协议通信,这个协议网上的资料很多,这里就不再赘述了。下面给出各功能的代码。
单片机告诉24C02开始发送数据:
/*******************************************************************************
* 函数名 : I2cStart()
* 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入 : 无
* 输出 : 无
* 备注 : 起始之后SDA和SCL都为0
*******************************************************************************/
void iic_start(){
sda = 1;
delay1;
scl = 1;
delay1;
sda = 0;
delay1;
scl = 0; //拉低scl,占用scl准备发送数据
delay1;
}
中间的delay1是一个短延时。延时时间是IIC协议规定的。起始信号就是当SCL为高电平时,SDA从高电平降到低电平。当24C02收到这个信号之后就会做好接收准备。
单片机写入字节到24C02:
/*******************************************************************************
* 函数名 : iic_wr(uint8 dat)
* 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入 : dat
* 输出 : none
* 备注 : 起始之后SDA和SCL都为1
*******************************************************************************/
void iic_wr(uint8 dat){
uint8 i;
for(i=0;i<8;i++){
if(dat & 0x80){
sda = 1;
}
else{
sda = 0;
}
dat = dat << 1;
scl = 1; //拉高scl,让从机接收sda
delay1;
scl = 0; //占用scl,让sda允许改变
delay1;
}
scl = 1; //释放SCL和SDA
delay1;
sda = 1;
delay1;
}
单片机从24C02读一个字节:
/*******************************************************************************
* 函数名 : iic_rd()
* 函数功能 : 使用I2c读取一个字节
* 输入 : none
* 输出 : dat
* 备注 :
*******************************************************************************/
uint8 iic_rd(){
uint8 i;
uint8 dat;
scl = 0; //在改变sda前先确保scl为低电平
delay1;
sda = 1; //主机释放sda,以便从机可以改变sda
delay1;
for(i=0;i<8;i++){
scl = 1; //拉高scl,接收从机发来的sda
delay1;
dat <<= 1;
dat |= sda;
scl = 0;
delay1; //拉低scl,让从机可以改变sda的电平
}
return dat;
}
单片机检测24C02是否发出了应答信号:
/*******************************************************************************
* 函数名 : wait_ack()
* 函数功能 : 检测24C02是否发出了应答信号
* 输入 : 无
* 输出 : 0 or 1
*******************************************************************************/
bit wait_ack(){
uint8 max_time = 0;
// scl = 1; //释放SCL和SDA
// delay1;
// sda = 1;
// delay1; //iic_wr后scl和sda都已经释放
while(sda){
if(++max_time > 200){ //等待时间超过200,视为无应答,发送停止信号
scl = 0;
iic_stop();
return 0;
}
}
scl = 0; //应答正常,拉低scl形成第9个时钟脉冲
return 1;
}
单片机向24C02发出停止信号:
/*******************************************************************************
* 函数名 : iic_stop()
* 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入 : 无
* 输出 : 无
* 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void iic_stop(){
sda = 0;
delay1;
scl = 1;
delay1;
sda = 1;
}
这就是IIC通信的核心部分代码。后面的读一个字节和写一个字节到24C02上都是调用上面这些功能代码。
往24C02某个位置写入数据
代码如下:
/*******************************************************************************
* 函数名 : write_byte(uint8 add,uint8 dat)
* 函数功能 : 往24c02的一个地址写入一个数据
* 输入 : add,dat
* 输出 : 无
*******************************************************************************/
void write_byte(uint8 add,uint8 dat){
iic_start();
iic_wr(0xa0); //写入设备地址
wait_ack();
iic_wr(add); //写入存放数据的地址
wait_ack();
iic_wr(dat); //写入数据
wait_ack();
iic_stop(); //结束写数据
}
读取24C02某个位置的数据:
/*******************************************************************************
* 函数名 : read_byte(uint8 add)
* 函数功能 : 读取24c02的一个地址的一个数据
* 输入 : add
* 输出 : dat
*******************************************************************************/
uint8 read_byte(uint8 add){
uint8 dat;
iic_start();
iic_wr(0xa0); //写入设备地址 最后一位为0代表主机要写入数据到从机
wait_ack();
iic_wr(add); //写入要读数据的地址
wait_ack();
iic_start();
iic_wr(0xa1); //写入设备地址 最后一位为1代表主机要读从机数据
wait_ack();
dat = iic_rd(); //保存读出的数据
iic_stop(); //结束读数据
return dat;
}
有了上面这些函数,只需要在主函数里面调用一下就能实现控制24C02来保存需要保存的数据了。
所有代码如下:
/**************************************************************************************
* EEPROM-IIC *
实现现象:下载程序后数码管后4位显示0,按K1保存显示的数据,按K2读取上次保存的数据,
按K3显示数据加一,按K4显示数据清零。最大能写入的数据是255.
注意事项:
***************************************************************************************/
#include <reg52.h>
#include "intrins.h"
#define delay1 {_nop_();_nop_();_nop_();_nop_();_nop_();}
//短延时
typedef unsigned char uint8;
typedef unsigned int uint16;
//************************************************端口声明
sbit sda = P2^0;
sbit scl = P2^1;
sbit ls138A = P2^2; //38译码器的A B C,用来选择需要显示的数码管(位选)
sbit ls138B = P2^3;
sbit ls138C = P2^4;
sbit key1 = P3^1;
sbit key2 = P3^0;
sbit key3 = P3^2;
sbit key4 = P3^3;
//************************************************变量 函数声明
uint8 code seg[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
uint8 com[4];
uint16 temp;
void iic_start();
void iic_stop();
bit wait_ack();
void iic_wr(uint8 dat);
uint8 iic_rd();
void write_byte(uint8 add,uint8 dat);
uint8 read_byte(uint8 add);
void delay(uint16 z); //延时
void scankey();
void display();
//*****************************************************************************
//* 函数名 : Delay10us()
//* 函数功能 :
//* 输入 : z
//* 输出 : 无
//*****************************************************************************/
void delay(uint16 z){
uint8 x,y;
for(x=z;x>0;x--){
for(y=110;y>0;y--);
}
}
/*******************************************************************************
* 函数名 : iic_start()
* 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入 : 无
* 输出 : 无
* 备注 :
*******************************************************************************/
void iic_start(){
sda = 1;
delay1;
scl = 1;
delay1;
sda = 0;
delay1;
scl = 0; //拉低scl,占用scl准备发送数据
delay1;
}
/*******************************************************************************
* 函数名 : iic_stop()
* 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入 : 无
* 输出 : 无
* 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void iic_stop(){
sda = 0;
delay1;
scl = 1;
delay1;
sda = 1;
}
/*******************************************************************************
* 函数名 : wait_ack()
* 函数功能 : 等待从机拉低SDA
* 输入 : 无
* 输出 : 无
* 备注 :
*******************************************************************************/
bit wait_ack(){
uint8 max_time = 0;
// scl = 1; //释放SCL和SDA
// delay1;
// sda = 1;
// delay1; //iic_wr后scl和sda都已经释放
while(sda){
if(++max_time > 200){ //等待时间超过200,视为无应答,发送停止信号
scl = 0;
iic_stop();
return 0;
}
}
scl = 0; //应答正常,拉低scl形成第9个时钟脉冲
return 1;
}
/*******************************************************************************
* 函数名 : iic_wr(uint8 dat)
* 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入 : dat
* 输出 :
* 备注 :
*******************************************************************************/
void iic_wr(uint8 dat){
uint8 i;
for(i=0;i<8;i++){
if(dat & 0x80){
sda = 1;
}
else{
sda = 0;
}
dat = dat << 1;
scl = 1; //拉高scl,让从机接收sda
delay1;
scl = 0; //占用scl,让sda允许改变
delay1;
}
scl = 1; //释放SCL和SDA
delay1;
sda = 1;
delay1;
}
/*******************************************************************************
* 函数名 : iic_rd()
* 函数功能 : 使用I2c读取一个字节
* 输入 : 无
* 输出 : dat
* 备注 :
*******************************************************************************/
uint8 iic_rd(){
uint8 i;
uint8 dat;
scl = 0; //在改变sda前先确保scl为低电平
delay1;
sda = 1; //主机释放sda,以便从机可以改变sda
delay1;
for(i=0;i<8;i++){
scl = 1; //拉高scl,接收从机发来的sda
delay1;
dat <<= 1;
dat |= sda;
scl = 0;
delay1; //拉低scl,让从机可以改变sda的电平
}
return dat;
}
/*******************************************************************************
* 函数名 : read_byte(uint8 add)
* 函数功能 : 读取24c02的一个地址的一个数据
* 输入 : add
* 输出 : dat
*******************************************************************************/
uint8 read_byte(uint8 add){
uint8 dat;
iic_start();
iic_wr(0xa0); //写入设备地址 最后一位为0代表主机要写入数据到从机
wait_ack();
iic_wr(add); //写入要读数据的地址
wait_ack();
iic_start();
iic_wr(0xa1); //写入设备地址 最后一位为1代表主机要读从机数据
wait_ack();
dat = iic_rd(); //保存读出的数据
iic_stop(); //结束读数据
return dat;
}
/*******************************************************************************
* 函数名 : write_byte(uint8 add,uint8 dat)
* 函数功能 : 往24c02的一个地址写入一个数据
* 输入 : add,dat
* 输出 : 无
*******************************************************************************/
void write_byte(uint8 add,uint8 dat){
iic_start();
iic_wr(0xa0); //写入设备地址
wait_ack();
iic_wr(add); //写入存放数据的地址
wait_ack();
iic_wr(dat); //写入数据
wait_ack();
iic_stop(); //结束写数据
}
/*******************************************************************************
* 函数名 : scankey()
* 函数功能 : 按键扫描
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void scankey(){
if(!key1){
delay(1000);
if(!key1){
write_byte(1,temp);
while(!key1);
}
}
if(!key2){
delay(1000);
if(!key2){
temp = read_byte(1);
while(!key2);
}
}
if(!key3){
delay(1000);
if(!key3){
if(temp < 65536) temp++;
while(!key3);
}
}
if(!key4){
delay(1000);
if(!key4){
if(temp > 0) temp--;
while(!key4);
}
}
}
/*******************************************************************************
* 函数名 : display()
* 函数功能 : 数码屏显示
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void display(){
uint8 i;
com[0] = seg[temp/1000];
com[1] = seg[temp%1000/100];
com[2] = seg[temp%1000%100/10];
com[3] = seg[temp%1000%100%10];
for(i=0;i<4;i++){
switch(i){
case 0: ls138A = 0;ls138B = 0;ls138C = 0;break;
case 1: ls138A = 1;ls138B = 0;ls138C = 0;break;
case 2: ls138A = 0;ls138B = 1;ls138C = 0;break;
case 3: ls138A = 1;ls138B = 1;ls138C = 0;break;
}
P0 = com[3-i];
delay(300);
P0 = 0x00; //消隐
}
}
void main(){
scl = 1;
sda = 1; //IIC初始化
temp = 0;
while(1){
scankey();
display();
}
}
实验现象没办法发动图出来,下图第一张是按K3将temp加到20,然后按K1保存到AT24C01的0x01这个地址。
重新上电之后初始状态是这样的,temp因为掉电被清零了,所以数码管上显示0。
按下K2之后,将AT24C01中0x01地址的数据赋值给temp,经过显示程序刷新之后数码管上次重新显示0020,与图1是一样的。