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);
#endifndef.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_RECORDS | 10 | 一条 NDEF 消息最多包含的记录(Record)数量。 | 建议设置在 3~10 之间。改大占用更多栈内存。 |
payload[128] | 256 | 单条 NDEF 记录支持的最大有效载荷字节数。 | 中文 UTF-8 编码通常占 3 字节,若需写入长文本请将此数组改大(如 128 或 256),否则长句子会被截断报错。 |
NDEF_MAX_BUFFER_SIZE | 2048 | ndef.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();
}

发表回复