NDEF格式库

ndef ,全称 nfc data exchange format ,即 “nfc 数据交换格式”。

本库依托于 RC522 驱动,支持将卡片中的 NDEF 消息与结构体互相转换;同时还支持将 NDEF 卡片与空白卡片的互相转换。

用户可以通过 Add 函数自由添加记录(支持 URL、TEXT 两种类型),并通过NDEF_WriteToCard将结构体写入卡片。

说明文档由 AI 生成,代码也是。

ndef.h
C
#ifndef __NDEF_H
#define __NDEF_H

#include "rc522.h"
#include <stdint.h>
#include <string.h>

// NDEF 记录类型枚举
typedef enum {
    NDEF_TYPE_TEXT = 0,
    NDEF_TYPE_URI  = 1
} NDEF_RecordType;

// URI 前缀标识码 (NFC Forum 规定)
#define URI_PREFIX_NONE         0x00
#define URI_PREFIX_HTTP_WWWDOT  0x01 // http://www.
#define URI_PREFIX_HTTPS_WWWDOT 0x02 // https://www.
#define URI_PREFIX_HTTP         0x03 // http://
#define URI_PREFIX_HTTPS        0x04 // https://

// 单个 NDEF 记录结构体
/*
    * NDEF_Record: 表示一条 NDEF 记录
    * - type: 记录类型 (URI 或 Text)
    * - uriCode: URI 记录的前缀码 (仅当 type=URI 时有效)
    * - language: Text 记录的语言代码 (仅当 type=Text 时有效)
    * - payload: 记录的载荷数据 (URI 的 URL 字符串或 Text 的文本内容)
    * - payloadLength: 载荷数据的实际长度 (字节数)
*/
typedef struct {
    NDEF_RecordType type;        
    uint8_t uriCode;             // 仅 URI 使用
    char language[3];            // 仅 Text 使用
    uint8_t payload[256];         // 载荷数据 (可根据单片机RAM调整大小)
    uint8_t payloadLength;       
} NDEF_Record;

// 限制一条消息最多包含的记录数,防止内存溢出
#define NDEF_MAX_RECORDS 8 


/*
 * NDEF_Message: 包含多个 NDEF_Record 的结构体
 * - records: 记录数组,最多包含 NDEF_MAX_RECORDS 个记录
 * - recordCount: 当前消息中包含的有效记录数量
 */
typedef struct {
    NDEF_Record records[NDEF_MAX_RECORDS]; // 记录数组
    uint8_t recordCount;                   // 当前包含的记录数量
} NDEF_Message;

// API 函数声明

// 初始化 NDEF 消息结构体,清空之前的记录
void NDEF_Message_Init(NDEF_Message *msg);
// 向消息中添加一条 URI (网址) 记录
int8_t NDEF_AddUriRecord(NDEF_Message *msg, uint8_t uriCode, const char *url);
// 向消息中添加一条 Text (纯文本) 记录
int8_t NDEF_AddTextRecord(NDEF_Message *msg, const char *language, const char *text);


// 向 M1 卡写入包含多条记录的 NDEF 消息
int8_t NDEF_WriteToCard(uint8_t *uid, NDEF_Message *msg);
// 从 M1 卡读取 NDEF 消息并解析成结构体
int8_t NDEF_ReadFromCard(uint8_t *uid, NDEF_Message *msg);


// 将出厂白卡初始化为标准 NDEF 卡片 (包含 MAD 目录)
int8_t NDEF_FormatBlankCard(uint8_t *uid);
// 自适应识别卡片状态并再格式化为 空白 NDEF 卡
int8_t NDEF_FormatUniversal(uint8_t *uid);
// 将任意卡片格式化为出厂白卡
int8_t NDEF_FormatToBlankCard(uint8_t *uid);


#endif

ndef.c
C
#include "ndef.h"

#define NDEF_MAX_BUFFER_SIZE 2048 
#define NDEF_START_BLOCK     4   

//  NDEF 格式化设计的控制位:自由读写,且 GPB 强制为 0x40 (MAD v1)
uint8_t CTRL_NDEF_FORMAT[4] = {0xFF, 0x07, 0x80, 0x40};

/**
 * @brief  [内部函数] 获取操作特定块所需的密钥
 */
static uint8_t* GetKeyForBlock(uint8_t absBlock)
{
    uint8_t sector = absBlock / 4;
    
    // 扇区 0 是 MAD 目录,使用公开目录密钥 Key_MAD (A0 A1 A2 A3 A4 A5)
    if (sector == 0) return Key_MAD;
    
    // 扇区 1~15 全是 NDEF 数据存放区,必须统一使用 Key_NDEF (D3 F7 D3 F7 D3 F7)
    return Key_NDEF;
}

// ======================= 配置与添加记录 API =======================

/**
 * @brief  初始化 NDEF 消息结构体,清空之前的记录
 */
void NDEF_Message_Init(NDEF_Message *msg) 
{
    memset(msg, 0, sizeof(NDEF_Message));
}

/**
 * @brief  向消息中添加一条 URI (网址) 记录
 */
int8_t NDEF_AddUriRecord(NDEF_Message *msg, uint8_t uriCode, const char *url) 
{
    if (msg->recordCount >= NDEF_MAX_RECORDS) return MI_ERR; // 超出最大记录数限制
    
    NDEF_Record *rec = &msg->records[msg->recordCount];
    rec->type = NDEF_TYPE_URI;
    rec->uriCode = uriCode;
    rec->payloadLength = strlen(url);
    
    if (rec->payloadLength > sizeof(rec->payload)) return MI_ERR; // 长度溢出保护
    
    memcpy(rec->payload, url, rec->payloadLength);
    msg->recordCount++;
    
    return MI_OK;
}

/**
 * @brief  向消息中添加一条 Text (纯文本) 记录
 */
int8_t NDEF_AddTextRecord(NDEF_Message *msg, const char *language, const char *text) 
{
    if (msg->recordCount >= NDEF_MAX_RECORDS) return MI_ERR;
    
    NDEF_Record *rec = &msg->records[msg->recordCount];
    rec->type = NDEF_TYPE_TEXT;
    
    strncpy(rec->language, language, 2);
    rec->language[2] = '\0';
    
    rec->payloadLength = strlen(text);
    if (rec->payloadLength > sizeof(rec->payload)) return MI_ERR;
    
    memcpy(rec->payload, text, rec->payloadLength);
    msg->recordCount++;
    
    return MI_OK;
}

// ======================= 读写底层实现 =======================

/**
 * @brief  向 M1 卡写入包含多条记录的 NDEF 消息
 */
int8_t NDEF_WriteToCard(uint8_t *uid, NDEF_Message *msg)
{
    if (msg->recordCount == 0) return MI_ERR; // 空消息拦截

    uint8_t buffer[NDEF_MAX_BUFFER_SIZE];
    uint8_t recordBuf[NDEF_MAX_BUFFER_SIZE - 4]; // 用于缓存所有记录的内容
    memset(buffer, 0, sizeof(buffer));
    
    uint16_t rIdx = 0; // 记录缓冲区偏移

    // 1. 遍历组装所有 NDEF Records
    for (uint8_t i = 0; i < msg->recordCount; i++) 
    {
        NDEF_Record *rec = &msg->records[i];
        
        // 生成 Header: SR=1 (短载荷), TNF=1 (Well-Known) -> 0x11
        uint8_t header = 0x11; 
        if (i == 0) header |= 0x80;                        // MB=1 (Message Begin)
        if (i == msg->recordCount - 1) header |= 0x40;     // ME=1 (Message End)
        
        recordBuf[rIdx++] = header;
        
        if (rec->type == NDEF_TYPE_URI) {
            recordBuf[rIdx++] = 0x01; // Type Length ('U')
            recordBuf[rIdx++] = rec->payloadLength + 1; // Payload Length
            recordBuf[rIdx++] = 'U';  
            recordBuf[rIdx++] = rec->uriCode; 
            memcpy(&recordBuf[rIdx], rec->payload, rec->payloadLength);
            rIdx += rec->payloadLength;
        } 
        else if (rec->type == NDEF_TYPE_TEXT) {
            uint8_t langLen = strlen(rec->language);
            if (langLen == 0) langLen = 2; 
            
            recordBuf[rIdx++] = 0x01; // Type Length ('T')
            recordBuf[rIdx++] = rec->payloadLength + langLen + 1; 
            recordBuf[rIdx++] = 'T';  
            recordBuf[rIdx++] = langLen; // 状态字节 
            memcpy(&recordBuf[rIdx], rec->language, langLen);
            rIdx += langLen;
            memcpy(&recordBuf[rIdx], rec->payload, rec->payloadLength);
            rIdx += rec->payloadLength;
        }
    }

    // 2. 将组装好的记录打包进 NDEF TLV (Type-Length-Value)
    uint16_t idx = 0;
    buffer[idx++] = 0x03; // Type: NDEF Message
    buffer[idx++] = (uint8_t)rIdx; // Length: 记录总长度
    memcpy(&buffer[idx], recordBuf, rIdx); // Value: 记录数据
    idx += rIdx;
    buffer[idx++] = 0xFE; // Type: Terminator

    // 3. 物理分块写入卡片逻辑 (与原先一致)
    uint8_t blocksNeeded = (idx % 16 == 0) ? (idx / 16) : (idx / 16 + 1);
    uint8_t currentAbsBlock = NDEF_START_BLOCK;
    uint8_t writeBuf[16];
    int8_t status;

    for (uint8_t b = 0; b < blocksNeeded; b++) 
    {
        if (currentAbsBlock % 4 == 3) currentAbsBlock++; // 避让控制块

        memset(writeBuf, 0, 16);
        uint8_t copyLen = (idx - b * 16) > 16 ? 16 : (idx - b * 16);
        memcpy(writeBuf, &buffer[b * 16], copyLen);

        uint8_t *keyToUse = GetKeyForBlock(currentAbsBlock);
        status = M1_WriteDataBlock_Abs(uid, currentAbsBlock, keyToUse, writeBuf);
        if (status != MI_OK) return status;

        #ifdef M1_AUTO_HALT_ENABLE
        uint8_t tagType[2];
        PcdRequest(PICC_REQALL, tagType); 
        PcdAnticoll(uid);
        #endif
        
        currentAbsBlock++;
    }

    return MI_OK;
}

/**
 * @brief  从 M1 卡读取并解析多记录的 NDEF 消息
 */
/**
 * @brief  从 M1 卡读取并解析多记录的 NDEF 消息
 */
int8_t NDEF_ReadFromCard(uint8_t *uid, NDEF_Message *msg)
{
    uint8_t readBuf[16];
    int8_t status;
    uint8_t currentAbsBlock = NDEF_START_BLOCK;
    
    // ==========================================
    // 第一部分:读取首个块寻找 TLV 头,获取数据长度
    // ==========================================
    uint8_t *keyToUse = GetKeyForBlock(currentAbsBlock);
    status = M1_ReadDataBlock_Abs(uid, currentAbsBlock, keyToUse, readBuf);
    if (status != MI_OK) return status;

    uint8_t tlvOffset = 0;
    while(tlvOffset < 16 && readBuf[tlvOffset] != 0x03) {
        if (readBuf[tlvOffset] == 0xFE) return MI_NOTAGERR; // 遇到结束符,卡是空的
        tlvOffset++;
    }
    
    if (tlvOffset >= 15) return MI_FORMATERR; 

    uint8_t ndefLen = readBuf[tlvOffset + 1];
    if (ndefLen == 0 || ndefLen > NDEF_MAX_BUFFER_SIZE) return MI_FORMATERR; 

    #ifdef M1_AUTO_HALT_ENABLE
    uint8_t tagType[2];
    PcdRequest(PICC_REQALL, tagType);
    PcdAnticoll(uid);
    #endif

    // ==========================================
    // 第二部分:根据长度,将所需的所有块拉取到 buffer 内存中
    // ==========================================
    uint8_t buffer[NDEF_MAX_BUFFER_SIZE];
    uint8_t totalBytesToRead = tlvOffset + 2 + ndefLen + 1; 
    uint8_t blocksNeeded = (totalBytesToRead % 16 == 0) ? (totalBytesToRead / 16) : (totalBytesToRead / 16 + 1);
    
    for (uint8_t b = 0; b < blocksNeeded; b++) 
    {
        if (currentAbsBlock % 4 == 3) currentAbsBlock++; 

        keyToUse = GetKeyForBlock(currentAbsBlock);
        status = M1_ReadDataBlock_Abs(uid, currentAbsBlock, keyToUse, &buffer[b * 16]);
        if (status != MI_OK) return status;

        #ifdef M1_AUTO_HALT_ENABLE
        PcdRequest(PICC_REQALL, tagType);
        PcdAnticoll(uid);
        #endif

        currentAbsBlock++;
    }

    // ==========================================
    // 第三部分:循环解析 buffer 里的 NDEF 多记录
    // ==========================================
    uint8_t ndefStart = tlvOffset + 2; 
    uint8_t bytesParsed = 0;
    
    NDEF_Message_Init(msg); // 清空结构体准备解析

    // 循环解析直到读取完毕或达到最大支持记录数
    while (bytesParsed < ndefLen && msg->recordCount < NDEF_MAX_RECORDS) 
    {
        uint8_t headerByte = buffer[ndefStart + bytesParsed];
        uint8_t isME = (headerByte & 0x40) != 0; // 提取 ME 位检查是否是最后一条
        
        uint8_t typeLen = buffer[ndefStart + bytesParsed + 1];
        uint8_t payloadLen = buffer[ndefStart + bytesParsed + 2];
        uint8_t typeChar = buffer[ndefStart + bytesParsed + 3];
        
        NDEF_Record *rec = &msg->records[msg->recordCount];
        uint8_t payloadStart = ndefStart + bytesParsed + 3 + typeLen;
        
        if (typeChar == 'U') {
            rec->type = NDEF_TYPE_URI;
            rec->uriCode = buffer[payloadStart];
            rec->payloadLength = payloadLen - 1;
            memcpy(rec->payload, &buffer[payloadStart + 1], rec->payloadLength);
            msg->recordCount++;
        } 
        else if (typeChar == 'T') {
            rec->type = NDEF_TYPE_TEXT;
            uint8_t langLen = buffer[payloadStart] & 0x3F;
            memcpy(rec->language, &buffer[payloadStart + 1], langLen);
            rec->payloadLength = payloadLen - 1 - langLen;
            memcpy(rec->payload, &buffer[payloadStart + 1 + langLen], rec->payloadLength);
            msg->recordCount++;
        }
        
        // 偏移量推进到下一条记录 (SR模式下Header占用固定3字节:头+TypeLen+PayloadLen)
        bytesParsed += (3 + typeLen + payloadLen);
        
        // 如果当前记录是最后一条 (ME 位被置 1),则停止解析
        if (isME) break; 
    }

    return MI_OK;
}

/**
 * @brief  [白卡格式化] 将出厂白卡初始化为标准 NDEF 卡片 (包含 MAD 目录)
 * @param  uid: 4字节卡号
 */
int8_t NDEF_FormatBlankCard(uint8_t *uid)
{
    int8_t status;
    uint8_t ndef_trailer[16];
    uint8_t mad_trailer[16];
    
    // 生成 NDEF 扇区的控制块 (KeyA=D3F7..., KeyB=FFFF...)
    Make_SectorTrailer(Key_NDEF, M1_DefaultKey, CTRL_NDEF_FORMAT, ndef_trailer);
    // 生成 MAD 扇区的控制块 (KeyA=A0A1..., KeyB=FFFF...)
    Make_SectorTrailer(Key_MAD,  M1_DefaultKey, CTRL_NDEF_FORMAT, mad_trailer);

    // 1. 写入 NFC Forum 规定的 MAD1 目录信息 (扇区 0)
    uint8_t mad_b1[16] = {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};
    uint8_t mad_b2[16] = {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1};

    M1_ForceWakeUp(uid); M1_WriteDataBlock_Abs(uid, 1, M1_DefaultKey, mad_b1);
    M1_ForceWakeUp(uid); M1_WriteDataBlock_Abs(uid, 2, M1_DefaultKey, mad_b2);
    
    // 修改扇区 0 密码为 Key_MAD
    M1_ForceWakeUp(uid); M1_WriteSectorControlBlock_Safe(uid, 0, M1_DefaultKey, mad_trailer);

    // 2. 遍历格式化扇区 1 到 15 (修改为 NDEF 密码)
    for (uint8_t sector = 1; sector < 16; sector++) 
    {
        M1_ForceWakeUp(uid);
        status = M1_WriteSectorControlBlock_Safe(uid, sector, M1_DefaultKey, ndef_trailer);
        if (status != MI_OK) return status;
    }

    // 3. 写入空的 NDEF 头部 (使用刚刚生效的新密码 Key_NDEF)
    uint8_t empty_ndef[16] = {0x03, 0x00, 0xFE, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    M1_ForceWakeUp(uid);
    status = M1_WriteDataBlock_Abs(uid, 4, Key_NDEF, empty_ndef);

    return status;
}

/**
 * @brief  [通用格式化] 自适应识别卡片状态并初始化
 * @param  uid: 4字节卡号
 * @return MI_OK(成功), MI_ACCESSERR(未知加密卡无法处理), MI_ERR(底层错误)
 */
int8_t NDEF_FormatUniversal(uint8_t *uid)
{
    int8_t status;
    uint8_t dummyBuf[16];

    // ==========================================
    // 阶段 1:试探是否已经是 NDEF 卡
    // ==========================================
    status = M1_ReadDataBlock_Abs(uid, 4, Key_NDEF, dummyBuf);
    if (status == MI_OK) 
    {
        // 密码正确,直接覆盖块 4 为空消息,清空旧数据
        uint8_t empty_ndef[16] = {0x03, 0x00, 0xFE, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        M1_ForceWakeUp(uid);
        return M1_WriteDataBlock_Abs(uid, 4, Key_NDEF, empty_ndef);
    }

    // ==========================================
    // 阶段 2:如果不是,试探是否是出厂白卡
    // ==========================================
    M1_ForceWakeUp(uid); // 试探失败卡片已休眠,必须强行唤醒
    
    status = M1_ReadDataBlock_Abs(uid, 4, M1_DefaultKey, dummyBuf);
    if (status != MI_OK) 
    {
        // 别人的加密卡,无法处理
        return MI_ACCESSERR; 
    }

    // ==========================================
    // 阶段 3:确认是出厂白卡,直接调用白卡格式化函数
    // ==========================================
    return NDEF_FormatBlankCard(uid);
}


/**
 * @brief  [恢复出厂设置] 将 NDEF 卡或其他已知密码的卡彻底恢复为出厂白卡状态
 * @param  uid: 4字节卡号
 * @return MI_OK(成功), MI_ACCESSERR(未知密码卡,无法恢复), MI_ERR(底层错误)
 */
int8_t NDEF_FormatToBlankCard(uint8_t *uid)
{
    int8_t status;
    uint8_t factory_trailer[16];
    uint8_t empty_data[16] = {0}; // 全 0 数据,用于抹除残留特征

    // 构造出厂默认的控制块:KeyA 和 KeyB 全是 0xFF,控制位为自由读写
    Make_SectorTrailer(M1_DefaultKey, M1_DefaultKey, CTRL_FREE_DEV, factory_trailer);

    // ==========================================
    // 遍历所有 16 个扇区,逐一“洗白”
    // ==========================================
    for (uint8_t sector = 0; sector < 16; sector++)
    {
        // 确定该扇区在 NDEF 标准下的预期密码
        uint8_t *expectedKey = (sector == 0) ? Key_MAD : Key_NDEF;

        // 1. 先尝试用出厂密码验证 (可能它本身就是没被改过的白卡扇区)
        M1_ForceWakeUp(uid);
        status = M1_WriteSectorControlBlock_Safe(uid, sector, M1_DefaultKey, factory_trailer);

        // 2. 如果出厂密码验证失败,说明密码被改过,尝试用预期的 NDEF 密码验证
        if (status != MI_OK)
        {
            M1_ForceWakeUp(uid); // 验证失败会导致卡片休眠,必须强行唤醒!
            status = M1_WriteSectorControlBlock_Safe(uid, sector, expectedKey, factory_trailer);

            // 3. 如果还是不行,说明这是一张未知密码的加密卡,直接中止操作并报错
            if (status != MI_OK) {
                return MI_ACCESSERR;
            }
        }

        // ==========================================
        // 清理残余特征数据 (使用刚刚恢复好的 M1_DefaultKey)
        // ==========================================
        if (sector == 0) 
        {
            // 抹除 MAD 目录
            M1_ForceWakeUp(uid); M1_WriteDataBlock_Abs(uid, 1, M1_DefaultKey, empty_data);
            M1_ForceWakeUp(uid); M1_WriteDataBlock_Abs(uid, 2, M1_DefaultKey, empty_data);
        }
        else if (sector == 1) 
        {
            // 抹除 NDEF 头部
            M1_ForceWakeUp(uid); M1_WriteDataBlock_Abs(uid, 4, M1_DefaultKey, empty_data);
        }
    }

    return MI_OK;
}



📖 NDEF 库使用说明文档 (基于 M1 卡 & RC522)

本库旨在为 STM32 等嵌入式系统提供轻量级、无动态内存分配(No malloc)的 NDEF(NFC Data Exchange Format)协议支持。通过本库,您可以将普通的 Mifare Classic 1K (M1卡) 格式化为标准 NFC 标签,并实现多记录(URI、纯文本)的读写操作,完美兼容苹果 iOS 与 Android 设备的“碰一碰”功能。

⚙️ 1. 核心配置项 (ndef.h)

为了适应不同单片机的 RAM 限制,本库通过宏定义控制内存开销。在编译前,请根据您的实际需求在 ndef.h 中调整以下参数:

配置项 / 变量默认值说明注意事项
NDEF_MAX_RECORDS10一条 NDEF 消息最多包含的记录(Record)数量。建议设置在 3~10 之间。改大占用更多栈内存。
payload[128]256单条 NDEF 记录支持的最大有效载荷字节数。中文 UTF-8 编码通常占 3 字节,若需写入长文本请将此数组改大(如 128 或 256),否则长句子会被截断报错。
NDEF_MAX_BUFFER_SIZE2048ndef.c 内部用于组装和解析 TLV 数据的静态缓冲区。必须大于 (payload大小 * 记录数) + 头部开销

🛠️ 2. 消息构建与管理 API

在写入卡片前,必须先在单片机内存中构建好 NDEF_Message 结构体。

2.1 初始化消息容器

C
void NDEF_Message_Init(NDEF_Message *msg);
  • 功能:清空结构体中的旧数据,将记录数重置为 0。在每次复用 NDEF_Message 变量前必须调用。
  • 参数msg – 指向消息结构体的指针。

2.2 添加 URI (网址) 记录

C
int8_t NDEF_AddUriRecord(NDEF_Message *msg, uint8_t uriCode, const char *url);
  • 功能:向消息中追加一条网址/URI记录。
  • 参数
  • msg:消息结构体指针。
  • uriCode:NFC 论坛规定的 URI 前缀标识码(如 URI_PREFIX_HTTPS_WWWDOT 代表 https://www.URI_PREFIX_NONE 代表无前缀)。
  • url:网址字符串。
  • 返回值MI_OK 成功,MI_ERR 失败(超出最大记录数或有效载荷超长)。

2.3 添加 Text (纯文本) 记录

C
int8_t NDEF_AddTextRecord(NDEF_Message *msg, const char *language, const char *text);
  • 功能:向消息中追加一条纯文本记录。
  • 参数
  • msg:消息结构体指针。
  • language:两字符语言码(如 "zh" 代表中文,"en" 代表英文)。
  • text:要写入的文本字符串(支持 UTF-8 中文字符)。
  • 返回值MI_OK 成功,MI_ERR 失败。

💡 综合示例:构建一条多记录消息

C
NDEF_Message myMsg;

// 1. 必须先初始化
NDEF_Message_Init(&myMsg);

// 2. 添加一条跳转哔哩哔哩的链接
NDEF_AddUriRecord(&myMsg, URI_PREFIX_HTTPS_WWWDOT, "bilibili.com");

// 3. 添加中文描述文本
NDEF_AddTextRecord(&myMsg, "zh", "欢迎来到我的嵌入式 NFC 世界!");

💽 3. 卡片格式化 API

M1 白卡必须经过特定的格式化(写入 MAD 目录、修改对应扇区密码为 NDEF 公开密钥)才能被智能手机识别。

3.1 通用自适应格式化

C
int8_t NDEF_FormatUniversal(uint8_t *uid);
  • 功能强烈推荐的开荒函数! 自动识别卡片状态。如果是白卡,则全盘格式化并建立 MAD 目录;如果是 NDEF 卡,则抹除旧记录;如果是未知加密卡,则安全退出。
  • 参数uid – 由 PcdAnticoll 获取的 4 字节卡号数组。
  • 返回值MI_OK 成功,MI_ACCESSERR 拒绝访问(加密卡)。

💡 示例:将任意新卡格式化为 NDEF 标签

C
if (PcdAnticoll(CardUID) == MI_OK) {
    int8_t status = NDEF_FormatUniversal(CardUID);
    if (status == MI_OK) {
        printf("卡片已成功格式化为 NDEF 标签!\r\n");
    } else if (status == MI_ACCESSERR) {
        printf("格式化失败:这是一张加密卡,无法操作。\r\n");
    }
    M1_ForceWakeUp(CardUID); // 格式化后必须强力唤醒,为后续操作准备
}

3.2 恢复出厂设置 (洗白卡片)

C
int8_t NDEF_FormatToBlankCard(uint8_t *uid);
  • 功能:将 NDEF 卡片彻底“洗白”,所有扇区密码恢复为 FF FF FF FF FF FF,清除 MAD 目录和 TLV 头。
  • 参数uid – 4 字节卡号。
  • 返回值MI_OK 成功。

📡 4. 读写交互 API

确保卡片已格式化为 NDEF 后(或直接使用手机格式化过的卡),即可进行数据流读写。

4.1 将消息写入卡片

C
int8_t NDEF_WriteToCard(uint8_t *uid, NDEF_Message *msg);
  • 功能:将内存中的 NDEF_Message 序列化并分块写入卡片,自动避开密码控制块,自动计算 TLV 和 MB/ME 标志。
  • 注意事项:调用此函数前,必须确保卡片处于唤醒及未死锁状态(建议调用 M1_ForceWakeUp(uid))。

💡 示例:完整的写入流程

C
// 寻卡及防冲突...
if (PcdAnticoll(CardUID) == MI_OK) {
    M1_ForceWakeUp(CardUID); // 确保卡片完全就绪

    NDEF_Message myMsg;
    NDEF_Message_Init(&myMsg);
    NDEF_AddUriRecord(&myMsg, URI_PREFIX_HTTPS_WWWDOT, "github.com");

    int8_t status = NDEF_WriteToCard(CardUID, &myMsg);
    if (status == MI_OK) {
        printf("写入成功!快用手机碰一碰吧!\r\n");
    }

    // 退出三部曲,释放射频场并重置加密机
    PcdHalt();
    PcdStopCrypto1(); 
}

4.2 从卡片读取消息

C
int8_t NDEF_ReadFromCard(uint8_t *uid, NDEF_Message *msg);
  • 功能:自动寻找 NDEF 消息头,将卡片中的字节流反序列化并解析到 NDEF_Message 结构体中。
  • 返回值MI_OK 成功,MI_NOTAGERR 读到了标签但是空消息,MI_FORMATERR 格式异常或内容过长。

💡 示例:读取并格式化输出卡片内容

C
// 寻卡及防冲突...
if (PcdAnticoll(CardUID) == MI_OK) {
    M1_ForceWakeUp(CardUID); // 读取前同样需要强力唤醒

    NDEF_Message readMsg;
    int8_t status = NDEF_ReadFromCard(CardUID, &readMsg);

    if (status == MI_OK) {
        printf("成功读取到 %d 条记录:\r\n", readMsg.recordCount);

        for (uint8_t i = 0; i < readMsg.recordCount; i++) {
            NDEF_Record *rec = &readMsg.records[i];

            if (rec->type == NDEF_TYPE_URI) {
                // 根据 uriCode 映射前缀字符串 (如 http://www. 等)
                const char *prefix = (rec->uriCode == URI_PREFIX_HTTPS_WWWDOT) ? "https://www." : "";
                // 注意:使用 %.*s 配合 payloadLength 打印无 '\0' 结尾的数组
                printf("[%d] 链接: %s%.*s\r\n", i+1, prefix, rec->payloadLength, rec->payload);
            } 
            else if (rec->type == NDEF_TYPE_TEXT) {
                printf("[%d] 文本: %.*s\r\n", i+1, rec->payloadLength, rec->payload);
            }
        }
    }

    PcdHalt();
    PcdStopCrypto1(); 
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


目录