【沁恒CH585开发板免费试用体验】NFC近场通信 - RISC-V MCU技术社区 - 电子技术论坛 - 广受欢迎的专业电子论坛
分享 收藏 返回

[文章]

【沁恒CH585开发板免费试用体验】NFC近场通信

开发环境:

IDE:MounRiver Studio

MCU:CH585

1 RFID技术简介

1.1 RFID概述

__RFID(Radio Frequency Identification)__射频识别,又称无线射频识别,是一种通信技术,可通过无线电讯号识别特定目标并读写相关数据,而无需识别系统与特定目标之间建立机械或光学接触。

RFID的原理为阅读器与标签之间进行非接触式的数据通信,达到识别目标的目的。只要是射频方式,并且能够通过这种方式识别到的都算作RFID范畴,按照频率来分,一般可分为低频、高频、超高频、2.4G等。RFID的应用非常广泛,典型应用有动物管理、车辆管理、生产线自动化、资产管理、人员管理、智慧医疗等。

1.2 NFC概述

__NFC(Near Field Communication)__技术相比RFID来说起步晚很多,大概是2003年前后,由Philips、Nokia和Sony主推的一种近距离无线通信技术,是一种短距离非接触式的通信方式,工作频率为13.56MHz,通信速率为106kbit/秒到 848kbit/秒。通过手机为载体,把非接触式IC卡应用结合于手机中,以卡、阅读器、点对点三种应用模式,实现手机支付、行业应用、积分兑换、电子票务、身份识别、防伪、广告等多种应用的服务产品。NFC的一个重要特点是其【非接触式】,因此那些【接触式】的并拥有极高安全等级的银行卡不属于NFC范畴。

NFC的【非接触式】特性,带来了另一项技术的快速发展,那就是【无线充电】。一般来讲,PICC(射频卡)中是没有电池或其他供电电源的,PCD(读卡器)要与PICC进行通信的前提是将电能无线传递给PICC,驱动PICC中的电路及芯片工作,芯片控制模拟电路产生电磁波,将数据调制解调成1和0的数字信号,从而进行通信。说起【无线充电】,简单来讲就是中学课本里的电磁感应,通过线圈,让变化的电场产生磁场,在另一端变化的磁场又产生了电场。

近场通信的卡片有太多的名词和英文缩写,这里做个简单的解释:

ID卡,这种卡最大的特点就是没有__复杂的电路和芯片__,内部只携带一个出厂后不可改变的身份ID,最多不超过32bit,读卡器只能读到ID,逻辑处理一般在应用层和数据库中进行操作。ID卡成本低,市面上有些低成本的门禁卡,考勤卡会使用ID卡,安全很低。

IC卡,这种卡相对ID卡来说拥有__复杂的电路和芯片__,因此在光线明亮的地方可以看到卡片内的电路和芯片,其内部携带的数据量也大大增加了,通常都有1K Byte以上的读写空间。所有的NFC卡都是IC卡,内部都有线圈和芯片,以及大容量的存储空间,我们的身份证甚至连个人照片都存在其中。IC卡只是表明卡内部有集成电路和芯片,但并不规定卡片是【接触式】还是【非接触式】。很多银行的【接触式】银行卡,芯片引脚是露在表面的,供ATM进行接触式读写,这种银行卡也属于IC卡。

RFID卡,这种卡属于IC卡的一种,有芯片,但规定了必须使用射频技术进行非接触式通信。RFID卡虽然表明了非接触式通信,但对通信时的载波频率并不做任何要求,低至125KHz,高至960M的RFID卡产品都有。

NFC卡,这是我们关注的重点,NFC相比RFID卡,规范了通信时的载波频率必须为13.56MHz,同时在又在软件层面规范了众多传输协议,比如NFC传输的WIFI用户名密码、名片、网址等等。

Mifare卡,这是NFC卡的一种,市面上最常用,最廉价的NFC卡之一。恩智浦(前身为飞利浦)公司出品,我们后续要讲的PN532芯片,以及其他PN系列芯片均来自这个公司。Mifare卡分为MF0, MF1, MF2, MF3这么几种类型,MF0不带密码控制,使用很少;最常用的是MF1,这种卡带密码控制,分为S50和S70,S50拥有1K存储空间,S70拥有4K存储空间。我们后续的实践使用S50这种卡片。

1.3 RFID和NFC区别与联系

NFC是RFID中间高频内的一部分,为近场通信,距离一般小于10cm。所以一般的NFC读写器都是指高频的读写器, 如YW-607HC。NFC类型的标签或者卡也比较多,如M1卡,NTAG,TopAZ,Felica等等。

1、工作频率

NFC的工作频率为13.56MHz;而RFID的工作频率有低频、高频、超高频等。下面列举下各自频率的特点:

  • 低频(LF):大部分是近距离的,距离在小于15cm,加大功率可以可以读取1米,卡片基本为只读的,有部分卡片可写。俗称ID卡。
  • 高频(HF):高频也是近距离的,距离一般小于10cm,常说的IC卡,CPU卡,NFC标签都是这个距离,可读可写。少数协议如ISO15693可以增加功率做到1米的距离。
  • 超高频(UHF):超高频的距离可以做到15米, 但是容易受到干扰, 标签一般是无源的。
  • 微波段距离可以做的更远,只不过一般都是有源的。

2、工作距离

NFC的工作距离理论上为020cm,但是在产品的实现上,由于采用了特殊功率抑制技术,使其工作距离只有010cm,从而更好地保证业务的安全性;由于RFID具有不同的频率,其工作距离在几厘米到几十米不等。

3、工作模式

NFC同时支持读写模式和卡模式;而在RFID中,读卡器和非接触卡是独立的两个实体,不能切换。

4、通信模式

NFC支持P2P模式,RFID不支持P2P模式。

5、标准协议

NFC的底层通讯协议兼容高频RFID的底层通信标准,即兼容ISO14443/ISO15693标准。NFC技术还定义了比较完整的上层协议,如LLCP,NDEF和RTD等。

6、应用领域

RFID更多的应用在生产线、仓储物流、资产管理等应用上,而NFC则工作在门禁、公交卡、手机支付等领域。

http://www.etagrfid.com/Upload/Images/202207131236029116.jpg

Figure ‑ RFID,NFC,IC卡关系图

NFC的诞生初衷是为了近场支付和松散认证的小数据交换,NFC选用了13.56MHz作为基础,同时增加了一个点对点的通信;传统的RFID同一时间只能一个READER与一个TAG交互,而NFC可以两个READER之间相互传数据,同时NFC并没有兼容所有的13.56MHz,只是有所取舍地兼容了14443和15693两种协议。虽然NFC是在RFID技术基础上发展而来的,但是面向不同的应用场景,并不存在替代作用,况且现在RFID的应用场景还是非常多的,NFC暂时还比不了,即使在支付领域和近距离物体识别领域,二维码在很多场景下也能起到NFC的作用,目前对智能手机来说,NFC还不是一个必需的通信接口。

2 通信协议

2.1 ISO14443A/B,以及ISO15963

这三个都是射频卡的一种标准,都属于RFID卡的范畴,其中ISO14443A/B属于NFC卡的范畴,而ISO15963不是。

ISO15963标准规定了通信距离长达1m,使用13.56MHz频率载波,通讯速度不超过56Kb/s,不加密。一般ID卡使用ISO15963标准设计。

ISO14443A/B标准规定了通信距离为7-15cm,使用13.56MHz频率载波,通信速度为106Kb/s,支持加密。ISO14443A与ISO14443B的区别主要在于编码方式、防冲撞机制等,这些区别的详细知识应用开发者不需要过多理解,大部分NFC芯片或者模组都是能读写不同类型的卡片的,对于应用开发者来说只需要确切地知道自己产品中使用的卡片类型是哪一种即可。我们后面的实践将采用Mifare S50卡,这种卡属于ISO1443A类型。

3 Mifare S50电子标签

M1芯片,是指菲利浦下属子公司恩智浦出品的芯片缩写,全称为NXP Mifare1系列,常用的有S50及S70两种型号,属于非接触式IC卡。非接触式IC卡又称射频卡,成功地解决了无源(卡中无电源)和免接触这一难题,是电子器件领域的一大突破。主要用于公交、轮渡、地铁的自动收费系统。M1卡,优点是可读可写的多功能卡,缺点是:价格稍贵,感应距离短。

1.png

Figure ‑ Mifare S50电子标签内部结构

3.1 Mifare S50工作原理

向M1卡发一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下,LC谐振电路产生共振,从而使电容内有了电荷,在这个电容的另一端,接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到2V时,此电容可做为电源为其它电路提供工作电压,将卡内数据发射出去或接取读写器的数据。

2.png

Figure ‑ Mifare S50操作流程

3.2 Mifare S50存储结构

每张Mifare S50卡拥有1KByte存储空间,片内将1K空间分成了16个扇区,每个扇区64Byte。每个扇区又分成4个数据块,每个数据块16Byte。其中第一扇区第一块数据为厂商出厂数据,内含4Byte全球唯一UUID号,原则上无法修改。

Table ‑ 数据分布

扇区号 块号 数据 块类型
0 0 厂商数据 厂商块
1 用户数据 数据块
2 用户数据 数据块
3 密码A,存取控制,密码B 控制块
... ... ... ...
15 60 用户数据 数据块
61 用户数据 数据块
62 用户数据 数据块
63 密码A,存取控制,密码B 控制块

每个扇区的最后一个数据块称之为控制块,这一数据块的16Byte数据按照6-4-6分组,前6个Byte为密码A,后6个Byte为密码B,中间4Byte为存取控制数据。这一数据块的作用是用来保护前三块数据块的读写权限的。

Mifare S50在出厂时,每一个控制块的密码A和密码B都为FF FF FF FF FF FF,习惯上我们将密码A和密码B设置为相同,将中间4块存取控制块保持FF 07 80 69不变。要想读写某一数据块的内容,必须使用该块所在片区的最后一块,也就是控制块的密码进行验证才可以。Mifare S50通过密码访问授权机制,具有一定的安全性,但随着越来越广泛的使用,市面上也出现了众多破解方式,最常见的就是使用字典进行暴力破解。如果涉及金额或者其他重要数据,开发者是要进行服务器存储和比对的,一旦发现数据异常应该锁卡,对于造成损失的恶意攻击,应当报警处理。

本文重点要理解了ISO14443A协议以及应用该规范的Mifare S50卡片。后续文章将围绕这张卡片的读写来进行讲解,读写器为CH585。

4 CH585 NFC简介

芯片提供一个近场通信无线接口,包含了收发器和兼容 ISO14443-A 的无线解码基带,支持读卡器(PCD)模式和卡(PICC)模式。本手册不对寄存器作详细介绍,无线通讯底层操作主要以子程序库提供应用支持。

主要特性:

  • 支持 ASK(Amplitude Shift Keying)调制和 NRZ(Non-Return-to-Zero)编码
  • PCD 模式支持 ISO14443-A 协议中的 106kbit/s 数据波特率
  • PCD 模式支持对 ISO14443-A 类卡片的读取、写入等操作
  • PCD 模式支持防碰撞:若多个卡片同时进入射频场,读卡器会执行防碰撞算法来识别每个卡片
  • PICC 模式支持模拟 ISO14443-A 类卡片,实现数据读取和写入操作
  • PICC 模式支持 NDEF 数据交互功能,支持与手机等设备智能联动:电子名片、蓝牙 OOB 配对
  • 提供优化的协议栈和应用层 API
  • 提供硬件加速的加解密算法 Crypto1,适用于 M1 类卡片

CH585 NFC硬件电路如下。

3.png

Figure ‑ CH585 NFC电路图

值得注意的是,硬件通信接口只指定通信介质和时序,跟软件通信协议无关,无论何种通信接口,其传输数据时收包和发包的内容都是一致的,由软件层面的通信协议来决定。

4 无线通信实践

接下来将CH585上,对M1卡进行读写操作。

4.1 初始化

主要是要对nfca pcd_的引脚进行初始化。核心代码如下。

* @fn      nfca_pcd_init
 *
 * [url=home.php?mod=space&uid=2666770]@Brief[/url]   nfc-a pcd读卡器初始化
 *
 * [url=home.php?mod=space&uid=3142012]@param[/url]   none
 *
 * [url=home.php?mod=space&uid=1141835]@Return[/url]  none
 */
void nfca_pcd_init(void)
{
    nfca_pcd_config_t cfg;
    uint8_t res;

    /* NFC引脚初始化为模拟输入模式 */
    GPIOB_ModeCfg(GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_16 | GPIO_Pin_17, GPIO_ModeIN_Floating);

    /* 关闭GPIO数字输入功能 */
    R32_PIN_IN_DIS |= ((GPIO_Pin_8 | GPIO_Pin_9) << 16);        /* 关闭GPIOB中GPIO_Pin_8和GPIO_Pin_9的数字输入功能 */
    R16_PIN_CONFIG |= ((GPIO_Pin_16 | GPIO_Pin_17) >> 8);       /* 关闭GPIOB中GPIO_Pin_16和GPIO_Pin_17的数字输入功能 */

    /* CH584F和CH585F内部PA9和PB9短接,需要将PA9也设置为模拟输入并关闭数字功能,M封装注释下面的两句代码,F封装取消注释 */
//    GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeIN_Floating);
//    R32_PIN_IN_DIS |= GPIO_Pin_9;

    /* 通讯结束信号回调,在中断中调用,获取信号后必须在主程序中调用nfca_pcd_get_communicate_status,才可以完成数据整理和解码。 */
    cfg.pcd_end_cb = NULL;

    cfg.data_buf = gs_nfca_pcd_data_buf;
    cfg.data_buf_size = NFCA_PCD_DATA_BUF_SIZE;

    cfg.send_buf = g_nfca_pcd_send_buf;
    cfg.send_buf_size = NFCA_PCD_MAX_SEND_NUM;

    cfg.recv_buf = g_nfca_pcd_recv_buf;
    cfg.recv_buf_size = NFCA_PCD_MAX_RECV_NUM;

    cfg.parity_buf = g_nfca_pcd_parity_buf;
    cfg.parity_buf_size = NFCA_PCD_MAX_PARITY_NUM;

    /* 将数据区指针传入给NFC库内BUFFER指针 */
    res = nfca_pcd_lib_init(&cfg);
    if(res)
    {
        PRINT("nfca pcd lib init error\n");
        while(1);
    }
#if NFCA_PCD_USE_NFC_CTR_PIN
    nfca_pcd_ctr_init();
#endif

当然在初始化后还可以对卡ADC值校准,和天线等相关进行校准。

接下来进入卡操作。

首先是开启读卡器功能,

/*********************************************************************
 * @fn      nfca_pcd_start
 *
 * @brief   nfc-a pcd读卡器功能开始运行
 *
 * @param   none
 *
 * @return  none
 */
__HIGH_CODE
void nfca_pcd_start(void)
{
#if NFCA_PCD_USE_NFC_CTR_PIN
    nfca_pcd_ctr_on();
#endif
    nfca_pcd_lib_start();
    PFIC_ClearPendingIRQ(NFC_IRQn);
    PFIC_EnableIRQ(NFC_IRQn);
}

接着开始寻卡。用于检测和识别场内的 NFC Type A 卡片(PICC),核心代码如下:

uint16_t PcdRequest(uint8_t req_code)
{
    uint8_t retry_cnt = 0;
    uint16_t res;
    uint16_t atqa;
    nfca_pcd_controller_state_t status;

    g_nfca_pcd_send_buf[0] = req_code;  // 设置请求命令
    nfca_pcd_set_wait_us(200);           // 设置超时200μs

retry:
    if(nfca_pcd_communicate(7, NFCA_PCD_REC_MODE_COLI, 0) == 0) // 发送7位请求
    {
        status = nfca_pcd_wait_communicate_end();  // 等待通信结束

        // 检查通信状态(成功或冲突)
        if((status == NFCA_PCD_CONTROLLER_STATE_DONE) || 
           (status == NFCA_PCD_CONTROLLER_STATE_COLLISION))
        {
            // 验证接收的比特数(2字节+奇偶校验位=18位)
            if(g_nfca_pcd_recv_bits == (2 * 9))
            {
                // 检查奇偶校验
                if(ISO14443ACheckOddParityBit(g_nfca_pcd_recv_buf, 
                                             g_nfca_pcd_parity_buf, 2))
                {
                    // 成功获取ATQA
                    PRINTF("ATQA:0x%04x\r\n", PU16_BUF(g_nfca_pcd_recv_buf)[0]);
                    return PU16_BUF(g_nfca_pcd_recv_buf)[0];
                }
                else  // 奇偶校验失败
                {
                    PRINTF("ODD BIT ERROR\r\n");
                    PRINTF("data:0x%02x 0x%02x\r\n", 
                           PU16_BUF(g_nfca_pcd_recv_buf)[0], 
                           PU16_BUF(g_nfca_pcd_recv_buf)[1]);
                    PRINTF("parity:%d %d\r\n", 
                           g_nfca_pcd_parity_buf[0], 
                           g_nfca_pcd_parity_buf[1]);
                }
            }
            else  // 接收比特数错误
            {
                PRINTF("BITS NUM ERROR: %d, 0x%04x\r\n", 
                       g_nfca_pcd_recv_bits, 
                       PU16_BUF(g_nfca_pcd_recv_buf)[0]);
            }
        }
        else  // 通信状态错误
        {
            PRINTF("STATUS ERROR: %d\r\n", status);
        }
    }
    else  // 通信启动失败
    {
        PRINTF("COMMUNICATE ERROR\r\n");
    }

end:
    return 0;  // 未检测到卡片
}

1、请求阶段 (REQ)

发送短帧 REQA (0x26) 或 WUPA (0x52)

  • REQA:唤醒所有处于 IDLE 状态的卡片
  • WUPA:唤醒所有卡片(包括 HALT 状态)

2、响应阶段 (ATQA)

卡片返回 16 位 ATQA (Answer to Request)

Table ‑ ATQA 解码表

ATQA 卡片类型 UID****长度 协议
0x0004 Mifare Classic
1K 4字节 ISO 14443-3
0x0002 Mifare Classic 4K 4字节 ISO 14443-3
0x0044 Mifare
Ultralight 7字节 ISO 14443-3
0x0344 NTAG213/215/216 7字节 ISO 14443-3

接下来就是防冲突检测。

uint16_t PcdAnticoll(uint8_t cmd)
{
    nfca_pcd_controller_state_t status;
    uint16_t res = PCD_COMMUNICATE_ERROR;

    // 设置防冲突命令和NVB(Number of Valid Bits)
    g_nfca_pcd_send_buf[0] = cmd;
    g_nfca_pcd_send_buf[1] = 0x20;  // 固定NVB=0x20(发送完整UID CLn)

    nfca_pcd_set_wait_ms(PCD_ANTICOLL_OVER_TIME);
    // 计算奇偶校验位
    ISO14443ACalOddParityBit((uint8_t *)g_nfca_pcd_send_buf, 
                            (uint8_t *)g_nfca_pcd_parity_buf, 2);

    // 启动通信(发送16位)
    if (nfca_pcd_communicate(16, NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();

        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            // 检查接收的45位数据(5字节*9位/字节)
            if (g_nfca_pcd_recv_bits == (5 * 9))
            {
                // 校验奇偶位
                if (ISO14443ACheckOddParityBit(g_nfca_pcd_recv_buf, 
                                              g_nfca_pcd_parity_buf, 5))
                {
                    // 检查BCC校验(UID CLn异或校验)
                    if (ISO14443A_CHECK_BCC(g_nfca_pcd_recv_buf))
                    {
                        res = PCD_NO_ERROR;
                    }
                    else
                    {
                        res = PCD_BCC_ERROR;
                        PRINTF("check bcc error\n");
                    }
                }
                else
                {
                    res = PCD_ODD_PARITY_ERROR;
                    PRINTF("ODD BIT ERROR\r\n");
                }
            }
        }
    }
end:
    return res;
}

核心步骤如下:

1、冲突检测:

  • 读卡器发送 ANTICOLLISION 命令(0x93/0x95/0x97)
  • 卡片返回 UID 部分或全部字节
  • 读卡器检测接收波形中的冲突位(0和1同时存在)

2、冲突处理

3、UID筛选:

  • 根据冲突位置更新 NVB(有效位数)
  • 发送新命令缩小UID匹配范围

防冲突状态机如下:

4.png

接下来就是卡选择。

{
    nfca_pcd_controller_state_t status;
    uint16_t res = PCD_COMMUNICATE_ERROR;

    // 准备发送缓冲区
    g_nfca_pcd_send_buf[0] = cmd;          // 选择命令 (0x93, 0x95, 0x97)
    g_nfca_pcd_send_buf[1] = 0x70;         // NVB (Number of Valid Bits)
    g_nfca_pcd_send_buf[6] = 0;             // 初始化BCC
    
    // 设置超时时间
    nfca_pcd_set_wait_ms(PCD_SELECT_OVER_TIME);
    
    // 复制UID并计算BCC
    for (res = 0; res < 4; res++) {
        g_nfca_pcd_send_buf[res + 2] = *(pSnr + res);
        g_nfca_pcd_send_buf[6] ^= *(pSnr + res); // BCC计算
    }
    
    // 计算CRC并附加到缓冲区
    ISO14443AAppendCRCA((uint8_t *)g_nfca_pcd_send_buf, 7);
    
    // 计算奇偶校验位
    ISO14443ACalOddParityBit((uint8_t *)g_nfca_pcd_send_buf, 
                           (uint8_t *)g_nfca_pcd_parity_buf, 9);

    // 启动通信
    if (nfca_pcd_communicate(9 * 8, NFCA_PCD_REC_MODE_NORMAL, 0) == 0) {
        status = nfca_pcd_wait_communicate_end();

        if(status == NFCA_PCD_CONTROLLER_STATE_DONE) {
            // 检查接收的比特数 (3字节 * 9位/字节)
            if (g_nfca_pcd_recv_bits == (3 * 9)) {
                // 检查奇偶校验
                if (ISO14443ACheckOddParityBit(g_nfca_pcd_recv_buf, 
                                             g_nfca_pcd_parity_buf, 3)) {
                    // 检查CRC
                    if (ISO14443_CRCA((uint8_t *)g_nfca_pcd_recv_buf, 3) == 0) {
                        g_m1_crypto1_cipher.is_encrypted = 0;
                        res = PCD_NO_ERROR;
                    } else {
                        res = PCD_CRC_ERROR;
                        PRINTF("check crc error\n");
                    }
                } else {
                    res = PCD_ODD_PARITY_ERROR;
                    PRINTF("ODD BIT ERROR\r\n");
                }
            }
        }
    }

end:
    return res;
}

SELECT 命令状态机如下图所示。

5.png

接下来就是卡状态检查。

Mifare Classic 使用 Crypto1 加密算法的三步认证协议:

6.png

核心代码如下:

/**
 * @brief   decrement or increment a value on the value block.
 *
 * @param   auth_mode - 0x60 = authenticate A key, 0x61 = authenticate B key.
 * @param   addr - the addr of the block.
 * @param   pValue - 4 bytes value(Little-Endian), for decrement or increment.
 *
 * @return  PCD_NO_ERROR = success.<BR>
 *          others = fail.<BR>
*/
uint16_t PcdAuthState(uint8_t auth_mode, uint8_t addr, uint8_t *pKey, uint8_t *pUid)
{
    nfca_pcd_controller_state_t status;
    uint16_t res = PCD_COMMUNICATE_ERROR;
    uint8_t tag_rsp[4];
    uint8_t tag_rsp_parity[4];

    PRINTF("Auth %02x, addr: %d\n", auth_mode, addr);

step1:
    g_nfca_pcd_send_buf[0] = auth_mode;
    g_nfca_pcd_send_buf[1] = addr;
    ISO14443AAppendCRCA((uint8_t *)g_nfca_pcd_send_buf, 2);

    nfca_pcd_set_wait_ms(PCD_AUTH_OVER_TIME);

    if (g_m1_crypto1_cipher.is_encrypted != 0)
    {
        nfca_crypto1_encrypt((nfca_crypto1_cipher_t *)&g_m1_crypto1_cipher, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_parity_buf, 4 * 8);
    }
    else
    {
        ISO14443ACalOddParityBit((uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_parity_buf, 4);
    }

    if (nfca_pcd_communicate((4 * 8), NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();
        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            if (g_nfca_pcd_recv_bits == (4 * 9))
            {
                if (nfca_pcd_crypto1_setup(
                            &g_m1_crypto1_cipher,
                            pKey,
                            pUid,
                            g_nfca_pcd_recv_buf,
                            g_nfca_pcd_parity_buf,
                            nfca_pcd_rand(),
                            g_nfca_pcd_send_buf,
                            g_nfca_pcd_parity_buf,
                            tag_rsp,
                            tag_rsp_parity) == 0)
                {
                    goto step2;
                }
                else
                {
                    /* 校验错误 */
                    res = PCD_AUTH_ERROR;
                    PRINTF("Auth err\n");
                }
            }
        }
    }
    goto end;

step2:
    if (nfca_pcd_communicate((8 * 8), NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();
        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            if (g_nfca_pcd_recv_bits == (4 * 9))
            {
                if ((tag_rsp[0] == g_nfca_pcd_recv_buf[0])
                        && (tag_rsp[1] == g_nfca_pcd_recv_buf[1])
                        && (tag_rsp[2] == g_nfca_pcd_recv_buf[2])
                        && (tag_rsp[3] == g_nfca_pcd_recv_buf[3])
                        && (tag_rsp_parity[0] == g_nfca_pcd_parity_buf[0])
                        && (tag_rsp_parity[1] == g_nfca_pcd_parity_buf[1])
                        && (tag_rsp_parity[2] == g_nfca_pcd_parity_buf[2])
                        && (tag_rsp_parity[3] == g_nfca_pcd_parity_buf[3]))
                {
                    g_m1_crypto1_cipher.is_encrypted = 1;
                    res = PCD_NO_ERROR;
                }
                else
                {
                    /* 校验错误 */
                    res = PCD_AUTH_ERROR;
                    PRINTF("Auth err\n");
                }
            }
        }
    }
end:
    return res;
}

4.2 读数据

接下来就是数据读取。

/**
 * @brief   Read data from the given block.
 *
 * @param   addr - the addr of the block.
 *
 * @return  PCD_NO_ERROR = success.<BR>
 *          others = fail.<BR>
*/
uint16_t PcdRead(uint8_t addr)
{
    uint16_t res = PCD_COMMUNICATE_ERROR;
    nfca_pcd_controller_state_t status;

    nfca_pcd_set_wait_ms(PCD_READ_OVER_TIME);

    g_nfca_pcd_send_buf[0] = PICC_READ;
    g_nfca_pcd_send_buf[1] = addr;

    ISO14443AAppendCRCA((uint8_t *)g_nfca_pcd_send_buf, 2);

    nfca_crypto1_encrypt(&g_m1_crypto1_cipher, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_parity_buf, (4 * 8));

    if (nfca_pcd_communicate((4 * 8), NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();
        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            if (g_nfca_pcd_recv_bits == (18 * 9))
            {
                if (nfca_crypto1_decrypt(&g_m1_crypto1_cipher, g_nfca_pcd_recv_buf, g_nfca_pcd_recv_buf, g_nfca_pcd_parity_buf, 18 * 8) == 0)
                {
                    res = PCD_NO_ERROR;
                }
                else
                {
                    res = PCD_DECRYPT_ERROR;
                }
            }
            else if (g_nfca_pcd_recv_bits == ACK_NAK_FRAME_SIZE)
            {
                if (nfca_crypto1_decrypt(&g_m1_crypto1_cipher, g_nfca_pcd_recv_buf, g_nfca_pcd_recv_buf, g_nfca_pcd_parity_buf, ACK_NAK_FRAME_SIZE) == 0)
                {
                    res = PICC_NAK_HEAD | g_nfca_pcd_recv_buf[0];
                }
                else
                {
                    res = PCD_DECRYPT_ERROR;
                }
            }
            else
            {
                res = PCD_FRAME_ERROR;
            }
        }
    }

end:
    return res;
}

4.3 写数据

数据写入核心代码如下。

/**
 * @brief   Writes data to the given block.
 *
 * @param   addr - the addr of the block.
 * @param   pData - 16 bytes data need to write.
 *
 * @return  PCD_NO_ERROR = success.<BR>
 *          others = fail.<BR>
*/
uint16_t PcdWrite(uint8_t addr, uint8_t *pData)
{
    nfca_pcd_controller_state_t status;
    uint16_t res = PCD_COMMUNICATE_ERROR;
    uint8_t i;

step1:
    nfca_pcd_set_wait_ms(PCD_WRITE_STEP1_OVER_TIME);

    g_nfca_pcd_send_buf[0] = PICC_WRITE;
    g_nfca_pcd_send_buf[1] = addr;

    ISO14443AAppendCRCA((uint8_t *)g_nfca_pcd_send_buf, 2);
    nfca_crypto1_encrypt(&g_m1_crypto1_cipher, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_parity_buf, (4 * 8));

    if (nfca_pcd_communicate((4 * 8), NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();

        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            if (g_nfca_pcd_recv_bits == ACK_NAK_FRAME_SIZE)
            {
                if (nfca_crypto1_decrypt(&g_m1_crypto1_cipher, g_nfca_pcd_recv_buf, g_nfca_pcd_recv_buf, g_nfca_pcd_parity_buf, ACK_NAK_FRAME_SIZE) == 0)
                {
                    if (g_nfca_pcd_recv_buf[0] == ACK_VALUE)
                    {
                        goto step2;
                    }
                    else
                    {
                        res = PICC_NAK_HEAD | g_nfca_pcd_recv_buf[0];
                    }
                }
                else
                {
                    res = PCD_DECRYPT_ERROR;
                }
            }
            else
            {
                res = PCD_FRAME_ERROR;
            }
        }
    }

    goto end;

step2:
    nfca_pcd_set_wait_ms(PCD_WRITE_STEP2_OVER_TIME);

    for (i = 0; i < 16; i++)
    {
        g_nfca_pcd_send_buf[i] = pData[i];
    }

    ISO14443AAppendCRCA((uint8_t *)g_nfca_pcd_send_buf, 16);

    nfca_crypto1_encrypt(&g_m1_crypto1_cipher, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_send_buf, (uint8_t *)g_nfca_pcd_parity_buf, (18 * 8));

    if (nfca_pcd_communicate((18 * 8), NFCA_PCD_REC_MODE_NORMAL, 0) == 0)
    {
        status = nfca_pcd_wait_communicate_end();

        if(status == NFCA_PCD_CONTROLLER_STATE_DONE)
        {
            if (g_nfca_pcd_recv_bits == ACK_NAK_FRAME_SIZE)
            {
                if (nfca_crypto1_decrypt(&g_m1_crypto1_cipher, g_nfca_pcd_recv_buf, g_nfca_pcd_recv_buf, g_nfca_pcd_parity_buf, ACK_NAK_FRAME_SIZE) == 0)
                {
                    if (g_nfca_pcd_recv_buf[0] == ACK_VALUE)
                    {
                        res = PCD_NO_ERROR;
                    }
                    else
                    {
                        res = PICC_NAK_HEAD | g_nfca_pcd_recv_buf[0];
                    }
                }
                else
                {
                    res = PCD_DECRYPT_ERROR;
                }
            }
            else
            {
                res = PCD_FRAME_ERROR;
            }
        }
    }

end:
    return res;
}

4.4 实验现象

接上设备,打印信息如下。

7.png

当M1卡靠近时,打印信息如下。
8.png

更多回帖

×
发帖