1. 项目缘起:一个被低估的自动化场景
最近在整理公司仓库的物料卡片,看着同事拿着一沓沓纸质标签,用扫码枪一个个扫,再手动录入到Excel表格里,效率低不说,还容易出错。我就在想,这活儿能不能用更“懒”的办法搞定?正好手头有几个闲置的PN532 NFC读卡器和一堆白卡,一个想法就冒出来了:能不能写个程序,用NFC读卡器快速“滴”一下卡片,就把信息自动录入到电脑,并实时保存到Excel里?这样,无论是资产盘点、会议签到,还是像我们这样的物料管理,都能省下大量重复劳动。
这个想法听起来简单,但真动手做,你会发现它串联了好几个挺有意思的技术点:Python的串口通信、NFC数据的解析、Excel文件的实时读写,还得有个简单直观的界面让非技术人员也能用。市面上当然有成套的解决方案,但要么太贵,要么不够灵活。自己动手,不仅能完全定制流程,更重要的是能把数据流转的每个环节都掌握在自己手里,后续想加什么分析、对接什么系统都方便。
所以,就有了这个系列的第一篇。我们不搞虚的,直接目标导向:用
Mind+
(一个对初学者友好的图形化编程软件,也支持Python代码)和
Python
,驱动
NFC读卡器
,实现信息的快速采集,并自动存入
Excel
表格。我会把从硬件连接、环境配置,到核心代码、数据处理逻辑的每一步都拆开讲清楚,特别是那些容易踩坑的地方。无论你是想解决实际工作中的效率问题,还是对物联网数据采集感兴趣,跟着走一遍,你都能得到一个马上能用的工具。
2. 硬件选型与连接:为什么是PN532?
工欲善其事,必先利其器。NFC读卡器有很多种,从手机模拟的软方案到各种模块。我选择
PN532
芯片的读卡器模块,原因很实在:它太通用了。价格便宜(二三十块),支持三种通信模式(I2C, SPI, UART),社区资料和开源库极其丰富,几乎成了DIY NFC项目的“标准答案”。对于我们要做的PC端应用,通过USB转TTL工具让其以串口(UART)模式与电脑通信,是最稳定、最直接的方式。
2.1 硬件清单与连接示意图
你需要准备以下东西:
PN532 NFC模块
(带必要电阻电容的成品板最好)。
USB转TTL串口模块
(如CH340、CP2102等,注意要支持3.3V电平)。
杜邦线
若干(母对母)。
可写入的NFC标签
(如NTAG213, 215, 216或Mifare Classic 1K白卡)。
连接是关键的第一步,接错了轻则无法通信,重则烧模块。PN532模块上通常会有VCC、GND、TXD、RXD、SDA、SCL等引脚。我们使用UART模式,所以只关注前四个。
注意
:务必确认电压!PN532是3.3V器件,USB转TTL模块的VCC输出必须跳线到3.3V,接5V大概率会损坏芯片。
正确的连接方式如下:
|
VCC |
3.3V |
电源正极,生命线,必须3.3V |
|
GND |
GND |
电源地线,构成回路 |
|
TXD |
RXD |
模块发送端接串口模块接收端 |
|
RXD |
TXD |
模块接收端接串口模块发送端 |
连接好后,将USB转TTL模块插入电脑USB口。此时,PN532模块上的电源指示灯应该亮起。
2.2 驱动安装与端口确认
插入USB转TTL后,电脑需要安装对应的驱动。CH340芯片在Windows 10/11下通常能自动识别,如果不行,去芯片厂商官网下载驱动手动安装即可。安装成功后,需要在设备管理器中找到新增的串口(COM)端口。
操作步骤
:
COMx
数字(比如COM3、COM4),这就是我们程序中要用的端口号。
如果列表里没有新增端口,或者有黄色感叹号,说明驱动有问题,需要重新安装或尝试另一个USB口。
3. 软件环境搭建:Mind+与Python库的协同
很多人以为Mind+只能做图形化编程,其实它的“Python模式”功能相当强大,本质上是一个集成了代码编辑器、库管理和硬件串口调试功能的IDE,特别适合硬件交互项目的初期开发和调试。我们将利用它作为开发环境。
3.1 Mind+中的Python环境配置
打开Mind+,切换到“Python模式”。你需要确保两件事:
Python解释器
:Mind+会自带或允许你指定一个Python环境。在“设置”或“项目管理”中查看,确保Python版本是3.6以上(推荐3.8+)。
安装必要的Python库
:我们将用到三个核心库:
-
pyserial
: 用于通过串口与PN532模块通信。
-
nfcpy
: 一个纯Python的NFC库,能处理PN532的底层协议,让我们专注于业务逻辑。
-
openpyxl
: 用于读写Excel
.xlsx
文件,功能比古老的
xlrd
/
xlwt
更强大现代。
在Mind+的“终端”或“命令行”面板里(通常位于软件底部),输入以下命令进行安装:
pip install pyserial nfcpy openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple
使用
-i
参数指定清华镜像源可以大幅加快下载速度。
3.2 验证硬件通信:第一个测试脚本
环境装好,硬件连上,接下来就是经典的“Hello World”环节——测试电脑能否和PN532模块说上话。在Mind+中新建一个Python文件,输入以下代码:
import serial
import time
# 配置串口参数,这些参数需要与PN532模块的UART配置匹配
# 端口号‘COM3’替换为你在设备管理器里看到的实际端口
ser = serial.Serial(
port='COM3', # 端口号
baudrate=115200, # 波特率,PN532 UART默认115200
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1 # 读超时时间(秒)
)
if ser.is_open:
print(f"成功打开串口 {ser.port}")
# 发送一个简单的测试指令(例如,获取固件版本)
# PN532指令通常以特定帧头开始,这里先发送一个唤醒字节
wakeup_byte = b'\\x55\\x55\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'
# 实际协议更复杂,此处仅为示意通信
# 更推荐直接用nfcpy库来测试
ser.write(wakeup_byte)
time.sleep(0.1)
# 尝试读取返回数据
if ser.in_waiting:
response = ser.read(ser.in_waiting)
print(f"收到响应: {response.hex()}")
else:
print("未收到响应,请检查连接和指令。")
ser.close()
else:
print("无法打开串口!")
运行这段代码,如果能看到“成功打开串口 COMx”并打印出一串十六进制响应(哪怕不是预期的版本信息,只是一些数据),也基本证明物理连接和串口通信是正常的。如果报错“找不到COM口”或“权限被拒绝”,请返回检查设备管理器中的端口号和驱动。
4. 核心原理:NFC数据如何被读取与解析?
直接通过
pyserial
发送原始字节指令来控制PN532是可行的,但协议复杂,容易出错。这就是为什么我们要用
nfcpy
这个库——它帮我们封装了底层的协议交互,提供了高级的、面向“标签”的API。
4.1 NFC标签的类型与存储结构
常见的NFC标签主要有两种:
Mifare Classic (M1)
:比如Mifare S50 1K卡。它的存储结构像一栋楼,分为16个扇区(Sector 0-15),每个扇区有4个块(Block 0-3),每个块16字节。其中每个扇区的第4块(Block 3)是密钥块,存放访问密码A/B和控制位。
第0扇区的第0块(Block 0)是只读的,存放卡的UID(唯一标识符)和厂商信息
。这就是为什么有时会提示“NFC卡未能读取到0扇区”,可能因为卡片被锁或读卡器权限不足。
NTAG系列
:如NTAG213/215/216。它的结构更简单,像一本连续的记事本。存储空间连续,没有复杂的扇区概念,读写更简单,且通常支持更多的读写次数。
我们的项目目标是将自定义信息(如物品名称、编号)写入标签,然后读取。对于M1卡,我们需要避开0扇区0块,选择其他可写的块(如1扇区的块0)。对于NTAG卡,则可以直接从指定地址开始写。
nfcpy
库的好处在于,它对这些差异做了抽象,我们通常只需要关心“连接标签”和“读写NDEF记录”这种高级操作。
4.2 使用nfcpy建立连接与读取UID
下面是一个使用
nfcpy
与PN532交互,并读取卡片UID的核心代码段。在Mind+中新建一个脚本,尝试运行:
import nfc
import time
def on_connect(tag):
# 当成功连接到一个标签时,此函数被调用
print(f"检测到标签类型: {tag.type}")
print(f"标签标识符 (UID): {tag.identifier.hex().upper()}")
# 尝试读取NDEF数据(如果标签里有的话)
try:
ndef_data = tag.ndef
if ndef_data:
print("发现NDEF数据:")
for record in ndef_data.records:
print(f" 类型: {record.type}, 内容: {record.text}")
else:
print("标签内无NDEF数据,或未被格式化。")
except Exception as e:
print(f"读取NDEF时发生错误: {e}")
return True # 返回True,读卡器将在处理完后尝试寻卡下一个
def main():
# 建立到第一个可用NFC读卡器的连接
# 对于USB串口连接的PN532,路径通常是 'tty:USB0' 或 'com:COM3' 格式
# 在Windows上,使用 'com:COM3',请替换成你的实际端口
clf = nfc.ContactlessFrontend('com:COM3')
if clf:
print("NFC读卡器初始化成功,请将卡片靠近读卡器…")
# 启动轮询,当有卡片靠近时触发on_connect函数
# ‘targets’指定要侦听的标签类型,这里侦听最常见的两种
clf.connect(rdwr={'on-connect': on_connect, 'targets': ['106A', '106B']})
clf.close()
else:
print("无法初始化NFC读卡器,请检查连接和端口。")
if __name__ == '__main__':
main()
代码逻辑解读
:
ContactlessFrontend
对象
clf
,它代表我们的读卡器前端。
clf.connect()
方法启动一个轮询循环。当有符合
targets
类型(ISO/IEC 14443 Type A)的标签进入射频场时,它会自动调用我们定义的
on_connect
回调函数,并将标签对象
tag
传递进去。
on_connect
函数里,我们打印了标签的类型和唯一标识符
UID
。这是识别一张卡最关键的ID。
运行这个脚本,靠近一张白卡,你应该能在Mind+的控制台看到类似这样的输出:
NFC读卡器初始化成功,请将卡片靠近读卡器…
检测到标签类型: 106A
标签标识符 (UID): 04XXXXXX
标签内无NDEF数据,或未被格式化。
恭喜!至此,你已经成功打通了从物理连接、驱动、串口通信到NFC协议库的整个链路,拿到了卡片的“身份证号”(UID)。这是整个项目最基础也是最关键的一步。
5. 数据流转设计:从NFC到Excel的桥梁
拿到UID只是开始。我们的目标是录入“信息”。这里的“信息”可以是物品名称、型号、入库日期等。如何将我们想存的信息和一张卡的UID关联起来?有两种主流思路:
思路A:UID关联式
。卡片本身只存储其UID(出厂固化,无法更改)。我们在电脑端的数据库(或Excel)里,建立一张表,将UID与对应的物品信息关联起来。读卡时,只读取UID,然后去表格里查找对应的信息。这种方式安全,卡片成本低(白卡即可),但依赖后台数据库的持续维护。
思路B:卡片存储式
。将物品信息直接写入NFC标签的存储区。读卡时,直接读出信息。这种方式“离线”可用,卡片即数据载体,但可存储数据量有限(通常几百字节到几K),且卡片有丢失或损坏导致数据丢失的风险。
为了演示的完整性,我们这个项目将融合两种方式,并侧重于
卡片存储式
,因为它涉及了“写卡”这个更完整的操作。我们会将一条简单的文本信息(如“物料A-编号001”)写入卡片,然后在读取时,不仅读出这条信息,还将“UID+信息+读取时间”作为一条记录,自动追加到Excel文件中,实现一个完整的“录入-存储”闭环。
5.1 定义数据格式与Excel表头
首先,我们需要规划Excel表格的结构。在项目根目录创建一个名为
nfc_records.xlsx
的文件(或者用代码自动创建)。表头可以这样设计:
| (十六进制字符串) | (从卡片读取或关联) | (首次写入时间) | (最后一次刷卡时间) | (额外信息) |
在Python中,我们将使用
openpyxl
来操作这个Excel文件。它的优势是可以直接编辑
.xlsx
格式,保持格式完好,并且能方便地获取和操作单元格。
5.2 构建核心工具类:NFC管理器
为了让代码更清晰、可复用,我们创建一个
NFCManager
类,它封装了与读卡器交互、读写标签、以及记录日志的核心功能。在Mind+中新建一个Python文件,例如
nfc_manager.py
。
import nfc
import time
from datetime import datetime
import openpyxl
from openpyxl import Workbook
import os
class NFCManager:
def __init__(self, port='COM3', excel_path='nfc_records.xlsx'):
"""
初始化NFC管理器
:param port: 串口路径,Windows为'COM3'等形式
:param excel_path: Excel记录文件路径
"""
self.port = port
self.excel_path = excel_path
self.clf = None
self._init_excel_file()
def _init_excel_file(self):
"""初始化Excel文件,如果不存在则创建并写入表头"""
if not os.path.exists(self.excel_path):
wb = Workbook()
ws = wb.active
ws.title = "NFC记录"
# 写入表头
headers = ["UID", "物品信息", "入库时间", "最近读取时间", "备注"]
ws.append(headers)
wb.save(self.excel_path)
print(f"已创建新的Excel文件: {self.excel_path}")
else:
print(f"使用现有Excel文件: {self.excel_path}")
def connect_reader(self):
"""连接NFC读卡器"""
try:
self.clf = nfc.ContactlessFrontend(f'com:{self.port}')
if self.clf:
print("NFC读卡器连接成功。")
return True
else:
print("连接失败,读卡器可能未就绪。")
return False
except Exception as e:
print(f"连接读卡器时发生错误: {e}")
return False
def write_text_to_tag(self, text):
"""
将文本信息写入当前靠近的NFC标签
:param text: 要写入的文本字符串
"""
if not self.clf:
print("读卡器未连接!")
return False
def on_connect_write(tag):
print(f"准备写入到标签: {tag.identifier.hex().upper()}")
# 创建一条NDEF文本记录
from ndef import TextRecord
record = TextRecord(text)
tag.ndef = nfc.ndef.Message(record) # 将记录写入标签
print(f"写入成功: {text}")
# 同时,将这次写入作为一条新记录存入Excel
self._save_to_excel(tag.identifier.hex().upper(), text, is_write_action=True)
return True
print("请将待写入的卡片靠近读卡器…")
try:
# 尝试连接并写入,超时时间设为10秒
self.clf.connect(rdwr={'on-connect': on_connect_write, 'targets': ['106A', '106B']}, terminate=lambda: False)
return True
except nfc.clf.TimeoutError:
print("写入超时,未检测到卡片。")
return False
except Exception as e:
print(f"写入过程中发生错误: {e}")
return False
def read_tag_and_log(self):
"""读取靠近的NFC标签信息,并记录到Excel"""
if not self.clf:
print("读卡器未连接!")
return
def on_connect_read(tag):
uid = tag.identifier.hex().upper()
print(f"读取到标签UID: {uid}")
item_info = "未知信息"
# 尝试读取NDEF信息
try:
if tag.ndef:
for record in tag.ndef.records:
if record.type == 'text':
item_info = record.text
print(f" 物品信息: {item_info}")
break
except Exception as e:
print(f" 读取NDEF数据失败: {e}")
# 将读取到的信息保存到Excel
self._save_to_excel(uid, item_info, is_write_action=False)
return True
print("请将卡片靠近读卡器进行读取…")
try:
self.clf.connect(rdwr={'on-connect': on_connect_read, 'targets': ['106A', '106B']}, terminate=lambda: False)
except nfc.clf.TimeoutError:
print("读取超时。")
except Exception as e:
print(f"读取过程中发生错误: {e}")
def _save_to_excel(self, uid, item_info, is_write_action=False):
"""
将记录保存到Excel文件
:param uid: 标签UID
:param item_info: 物品信息
:param is_write_action: 是否为写入操作(True则更新‘入库时间’)
"""
try:
wb = openpyxl.load_workbook(self.excel_path)
ws = wb.active
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
row_found = None
# 查找是否已有该UID的记录
for row in range(2, ws.max_row + 1):
if ws.cell(row=row, column=1).value == uid:
row_found = row
break
if row_found:
# 更新现有记录
ws.cell(row=row_found, column=4).value = current_time # 更新最近读取时间
if is_write_action:
ws.cell(row=row_found, column=3).value = current_time # 如果是写入,也更新入库时间
if item_info != "未知信息" and ws.cell(row=row_found, column=2).value is None:
ws.cell(row=row_found, column=2).value = item_info # 如果原信息为空,则更新
print(f" 已更新Excel中UID为 {uid} 的记录。")
else:
# 新增一条记录
new_row = [uid, item_info]
if is_write_action:
new_row.extend([current_time, current_time, ""]) # 入库和读取时间相同
else:
new_row.extend([None, current_time, ""]) # 入库时间未知
ws.append(new_row)
print(f" 已在Excel中为UID {uid} 创建新记录。")
wb.save(self.excel_path)
except Exception as e:
print(f"保存到Excel时发生错误: {e}")
def close(self):
"""关闭读卡器连接"""
if self.clf:
self.clf.close()
print("NFC读卡器连接已关闭。")
这个类已经具备了核心功能:连接读卡器、写入文本到标签、读取标签信息、并将所有操作流水式记录到Excel。
_save_to_excel
方法实现了简单的“查重-更新/新增”逻辑,确保同一张卡多次读取不会重复创建行,而是更新“最近读取时间”。
6. 整合与测试:打造你的第一个NFC信息录入系统
有了强大的
NFCManager
类,主程序就变得非常简洁。我们创建一个
main.py
作为程序的入口,提供一个简单的交互菜单。
# main.py
from nfc_manager import NFCManager
import time
def main():
# 初始化管理器,指定你的COM端口
manager = NFCManager(port='COM3') # 请修改为你的实际端口
if not manager.connect_reader():
print("无法连接读卡器,程序退出。")
return
try:
while True:
print("\\n=== NFC信息录入系统 ===")
print("1. 读取卡片信息并记录")
print("2. 写入信息到卡片")
print("3. 退出程序")
choice = input("请选择操作 (1/2/3): ").strip()
if choice == '1':
manager.read_tag_and_log()
elif choice == '2':
text_to_write = input("请输入要写入卡片的文本信息: ").strip()
if text_to_write:
manager.write_text_to_tag(text_to_write)
else:
print("输入不能为空!")
elif choice == '3':
print("感谢使用,再见!")
break
else:
print("无效选择,请重新输入。")
time.sleep(1) # 短暂停顿,让输出更清晰
except KeyboardInterrupt:
print("\\n程序被用户中断。")
finally:
manager.close()
if __name__ == '__main__':
main()
现在,在Mind+中运行
main.py
。你会看到一个文本菜单。准备两张白卡(或一张卡反复用)。
测试流程1:写入信息
2
。
“实验室示波器 – 资产编号: DEV-2024-001”
。
测试流程2:读取并记录信息
1
。
nfc_records.xlsx
文件,你会发现多了一行记录,包含了UID、物品信息、以及当前的“入库时间”和“最近读取时间”。
至此,一个完整的、闭环的NFC信息录入与Excel存储系统就搭建成功了。你可以反复用不同的卡片和文字测试,观察Excel表格是如何自动更新和新增记录的。
7. 避坑指南与实战心得
在实际操作中,你几乎一定会遇到下面这些问题。这里我把踩过的坑和解决方案总结出来,能帮你节省大量时间。
7.1 常见错误与排查思路
nfc.clf.TimeoutError
或 一直提示“请将卡片靠近…”
-
可能原因1:串口被占用或错误
。确保Mind+或其他软件(如串口助手)没有占用同一个COM口。关闭所有可能占用串口的程序,重启Mind+再试。
-
可能原因2:波特率不匹配
。虽然PN532 UART模式常用115200,但有些模块可能需要特定初始化。确保
nfcpy
和你的硬件匹配。可以在
ContactlessFrontend
初始化时尝试更详细的参数,但通常库会自动处理。
-
可能原因3:标签类型不支持
。我们代码中
targets
参数指定了
['106A', '106B']
,这是最常见的ISO14443A标准。如果你用的是其他类型的卡(如Felica),需要修改这个参数。对于绝大多数M1和NTAG卡,这个设置是对的。
-
排查步骤
:首先运行第3.2节的纯串口测试脚本,确认硬件通信基本正常。然后,在
nfcpy
代码中,在
clf.connect
前后加入更多打印信息,并尝试延长超时时间。
写入失败,提示
NDEF Read/Write Error
-
可能原因1:标签已写保护
。特别是M1卡,某些扇区可能被密钥锁死,无法写入。尝试换一张全新的白卡,或者使用NTAG卡,它们通常默认可写。
-
可能原因2:存储空间不足
。写入的文本太长。一条NDEF文本记录本身有开销,对于NTAG213,可用空间只有100多字节。估算一下,一个中文字符UTF-8编码可能占3字节。尽量保持信息简洁。
-
可能原因3:标签未格式化
。有些全新的标签需要先格式化为NDEF格式。
nfcpy
的
tag.ndef = …
操作通常会自动处理,但如果失败,可以尝试先执行
tag.format()
(需谨慎,会清空数据)。
Excel文件被占用,无法保存
-
现象
:程序报权限错误,或者保存后文件内容没变。
-
解决
:确保Excel文件没有被用Microsoft Excel或其他编辑器打开。Python的
openpyxl
在保存时需要独占写入权限。如果程序崩溃,也可能导致文件句柄未释放,尝试删除该文件,让程序重新创建。
7.2 性能优化与稳定性提升
降低误触发
:在循环读卡时,同一张卡放在读卡器上可能会被连续读取多次,导致Excel里生成多条记录。我们代码中通过查找UID来更新记录,避免了重复行,但日志会刷屏。可以在
on_connect
函数里加入防抖逻辑,比如记录上次读取的UID和时间,如果短时间内是同一张卡,则忽略。
异常处理增强
:现在的代码骨架为了清晰,异常处理比较基础。在生产环境中,应该在每个可能失败的步骤(如串口打开、数据读写、文件操作)周围都用
try…except
包裹,并给出更友好的错误提示,而不是让整个程序崩溃。
使用
with
语句管理资源
:对于串口和文件操作,使用
with
语句可以确保即使在发生异常时,资源也能被正确关闭,避免端口锁定或文件损坏。例如:
with serial.Serial(port='COM3', …) as ser:
# 使用ser进行操作
# 退出with块后,ser会自动关闭
7.3 扩展思路:这个系统还能做什么?
现在这个系统已经是一个可用的工具了。但它的潜力远不止于此,你可以基于此骨架轻松扩展:
-
图形化界面(GUI)
:用
tkinter
、
PyQt
或
DearPyGui
为它做一个简单的窗口程序,放上“读卡”、“写卡”、“导出Excel”几个按钮,更适合给非技术人员使用。
-
批量初始化工具
:结合一个包含物品信息的CSV文件,写一个循环脚本,自动将不同信息写入一堆空白卡,实现资产标签的快速制作。
-
与数据库联动
:将数据存储从Excel迁移到SQLite甚至MySQL,便于更复杂的查询和管理。每次读卡后,除了本地记录,还可以通过HTTP API同步到服务器。
-
增加数据加密
:如果信息敏感,可以在写入前用简单算法(如XOR)或库(如
cryptography
)进行加密,读取时再解密。确保即使卡片丢失,信息也不易被直接读取。
这个项目就像搭乐高,核心的通信和数据处理模块已经完成。剩下的,就是根据你的具体场景,往上添加需要的功能组件。希望这篇超详细的指南,能帮你顺利跨过NFC与Python结合的第一道门槛,亲手打造出提升效率的自动化工具。






