UART 外设告一段落。今天换 SPI,先读卡。版本寄存器能读出来,芯片才算活着,然后再贴卡。

open("/dev/nfc") 返回正数,chmod 0666 也做了。读版本寄存器得到 0x00。于是有人开始刷卡、换天线、把 SPI 升到 20 MHz、怀疑卡坏了。

先别刷卡。0x00 和 0xFF 的意思是 SPI 上没读到芯片。卡还没上场。VersionReg 地址是 0x37,不是 0x00。你看到的 0x00 往往是:没软复位、把 read() 的返回值当字节数、或者把「先 write 地址再 read 数据」这套 I2C 习惯套到了这个驱动上。

产品 rk3568_evb。OpenHarmony 4.1,用户态 32 位 armv7 musl。节点 /dev/nfc,硬件 SPI,不是 GPIO 位拍。终极判据是 4 字节 UID,不是版本号好看不好看。


1. 现象

三类现场,看起来都像「读卡失败」,根因差很远。

A. 节点在,版本是 0x00 / 0xFF。 ls -l /dev/nfc 是 crw,open 成功。读 0x37 得到 0。有人立刻去调 ISO14443 状态机,FIFO 空、Request 超时,于是再换卡。芯片根本没在总线上应答,刷一百张也是 0。

B. 版本是 0xB2,页面报异常。 旧逻辑只认 0x92。兼容片能读卡,UID 正常。把判定放宽之前,SPI 速率、CS 极性、3.3 V 全换了一遍,全是空操作。

C. 版本正常,UID 永远 no-card。 刷的是 125 kHz ID 卡。RC522 是 13.56 MHz、ISO14443A。低频卡物理层就不对,不是天线没开。

根因按层拆:

层看什么
节点`/dev/nfc` 是字符设备,0666
协议`read`/`write` 成功返回 **0**;读是「1 字节地址放进缓冲再 read 回填」
复位先 `CommandReg=0x0F`,等 50 ms,再读 `VersionReg=0x37`
版本`0x91`/`0x92` 正品;`0x88`/`0x90`/`0xB2`/`0xC2` 兼容片可用;`0x00`/`0xFF` 未检出
读卡Request `0x26`,Anticoll `0x93 0x20`,4 字节 UID + BCC
卡ISO14443A 白卡;125 kHz ID 卡读不了

2. 硬件 SPI,不是 GPIO 位拍

应用 open/read/write
        │  /dev/nfc
        ▼
内核 rc522:把寄存器地址编成 SPI 第一拍,spi_sync
        │
        ▼
SPI3  CS0 / CLK / MOSI / MISO  ≤ 8 MHz
        │
        ▼
RC522  VersionReg / FIFO / TxControl
        │
        ▼
13.56 MHz 天线  ──►  ISO14443A 卡  ──► UID

MAX7219 点阵常用两根 GPIO 自己翻 CLK/MOSI,那是位拍,没有 /dev/nfc,也没有寄存器地址这一说。RC522 走控制器 SPI3,片选 0。两套协议不能混:把位拍时序抄过来,CS 乱跳,VersionReg 永远是 0。

SPI3 的脚和部分扩展模组共。共脚就一次一个。MAX7219 还插着的时候,CLK/MOSI 被两边抢,RC522 读出来的也是 0x00。验收 RFID 时把点阵拔掉。

电气:3.3 V。SCK、MOSI、MISO、CS 对上 SPI3 的 m1 组。TX1/TX2 天线线圈在模块上,用户态只开 TxControlReg 的 TX1/TX2 输出,不要去 GPIO 里找「天线脚」。


3. 设备树

控制器 SPI3,片选 0,频率 8 MHz。板级:

device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts
&spi3 {
    pinctrl-names = "default";
    pinctrl-0 = <&spi3m1_cs0 &spi3m1_pins>;
    status = "okay";

    rc522: rc522@0 {
        compatible = "board,rc522";
        reg = <0>;
        spi-max-frequency = <8000000>;
    };
};

compatible 必须和驱动 of_device_id 一致。写成随便的字符串,probe 不发生,/dev/nfc 不出现。reg = <0> 是 CS0。频率 8 MHz 够,飞线到 20 MHz 波形塌,读出来又是 00。

改 DTS 之后 dtb 进 boot_linux 和 resource。生效经常是 resource 里的 rk-kernel.dtb。dd 打 /dev/block/ 下的分区,先 cat /proc/partitions 认介质。

现场核对:

hdc shell "ls -l /dev/nfc"
hdc shell "dmesg | grep -i nfc"
hdc shell "ls /sys/bus/spi/devices"

节点必须是 crw。第一位是 -,是有人 echo > /dev/nfc 建的假文件,open 也许成功,后面 read 不是 SPI。删掉假文件,让驱动重新创 c 节点。

dmesg 期望看到 probe 成功。没有 probe:compatible 对不上,或 &spi3 仍是 disabled。

HAP 不是 root。临时 chmod 0666 /dev/nfc,重启会丢。开机脚本放 device/board/rk/rk3568_evb/board_perm/board-perm.sh,由 *.rk30board.cfg 里 once = 1 的服务拉起来。hdc 能读、应用不能,就是 0666 没进镜像。


4. 用户态协议:一次 read 就是一次读寄存器

内核把 SPI 地址位移、bit7 读写位藏掉。应用看到的约定是:

  • 读: 1 字节缓冲区先放寄存器地址,调用一次 read。驱动 copy_from_user 取出地址,SPI 读,把值 copy_to_user 回同一块缓冲。
  • 写: 2 字节 {addr, val} 调用一次 write。

驱动 .read / .write 成功返回 0,不是 POSIX 教科书上的字节数。if (read(fd, &b, 1) != 1) 会把好结果当失败,接着用未更新的 b。若 b 当初就是地址 0x37,你可能误以为读到了版本;若 b 当初清零,VersionReg 看起来永远是 0。

不要 write(addr); read(&val) 两次系统调用。第二次 read 会被当成「再读一个寄存器」,地址是上一次留下的值。I2C 的「先写指针再读」在这里是错的。完整 RcRead / RcWrite 在下面那份 C 里。

read/write 不走 ioctl。不要给 RFID 无故加 ioctl。

内核侧 SPI 第一拍:读 ((reg << 1) & 0x7e) | 0x80,写 (reg << 1) & 0x7e。应用不要自己移位。把 0x37 直接放进缓冲即可。

CS 由控制器按 reg = <0> 拉。驱动里若再 gpio 翻 CS,和控制器打架。这份驱动的片选使能是空的,靠控制器。


5. 先软复位,再读 VersionReg

不复位就读,上电后寄存器可能是垃圾,偶发 0x00,被判未检出。50 ms 不是玄学,复位命令本身要时间。

enum { CommandReg = 0x01, VersionReg = 0x37 };

RcWrite(CommandReg, 0x0F);   /* PCD_RESETPHASE */
usleep(50000);
v = RcRead(VersionReg);

判定:

值含义
`0x91` `0x92`正品
`0x88` `0x90` `0xB2` `0xC2`兼容片,可用
`0x00` `0xFF` 或读失败未检出:SPI、供电、接线、权限、没复位、把返回值当字节数
其它非零SPI 通了,直接试读卡

本板见过 0xB2。兼容片不是故障。版本号只说明 SPI 和芯片还活着。UID 才是终极判据。0x00 时不要刷卡,先查节点、probe、read 返回值、复位,再查线。


6. ISO14443A:Request 0x26,Anticoll 0x93 0x20

Type A 卡。Request 用 PICC_REQIDL=0x26(空闲卡),短帧 7 位。防冲突发 0x93 0x20,返回 4 字节 UID + 1 字节 BCC(四字节异或)。uid[0]^uid[1]^uid[2]^uid[3] == bcc,否则 BCC 错。

无卡返回 no-card,这是常态,不是 SPI 坏了。先确认 VersionReg 已经不是 00,再谈无卡。

125 kHz ID 卡(门禁里那种又厚又圆、印刷 10 位十进制号的)物理层是 125 kHz ASK,不是 13.56 MHz Type A。Request 一定失败。验收用普通 13.56 MHz 白卡,贴天线 0~4 cm。

两张卡叠在一起走完整防冲突,这份用户态只做单卡。叠卡报 anticoll-err,先拿开一张。

芯片不提供「这次传输结束」的用户态信号,靠读 ComIrqReg:清 FIFO,把命令推进 FIFO,写 PCD_TRANSCEIVE,置 BitFramingReg 的 StartSend,轮询 Idle/Rx。超时还没有标志:没卡或天线没开。ErrorReg 的 0x1B 有位:协议/CRC/碰撞,当失败。

这段必须在 C 里跑。ArkTS 去轮询 SPI 寄存器会把 UI 卡成幻灯片,也更容易把时序拉长到芯片超时。寄存器常量和命令字写在下面那份 C 里,不要漏 CommandReg/FIFODataReg/TxControlReg/VersionReg。


7. 完整 C:打开、复位、版本分类、Request、Anticoll、打印 UID

rc522_uid.c:

/* rc522_uid.c — OpenHarmony 4.1 用户态,32 位 musl
 *   rc522_uid
 * 打开 /dev/nfc,软复位,读 VersionReg,再 Request+Anticoll 打 UID。
 * 驱动 read/write 成功返回 0,不要当字节数。
 */

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define PCD_IDLE        0x00
#define PCD_AUTHENT     0x0E
#define PCD_RECEIVE     0x08
#define PCD_TRANSMIT    0x04
#define PCD_TRANSCEIVE  0x0C
#define PCD_RESETPHASE  0x0F
#define PCD_CALCCRC     0x03
#define PICC_REQIDL     0x26
#define PICC_ANTICOLL1  0x93

#define CommandReg      0x01
#define ComIEnReg       0x02
#define DivIEnReg       0x03
#define ComIrqReg       0x04
#define DivIrqReg       0x05
#define ErrorReg        0x06
#define Status1Reg      0x07
#define Status2Reg      0x08
#define FIFODataReg     0x09
#define FIFOLevelReg    0x0A
#define WaterLevelReg   0x0B
#define ControlReg      0x0C
#define BitFramingReg   0x0D
#define CollReg         0x0E
#define ModeReg         0x11
#define TxModeReg       0x12
#define RxModeReg       0x13
#define TxControlReg    0x14
#define TxAutoReg       0x15
#define TxSelReg        0x16
#define RxSelReg        0x17
#define RxThresholdReg  0x18
#define DemodReg        0x19
#define CRCResultRegM   0x21
#define CRCResultRegL   0x22
#define ModWidthReg     0x24
#define RFCfgReg        0x26
#define GsNReg          0x27
#define CWGsPReg        0x28
#define ModGsPReg       0x29
#define TModeReg        0x2A
#define TPrescalerReg   0x2B
#define TReloadRegH     0x2C
#define TReloadRegL     0x2D
#define TCounterValRegH 0x2E
#define TCounterValRegL 0x2F
#define VersionReg      0x37

#define MI_OK        0
#define MI_NOTAGERR  (-1)
#define MI_ERR       (-2)
#define MAXRLEN      18

static int g_rc = -1;

static unsigned char RcRead(unsigned char addr)
{
    unsigned char b = addr;
    int ret = (int)read(g_rc, &b, 1);
    if (ret < 0)
        fprintf(stderr, "read addr 0x%02x: %s\n", addr, strerror(errno));
    return b;
}

static void RcWrite(unsigned char addr, unsigned char val)
{
    unsigned char b[2];
    b[0] = addr;
    b[1] = val;
    int ret = (int)write(g_rc, b, 2);
    if (ret < 0)
        fprintf(stderr, "write 0x%02x: %s\n", addr, strerror(errno));
}

static void SetBit(unsigned char reg, unsigned char mask)
{
    RcWrite(reg, (unsigned char)(RcRead(reg) | mask));
}

static void ClrBit(unsigned char reg, unsigned char mask)
{
    RcWrite(reg, (unsigned char)(RcRead(reg) & (unsigned char)~mask));
}

static const char *verdict(int v)
{
    if (v == 0x91 || v == 0x92)
        return "正品";
    if (v == 0x88 || v == 0x90 || v == 0xB2 || v == 0xC2)
        return "兼容片,可用";
    if (v == 0x00 || v == 0xFF)
        return "未检出";
    return "未知,SPI 通了,去读 UID";
}

static void antenna_on(void)
{
    if (!(RcRead(TxControlReg) & 0x03))
        SetBit(TxControlReg, 0x03);
}

static int pcd_com(unsigned char cmd, unsigned char *in, unsigned char inlen,
                   unsigned char *out, unsigned int *outbits)
{
    unsigned char irq_en = 0, wait_for = 0, n, last;
    unsigned int t;
    int status = MI_ERR;

    if (cmd == PCD_AUTHENT) {
        irq_en = 0x12;
        wait_for = 0x10;
    } else if (cmd == PCD_TRANSCEIVE) {
        irq_en = 0x77;
        wait_for = 0x30;
    }

    RcWrite(ComIEnReg, (unsigned char)(irq_en | 0x80));
    ClrBit(ComIrqReg, 0x80);
    RcWrite(CommandReg, PCD_IDLE);
    SetBit(FIFOLevelReg, 0x80);
    for (t = 0; t < inlen; t++)
        RcWrite(FIFODataReg, in[t]);
    RcWrite(CommandReg, cmd);
    if (cmd == PCD_TRANSCEIVE)
        SetBit(BitFramingReg, 0x80);

    t = 2000;
    do {
        n = RcRead(ComIrqReg);
        t--;
    } while (t && !(n & 0x01) && !(n & wait_for));

    ClrBit(BitFramingReg, 0x80);

    if (t) {
        if (!(RcRead(ErrorReg) & 0x1B)) {
            status = MI_OK;
            if (n & irq_en & 0x01)
                status = MI_NOTAGERR;
            if (cmd == PCD_TRANSCEIVE) {
                n = RcRead(FIFOLevelReg);
                last = RcRead(ControlReg) & 0x07;
                if (last)
                    *outbits = (unsigned int)(n - 1) * 8 + last;
                else
                    *outbits = (unsigned int)n * 8;
                if (n == 0)
                    n = 1;
                if (n > MAXRLEN)
                    n = MAXRLEN;
                for (t = 0; t < n; t++)
                    out[t] = RcRead(FIFODataReg);
            }
        } else {
            status = MI_ERR;
        }
    }
    SetBit(ControlReg, 0x80);
    RcWrite(CommandReg, PCD_IDLE);
    return status;
}

static int pcd_request(unsigned char *tag)
{
    unsigned char buf[MAXRLEN];
    unsigned int bits = 0;
    int st;

    ClrBit(Status2Reg, 0x08);
    RcWrite(BitFramingReg, 0x07);
    SetBit(TxControlReg, 0x03);
    buf[0] = PICC_REQIDL;
    st = pcd_com(PCD_TRANSCEIVE, buf, 1, buf, &bits);
    if (st == MI_OK && bits == 0x10) {
        tag[0] = buf[0];
        tag[1] = buf[1];
        return MI_OK;
    }
    return MI_ERR;
}

static int pcd_anticoll(unsigned char *uid)
{
    unsigned char buf[MAXRLEN];
    unsigned char bcc = 0, i;
    unsigned int bits = 0;
    int st;

    ClrBit(Status2Reg, 0x08);
    RcWrite(BitFramingReg, 0x00);
    ClrBit(CollReg, 0x80);
    buf[0] = PICC_ANTICOLL1;
    buf[1] = 0x20;
    st = pcd_com(PCD_TRANSCEIVE, buf, 2, buf, &bits);
    if (st == MI_OK) {
        for (i = 0; i < 4; i++) {
            uid[i] = buf[i];
            bcc ^= buf[i];
        }
        if (bcc != buf[4])
            return MI_ERR;
    }
    SetBit(CollReg, 0x80);
    return st;
}

static void iso14443a_init(void)
{
    RcWrite(CommandReg, PCD_RESETPHASE);
    usleep(50000);
    RcWrite(ModeReg, 0x3D);
    RcWrite(TReloadRegL, 30);
    RcWrite(TReloadRegH, 0);
    RcWrite(TModeReg, 0x8D);
    RcWrite(TPrescalerReg, 0x3E);
    RcWrite(TxAutoReg, 0x40);
    ClrBit(Status2Reg, 0x08);
    RcWrite(RxSelReg, 0x86);
    RcWrite(RFCfgReg, 0x7F);
    usleep(10000);
    antenna_on();
}

int main(void)
{
    unsigned char tag[2], uid[4];
    int v, i, st;

    g_rc = open("/dev/nfc", O_RDWR);
    if (g_rc < 0) {
        fprintf(stderr, "open /dev/nfc: %s\n", strerror(errno));
        return 1;
    }

    RcWrite(CommandReg, PCD_RESETPHASE);
    usleep(50000);
    v = RcRead(VersionReg);
    printf("VersionReg=0x%02X  %s\n", v, verdict(v));
    if (v == 0x00 || v == 0xFF) {
        fprintf(stderr, "未检出,先别刷卡。查 SPI3、0666、复位、read 返回值。\n");
        close(g_rc);
        return 2;
    }

    iso14443a_init();
    st = pcd_request(tag);
    if (st != MI_OK) {
        printf("no-card(贴 13.56 MHz ISO14443A 白卡,0~4 cm;125 kHz ID 卡读不了)\n");
        close(g_rc);
        return 3;
    }
    printf("ATQA=%02X %02X\n", tag[0], tag[1]);

    st = pcd_anticoll(uid);
    if (st != MI_OK) {
        printf("anticoll-err 或 card-bcc-err(先拿开叠着的第二张)\n");
        close(g_rc);
        return 4;
    }
    printf("UID=");
    for (i = 0; i < 4; i++)
        printf("%02X", uid[i]);
    printf("\n");
    close(g_rc);
    return 0;
}

32 位 musl:

OHOS=$HOME/code/opcode/OpenHarmony
CLANG=$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang
SYSROOT=$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot

$CLANG --target=arm-linux-ohos --sysroot=$SYSROOT \
    -O2 -Wall \
    -Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1 \
    -o rc522_uid rc522_uid.c

file rc522_uid
# ELF 32-bit LSB executable, ARM, interpreter /system/lib/ld-musl-arm.so.1
hdc file send rc522_uid /data/local/tmp/rc522_uid
hdc shell "chmod 0755 /data/local/tmp/rc522_uid"
hdc shell "chmod 0666 /dev/nfc"
hdc shell "/data/local/tmp/rc522_uid"

file 若是 aarch64,exec 失败。不要用主机 gcc。用户态必须 armv7 musl。


8. 验收

先跑一遍,卡离天线。期望类似:

VersionReg=0xB2  兼容片,可用
no-card(贴 13.56 MHz ISO14443A 白卡,0~4 cm;125 kHz ID 卡读不了)

0x91 / 0x92 / 0x88 / 0x90 / 0xC2 同样过。到这里 SPI 通了。再贴白卡,0~4 cm,再跑:

VersionReg=0xB2  兼容片,可用
ATQA=04 00
UID=A1B2C3D4

同一张卡 UID 稳定。换一张,UID 变。这是终极判据。

0x00 未检出:不要在空寄存器上跳 ISO 状态机。No such file 是 DTS/probe;Permission denied 是 0666。版本正常仍 no-card:卡距、卡类型、125 kHz。版本永远 0 且代码写了 read(...) != 1:驱动成功返回 0。


9. 排障顺序

1. ls -l /dev/nfc  字符设备?0666?
2. dmesg probe,spi3.0 在不在
3. compatible 是 board,rc522,和驱动 of_device_id 一致
4. 软复位 CommandReg=0x0F,等 50 ms,读 0x37
5. 00/FF:供电 3.3V、MISO、CS0、read 返回值、不要 write 再 read
6. 91/92/88/90/B2/C2:去读 UID,兼容片不是故障
7. no-card:13.56 MHz 白卡,0~4 cm;125 kHz ID 卡读不了
8. anticoll-err:叠卡、BCC
9. 工具行通、HAP 不行:权限、32 位 so
10. 和 MAX7219 共脚:一次一个;位拍协议不要抄到 SPI3

路径:

用途路径
DTS`device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts` `&spi3` `rc522@0`
驱动`device/board/rk/rk3568_evb/kernel/rc522.c`
节点`/dev/nfc`
工具`/data/local/tmp/rc522_uid`
放权`device/board/rk/rk3568_evb/board_perm/board-perm.sh`

系列第 51 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐