周立功教授新书《面向AMetal框架与接口的编程(上)》,对AMetal框架进行了详细介绍,通过阅读这本书,你可以学到高度复用的软件设计原则和面向接口编程的开发思想,聚焦自己的“核心域”,改变自己的编程思维,实现企业和个人的共同进步。
第六章为重用外设驱动代码,本文内容为6.4 读写卡模块。
6.4 读写卡模块
>>> 6.4.1 基本功能
ZLG600A 是ZLG 提供的符合14443 标准的13.56MHz 读写卡模块,支持Plus CPU、Mifare DesfireCPU 卡、Mifare 1 S50/S70、Mifare 0 ultralight、Mifare Pro 卡,还支持ISO7816 协议读写接触式IC 卡。可以搭载两个天线满足不同位置的多卡读写要求,连接从机数多达127 个,支持I2C、UART、RS-232C、RS-485 多种接口。具备主动检测卡进入功能,当检测到卡时,则产生中断且通过串口、I2C 输出数据。
ZLG600A 系列读写卡模块选型指南详见表6.18,电源电压为5V,平均电流为77mA,TypeA、TypeB 和PLUS CPU卡读卡距离分别为7.5cm、4cm 和5cm。
表6.18 选型指南表
由于RS-232C 和RS-485 与UART通信方式完全相同,仅在硬件上增加了一些转换电路,因此后面不再特别说明。在这里仅介绍I2C 和UART 两种通信方式,其引脚含义详见表6.19。
表6.19 通信接口定义
注:方形焊盘为第1 管脚,3.3V 模块J1-5 接3.3V 电源,5V 模块J1-5 接5V 电源。
当使用UART 通信时,只需要连接J1 中的电源引脚和TXD、RXD 引脚,其它引脚悬空,将LPC824 的USART0、USART1 或USART2 与ZLG600A 相连。当使用I2C 通信时,需要连接J1 中的电源引脚和SCL、SDA 引脚。由于I2C 从机不能主动向I2C 主机发送数据,因此需要使用INT 引脚,用于从机通知主机处理事务。其它未使用的引脚通过10K 的上拉电阻与高电平相连,将LPC824 的I2C0、I2C1、I2C2 或I2C3 与ZLG600A 相连。注:SCL 和SDA 模块内部都有上拉电阻,使用I2C 通信时无需再外接上拉电阻。
>>> 6.4.2 初始化
AMetal 已经提供了ZLG600A 的驱动函数,在使用其它各功能函数前必须先完成初始化,其初始化函数详见表6.20。
表6.20 ZLG600A 初始化接口函数
初始化ZLG600A 意在获取ZLG600A 的实例句柄(handle),虽然初始化函数不同,但返回值均为ZLG600A 的实例句柄,该实例句柄将作为其它功能接口函数handle 的实参。因此,无论选择I2C 或UART 通信方式,只要基于实例句柄编程,则应用程序与具体的通信方式无关。即便底层通信方式改变了,仅需在获取实例句柄时换一个初始化函数,而应用程序“一行代码”都不用修改。
1. UART 初始化
使用UART 时,其初始化函数原型为:
-
p_dev 为指向am_zlg600_uart_dev_t 类型实例的指针;
-
p_devinfo 为指向am_zlg600_uart_devinfo_t 类型实例信息的指针。
(1)实例
定义am_zlg600_uart_dev_t 类型(am_zlg600.h)实例如下:
其中,g_zlg600_uart_dev 为用户自定义的实例,其地址作为p_dev 的实参传递。
(2)实例信息
实例信息主要描述了ZLG600A 使用UART 通信时的相关信息,包括UART 缓冲区信息、波特率等信息。其类型am_zlg600_uart_devinfo_t 定义(am_zlg600.h)如下:
实例信息主要包含帧格式、ZLG600A 模式、波特率和缓冲区信息。
-
帧格式
frame_fmt 表示初始化时ZLG600A 使用的帧格式,为了兼容早期产品,ZLG600A 支持新帧格式和旧帧格式,其对应的宏详见表6.21。在初始化完成后,可以通过相应的接口函数修改ZLG600A 使用的帧格式。但在初始化时,要知道ZLG600A 当前使用的帧格式,出厂默认使用的帧格式为旧帧格式,frame_fmt 的值设置为:AM_ZLG600_FRAME_FMT_OLD。
表6.21 帧格式对应的宏(am_zlg600.h)
注:若本次初始化完成后,通过后续相关功能接口函数更换使用的帧格式(如将旧帧格式更换为新帧格式),那么在系统下次启动调用ZLG600A 初始化函数时,需要确保frame_fmt成员的值为更新后的帧格式(如新帧格式)。
-
ZLG600A 模式
now_mode 为初始化时使用的模式,ZLG600A 支持3 种模式:自动侦测模式、I2C 模式、UART 模式。其对应的宏详见表6.22,出厂默认为自动侦测模式。
表6.22 模式对应的宏(am_zlg600.h)
注:由于在自动侦测模式下,为了使用UART 通信,需要Host 连续发送两次0x20(两次的时间间隔需要30us 以上),便于ZLG600A 检测有效波特率,将会使得每次初始化ZLG600A 的耗费时间较长。因此在本次初始化完成后,通过后续接口函数将模式固定为UART 模式。更新模式后,系统在下次启动调用ZLG600A 初始化函数时,需确保now_mode 成员的值为更新后的模式(如UART 模式)。
在自动侦测模式下,UART、I2C 接口均处于接收状态。若ZLG600A 从UART 通信线上检测到有效的波特率(需要Host 连续发送两次0x20,且两次的时间间隔在30us 以上,便于ZLG600 检测到有效波特率),则模块使用UART 通信方式;若模块从I2C 总线上接收到匹配的从机地址,则模块使用I2C 通信方式。只要其中一个接口先收到有效数据,模块将以此方式与主机通信,且关闭另外一种接口。
在UART 通信模式下,通信接口固定为UART,关闭I2C 接口,无需Host再连续发送两次0x20 检测ZLG600A 波特率。在I2C 通信模式下,关闭UART接口。在完成初始化后,可以通过相应的接口函数修改ZLG600A 使用的模式。但在初始化时,要知道ZLG600A 当前使用的模式,出厂默认使用的模式为自动侦测模式,now_mode 的值设置为:
AM_ZLG600_MODE_AUTO_CHECK。
-
波特率
baudrate 为UART 波特率,支持波特率的宏定义详见表6.23。在自动侦测模式下,可以选择表中任一有效的波特率,ZLG600A 会自动检测使用的波特率。若处于UART 模式,波特率将为固定的值,该值为配置ZLG600A 是UART 模式时使用的波特率。
表6.23 支持的UART 波特率对应的宏(am_zlg600.h)
由于出厂默认模式为自动侦测模式,因此该值为任一有效的波特率,如定义波特率为115200,则该值为:AM_ZLG600_BAUDRATE_115200。
-
缓冲区信息
当选择UART 方式通信时,发送和接收都需要一个保存数据的缓冲区,以提高数据处理的效率和确保接收数据不会因为正在处理事务而丢失。缓冲区的大小由用户根据实际情况指定,建议在64 字节以上,一般设置为128 字节。p_uart_rxbuf 和rxbuf_size 描述了接收缓冲区的首地址和大小,p_uart_txbuf 和txbuf_size 描述了发送缓冲区的首地址和大小。如果设置128 字节的缓冲区供发送和接收使用,其定义如下:
其中,g_zlg600_uart_txbuf[128]为用户自定义的数组空间供发送使用,充当发送缓冲区,其地址(数组名g_zlg600_uart_txbuf 或首元素地址& g_zlg600_uart_txbuf[0])作为实例信息中p_uart_txbuf 成员的值,数组大小(这里为128)作为实例信息中txbuf_size 成员的值。同理,g_zlg600_uart_rxbuf[128]充当接收缓冲区,其地址作为实例信息中p_uart_rxbuf 成员的值,数组大小作为实例信息中rxbuf_size 成员的值。基于此,实例信息定义如下:
(3)UART 句柄uart_handle
若选择LPC824 的USART1 与ZLG600A 通信,则可以通过LPC82x 的USART1 实例初始化函数am_lpc82x_usart1_inst_init()获得UART 句柄。即:
获得的UART 句柄即可直接作为uart_handle 的实参传递。
(4)实例句柄
ZLG600A 初始化函数am_zlg600_uart_init()的返回值即为ZLG600A 实例的句柄,该句柄将作为其它功能接口的第一个参数(handle)的实参。
其类型am_zlg600_handle_t(am_zlg600.h)定义如下:
若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回一个有效的handle。
基于模块化编程思想,将初始化相关的实例、实例信息等的定义存放到对应的配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单6.98 和程序清单6.99。
程序清单6.98 ZLG600A(串口通信)实例初始化函数实现(am_hwconf_zlg600.c)
程序清单6.99 ZLG600A(串口通信)实例初始化函数声明(am_hwconf_zlg600.h)
后续只需要使用无参数的实例初始化函数,即可获取ZLG600A 的实例句柄。即:
2. I2C 初始化
使用I2C 通信方式时,其初始化函数原型为:
-
p_dev 为指向am_zlg600_i2c_dev_t 类型实例的指针;
-
p_devinfo 为指向am_zlg600_i2c_devinfo_t 类型实例信息的指针。
(1)实例
定义am_zlg600_i2c_dev_t 类型(am_zlg600.h)实例如下:
其中,g_zlg600_i2c_dev 为用户自定义的实例,其地址作为p_dev 的实参传递。
(2)实例信息
实例信息主要描述了ZLG600A 使用I2C 通信时的相关信息,包括I2C 缓冲区信息、波特率等信息。其类型am_zlg600_i2c_devinfo_t 的定义(am_zlg600.h)如下:
实例信息主要包含帧格式、ZLG600A 模式、7 位I2C 从机地址和中断引脚信息。
frame_fmt 的含义与使用UART 通信方式时一致,对于出厂设置的模块,frame_fmt 的值应设置为:AM_ZLG600_FRAME_FMT_OLD。
now_mode 的含义与使用UART 通信方式时一致,对于出厂设置的模块,now_mode 的值应设置为:AM_ZLG600_MODE_AUTO_CHECK。在本次初始化完成后,若通过后续接口函数将模式固定为I2C 模式。则系统在下次启动调用ZLG600A 初始化函数时,必须确保now_mode 成员的值为更新后的I2C 模式。
slv_addr 为ZLG600A 的7 位I2C 从机地址,出厂默认值为0x59,数据手册中描述为0xB2,该值为8 位地址,右移一位,即移除表示读写方向的位后,值即为0x59。在本次初始化完成后,若通过后续接口函数修改I2C 地址。则系统在下次启动调用ZLG600A 初始化函数时,必须确保slv_addr 成员的值为更新后的I2C 地址。
int_pin 为ZLG600A 的INT 引脚与实际微控制器(如LPC824)连接的引脚号。比如,选择LPC824 的PIO0_13 与ZLG600A 的 INT 引脚相连,则该值应设置为PIO0_13。
基于此,实例信息定义如下:
(3)I2C 句柄i2c_handle
若选择LPC824 的I2C1 与ZLG600A 通信,则通过LPC82x 的I2C1 实例初始化函数am_lpc82x_i2c1_inst_init()获得I2C 句柄。即:
获得的I2C 句柄即可直接作为i2c_handle 的实参传递。
(4)实例句柄
am_zlg600_i2c_init()与am_zlg600_uart_init()的返回值相同,该返回值为ZLG600A 实例的句柄,该句柄将作为其它功能接口的第一个参数(handle)的实参。若返回值为NULL,说明初始化失败;若返回值不为NULL,说明返回一个有效的handle。
基于模块化编程思想,将初始化相关的实例、实例信息等的定义存放到对应的配置文件中,通过头文件引出实例初始化函数接口,源文件和头文件的程序范例分别详见程序清单6.100 和程序清单6.101。
程序清单6.100 新增ZLG600A 的I2C 通信方式的实例初始化函数(am_hwconf_zlg600.c)
程序清单6.101 am_hwconf_zlg600.h 文件内容更新
后续只需要使用无参数的实例初始化函数,即可获取到ZLG600A 的实例句柄。即:
>>> 6.4.3 设备控制类接口函数
ZLG600A 支持多种IC 卡,比如,Mifare S50/S70、ISO7816-3、ISO14443(PICC)、PLUSCPU 卡等,每种卡都有对应的命令。命令与接口函数基本上是一一对应的关系,ZLG600A的命令较多,分为以下5 类:设备控制类命令、Mifare S50/S70 卡类命令、ISO7816-3 类命令、ISO14443(PICC)卡类命令和PLUS CPU 卡类命令。
设备控制类接口函数与具体卡片没有直接关系,主要用于直接操作ZLG600A,比如,获取ZLG600A 的设备信息和存储IC 卡密钥等,详见表6.24。
表6.24 ZLG600A 设备控制类接口函数(am_zlg600.h)
1. 读取设备信息
该函数意在获取ZLG600A 的基本信息,包括产品信息和版本信息等,获取的信息为字符串,比如,“ZLG600A V1.00”。其函数原型为:
其中,p_info 为获取信息的指针,由于字符串长度为20 字节,因此需要提供一个长度为20 字节的内存空间,以便存放获取到的信息。若返回值为AM_OK,说明获取信息成功,反之失败,范例程序详见程序清单6.102。
程序清单6.102 读取ZLG600A 设备信息范例程序
假定选择UART 通信方式,即可使用am_zlg600_uart_inst_init()实例初始化函数获取ZLG600A 的实例句柄。同时包含标准C 头文件string.h,便于使用strcmp()函数判断字符串是否为“ZLG600A V1.00”?
由于这次使用的是ZLG600A V1.00 模块,因此获取的信息一定为“ZLG600A V1.00”。如果你使用的是其它型号或版本的模块,则要注意将比较信息的字符串修改,否则就算成功读取信息,比较结果也是不相等的。
2. 装载IC 卡密钥
卡片内存储的数据均是加密的,必须验证成功后才能读写数据。验证就是将用户提供的密钥与卡片内部存储的密钥对比,只有相同才认为验证成功。ZLG600A 提供了用于存储卡片验证的密钥的E2PROM,装载IC 卡密钥的作用就是将密钥存放到指定的E2PROM 存储区,其函数原型为:
其中,key_type 为密钥类型,密钥一般分为2 类,其分别为TypeA 和TypeB。其对应的宏详见表6.25。ZLG600A 能保存A 类型密钥16组,B 类型密钥16 组。
表6.25 密钥类型(am_zlg600.h)
注:之所以存在两类密钥,是由于实际卡片中往往存在两类密钥,两类密钥可以更加方便地进行权限管理,比如,TypeA 验证成功后只能读,而TypeB 只有验证成功后才能写入,但权限可以自定义设置。
key_sec 为保存的区号,由于ZLG600A 能保存A 类密钥16 组和B 类密钥16 组,因此每种类型保存的区域有16 个,其对应区号为0 ~ 15。
p_key 指向了实际待保存密钥的缓冲区,key_length 为密钥的长度,密钥最大长度为16 字节。保存一组6 字节长度的A 类型密钥至区号0 的范例程序详见程序清单6.103。
程序清单6.103 装载密钥范例程序
当后续需要验证卡片时,只需要指定密钥存放的E2PROM 区号0,无需再将密钥发送给ZLG600A。
>>> 6.4.4 操作接口函数
Mifare 卡是一种符合ISO14443 标准的A 型卡,其接口函数详见表6.26。
表6.26 Mifare S50/S70 接口函数(am_zlg600.h)
经常使用的公交卡、房卡、水卡和饭卡等均是Mifare 卡。比如,S50 和S70,它们的区别在于容量的不同。S50 为1Kbyte,共16 个扇区,每个扇区4 块,每块16 字节。S70 为4Kbyte,共40 个扇区,前32 个扇区每个扇区4 块,每块16 字节,后8 个扇区每个扇区16 块,每块16 字节。
1. 自动检测
-
设置自动检测回调函数
当有卡片靠近ZLG600A 时,将会自动调用用户设定的回调函数,读取卡片的相关信息,因此需要先设置一个回调函数。其函数原型为:
其中,pfn_callback 为指向回调函数的指针,p_arg 为回调函数的参数。若返回AM_OK,表示设置成功,反之失败,范例程序详见程序清单6.104。
程序清单6.104 设置自动检测回调函数范例程序
程序中定义了一个detect_flag 变量,表示是否检测到卡片。如果初始值为0,说明未检测到卡片。在设置自动检测回调函数时,将其地址作为回调函数的p_arg 参数。因此在回调函数中,p_arg 实质上是指向detect_flag 的指针,通过该指针将detect_flag 设置为1,表明当前检测到卡片。
-
启动自动检测
当设置好回调函数后,即可启动自动检测。其函数原型为:
其中,p_info 为指向自动检测相关信息的指针,其类型am_zlg600_auto_detect_info_t 定义如下:
该信息结构体包含检测模式、天线模式、请求模式和密钥验证相关的信息。
-
检测模式
ad_mode 表示检测模式,用于配置检测相关动作。当检测到卡片时,是否挂起该卡片,若选择挂起,设置ad_mode 的值为AM_ZLG600_MIFARE_CARD_AD_HALT(am_zlg600.h),则在检测到一次该卡片后,就将该卡片挂起,后续检测将忽略该卡片。若需再次检测该卡片,必须将卡片远离ZLG600A 后重新靠近才有效。若设置ad_mode 的值为0,则不会挂起卡片,每次自动检测均可以检测到靠近的卡片。
举个简单的例子可能更容易理解,在平常刷公交卡时,当卡片靠近刷卡器时,会扣费一次,刷卡成功。若公交卡不离开刷卡器,则不会再次扣费,此时卡片已经被刷卡器挂起了,不会再被识别到。若将公交卡离开刷卡器后再次靠近,将可能再次扣费。
当ad_mode 的值均设置为AM_ZLG600_MIFARE_CARD_AD_HALT 时,则可以避免重复检测到同一张卡片。
-
天线模式
tx_mode 设置天线的工作模式,ZLG600A 有2 个天线TX1 和TX2,4 种工作模式:仅使用TX1、仅使用TX2、TX1 和TX2 交替使用、TX1 和TX2 同时使用,各种模式对应的tx_mode 的值宏定义详见表6.27。
表6.27 天线工作模式(am_zlg600.h)
-
请求模式
req_mode 表示请求模式,即检测所有的卡还是只检测空闲卡,对应的值宏定义详见表6.28,一般来讲只检测空闲卡。
表6.28 请求模式(am_zlg600.h)
-
密钥验证
由于绝大部分卡片在检测到卡片时,都要先读取一块数据,因此可以将读取数据作为自动检测的一个附加功能。即在检测到卡片时,自动读取1 块(16 字节)数据。由于读取数据前均需要验证,这就需要在启动自动检测时,指定密钥验证相关的信息。
所谓密钥验证就是将用户提供的密钥与卡片内部存储的密钥对比,只有相等方能验证成功。auth_mode 指定了3 种验证模式,其对应的宏表6.29。
表6.29 验证模式(am_zlg600.h)
如果在自动检测到卡片时,不需要读取数据,则应该将auth_mode 的值设置为AM_ZLG600_MIFARE_CARD_AUTH_NO。由于不会用到信息结构体中key_type、key[16]、key_len、nblock 四个成员的值,因此无需设置。
如果需要读取数据,则必须将auth_mode 置为AM_ZLG600_MIFARE_CARD_AUTH_E2或AM_ZLG600_MIFARE_CARD_AUTH_DIRECT,其主要区别是验证密钥存放位置不同。
如果使用“直接验证”(AM_ZLG600_MIFARE_CARD_AUTH_DIRECT)的方式,则信息结构体的key[16]包含了实际的密钥,key_len 表示了密钥的长度。
如果使用“E2 验证”(AM_ZLG600_MIFARE_CARD_AUTH_E2)方式,则验证密钥存放在ZLG600A 的E2PROM 中。此时,信息结构体的key[16] 仅使用了首元素key[0],其值为密钥在E2PROM 中的区号(0 ~ 15)。自动检测时,将使用ZLG600A 中E2PROM 对应区号中的密钥进行验证。key_len 表示密钥的长度。显然,若使用“E2 验证”,则需要确保已经使用am_zlg600_ic_key_load()将密钥存放到了ZLG600A 中E2PROM 相应的区域。
信息结构体中的nblock 指定了要验证的块,即读取数据的块,只有该块被验证成功后,才能读取数据。Mifare S50 和Mifare S70 卡片包含的块数目详见表6.30。
表6.30 常见卡片的块数目(am_zlg600.h)
假定无需读取数据,可以定义自动检测信息如下:
定义好相关信息后,可以使用启动函数启动自动检测,即:
-
读取卡片信息
当启动自动检测后,若前面注册的回调函数被调用,表明检测到卡片,此时可以使用该接口读取卡片的信息。其函数原型为:
其中,p_card_info 为指向卡片信息的指针,用于获取卡片信息。卡片信息的类型am_zlg600_mifare_card_info_t(am_zlg600.h)定义如下:
该信息结构体包含了天线驱动模式、卡片唯一序列号和读取的数据等相关的信息。
-
天线驱动模式
tx_mode 表示天线的驱动模式,在启动自动检测时,天线的模式有4 种:仅使用TX1、仅使用TX2、TX1 和TX2 交替使用、TX1 和TX2 同时使用。若启动自动检测时使用的模式为TX1 和TX2 交替使用,那么该值将会被设置为实际检测到卡片的天线。tx_mode 可能被设置的值详见表6.31。
表6.31 读取到的天线驱动模式(am_zlg600.h)
当TX1 和TX2 同时使用时,将无法区分具体检测到卡片的天线。
-
片唯一序列号
每张卡片都具有一个唯一序列号,即UID。所有卡片的UID 都是不相同的。卡的序列号长度有三种:4 字节、7 字节和10 字节。uid_len 表明了读取到的UID 的长度,uid[10]中存放了读取到的UID(字节数)。
-
读取的数据
在启动自动检测时,指定了读取卡片数据相关的验证信息,若auth_mode 不为AM_ZLG600_MIFARE_CARD_AUTH_NO,且对应的密钥正确,验证成功,将读取启动自动检测时信息结构体的nblock 成员指定的块(由信息结构体的nblock 指定)的数据。读取的数据存放在card_data[16]数组中,读取卡片信息的范例程序详见程序清单6.105。
程序清单6.105 读取卡片信息范例程序
读取卡片信息成功后,将通过调试串口打印出读取到的UID 信息。并翻转LED1 灯的状态,指示读取一次卡片信息成功。
2. 卡片验证
由于卡片内存储的数据是加密的,因此必须验证成功后才能读写数据。验证方式有“E2验证”和“直接验证”,它们的区别是用于验证的密钥存放的位置不同。
-
E2 验证
用于验证的密钥是存放在ZLG600A 的E2PROM 中,其函数原型为:
其中,key_type 的类型是密钥类型,它的值为AM_ZLG600_IC_KEY_TYPE_A 或AM_ZLG600_IC_KEY_TYPE_B,分别代表A 类型密钥和B 类型密钥。
p_uid 为指向UID 高4 字节缓冲区的指针,若UID 为4 字节,其值为获取的UID 的首元素地址,即&card_info.uid[0];若UID 为7 字节,其值为获取的UID 的第3 号元素的地址,即&card_info.uid[3];若UID 为10 字节,其值为获取的UID 的第6 号元素的地址,即&card_info.uid[6]。key_sec 为密钥存放在ZLG600A 的E2PROM 中的区号,该值应该与使用am_zlg600_ic_key_load()函数存储对应密钥时使用的区号一致。
nblock 指定本次验证的块号,返回值为AM_OK 时表明验证成功,反之失败,使用区号0 中的A 类密钥验证块1 的范例程序详见程序清单6.106。
程序清单6.106 E2 验证范例程序
自动检测获取卡片信息card_info 详见程序清单6.101,调用am_zlg600_ic_key_load()将密钥存放在在0 区的E2PROM 中的程序详见程序清单6.103。
实际上绝大部分卡都是4 字节的,而7 或10 字节的UID 卡极少。如果确定使用的卡片UID 为4 字节,则p_uid 的值为&card_info.uid[0]。
-
直接验证
用于验证的密钥是由接口函数提供,其函数原型为:
其中,key_type、p_uid、nblock 的含义与“E2 验证”相同,由于需要直接提供密钥,因此使用p_key 指向密钥存放的缓冲区,key_len 表示密钥的长度。返回值为AM_OK,表明验证成功,反之失败,直接使用6 字节密钥进行验证的范例程序详见程序清单6.107。
程序清单6.107 直接验证范例程序
程序使用6 字节全0xFF 作为密钥验证块1,之所以这样,是因为Mifare S50/S70 卡片的密钥出厂默认为全0xFF,且密钥长度为6 字节。对于出厂默认设置,使用A 类密钥和B类密钥验证均可。对于一些有权限控制的卡片,如验证A 类密钥后仅只读,验证B 类密钥后可写,则需要根据实际情况进行验证,密钥和权限控制的修改后面会进一步介绍。
3. 读写数据
若验证成功,则开始读写已验证的块。读写数据都是以块为单位的,其大小为16 字节。
-
读取数据
读取数据就是读取某一块的数据,其函数原型为:
其中,nblock 指定读取的块号,p_buf 为指向存放数据的缓冲区,缓冲区大小为16 字节。返回值为AM_OK 时,表明读取数据成功,反之失败。如程序清单6.108 所示为读取块1 数据的范例程序。
程序清单6.108 读取数据范例程序
-
写入一块数据
写入数据就是将数据写入到某一块,其函数原型为:
其中,nblock 指定写入的块号,p_buf 为指向写入数据的缓冲区,缓冲区大小为16 字节。返回值为AM_OK 时,说明写入数据成功,反之失败。如程序清单6.109 所示为写入16 字节数据至块1 的范例程序。
程序清单6.109 写入数据范例程序
如程序清单6.110 所示是展示卡片自动检测、验证和读写数据的综合测试范例程序。
程序清单6.110 ZLG600 综合测试范例程序
程序每检测到一次卡片(detect_flag 的值为1),将读取一次信息,然后使用调试串口打印UID 的值,接着进行读写检测。若读写检测失败,表明验证失败,很可能密钥不是初始密钥,已经被修改过了。
由于读取UID 并不需要验证,因此,当测试程序运行时,将常见的公交卡、门禁卡、饭卡等卡靠近ZLG600A,均可以读取卡片的UID。由于这些卡片的密钥并不知晓,因此读写数据测试很可能会失败。
>>> 6.4.5 密钥和权限控制
Mifare S50/S70 卡的初始密钥全为0xFF,显然,对于实际产品来讲,希望能够更改其密钥为其它值。由于存在密钥A和密钥B,可以对每个密钥设定不一样的权限,如验证密钥A后仅只读,验证密钥B 后可写。下面以Mifare S50 为例,介绍密钥和权限控制的修改方法。
密钥和权限控制是针对扇区而言的,即一个扇区的密钥是相同的,不同扇区的密钥可以不同。S50 共计16 个扇区,每个扇区4 块,每块16 字节,前3块为普通的数据块,最后一块(尾块)为密钥和权限控制块。对最后一块存储的数据进行修改,即可完成密钥和权限控制的修改。操作最后一块的存储数据时要格外小心,数据稍有错误,就可能导致扇区被锁死。尾块的前6 字节为A 密钥,后6字节为B 密钥,中间4 字节用于权限控制,详见图6.11。
图6.11 尾块格式
如需修改密钥和控制权限,重点在理解字节6、7 和8(字节9 是一个普通的数据)的含义。3 个字节共计24 位,每6 位(分别为C1、C2、C3、
由于存在此关系,因此实际控制位的含义仅通过C1、C2、C3 三个位即可确定。控制尾块和数据块的控制位含义是不同的。对于尾块,其控制了密钥A、密钥B 以及控制区域的访问权限。控制位的含义详见表6.32。
表6.32 尾块控制位含义
表中,“×”表示任何情况下都无权限,“KeyA”表示通过密钥A 验证后可以取得权限,KeyB 表示通过密钥B 验证后可以取得权限,“KeyA | B”表示通过密钥A 或者密钥B 验证后均可取得权限。由此可见,密钥A 的安全性很高,任何情况下都无法读出。特殊情况下,当C1C2C3 的值为000、010 或001 时,验证密钥A 后即可读取密钥B 区域的数据。
无论什么情况,验证密钥A 后,均可获得控制区域的读权限。通过读取控制区域,可以知道当前C1、C2、C3 的值,以判断需要验证哪个密钥后可以获得密钥区域或控制区域的写权限,进而修改密钥和控制区域的值。比如,当前的C1、C2、C3 的值为0、1、1,为了修改密钥A,则需要先验证密钥B,验证密钥B 后,即可对尾块进行写入,写入时其它数据保持不变,仅修改前6 字节(KeyA 区域)的值即可完全对密钥A 的修改。
对于数据块,C1、C2、C3 控制了对块中存储数据的操作权限,详见表6.33。
表6.33 数据块控制位含义
同样,表中“×”表示任何情况下都无权限,“KeyA”表示通过密钥A 验证后可以取得权限,KeyB 表示通过密钥B 验证后可以取得权限,“KeyA | B”表示通过密钥A 或者密钥B 验证后均可取得权限。
加值操作(相当于充值)和减值操作(相当于消费)是对块中存放的值进行增加和减少操作,加值和减值均有对应的命令可以直接使用。例如,当前块1 的C1、C2、C3 控制位的值为0、0、0(默认值),只要密钥A 或密钥B 验证通过后,均可取得数据块的读、写、加值、减值的权限。可以根据实际需要,修改尾块中相应控制位的值(修改时,需确保具有写入控制区域的权限),以对数据进行保护。
需要注意的是,凡是表中标识验证密钥B 后可以取得权限的,在特殊情况下验证密钥B后可能并不能取得权限。在介绍尾块控制位含义时,当C1、C2、C3 的值为000、010 或001时,KeyB 区域将可能被读取,详见表6.32。这些情况下,由于密钥B 可能被读取,为了确保安全,此时密钥B 验证将无效,即使密钥B 验证通过,同样无法取得相应的权限。
-
RS485
+关注
关注
39文章
1155浏览量
82284 -
周立功
+关注
关注
38文章
130浏览量
37613 -
致远电子
+关注
关注
13文章
406浏览量
31301
原文标题:周立功:重用外设驱动代码——读写卡模块
文章出处:【微信号:Zlgmcu7890,微信公众号:周立功单片机】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论