IC卡读写器连接示例


官方网站为本节内容提供了示例,下载地址:
http://www.foxtable.com/samples/iccard.rar

. 关于智能卡读写器

本节的例子以深圳市明华澳汉科技股份有限公司的USB非接触式读写器 EYE系列eye-U010为例,该公司的网址:

http://device.mwcard.com/

需要说明的是,该公司和我们没有任何关系,类似的智能卡读写器有很多,大家可以根据需要选择。

本节的例子可以参考CaseStudy目录下的文件:智能卡读写器连接示例.Table

eye-U010为无驱类型,插上USB口即可自动识别使用。

本示例智能卡以市场上常用的M1Mifare l)智能卡读写为示例。其它类型的卡使用函数可能有不同,详情请参考SDK开发文档。

. 读写器智能卡开发参考
2.1
使用SDK

2.1.1根据该设备的开发文档,将其SDK文件复制到Foxtable的安装目录下,该设备的SDK文件仅有一个DLL文件:mwrf32.dll

在编写本节内容时,其SDK的最新版本是2.02


提示:由于他们的开发包是传统的非托管DLL,所以只需复制到Foxtable安装目录即可使用,千万不要再去引用这些DLL文件。

2.2.2. 参考开发文档和例子,在全局代码中定义好API函数和必须的全局变量,由于内容较多,具体请看示例。

 

2.2 开发要点说明

2.2.1 关于M1 

M1卡为8K位的非接触式IC卡,详细的特性介绍请参考SDK的帮助文档。这里主要针对一些需要注意的地方进行说明。 

2.2.1.1 关于存储 

M1卡分为16个扇区,每个扇区4块(块03),共64块,按块号编址为063。第0扇区的块0(即绝对地址0块)用于存放厂商代码(前4个字节为卡的序列号,其它为厂商代码),已经固化,不可更改。其他各扇区的块0、块1、块2数据块,用于存贮数据;块3控制块,存放密码A、存取控制、密码B,其结构如下:

A0A1A2A3A4A5

FF 07 80 69

B0B1B2B3B4B5

密码A(6字节)

存取控制(4字节)

密码B(6字节)

M1卡每一个块都可以存储最多16个字节的内容(16个字符或8个汉字),以十六进制编码存储,如需存储更多的内容只能是分割存储到多个块中。 

SDK向智能卡存储汉字采用的是GBK内码,存储普通字符采用的是ASCII编码,值方式存储的是16进制值,均为十六进制形式,如下面厂家Demo截图所示,块0存储的是“湛d5bfbdadbbd4d1b8c8edbcfe,2存储的是字符F13800138000,ASC码为463133383131313338303030。不满16个字节后面加00Chr(0))作为结束,图中00后面的字符为SDK的控制符,不用理会,实际使用时,取00为结束符截断后面的内容即可,否则00后面出现的是乱码。

 

2.2.1.2 关于密码 

M1卡块每个扇区的第三块为控制块,左边6个字节是密码A,按SDK和官方的说明是屏蔽不可读的,所以无论向密码A中写入任何密码都显示不了,通过SDK返回的都是000000000000 

控制块中间4个字节(第6至第9字节)是存取控制,具体的控制位设置参考SDK文档,这里不做详述。需要说明的是存取控制一般采用智能卡出厂的默认值(ff078069)即可,如需自行控制扇区的读写权限请先参考SDK理解透存取控制位的变化,乱写的话可能导致扇区被锁无法操作扇区里的数据。 

控制块最后6个字节是密码B,写入的密码可以获取显示,不需要密码B的话也可以用来存储其它自定义的内容。 

密码使用必须是12个十六进制字符,即是0~9a~f这十六个字符。 

建议实际应用的时候存储的内容都进行加密处理。 

更改密码有2种方式:

一是专用块3修改函数rf_changeb3,可以根据参数分别控制密码A、密码B和控制位的修改。可以参考示例“改写密码”按钮代码。 

二是使用写数据的方式更改块3 的数据,比如函数rf_write_hex,和rf_changeb3不同的是,rf_write_hex需要把块3的内容拼成一起作为参数传递。可以参考示例“写入数据”按钮代码。

2.2.2 关于密码验证 

智能卡的密码验证,有装载密码验证和直接密码验证2种方式: 

装载密码即是把需要校验的密码装载到读写器的存储单元(RAM)中,可以单独装载单个扇区的密码A或者密码B,也可以一次性装载所有扇区的密码。这种验证方式安全性高,装载密码可以和卡片交易可以分离操作;缺点是读写器密码存储单元有使用寿命,一般为十万次,如果装载过于频繁超过使用寿命,它将会坏掉不能再装载密码。 

直接密码验证指PC机向读写器发送卡片认证指令时将认证密码作为命令参数一起发送,直接使用参数中的密码对卡片进行认证。这种验证方式没有装载密码验证的使用寿命限制;缺点是安全性不够。 

厂商建议使用装载密码验证的方式,毕竟实际应用中频繁断电和更改密码还是很少的,十万次的使用寿命一般都够用了。 

示例设计了一个表“读写器密码”记录装载的密码。为了测试方便,用的是明文,实际应用可以进行加密。 

2.2.3 SDK函数使用规则 

对已经连接设备并打开设备的情况下,对智能卡操作的函数调用建议如下:

1)读卡:调用 rf_card()

2)装载密码,调用 rf_load_key()

如果在打开设备的时候已经一次性装载所有扇区的密码则可以省略此步骤

3)验证密码,调用 rf_authentication()

4)进行操作,包括对卡的读写及值操作

5)结束本次操作,调用rf_halt() 

每次操作都需要重复调用以上步骤,不然大部分低级函数会报错,返回无卡,或者验证密码失败的操作结果。 

比如要读出扇区2的第0块的数据,调用方式如下,其中icdev为定义的全局变量,连接设备时存储设备句柄:

Dim snr(4) As Byte
Dim
snr1 As New String(snr1, 8)
Dim
databuff As New String(databuff, 16)
Dim
sector = 2
Dim st = mwrf32.rf_card(icdev, 1, snr(0))'读卡
If
st = 0 Then
    Mwrf32.hex_a(snr(0), snr1, 4)
'
转换为可识别的字符串
    e.Form.Controls("txtCardNumber").Text =
snr1
    st = mwrf32.rf_authentication(icdev, 0, sector )
'
验证该扇区密码
    If st = 0
Then
        st = mwrf32.rf_read(icdev, sector * 4, databuff)
        If st = 0
Then
            databuff  = ClearNullString(databuff)
            e.Form.Controls("txtData0").Text =
databuff
       
Else
           
'读块数据出错提示
        End
If
   
Else
       
'密码验证出错提示
    End
If

Else

   
'读卡出错提示

End
If
mwrf32
.rf_halt(icdev)

为了重用方便,示例用一个函数CheckCard,包装了rf_cardrf_authentication,这样上面的例子及可以像下面这样,减少代码的重复: 

Dim sector = 2
Dim
databuff As New String(databuff, 16)
If
CheckCard(sector ) Then
    Dim st = mwrf32.rf_read(icdev, sector * 4, databuff)
    If st = 0
Then
        databuff  = ClearNullString(databuff)
        e.Form.Controls("txtData0").Text =
databuff
   
Else
       
'读块数据出错提示
    End
If

End
If 

SDK函数分低级函数和高级函数2种,具体区分详见SDK文档函数的功能描述。 

其中高级函数包含几个低级函数的调用,都已经包含寻卡和验证密码的操作,并且函数结束会调用rf_halt(),无需按照上面的5个步骤来操作,如示例刷新数据中调用的M1开专用高级读函数rf_HL_read 

低级函数则需要按照上面的5个步骤按序操作。 

2.2.4 寻卡模式 

SDK的寻卡模式说明可能会被误导: 

寻卡模式分三种情况:IDLE模式、ALL模式及指定卡模式。

0——表示IDLE模式,一次只对一张卡操作;

1——表示ALL模式,一次可对多张卡操作;

2——表示指定卡模式,只对序列号等于snr的卡操作(高级函数才有)

 

模式0,一次只对一张卡操作,是指对这张卡操作完成(调用高级函数,或者低级函数调用(如果是ALL模式函数操作后调用rf_halt()也一样)结束)后必须拿离感应区再置回感应区内才能使函数调用再次生效。 

模式1,一次可对多张卡操作,并非可以同时操作多张卡,而是指对这张卡操作完成后,智能卡无需离开感应区即可进行其它函数的调用。模式1是可对一张卡进行多次操作的意思。 

模式2,指定序列号,必须是在先寻卡知道序列号的情况下才能使用,应用场景不多。少数几个高机函数有,在这些高级函数中,如果不传入序列号,则默认取当前在感应区中可用的卡,如果传入序列号,则只操作对应序列号的卡。 

示例为了测试方便,大多采用了模式1的方式操作智能卡。 

为了避免智能卡的误操作,厂方建议还是使用模式0的方式,然后使用rf_halt()结束一次操作,这样操作完后,必须重新拿离卡才能再次操作,避免了类似忘记换卡又操作了一次的情况出现。当然也可以通过良好的设计来避免这种情况,比如写操作前进行序列号验证,多次写操作提示等等,这些具体的用法就不在此详述了。 

示例批量添加卡就使用了模式0的特点。比如,需要增加一些新卡进行管理,可以一次放20张智能卡在读写器的感应区内,然后使用模式0循环寻卡,由于一张卡读过后只要不拿离感应区就不会被再次寻卡,这样直到所有的卡都被读过后,最后返回无卡的操作结果,结束循环。主要部分代码如下:

......

Dim st = 0
Dim
dr As DataRow
Do
While st = 0 '所有卡都读过后最后会返回1,表示无卡,然后离开循环
    st = Mwrf32.rf_card(icdev,0,snr(0))
    AppendStatus(GetErrorDesc(st))
    If st = 0
Then
        Mwrf32.hex_a(snr(0), snr1, 4)
        dr = DataTables(
"
智能卡").Find("序列号 = '" & snr1 & "'")'查找是否已经添加过这张卡,避免重复添加
        If dr IsNot Nothing
Then
            AppendStatus(
"
序列号 = " & snr1 & "(卡号:" & dr("卡号") & ")的智能卡已存在,无法重复添加")
       
Else
            dr = DataTables(
"
智能卡").AddNew()
           
dr("序列号") = snr1
            dr("卡号") = i.ToString().PadLeft(5,"0")
            i = i+
1
            f = f+
1
        End
If
    End
If

Loop
......

2.2.5 关于值操作

我们经常见到例如公交卡、饭卡、购物卡等等智能卡里面都是有金额的,可以刷卡消费。可见智能卡的值操作是很常见的,SDK同样提供了方便的值操作函数,不过有一定的局限,就是只能操作正整数的值,当然这是可以理解的。

SDK的值操作是把数字转换为16进制值存储的,而不是存储数值的十六进制编码。 

其中rf_initvalrf_incrementrf_decrement分别实现初始化值、加值和减值操作,直接传入整形的值即可。 

rf_readval读值函数返回的是值的内存地址,返回的只是前4个字节,必须做处理才能获取正确的值。 

从函数可以看出,SDK的值操作只支持整形,至于可以存储多大范围的值,下面会讲到。 

比如我们往一个块里用初始化值函数存入1000,查看块里存入的16进制串,是这样的:

e803000017fcffffe803000004fb04fb 

嘿,我的1000去那里了呢。这串字符其实要这样理解的,先把字符分成4份,4个字节一份,就是这样:

1 2 3 4
e8030000 17fcffff e8030000 04fb04fb

可以看出第1份和第3份是一样的。我们把第1份的4个字节倒过来,成了这样000003e8,实际上就是&H3e8,转换成10进制就是1000。也就是说SDK是把十进制值转换成16进制值后,不够4个字节(32位)的前面用0凑齐4个字节,然后把这4个字节按低位开始倒序存储。比如10进制值50000转换为16进制值就是C350,存入块中就是50C30000

基于这个特性,取值的时候需要做个转换,示例写了个全局函数

'根据值字节(4个字节长度)返回正确的值,适用于值操作,因为值操作块中存储的是值的16进制数值,而不是值的16进制编码
Public
Function HexByteToLongValue(ByVal hex() As Byte) As Long
  Dim
str As String = "&H"
  For
i As Integer = hex.Length-1 To 0 Step -1
    str &= hex(i).ToString("X2")

  Next
  Return
val(str)
End
Function

那么第2份又是什么东西?同样把4个字节倒排为fffffc17,然后取反就知道了,大家在命令窗口用Output.Show(not &Hfffffc17)看看结果。

最后4个字节是厂家的控制信息,每个块固定都一样的,不用理会就是。 

如果块存储的不是值形式,而是用非值函数存入的数据,那么用值函数操作就会返回“该块数据不是值格式”的错误代码。 

可以看出,通过值方式操作的值范围是从0&HFFFFFFFF之间,当然一般也用不了那么大。 

2.3 常用功能函数归纳

2.3.1 连接设备(初始化串口)

icdev = Mwrf32.rf_init(0, 9600)

成功返回设备句柄,由于eye-U010为无驱类型,所以参数不用设置,默认即可。

设备句柄icdev定义为全局变量,为其它功能操作必备。

2.3.2 关闭设备(释放串口)

Dim st = Mwrf32.rf_exit(icdev)

程序退出后必须释放设备的占用,否则不能被其它程序使用。

2.3.3 读取智能卡内容

主要有5个函数,分别是rf_HL_readrf_HL_readhexrf_readrf_read_hexrf_readval,前2个是高级函数,最后一个是读值操作,其它是低级函数。

这里注意的是读值函数rf_readvalSDK的值操作是把数字转换为16进制值存储的,而不是存储数字的十六进制编码。

2.3.4 往智能卡写内容

通用函数(低级函数),分别是rf_writerf_write_hex

高级函数,分别是rf_HL_writerf_HL_writehex(示例测试时发现高级函数写块3会出错,厂家建议改回使用低级函数)

值操作函数,分别是rf_HL_initvalrf_incrementrf_decrement


本页地址:http://www.foxtable.com/webhelp/scr/3052.htm