为兴趣挑灯夜战
分类: 嵌入式
2017-03-12 04:27:40
一、简介:
首先看看RFID标签长什么样:
这张图片叫做晶圆,上面秘密麻麻的刻好的RFID芯片的裸片,还未封装,就图中这一块晶圆上就有2万颗RFID芯片,把每一块芯片取下来固定到胶片上,加上天线焊盘的后的芯片叫绑定片,如下图中第一张图中线圈中心的那个黑块。再加上最外层的封装后做成卡片状、米粒状、扣状等等,但不论其外形如何,内部都是一个线圈加一个RFID芯片,如左边第一张图一样,芯片中间黑色的就是芯片,芯片通过线圈从RFID设备获取能量,原理和变压器是一会事,RFID产生电场,耦合到RFID芯片的线圈,给RFID芯片提供能量;同时,线圈也用于通信作用,RFID芯片或RFID设备通开启和关闭线圈上的电场来发送信号,这里并不是关闭电场就代表0,开启电场就代秒1,而是有一定的编码方式,比如说曼切斯特编码(一下简称曼码)。
二、编码简介
下面就先来看看曼码的描述,此部分内容来源于RFID世界网。
曼彻斯特编码也被称为分相编码(Split-Phase Coding)。在曼彻斯特编码中,某位的值是由该位长度内半个位周期时电平的变化(上升/下降)来表示的,在半个位周期时的负跳变表示二进制“1”,半个位周期时的正跳变表示二进制“0″,如下图所示。曼彻斯特编码在采用负载波的负载调制或者反向散射调制时,通常用于从电子标签到读写器的数据传输,因为这有利于发现数据传输的错误。这是因为在位长度内,“没有变化”的状态是不允许的。当多个电子标签同时发送的数据位有不同值时,接收的上升边和下降边互相抵消,导致在整个位长度内是不间断的副载波信号,由于该状态不允许,所以读写器利用该错误就可以判定碰撞发生的具体位置。
上面的文字描述要慢慢理解,简单的来说就是RFID线圈的电场在一个周期的中间时关闭代表1,在一个周期的中间开启代表0,要注意的一点是RFID标签线圈和RFID设备的线圈上的0和1恰好是相反的。在应用中,我们只关心RFID设备上线圈的信号。
在低频RFID中,有一个词叫ID64,是什么意思呢,其中ID就是identity,身份的意思,64就是说用于表示这个身份的号码是64个字节,也就是一个ID号而已,比如市面上现在很多低端的门禁锁,用的就是ID64.下面就以EM4100为例来介绍。从EM4100的手册中,可知其操作频率范围是100k-150Hz,具体使用的频率是要看我们给他匹配的线圈,但这是RFID制卡商的事了,一般是125HZ或134KHz.其他不多说,先来看看EM4100的数据结构:
ID64的数据结构是,9位的头+8位版本号或簇ID+32位数据,最右边的一列的P0-P9每一位对应左边Dx0-Dx3四位数据的奇校验,最后以行的PC0-PC3对应上方D0X-D9X位的奇校验。最后一位S0固定为0.
这款RFID芯片的编码模式有三种:Manchester、Biphase
Code和PSK
Code,有生产时配置好,配置编码模式的指令,这些配置的指令一般都是保密的,只有制卡厂家有修改这些配置的设备,所以具体怎么配置我们就不用管了,这里来介绍一下编码模式,Manchester(曼码)前面已介绍过了。
biphase(双相码)编码的特点就是信号1在周期结束时电场跳转(开启或关闭)在一个周期的中间不跳转,而信号0的电场在周期中间和结束都有跳转。
PSK(相移键控)就是用相位的改变表示信号从0到1的改变。
三、RFID设备硬件设计
为了方便,之后称RFID设备为读写器,设计低频读卡器,可以用用三极管、二极管、电容、电阻、线圈设计成发射电路和检波电路,发射电路很简单,其原理就是LC振荡器,一个电容C和一个电感 L的串联,再加一个三极管来驱动,检波电路绕为复杂一点,但也不难,就是滤波+信号转换废话少说,直接上图:
图中的C1在实际中要在理论值附近调整时期达到最优, 如果不想麻烦,直接用专用解码IC就行了,电路简单易调,比如EM4095.
EM4100卡工作在134KHz,所以直接用单片机的IO输出134KHz的方波来去动TX,电容C1和L在驱动节拍下充电放电下,只要L和C的谐振频率和驱动信号的频率相近,LC电路就会振荡起来,电感上的电压能达到上百伏,但不用当心安全问题,因为功耗很小,不会伤到人。当EM4100卡进入电感
L也就是天线的电场内时,EM4100通过其天线和读卡器的天线耦合获取到足够能量后,就开始向读卡器发送自己64位的信号。在调制信号的作用下下,输入二极管端的是包络波形,经过二极管和R2、C2和C3组成的检波电路后就得到我们先要的有用信号。
读卡器天线上发送信号时的波形如下:
继续放大后是这样的:
天线端的信号时这样的,图中蓝色的那个。
通过检波电路后的波形是上图图中黄色波形,再来看一张检波后的波形
四、解码程序
解码就是要将标签的信号按曼码的格式逐位解出来,然后再验证校验位,校验通过后,说明数据接收正确,解码的方式有两种,一种是先判断头部,是否为9个1,如果判断头部通过,再继续接收后面的数据,如果不通过,则重新从下一次头部判断,另一种是按64位数据帧长的时间抓取两贝帧长时间的数据,然后再从在一段数据中查找帧头(9个1),查到帧头后就就继续读枕头的数据,如果查不到,则重新下一次数据抓取。
通过位速率,比如64时钟周期,就是64个时钟周期才传送一个bit,那在125kHz时,一个比特的时间512us,半个位的时间是256us,在上图中,宽的用红色数字标记一个0或1的正脉宽或负脉宽就是半个周期的时间没也就是256us,但在实际中这个值是有偏差的,所以要设定一个容限,若在接收的过程中那个比特的时间超出容限值,就结束本次接收数据,重新再接收,接收数据时,如记录到一个正脉宽的时间在大于一个周期小于一个周期的容限内,则在接收缓冲区里记录下11,若是负脉宽则记录00,如记录到半个周期时间容限的正脉宽,则记录一个1,若是负脉宽在记录一个0,如上图中标记所示,这样做是为了方便解码。这个码就叫它为曼码。
曼码接收函数:这是用EM4095解码芯片设计的解码电路,接收时不判断头部,直接抓取数据存缓冲区。
uint8 ReceiveMan(uint8 *mandata,uint16 datanum,uint8 maxfullT,uint8 minfullT,uint8 maxhalfT,uint8 minhalfT)
{
uint8 i=0,j=0,clkcount=0;
uint16 bitcount=0;
TH0=0;TL0=0;TR0=1;
while(bitcount
{
modout=!modout;//mudout j接EM4095de的DEMOD_OUT脚,用51单片机计时器计算此脚的电平翻转时间来确定接受到的数据。
while(DEMOD_OUT==modout)
{
if (TL0>maxfullT)
{
TR0 = 0 ; clkcount=TL0;
if(bitcount==(datanum-3))//the end number is 01
{
mandata[i]<<=1;
mandata[i]|=modout;
mandata[i]<<=1;
mandata[i]|=modout;
bitcount=bitcount+3;
return ok;
}
else if(bitcount>(datanum-3))//the end number is 11
{
mandata[i]<<=1;
mandata[i]|=modout;
bitcount=bitcount+3;
return ok;
}
return fail ;
}
}
TR0=0;
clkcount=TL0;
TL0=0;TR0=1;
if((clkcount>=minfullT)&&(clkcount<=maxfullT)) //received 1 bit
{
mandata[i]<<=1;
mandata[i]|=modout;
j++;
if (j>=8)
{
i++;
j=0;
}
mandata[i]<<=1;
mandata[i]|=modout;
j++;
if (j>=8)
{
i++;
j=0;
}
bitcount=bitcount+2;
}
else if((clkcount>=minhalfT)&&(clkcount<=maxhalfT)) //receive half bit
{
mandata[i]<<=1; //input the bit into allhex;
mandata[i]|=modout;
j++;
if (j==8)
{i++; j=0; }
bitcount=bitcount+1;
}
else
{
TR0=0;
return fail ;
}
}
return ok;
}
然后我们要将曼码转换成二进制bit码,我们就直接看转换函数了。
//sourcebuf为曼码缓存区
uint8 ManTohex(uint8 num,uint8 *sourcebuf,uint8 *destbuf)//manchester to hex
{
uint8 i=0,j=0,m=0,manchk=0;
uint8 bitcount=0;
for (i=0;i
for (j=0;j<4;j++)//一次检查2个bit,一个字节4次检查完,故循环4次
{
manchk=sourcebuf[i]&0xc0;//1100 0000b
if(manchk==0x40) //被检测的两位为01b,则该曼码为0
{
destbuf[m]>>=1;
bitcount++;
if (bitcount==8)
{m++; bitcount=0;}
sourcebuf[i]<<=2;
}
else if (manchk==0x80) //被检测的两位为10b,则改为曼码为1
{
destbuf[m]>>=1;
destbuf[m]|=0x80;
bitcount++;
if (bitcount==8)
{m++; bitcount=0;}
sourcebuf[i]<<=2;
}
else//若被检测的两位为00或11,说明是错误的位,结束转换。
{
return fail ;
}
}
}
return ok;
}
通过简单的注释不难理解代码的逻辑,在此就不多讲了。接下来就是查找头部9个1,然后去调头部的九个1,如果接收时是先判断头部再接收,此步可以跳过。
//delay_50us(10);
//*******************************//
// push "111111111" to first byte//
//*******************************//
for (i=0;i<64;i++)
{
emchk=emdata[0]&0x80;
byte0=0;
if (emchk==0x80)
{byte0=1;}
for(j=0;j<8;j++)
{
emdata[j]<<=1;
emchk=emdata[j+1]&0x80;
if (emchk==0x80 && j<7)
{emdata[j]|=0x01;}
}
emdata[7]|=byte0;
if (emdata[0]==0xff && emdata[1]>=0x80)
{i=69;}
}
//delay_50us(10);
// LED_GREEN = 0;
if (i!=70)
{TR0 = 0 ; return Fail ;}//{goto r_round;}
//---check the chksum if the last bit not 0,error. //20020610
chksum=emdata[7];
emchk=chksum&0x01;
if (emchk==1)
{TR0 = 0 ; return Fail ;}//{goto r_round;}
chksum>>=1;
//*******************************//
// take away "111111111" //
//*******************************//
for (i=0;i<9;i++)
{
emchk=emdata[0]&0x80;
byte0=0;
if (emchk==0x80)
{byte0=1;}
for(j=0;j<8;j++)
{
emdata[j]<<=1;
emchk=emdata[j+1]&0x80;
if (emchk==0x80 && j<7)
{emdata[j]|=0x01;}
}
emdata[7]|=byte0;
}
然后执行行奇校验和列奇校验,若果校验通过,则接收数据正确。下面是别人进行校验的函数,我当初直接就用了,也没有自己写这一部分:
//********************************//
//four bit data and one bit parity//
//********************************//
if(emdata[0]!=0x96)
i=i;
byte1=0;
for (i=0;i<5;i++)
{
for (j=0;j<10;j++)
{
byte0=0;
if ((emdata[0]&0x80)==0x80)
{byte0=1;}
for (m=0;m<8;m++)
{
emdata[m]<<=1;
emchk=emdata[m+1]&0x80;
if (emchk==0x80 && m<7)
{emdata[m]|=0x01;}
}
if((j!=4) && (j!=9))
{allhex[i]<<=1;
allhex[i]|=byte0;
byte1=byte1^byte0;
}
if((j==4) || (j==9)) //check row parity
{
if(byte1!=byte0)
{TR0 = 0 ; return Fail ;}//{goto r_round;}
byte1=0;
}
}
}
emdata[0]=allhex[0];
for(i=1;i<5;i++)
emdata[0]=emdata[0]^allhex[i];
emdata[0]=(emdata[0]>>4)^emdata[0];
emdata[0]=emdata[0]&0x0f;
chksum=chksum&0x0f;
if(emdata[0]==chksum)
{
for(i=0;i<5;i++)
{ hcode[i]=allhex[i];}
return Ok;
}
else
{TR0 = 0 ; return Fail ;}// goto r_round;