1. 前言

之前做的 无源NFC墨水屏制作 发现对于一些手机识别不太好(应该是天线设计的问题),我已经做成了几个设备,然后邮寄给我朋友发现他们手机识别效果不好,这是我做 NFC图片刷写器的直接原因。毕竟如果不能更换图片,我不如直接给我朋友发一张滴胶封的纸 : (

效果演示

请添加图片描述

2. 硬件选择与设计

2.1 硬件选择

NFC传输部分,我考虑用现成的PN523设备:
请添加图片描述
其通过上面的拨码开关可切换以下三种模式:

模式 作用
HSU 大部分解密IC卡都是通过此模式完成的,因为不需要主控MCU,直接用个USB-TTL小板即可与这个板子进行通信, 通过串口发送数据帧完成对目标卡的读写
I2C 需要主控MCU, 与这块板子进行I2C连接,发送数据帧实现对目标卡的读写
SPI 与I2C相同,只不过使用的协议是SPI

主控MCU使用ESP-12模块,联网比较方便,传输图片就可以跨平台使用了。
要使用MCU的 HSU模式也需要有一个 USB传TTL的芯片(ch34c)。

2.2 硬件设计

ESP-12模块通过SPI协议与pn532小红板相连, 还要保留其HSU模式,这样我还可以使用这个设备对IC卡解密(有很多配套的软件),所有还需要一个 USB转ttl芯片(ch340c),ch340c还可以给 ESP-12模块进行下载程序调试日志。
所有这里使用一个单刀双置开关,用于切换ch340c 与 ESP-12模块连接 和 PN532小红板的连接。

请添加图片描述
请添加图片描述

原理图没什么可说的,很简单,关键在于PCB的绘制螺丝孔位置,我前后打了俩次板子,总是对不齐,开始时我用尺子测量,导致俩个定位孔总是有偏差。
后来我的小红板进行拍照,然后进行透视变换最后等比例放到PCB绘制中才完美的进行孔位对齐。(如果有更好的办法也希望评论区大佬给我些思路)

底板 通过排针和底座与 pn532小红板相连,并且我加入了螺柱,螺柱型号为: M2*11+4

2.3 其他硬件问题

在底板做成后,我发现识别信号很不好,导致我改版了几次,考虑因为底板铺铜影响识别,去除了底板铺铜,但是效果依然不好,应该是esp12模块上的屏蔽罩是一块完整导体,所有这里我还用了 铁氧防磁 贴,贴在了 小红板下方,识别效果立刻有显著提升:

请添加图片描述

3. 软件部分

其实值得说的就是软件部分,因为网上对 pn532对ntag读写资料很少,也花费了我很长时间。这里我用Arduino框架+ Adafruit_PN532 库实现。
软件部分主要实现的功能:

  1. 通过网络对esp12f模组发送图片数据
  2. esp12f接收到数据图片将数据发送给pn532小红板(spi协议)
3.1 esp8266写数据

向 nt3h2111芯片的里面写入数据:

nfc.ntag2xx_WritePage(address, data);
nfc.ntag2xx_ReadPage(address, data);

可以通过这俩个函数对 nt3h2111 进行读写数据,不过其限制写入的地址:

uint8_t Adafruit_PN532::ntag2xx_ReadPage(uint8_t page, uint8_t *buffer) {
  // TAG Type       PAGES   USER START    USER STOP
  // --------       -----   ----------    ---------
  // NTAG 203       42      4             39
  // NTAG 213       45      4             39
  // NTAG 215       135     4             129
  // NTAG 216       231     4             225

  if (page >= 231) {
#ifdef MIFAREDEBUG
    PN532DEBUGPRINT.println(F("Page value out of range"));
#endif
    return 0;
  }

#ifdef MIFAREDEBUG
  PN532DEBUGPRINT.print(F("Reading page "));
  PN532DEBUGPRINT.println(page);
#endif

如果大于0xe7 就会报错 Page value out of range。所以这里可以通过这个函数去改写NDEF 数据(EEPROM中),而SRAM在开启passthrough后其对应NFC内存布局地址是 F0-FF:
请添加图片描述
请添加图片描述
在NFC接口下,不同于I2C接口16字节数据, 其一个地址对应4个字节,所以16*4 = 64 字节正好也是 64字节SRAM的大小。

所以可以对 Adafruit_PN532 框架的代码进行修改,突破 0xe7 的限制(注释即可)。

3.2 启用FAST WRITE

参考:https://community.nxp.com/t5/NFC/I-can-t-read-the-session-register-in-NT3H1101-from-RF-side-with/td-p/558556
FAST WRITE指令 是非标准的数据交换指令,如果要实现这个功能就需要调用更底层的函数:

nfc.sendCommandCheckAck(cmd, cmdlen, timeout)

对于调试Adafruit_PN532 库 需要开启 #define PN532DEBUG
这样可以看到更底层的数据帧发送的情况:

Sending : 0x0, 0x0, 0xFF, 0x45, 0xBB, 0xD4, 0x42, 0xA6, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0xAD, 0x6A, 0xEA, 0xAA, 0xD7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0xD6, 0xB7, 0x55, 0x55, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x5B, 0x5A, 0xAA, 0xAA, 0xAF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF5, 0x6A, 0xAA, 0xB5, 0xB5, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0xAA, 0xAA, 0xAA, 0xD5, 0x2F, 0xC, 0x0, 
41

请添加图片描述

数据帧构造:

uint8_t data[68];
data[0] = 0x42;  // FAST_WRITE 指令代码
data[1] = 0xA6; // Cmd
data[2] = 0xF0; // START_ADDR
data[3] = 0xFF; // END_ADDR

data中剩余的64字节就是图片数据,值得一提的是,后面的CRC 无需构造,这个是库处理的:

void Adafruit_PN532::writecommand(uint8_t *cmd, uint8_t cmdlen) {
  if (spi_dev) {
    // SPI command write.
    uint8_t checksum;
    uint8_t packet[9 + cmdlen];
    uint8_t *p = packet;
    cmdlen++;

    p[0] = PN532_SPI_DATAWRITE;
    p++;

    p[0] = PN532_PREAMBLE;
    p++;
    p[0] = PN532_STARTCODE1;
    p++;
    p[0] = PN532_STARTCODE2;
    p++;
    checksum = PN532_PREAMBLE + PN532_STARTCODE1 + PN532_STARTCODE2;

    p[0] = cmdlen;
    p++;
    p[0] = ~cmdlen + 1;
    p++;

    p[0] = PN532_HOSTTOPN532;
    p++;
    checksum += PN532_HOSTTOPN532;

    for (uint8_t i = 0; i < cmdlen - 1; i++) {
      p[0] = cmd[i];
      p++;
      checksum += cmd[i];
    }

    p[0] = ~checksum;
    p++;
    p[0] = PN532_POSTAMBLE;
    p++;

#ifdef PN532DEBUG
    Serial.print("Sending : ");
    for (int i = 1; i < 8 + cmdlen; i++) {
      Serial.print("0x");
      Serial.print(packet[i], HEX);
      Serial.print(", ");
    }
    Serial.println();
#endif

    spi_dev->write(packet, 8 + cmdlen);
  } else if (i2c_dev || ser_dev) {
    // I2C or Serial command write.
    uint8_t packet[8 + cmdlen];
    uint8_t LEN = cmdlen + 1;

    packet[0] = PN532_PREAMBLE;
    packet[1] = PN532_STARTCODE1;
    packet[2] = PN532_STARTCODE2;
    packet[3] = LEN;
    packet[4] = ~LEN + 1;
    packet[5] = PN532_HOSTTOPN532;
    uint8_t sum = 0;
    for (uint8_t i = 0; i < cmdlen; i++) {
      packet[6 + i] = cmd[i];
      sum += cmd[i];
    }
    packet[6 + cmdlen] = ~(PN532_HOSTTOPN532 + sum) + 1;
    packet[7 + cmdlen] = PN532_POSTAMBLE;

#ifdef PN532DEBUG
    Serial.print("Sending : ");
    for (int i = 1; i < 8 + cmdlen; i++) {
      Serial.print("0x");
      Serial.print(packet[i], HEX);
      Serial.print(", ");
    }
    Serial.println();
#endif

    if (i2c_dev) {
      i2c_dev->write(packet, 8 + cmdlen);
    } else {
      ser_dev->write(packet, 8 + cmdlen);
    }
  }
}

我们只需要实现对图片数据的 64字节切片,构建这个数据帧最后再使用 sendCommandCheckAck 进行发送数据,就可以实现图片数据的传输。

3.3 网络通信部分实现

这里直接借用了微雪的部分源码,对其进行了删减以及修改:

const Route routes[] = {
    {"/", "text/html", MAIN_page},
    {"/scriptA.js", "application/javascript", scriptA},
    {"/scriptB.js", "application/javascript", scriptB},
    {"/scriptC.js", "application/javascript", scriptC},
    {"/scriptD.js", "application/javascript", scriptD},
    {"/styles.css", "text/css", styles}
};

通过浏览中的开发者工具直接复制 微雪 部分的html js, 省下了解决转义字符的时间,以及方便我临时调试js,调试好后再集成回mcu中:

#include <pgmspace.h>

const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
        <title>PriceTag</title>
        <link rel='icon' href='data:;base64,='>
        <link rel='stylesheet' href='styles.css'>
        <script src='scriptA.js'></script>
    '''
    '''
)rawliteral";

关于web端部分我主要修改了上传图片的api接口,原始的微雪程序中,对图片数据进行了切片传输,并且js部分与 墨水屏型号 耦合度较高,所以这一部分全部删除了。因为我所需要传输的图像数据无关与墨水屏的型号(只有图像大小的区别),对于驱动墨水屏的逻辑是stm32中实现的(具体参考: 无源NFC墨水屏制作)。

js 接口:

function uploadImage() {
    var c = getElm('canvas');
    var w = dispW = c.width;
    var h = dispH = c.height;
    var p = c.getContext('2d').getImageData(0, 0, w, h);
    var a = new Array(w * h);
    var i = 0;
    for (var y = 0; y < h; y++)
        for (var x = 0; x < w; x++,
        i++) {
            a[i] = getVal(p, i << 2);
        }
    xhReq = new XMLHttpRequest();
    rqPrf = 'http://' + getElm('ip_addr').value + '/upload';
    xhReq.open('POST', rqPrf, true);
     const uint8Data = new Uint8Array(packBitsToBytes(a));
    xhReq.send(uint8Data);
}

这里直接使用post请求对整张图片数据进行传输,因为一张2.13寸墨水屏图像数据:104*212/8=2756 字节,这个大小对于esp8266是足够的, 内存是足够的。

esp8266 接收端:

  server.on("/upload", HTTP_POST, []() {
    digitalWrite(LED, LOW);  
    String imgData = server.arg("plain");
    if(imgData.length() == 0) {
        server.send(400, "text/plain", "No data received");
        return;
    }
    Serial.println(imgData.length());
    // for (size_t i = 0; i < imgData.length(); i++)
    // {
    //   Serial.print(imgData[i]);
    // }  
    const uint8_t* imgRaw = (const uint8_t*)imgData.c_str();
    server.send(200, "text/plain", "ok");
    displayImg(imgRaw, imgData.length());
    
    digitalWrite(LED, HIGH);  
});

另外新增了一个对图像缩放的功能(原始的微雪程序只能对图片进行裁剪):
请添加图片描述

3.4 HSU 与 SPI模式 切换问题

这个底板设备,当初设计时我就想能用到其HSU的功能,可以对 IC卡进行解密,但是我在切换到HSU模式时发现,无论如何上位机软件也无法检查到我的pn532 设备。怀疑是 esp8266 对 spi引脚的OUTPUT模式导致pn532不能进入 HSU模式。所以增加了模式切换部分代码:

void setup() { 
  Serial.begin(115200);
  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    useFlag = false;
  }
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);
  if (useFlag){
    Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
    Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
    Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
    Serial.println("Waiting for an ISO14443A Card ...");
    setupWifi();
    }else{
      pinMode(PN532_SCK, INPUT);
      pinMode(PN532_MOSI, INPUT);
      pinMode(PN532_SS, INPUT);
      pinMode(PN532_MISO, INPUT);
    }

}

原理很简单,如果spi接口检测不到 pn532小红板,就将spi的几个引脚置为 INPUT模式,果然问题就解决了。

注意:每次切换模式是,在拨动pn532上面的拨码开关后,以及我设计的底板上的双刀双置开关后需要对设备重新 上下电才可以完成模式的切换,这个应该是pn532固件的问题,其不支持在上电过程中完成模式的切换,所以这里我的esp8266程序也没有实现上电切换模式部分。

3.5 指示灯状态

在硬件设计中我放置了LED用于标示板子的状态:

  1. 上电默认常亮状态 。
  2. 如果检查到pn532 小红板,指示灯熄灭。
  3. 检测不到 pn532小红板,指示灯规律闪烁 (用于切换HSU模式)。
  4. 接收到图片数据,指示灯常亮。
  5. 传输图片数据过程中,指示灯闪烁。
  6. 图片传输完成,指示灯熄灭。

4. 开源地址占位

https://oshwhub.com/wshuo426/pn532-di-ban-_-wu-pu-tong

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐