news 2026/10/28 8:01:04

C# RFID读写实战:M1卡UID读取与扇区操作详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C# RFID读写实战:M1卡UID读取与扇区操作详解

简介:这份资源面向具备一定C#基础、希望入门RFID硬件交互的开发者,围绕射频识别卡的读取与写入展开,可用于门禁、物流跟踪、商品防伪等场景的原型搭建与学习。压缩包共29个文件,约66KB,以cs源码文件为核心,配合sln解决方案、csproj项目配置、resx资源文件及exe可执行程序,另有缓存与日志类文件,构成一个可直接打开运行的完整工程。已有4662人学习下载,说明其在同类入门资料中具备一定参考价值。代码覆盖串口连接配置、事件驱动监听、标签数据解析、读写命令下发与错误处理等关键环节,并演示了将硬件交互封装为独立类库、再由界面层调用的分层思路,便于读者理解C#与RFID阅读器通信的完整链路,也方便日后替换硬件时减少业务代码改动。

1. 从一张门禁卡说起:这套 C# RFID 读写代码到底能干什么

手里有一张 13.56MHz 的 M1 卡,想用 C# 读出它的 UID、扇区数据,甚至改写块内容,很多人第一反应是去搜“C# RFID 读写源码”,结果下载下来一堆跑不起来的半成品。这套基于 C# 的 RFID 卡识别与读写资源,解决的就是这个落地问题:它把串口通信、寻卡、防冲突、密钥认证、扇区读写这几步串成一条可运行的链路,适合做门禁原型、考勤终端、资产标签管理的开发者直接拆用。读者对象很明确——会用 Visual Studio、懂一点串口但没系统做过 RFID 的 .NET 从业者。下面按“它是什么、怎么跑通、参数怎么调、哪里会翻车”的顺序拆,能照着复现。

2. 拆开代码包看通信链路:串口帧、寻卡与密钥认证

RFID 读写不是调一个 API 就出结果,底层是读卡器模块和上位机之间的一问一答。这套代码的核心价值在于把协议帧的拼装和解析做成了可复用的类,而不是把命令散落在窗体事件里。先把这个链路讲清楚,后面改参数、排故障才有依据。

2.1 串口帧结构:帧头、长度、命令字与校验

常见做法是读卡器走 UART,波特率 9600 或 115200,8 数据位、1 停止位、无校验。一帧典型结构是:帧头(如 0xAA 0xBB)、长度、命令字、数据域、校验和。校验多为累加和取低字节,少数模块用 CRC16。代码里一般会封装一个BuildFrame(byte cmd, byte[] data)和一个ParseFrame(byte[] buffer),收发都走这两个入口。

// 帧拼装:帧头 + 长度 + 命令 + 数据 + 累加和校验 private byte[] BuildFrame(byte cmd, byte[] data) { var len = (byte)(data.Length + 1); // 命令字 + 数据长度 var frame = new byte[4 + data.Length + 1]; frame[0] = 0xAA; // 帧头高字节 frame[1] = 0xBB; // 帧头低字节 frame[2] = len; // 长度域 frame[3] = cmd; // 命令字,如 0x20 寻卡 Array.Copy(data, 0, frame, 4, data.Length); byte sum = 0; for (int i = 2; i < frame.Length - 1; i++) sum += frame[i]; frame[frame.Length - 1] = sum; // 累加和,只算长度到数据域 return frame; }

这段代码的关键在长度域和校验范围:长度从命令字算起,校验从长度域算到数据域末尾,帧头不参与。不同模块这两处定义经常不一样,拿到新模块第一件事就是翻手册核对,别直接套。串口打开时把ReadTimeout设成 200ms 左右,避免读不到数据时线程卡死。

2.2 寻卡与防冲突:一次只认一张还是多张

寻卡命令(常见 0x20 或 0x52)返回卡片的 UID 和类型(M1、CPU 卡等)。多卡同时进场时要做防冲突,代码里通常用Request加Anticollision两步:先请求所有卡应答,再逐位比较序列号筛出唯一一张。单卡场景可以跳过防冲突直接读 UID,速度快但多卡会乱。

// 寻卡并取 UID,单卡模式 public string ReadUid() { var resp = SendAndReceive(BuildFrame(0x20, new byte[] { 0x01 })); if (resp == null || resp.Length < 8) return null; // 假设返回:帧头2 + 长度1 + 命令1 + 状态1 + UID4 + 校验1 if (resp[4] != 0x00) return null; // 状态非 0 表示无卡或失败 return BitConverter.ToString(resp, 5, 4).Replace("-", ""); }

SendAndReceive里要做超时重试,一般重试 2 到 3 次,间隔 50ms。状态字节非 0 时不要当异常抛,直接返回空让上层轮询,门禁场景里“没卡”是常态不是错误。UID 拿到后建议做大小端转换确认,有些模块返回的是反序,直接拿去比对会一直不匹配。

2.3 密钥认证与扇区读写:M1 卡的 16 个扇区怎么动

M1 卡分 16 个扇区,每扇区 4 块,每块 16 字节,最后一块是控制块存密钥和权限。读写前必须先用密钥(Key A 或 Key B)认证对应扇区,认证命令常见 0x60(Key A)/0x61(Key B),后面跟块号和 6 字节密钥。

// 认证扇区 1(块 4),Key A 默认全 FF public bool AuthSector(byte block, byte[] keyA) { var data = new byte[7]; data[0] = 0x60; // 0x60=KeyA, 0x61=KeyB data[1] = block; Array.Copy(keyA, 0, data, 2, 6); var resp = SendAndReceive(BuildFrame(0x40, data)); return resp != null && resp[4] == 0x00; }

认证通过后才能读块(0x30)或写块(0xA0)。写块前务必确认目标块不是控制块,误写控制块会把扇区锁死,这张卡基本就废了。默认密钥全 FF 只适合测试,正式项目要在发卡环节改成自定义密钥并记录,否则等于门禁形同虚设。读到的 16 字节按需解析,做门禁一般只取 UID 或某一块的卡号字段,不必整卡 dump。

3. 从零跑通一次读写:环境、连接与第一个 UID

代码能编译不等于能读到卡,中间隔着驱动、端口、模块供电几道坎。这一章按实际动手顺序走一遍,目标是让读者在自己的机器上看到第一串 UID。

3.1 环境准备与串口枚举

先确认 .NET 版本和串口库。老项目多用System.IO.Ports,.NET Core 之后需要单独引System.IO.PortsNuGet 包。读卡器插上后设备管理器里会出现 COM 口,代码里不要写死COM3,用SerialPort.GetPortNames()枚举让用户选。

using System.IO.Ports; var ports = SerialPort.GetPortNames(); foreach (var p in ports) Console.WriteLine($"可用串口: {p}"); var port = new SerialPort("COM3", 115200, Parity.None, 8, StopBits.One) { ReadTimeout = 200, WriteTimeout = 200 }; port.Open();

波特率必须和模块一致,9600 与 115200 混用是最常见的“打开了但没反应”。如果枚举不到端口,先查 USB 转串口驱动,再看模块是否独立供电——很多模块靠 USB 供电带不动射频部分,表现为能发命令但寻不到卡。

3.2 连接、寻卡、读块的完整调用顺序

一次完整读卡流程是:打开串口 → 寻卡取 UID → 选卡 → 认证扇区 → 读块 → 关串口。选卡(Select)这步容易被省,但部分模块不选卡直接认证会返回失败。下面把主流程串起来。

public string ReadBlock(byte block, byte[] keyA) { if (!AuthSector(block, keyA)) return null; // 先认证 var resp = SendAndReceive(BuildFrame(0x30, new byte[] { block })); if (resp == null || resp[4] != 0x00) return null; return BitConverter.ToString(resp, 5, 16).Replace("-", ""); } // 主流程 port.Open(); var uid = ReadUid(); if (uid != null) { Console.WriteLine($"UID: {uid}"); var block4 = ReadBlock(4, new byte[] { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }); Console.WriteLine($"块4: {block4}"); } port.Close();

调用顺序不能乱:认证是对某个块所在扇区生效的,换扇区要重新认证。读块返回的 16 字节里,做卡号通常取前 4 字节做转换,具体偏移看发卡规则。调试阶段建议每步打日志,把发出的帧和收到的帧都按十六进制打出来,出问题时一眼能看出是发错还是收错。

3.3 参数怎么改:波特率、超时与重试

三个参数决定稳定性:波特率、读超时、重试次数。波特率跟模块手册走,改完两边必须同步;读超时 200ms 适合近距离单卡,远距离或弱场可放到 500ms;重试 2 到 3 次足够,再多会拖慢轮询节奏。

参数常用值调整依据改错的典型现象
波特率9600 / 115200模块手册打开成功但无任何返回
读超时200~500ms读卡距离、卡片类型偶发读不到、线程卡顿
重试次数2~3现场干扰程度响应变慢、重复触发
认证密钥测试全 FF发卡规则认证失败、读块返回错误

轮询场景下别在主线程里死循环读,放到后台线程或定时器里,间隔 100 到 200ms,避免 UI 卡死。多读卡器并存时每个实例独立持有自己的SerialPort,不要共享。

4. 避坑与排查:那些让读卡器“时好时坏”的细节

RFID 现场问题八成不是代码逻辑错,而是电气、协议细节和卡片状态。下面几条是实际拆项目时反复遇到的,按现象、原因、解决写清楚。

4.1 现象:能寻到卡但认证一直失败

原因多半是密钥或块号对不上。M1 卡每个扇区独立密钥,测试卡常是全 FF,但有些出厂卡已改过密钥;另外认证传的是块号不是扇区号,传成扇区号会认证到错误位置。解决:先用全 FF 试,失败就换 Key B,再不行用已知密钥的空白卡对照;确认块号落在目标扇区内(扇区 n 的块号是 4n 到 4n+3)。

4.2 现象:读到的 UID 每次顺序不一样

原因是大小端或字节序没统一。不同模块返回 UID 的字节顺序可能相反,代码里直接BitConverter转字符串,换模块就对不上。解决:在ReadUid里统一做一次字节反转开关,用配置项控制,入库前固定成一种顺序,别在业务层到处转。

4.3 现象:连续读几分钟后串口假死

原因是接收缓冲区没清干净或异常没释放端口。上一帧残留数据会让下一帧解析错位,久而久之缓冲区溢出。解决:每次SendAndReceive前DiscardInBuffer(),异常路径用try/finally保证Close();长时间运行加一个看门狗,连续 N 次失败就重开端口。

4.4 现象:写块成功但卡行为异常

原因是误写了控制块或写了非法权限位。控制块(每扇区第 4 块)存密钥和访问位,写错会把扇区设成只读甚至锁死。解决:写之前判断block % 4 != 3,控制块操作单独走高风险流程并二次确认;正式发卡前先在空白卡上验证权限配置。

提示:调试期把每帧收发都按十六进制落日志,比单步调试快得多,现场排障也靠它。

5. 进阶:把读写封装成可复用服务并做稳定性验证

代码跑通只是起点,真正上线要把寻卡、认证、读写收成一个服务类,对外只暴露TryReadCard(out CardInfo)这类方法,内部处理重试、端口重连和日志。我一般会加一个简单的压测:连续寻卡 1000 次,统计成功率和平均耗时,成功率低于 98% 就回头查供电和天线位置。验证密钥时用两张卡对照——一张全 FF 测试卡、一张已改密钥的正式卡,确保认证分支都走到。还有个习惯:每次换读卡器模块,先只跑寻卡和读 UID,确认帧格式对上了再动扇区读写,别一上来就整卡操作。这套流程走下来,后面接门禁业务逻辑基本不会再被底层通信拖住。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 8:00:58

WinForms中用DataGridView模拟树形结构的实战方案

简介&#xff1a;本资源是一份面向C# WinForms初学者与进阶开发者的实用技术实践包&#xff0c;聚焦解决DataGridView控件原生不支持树形结构展示的核心痛点&#xff0c;适用于需在桌面应用中呈现层级化数据&#xff08;如组织架构、文件目录、分类菜单&#xff09;的开发场景。…

作者头像 李华
网站建设 2026/10/28 7:58:04

电商设计师必懂的免费商用字体合规指南

1. 为什么“免费商用字体”在电商设计里是个高频踩坑区&#xff1f; 我帮过不下二十个做淘宝、拼多多、抖音小店的运营朋友改图&#xff0c;几乎每三个人里就有一个被字体版权问题找上门——不是平台下架主图&#xff0c;就是收到律师函要求赔偿。最典型的一次是某食品类目店主…

作者头像 李华
网站建设 2026/10/28 7:57:01

矩阵置零算法:优化解法与实现细节

1. 矩阵置零问题概述矩阵置零&#xff08;Set Matrix Zeroes&#xff09;是算法领域的一个经典问题&#xff0c;题目要求&#xff1a;给定一个mn的矩阵&#xff0c;如果某个元素为0&#xff0c;则将其所在行和列的所有元素都设为0。这个问题看似简单&#xff0c;但在实际处理中…

作者头像 李华
网站建设 2026/10/28 7:56:52

多Agent系统设计:从窗口堆叠到作战编队

1. “多 Agent”被误解的十年&#xff1a;从窗口堆叠到作战编队的认知跃迁“多 Agent”这个词&#xff0c;现在点开任何技术社区&#xff0c;扑面而来的全是“启动5个Agent”“并行跑10个智能体”“用LangChain开一堆线程”。我去年帮一家做金融风控的客户做POC&#xff0c;他们…

作者头像 李华
网站建设 2026/10/28 7:56:17

MySQL 8.0角色管理实战指南

1. MySQL角色管理基础概念在数据库管理系统中&#xff0c;角色管理是权限控制的核心机制。MySQL从8.0版本开始正式引入了角色功能&#xff0c;彻底改变了以往只能基于用户直接授权的方式。角色本质上是一组权限的集合&#xff0c;可以像用户一样被授予权限&#xff0c;然后这些…

作者头像 李华
网站建设 2026/10/28 7:56:00

Linux进程调度原理与优化实战指南

1. 进程调度的核心价值与挑战在Linux系统中&#xff0c;进程调度器就像交响乐团的指挥&#xff0c;负责决定哪个进程能获得CPU这个稀缺资源。我曾在生产环境遇到过一次典型的调度问题&#xff1a;某个关键服务进程响应延迟高达2秒&#xff0c;但CPU利用率却显示只有30%。通过pe…

作者头像 李华