/******************************************************************************************************************************/
/***补充:源码中有一部分是想要做串口通信的,后来一直在调按键之类的小问题,就耽搁了************/
/*********************************************************************************************************************/
一、目的
这一次的实验是个自选实验,刚开始选了RFID,本来以为难度没有很大,看了一下暑期用的工程感觉这个东西还是挺难的,从实验室借来的RFID识别的元件也不能用,就临时换了选题,选了这个非易失存储。
实验使用的是STC-B学习板,板上有一个非易失存储元件,要做的事情是使我们板上的芯片和这个存储元件可以“沟通”,完成存储器重要的功能:读,写,非易失
二、原理
存储部分使用的是24C02芯片,通过IIC协议与芯片实现沟通,板上共有八个24C02芯片,每个芯片有256个8bit的空间。
IIC使用了两条总线,这样说不太好,是一条总线中有两条信号线,一条代表数据线,一条代表时钟线,分别可以命名为SDA和SCL。
数据线的功能是比较丰富的,既可以传送地址信息又可以传送数据信息,所以需要分别来看
寻址过程:每次都发送一个字节的信息到存储芯片,芯片的高四位是固定的1010,低四位的高三位代表寻址范围,所以我们可以查找000-111共八个芯片,最后一位表示方向,0代表接下来的是写操作,1代表接下来的是读操作。
三种信号
1.start
/*IIC总线启动信号
*clk为高电平时,data由高电平向低电平跳变,开始传送数据
*/
void start()
{
dt = 1;
/*先置data线为1 再置clk为1 再跳变*/
Delay8();
clk = 1;
Delay8();
dt = 0;
Delay8();
}
其中的dt和clk分别代表数据线和时钟线,分别对应板上的P4^4和P5^0号接口,能传递的只有两种状态,高/低,所以前面说的通过数据线传送字节信息都是串行传送的。start信号的标志就是时钟线为高,数据线由高向低跳变,表示总线启动的信号,所以我们要先将数据线置一,再将时钟线置一,然后再将数据线置零,模拟启动信号需要的两条线上的情况,这里我自己写的时候是没有延时的,然后参考了一下之前的样例,发现这里有延时效果会好一点,给一个信号接收和处理的时间。
2.回复信号
/*IIC总线从机回复信号
*主设备每发送完8bit数据后等待从设备的ACK
*/
void res()
{
/*数据位为1统计次数*/
uchar time=0;
/*上升沿*/
clk = 1;
Delay8();
/*某时刻dt为低电平表示从机做好准备*/
while(dt == 1 && (time<256))
time++;
clk=0;
Delay8();
/*回复完毕*/
}
这里要做的事情主要是用于判断是否从机已经做好了准备,读或者写数据,当主机发送完8bit数据之后,会进入一种等待状态,当从机接收完信号,并做好了准备之后,会将数据线置零,所以如果主机发现数据线不再是高电平,或者超过了等待的限度,这个时候就将数据线拉低,后面如果需要其他操作的话再作修改。
3.终止信号
/*IIC总线终止信号
*clk为高电平时,data由低电平向高电平跳变,结束传送数据
*/
void fin()
{
dt = 0; /*先置data为0 再置clk为1 再跳变到高电平*/
Delay8();
clk = 1;
Delay8();
dt = 1;
Delay8();
}
终止信号可以类比开始信号来看,开始信号是时钟线为高的时候,数据线由高到低,而终止信号是时钟线为高的时候,数据线由低到高。要说这几种信号有什么作用呢,由前面的介绍可以知道,IIC协议只使用了这两条线进行信号传输,又是每次只能传输1bit所以需要定义多种bit的组合来实现后续的一些动作,比如我们根据两条线上的bit状态,定义了这三种信号,后面我们就可以根据这三种信号的搭配,实现更复杂的动作
三、动作
这里的动作可以粗略地概括为:向从机写数据,从从机读数据
写数据
/*向从机写一个字节的内容
* 将要传送的内容按bit发送到data线上
*/
void wrtbyte(uchar c)
{
uint i=0;
uchar temp=c;
for(i=0;i<8;i++)
{
temp = temp<<1;
clk = 0;
Delay8();
dt = CY;/*PSW寄存器表示溢出值,原最高位的值*/
Delay8();
clk = 1;
Delay8();
}
clk = 0;
Delay8();
dt = 1;
Delay8();
}
写数据需要注意的一点是,当时钟线为低的时候,我们才能改变数据线上的值
这里我们将需要写给从机的字节信息按位传送,每次将数据左移一位,这样每次就会溢出一位,溢出的一位在PSW寄存器中的CY位中,所以我们将CY的值赋给数据线,就实现了字节信息按位传送到数据线上,这里需要注意的是传送前将时钟线拉低,传送之后立即将时钟线拉高,防止产生一些错误。之后将时钟线拉低,这里的作用和循环中的拉低是一样的,再将数据线拉高,像前面所讲的,为等待从机回复做准备。
读数据
/*从从机读取一个字节的内容
*拆解成bit
*/
uchar rdbyte()
{
uint i=0;
uchar result;
clk = 0;
Delay8();
dt = 1;
Delay8();
/*按位读取出来每一位,由于从从机中弹出的bit是最高位
*所以左移之后,再与新来的bit合并*/
for(i=0;i<8;i++)
{
clk = 1;
Delay8();
result = (result<<1)|dt;
Delay8();
clk = 0;
Delay8();
}
Delay8();
return result;
}
前面说过,时钟线为低,才能改变数据线,所以先将时钟线拉低,然后先将数据线拉高,后面存储设备还可以对数据线做改动。
循环中,首先将时钟线拉高,保证数据线上的数不会改变了,然后将原数据与数据线做或运算,由于数据线上只有一位,所以每一次都是将数据线上的bit保存到低位,再将时钟线拉低,为后面的bit读取做准备。
四、更高级的功能
到这里只是实现了两种基本功能,与我们预期的实现非易失存储还有差距,差距在目前可以实现字节的读和取,但这字节的意义并没有明确,所以通过已有的功能,我们来完善一下。
从地址中读数据
/*从从机读取一个字节出来 执行顺序:
*devsel byteaddr devsel dataout
*/
uchar bytefromadd(uchar add)
{
uchar result;
start();
wrtbyte(0xa0); /*1010 0000*/
res();
wrtbyte(add);
res();
start();//重新start 发送取数据指令
wrtbyte(0xa1);
res();
result = rdbyte();
fin();
return result;
}
注释中已经介绍了读数据的时序问题,首先我们执行一次启动信号,告诉从机 我要对你有一些操作了
然后向从机发送一个信号,这个信号是寻找芯片的,像前面说过的,有八个存储芯片,要根据这一次传输的字节信息找到所需的芯片。然后等待一下从机的回应,等待到了之后再发送一个地址信息,这个地址对应的是片内地址,也就是我们之前说的256个8bit中的某一个再等待,最后向从机发送一个0xa1,最后一位是1,我们前面说过,为1是读操作,这之后我们就可以调用前面写好的从从机读取一个字节的方法,将数据读取出来了
向地址写数据
/*写字节时序 执行顺序:
*devsel byte addr datain
*向给定的地址写入给定的数据
*/
void byte2add(uchar adr,uchar dat)
{
/*启动*/
start();
wrtbyte(0xa0);
/*0xa0是24C02地址 1010 0000
*前面四位固定 最后一位:0写1读
*其余三位表示寻址范围,共八个
*/
res();//从机确认
wrtbyte(adr);
res();//从机确认
wrtbyte(dat);
res();//从机确认
fin();
return;
}
这个地方和读数据是很相似的,相比来说要简单一点。
同样的,要给从机一个启动的信号,然后输入芯片的寻址信息,查到8个存储芯片中的某一个,然后我们将要写入的片内地址发给从机,从机这时候就应该知道是对这个位置进行修改,然后我们将需要写入的字节信息发给从机,从机就可以把这个数据存到指定芯片的指定地址了。
五、收获和不足
这次实验的收获是了解了非易失存储的一些通信方式,像IIC协议的大致内容
不足在于这个实验先看了之前案例的说明和一些博客上的IIC的介绍,然后出现了很多的bug,比如没有没有做到非易失,按键修改修改不好,这两个问题现在已经解决,还有一个问题是每次保存之后,都不能立即看到存入地址的信息,需要重新刷新之后才能看见的确是存入了,这个问题现在还没有改好,也没什么头绪
六、源码
#include <STC15F2K60S2.h>
#include<intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define SendMax 2
#define NMAX_KEY 10
sbit K1 = P3^2;
sbit K2 = P3^3;
sbit K3 = P1^7;
sbit led = P2^3;
sbit dt = P4^0;
sbit clk = P5^5;
uchar duanxuan[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
uchar weixuan[] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};
uchar pos;/*位选变量*/
uchar adrs;/*写入的地址*/
uchar wrtdt;/*write data写入的数据*/
uchar rddt;/*read data读出的数据*/
bit flag_1ms;
uint cnt;/*timer0 interrupt times*/
int sendcnt=0;
int sendflag = 0;//0 空闲 1 busy
uint k1cnt;
uint k2cnt;
uint k3cnt;
uint kcnt;
bit k1p;
bit k1c;
bit k2p;
bit k2c;
bit k3p;
bit k3c;
void Delay8() //@11.0592MHz
{
// uchar i;
// i = 19;
// while (--i);
;;
}
void UartInit()
{
SCON = 0x50;
AUXR |= 0x40;
AUXR &= 0xFE;
TMOD &= 0x0F;
TL1 = 0x00;
TH1 = 0xF7;
ET1 = 0;
TR1 = 1;
ES = 1;
EA = 1;
}
void timer0_init()
{
TMOD = 0x01;
AUXR = 0x80;
EA = 1;
ET0 = 1;
TH0 = (65535-1000)/256;
TL0 = (65535-1000)%256;
TR0 = 1;
}
void IIC_init()
{
dt=1;
//Delay8();
clk = 1;
//Delay8();
}
void key_init()
{
cnt = 0;
flag_1ms = 0;
k1cnt = 0x80 + NMAX_KEY/3*2;
k2cnt = 0x80 + NMAX_KEY/3*2;
k3cnt = 0x80 + NMAX_KEY/3*2;
kcnt = NMAX_KEY;
k1p = 1;
k1c = 1;
k2p = 1;
k2c = 1;
k3p = 1;
k3c = 1;
}
void Init()
{
P2M0 = 0x08;
P2M1 = 0x00;
P0M0 = 0xff;
P0M1 = 0x00;
led = 0;
P0 = 0x00;
wrtdt = 0x00;
adrs = 0x00;
rddt = 0x00;
timer0_init();
IIC_init();
key_init();
UartInit();
}
/*动态扫描
*显示地址 写入数据 当前数据
*/
void display()
{
pos++;
P0 = 0x00; //不加这一句会有残影
if(pos==8)
pos=0;
P2 = weixuan[pos];
if(adrs >= 0xff)
adrs = 0x00;
if(wrtdt>= 0xff)
wrtdt = 0x00;
if(rddt>= 0xff)
rddt = 0x00;
switch(pos)
{
case 0x00:P0 = duanxuan[adrs/16];break;
case 0x01:P0 = duanxuan[adrs%16];break;
case 0x03:P0 = duanxuan[wrtdt/16];break;
case 0x04:P0 = duanxuan[wrtdt%16];break;
case 0x06:P0 = duanxuan[rddt/16];break;
case 0x07:P0 = duanxuan[rddt%16];break;
default:P0 = 0x40;break;
}
}
/*IIC总线启动信号
*clk为高电平时,data由高电平向低电平跳变,开始传送数据
*/
void start()
{
dt = 1;
/*先置data线为1 再置clk为1 再跳变*/
Delay8();
clk = 1;
Delay8();
dt = 0;
Delay8();
}
/*IIC总线从机回复信号
*主设备每发送完8bit数据后等待从设备的ACK
*/
void res()
{
/*数据位为1统计次数*/
uchar time=0;
/*上升沿*/
clk = 1;
Delay8();
/*某时刻dt为低电平表示从机做好准备*/
while(dt == 1 && (time<256))
time++;
clk=0;
Delay8();
/*回复完毕*/
}
/*IIC总线终止信号
*clk为高电平时,data由低电平向高电平跳变,结束传送数据
*/
void fin()
{
dt = 0; /*先置data为0 再置clk为1 再跳变到高电平*/
Delay8();
clk = 1;
Delay8();
dt = 1;
Delay8();
}
/*向从机写一个字节的内容
* 将要传送的内容按bit发送到data线上
*/
void wrtbyte(uchar c)
{
uint i=0;
uchar temp=c;
for(i=0;i<8;i++)
{
temp = temp<<1;
clk = 0;
Delay8();
dt = CY;/*PSW寄存器表示溢出值,原最高位的值*/
Delay8();
clk = 1;
Delay8();
}
clk = 0;
Delay8();
dt = 1;
Delay8();
}
/*从从机读取一个字节的内容
*拆解成bit
*/
uchar rdbyte()
{
uint i=0;
uchar result;
clk = 0;
Delay8();
dt = 1;
Delay8();
/*按位读取出来每一位,由于从从机中弹出的bit是最高位
*所以左移之后,再与新来的bit合并*/
for(i=0;i<8;i++)
{
clk = 1;
Delay8();
result = (result<<1)|dt;
Delay8();
clk = 0;
Delay8();
}
Delay8();
return result;
}
/*写字节时序 执行顺序:
*devsel byte addr datain
*向给定的地址写入给定的数据
*/
void byte2add(uchar adr,uchar dat)
{
/*启动*/
start();
wrtbyte(0xa0);
/*0xa0是24C02地址 1010 0000
*前面四位固定 最后一位:0写1读
*其余三位表示寻址范围,共八个
*/
res();//从机确认
wrtbyte(adr);
res();//从机确认
wrtbyte(dat);
res();//从机确认
fin();
return;
}
/*从从机读取一个字节出来 执行顺序:
*devsel byteaddr devsel dataout
*/
uchar bytefromadd(uchar add)
{
uchar result;
start();
wrtbyte(0xa0); /*1010 0000*/
res();
wrtbyte(add);
res();
start();//重新start 发送取数据指令
wrtbyte(0xa1);
res();
result = rdbyte();
fin();
return result;
}
void timer0() interrupt 1
{
cnt++;
//rddt = bytefromadd(adrs);
TH0 = (65535-1000)/256;
TL0 = (65535-1000)%256;
if(cnt==10)
{
cnt=0;
//rddt = bytefromadd(adrs); // 加入这一句之后按键无法工作
flag_1ms = 1;
}
display();
}
/*80ms*/
//void Delay() //@11.0592MHz
//{
// unsigned char i, j, k;
//
// _nop_();
// _nop_();
// i = 4;
// j = 93;
// k = 155;
// do
// {
// do
// {
// while (--k);
// } while (--j);
// } while (--i);
//}
void Uartisr() interrupt 4 //串口中断函数
{
if (TI)
{
TI = 0;
if (sendcnt >= SendMax)
sendflag = 0;
else //发送当前地址 和 内容
{
if(sendcnt == 0)
SBUF = adrs;
else if(sendcnt == 1)
SBUF = rddt;
sendcnt++;
}
}
else RI=0;
}
int main()
{
Init();
while(1)
{
while(flag_1ms)
{
rddt = bytefromadd(adrs);
flag_1ms = 0;
if(K1 == 0)
k1cnt--;
if(K2==0)
k2cnt--;
if(K3==0)
k3cnt--;
kcnt--;
if(kcnt <= 0x00)
{
if(k3cnt < 0x80)//k3按下
{
k3c = 0;
if(k3p == 1)//下降沿
{
k3p = 0;
adrs++;
if(adrs == 0xff)
{
adrs = 0x00;
}
}
}
if(k2cnt < 0x80) //k2按下
{
k2c = 0;
if(k2p == 1)
{
k2p = 0;
wrtdt++;
if(wrtdt == 0xff)
wrtdt = 0x00;
}
}
if( k1cnt <0x80)//k 1按下
{
k1c = 0;
if(k1p == 1)
{
k1p = 0;
byte2add(adrs,wrtdt);
rddt = bytefromadd(adrs);
}
}
if(k3cnt >= 0x80)
{
k3c = 1;
if(k3p == 0)
{
k3p = 1;
}
}
if(k2cnt >= 0x80)
{
k2c = 1;
if(k2p == 0)
{
k2p = 1;
}
}
if(k1cnt >= 0x80)
{
k1c = 1;
if(k1p == 0)
{
k1p = 1;
}
}
/*错误记录,之前将计数重置放在了外面,导致按键功能无法执行*/
k1cnt = 0x80 + NMAX_KEY/3*2;
k2cnt = 0x80 + NMAX_KEY/3*2;
k3cnt = 0x80 + NMAX_KEY/3*2;
kcnt = NMAX_KEY;
}
}
}
}