摘 要 介绍了射频卡的工作原理及结构,并给出了使用多线程技术实现射频卡监听和读写的方法及其在C#下的具体实现。指出利用多线程实现射频卡的监听和读写能够提高射频卡读写程序的并发性、可靠性和运行效率,从而提高整个应用系统的性能。
射频卡又称非接触式IC卡, 是世界上最近几年发展起来的一项新技术, 它成功的将射频识别技术和IC卡技术结合起来。与传统的接触式IC卡相比, 射频卡具有操作便捷、安全性高、抗干扰性强等优点,并且由于它不需要与读卡机有直接物理性接触,所以增加了读卡机的读写寿命。目前,射频卡已经在很多领域逐步得到应用,特别是在安全性、保密性、便利性要求比较高的应用场所,代表了刷卡领域的发展方向。射频卡的特点要求实现卡中数据读写的自动化,即当有射频卡进入读写器的感应区内,读写器能够立即自动读写卡中数据,并实时传入计算机进行显示和处理,这就要求计算机对读写器进行循环监听检测。本文将利用多线程技术在后台生成一个单独的线程,用来xxxxx的读写而不影响用户在前台的其它操作,从而提高应用系统的并发性、可靠性和执行效率。
笔者选用的射频卡为PHILIPS公司的Mifare S50(M1)卡,卡的电气部分由一个天线和ASIC组成,卡片的天线是几组绕线的线圈,适于封装到IS0卡片中,ASIC由一个高速106KB波特率的RF接口,一个控制单元和一个8K位EEPROM组成。在工作时,读写器向M1卡发送一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下,LC谐振电路产生共振,使电容内产生电荷,电容的另一端接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到2V时,该电容可作为电源为其它电路提供工作电压,将卡 内数据发射出去或者获取读写器的数据。在此过程中使用的M1射频卡结构如图1所示。
射频接口模块用来获得读写器的电源电压、复位信号、时钟信号,同时卡内芯片中的有关电路对此信号进行调制、解码、解密, 然后对命令请求、密码、权限等进行判断,以实现存储区的合法读写。防碰撞模块处理多张卡进入读写器有效范围的情况,这时防碰撞机制会从其中选择一张进行操作,未选中的卡则处于空闲模式等待下一次选卡,该过程会返回被选中卡的序列号。读写验证模块验证用户的读写密码与存储区密码是否相符,M1卡提供了两套密码KeyA 和KeyB,并结合控制位的使用可以实现对每一个扇区的读或写的控制,只有在密码合法的情况下,才能对指定区进行操作。控制及ALU单元实现值的特殊形式存储,并能实现基本的加值和减值操作。
EEPROM是射频卡的数据存储区,分为16个扇区,每个扇区由4块(块0、块1、块2、块3)组成,每块大小为16个字节,16个扇区的64个块也可按{jd1}地址编号为0~63。第0扇区的块0用于存放厂商代码,已经固化,不可更改。其它每个扇区的块0、块1、块2为数据块,可用于存贮数据。 数据块可用作一般的数据保存,进行读、写操作,也可以用作数据值,可以进行初始化值、加值、减值、读值等操作。每个扇区的块3为控制块,包括了密码A、存取控制、密码B三个部分,每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制,具体结构如图2所示。
6字节 4字节 6字节
在应用程序中使用多线程,可以将冗长的或非常耗时的任务放在后台处理。即使在只有单处理器的计算机上,使用多线程也可以非常显著地提高应用程序的响应能力和可用性。为了实现射频卡读写器主动寻卡功能并且将返回的数据通知应用程序,可以采用多线程技术。多线程具有异步操作的特点,使得读写器可以只占有很少的CPU资源,提高程序的运行效率。以下将利用C#语言具体实现射频卡的多线程监听读写,当启动多线程读写功能后,主线程将建立一个单独的后台线程xxxxx,当有射频卡进入读写器有效范围内就可以实现该卡的自动识别和读写功能,并且不管卡在读写器有效范围内的时间有多长,只实行一次操作,只有当该卡重新进入感应区才进行下一次读写。在读写之前还要实现在有效范围内检查卡、选择卡、防碰撞、密码验证等预操作。操作射频卡的主要流程如图3所示。
C#提供了进行多线程编程的类和接口,线程的创建可以通过Thread、ThreadPool、Timer三种方法。Thread方法适用于需对线程进行复杂控制的场合; ThreadPool是一种相对较简单的方法,适应于一些需要多个线程而又较短任务,缺点是对创建的线程不能加以控制,也不能设置其优先级;Timer则适用于需周期性调用的方法,它不在创建计时器的线程中运行,而是在由系统自动分配的单独线程中运行。本次采用了Thread方法,相比之下Thread方法实现相对复杂,但控制更为灵活。
为了方便实现射频卡的读写编程功能,本程序利用了读写器附带的动态连接库,.NET框架提供了调用动态链接库的服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,包括操作系统提供的Windows API函数,能够定位和调用输出函数,根据需要组织其各个参数跨越互操作边界。但动态链接库中很多函数将指针作为参数,而在C#中没有指针的概念,用户在受管理的代码中不允许如直接存取内存等不安全的操作,为了保持函数引用声明中参数类型的一致性,在C#中通过引用封送指向这些数据类型的指针。程序实现的核心代码如下:
MessageBox.Show("端口打开失败!");
if (RC500_232_config()!=0)
MessageBox.Show("初始化失败!");
MessageBox.Show("端口打开成功,读卡器初始化成功!");
if (RC500_232_request(0, ref tagtype)!=0) //检查有效范围是否有卡
{
continue;
}
if (RC500_232_anticoll(0, ref snr)!=0) //防碰撞控制,snr返回卡的序列号
{
MessageBox.Show("防碰撞错误!");
continue;
}
if (RC500_232_select(snr,ref size)!=0) //选择某一序号的卡,
{
MessageBox.Show("选择错误!");
continue;
}
if (RC500_232_authkey(0,0,key)!=0) //密码验证,密码存放在 key数组中
{
MessageBox.Show("密码验证错误!");
continue;
}
if (RC500_232_read(0,data)!=0)// 将0扇区数据读入byte类型数组data中,
{
MessageBox.Show("数据读取失败");
continue;
}
textBox1.Text="";
for(i=0;i<16;i++)
textBox1.Text=textBox1.Text+" "+Convert.ToString(data[i],16);
RC500_232_halt();//读写一次后卡挂起,直到卡重新进入有效范围
RC500_232_alarm(1,25,25,1); //读写器发出蜂鸣,指示操作成功
Thread mythread = new Thread(new ThreadStart(Rf_232_Read));
mythread.Start();//线程启动,执行Rf_232_Read函数
|