纯技术干货:多卡种兼容读卡器(DAIC-MJ-RW)通用性说明及对接数据格式+二次开发数据协议SDK。通用性:多奥门禁控制器/考勤机/智能通道门禁控制器/消费机/在线巡更读头/梯控主板/电子班牌/等
本文详细介绍了多卡种兼容读卡器DAIC-MJ-RW的通信协议及SDK实现方案。主要内容包括: 数据协议解析:详细说明了帧结构(包含Header、Length、CardType等字段)、支持7种卡片类型(如Mifare、EM4100等)及其对应的数据长度规范 校验算法:提供XOR校验的C语言实现及验证方法 完整SDK实现:包含状态机解析、卡号转换、回调处理等核心功能,支持IC卡、Felica等多种卡
分析这个多卡种兼容读卡器(DAIC-MJ-RW)的技术协议,并提供一个完整的SDK实现参考。
一、数据协议解析
1.1 帧结构详解
| 字段 | 长度 | 说明 |
|---|---|---|
| Header | 1字节 | 固定 0x02 |
| Length | 1字节 | 数据总长度(含Length字段到Checksum字段) |
| Card Type | 1字节 | 见下方卡片类型表 |
| Card Data | 4-8字节 | 卡号序列号(大端模式) |
| XOR Checksum | 1字节 | Length→Checksum所有字节的异或校验 |
| Footer | 1字节 | 固定 0x03 |
1.2 卡片类型对照表
#define CARD_TYPE_IC_MIFARE 0x10 // IC卡 (Mifare Classic)
#define CARD_TYPE_EM4100 0x01 // EM4100 ID卡
#define CARD_TYPE_B_CARD 0x21 // B卡
#define CARD_TYPE_CID 0x20 // CID卡
#define CARD_TYPE_FELICA_ICLASS 0x32 // Felica / iCLASS
#define CARD_TYPE_ISO15693 0x31 // ISO15693标签
#define CARD_TYPE_HID_PROX 0x03 // HID Prox卡
#define CARD_TYPE_ISO14443A 0x25 // ISO14443A CPU卡
1.3 帧长度规范
| 卡类型 | Length值 | Card Data长度 | 示例总帧 |
|---|---|---|---|
| IC卡/ID卡 | 0x09 |
4字节 | 02 09 10 A1B2C3D4 XX 03 |
| Felica/iCLASS | 0x0D |
8字节 | 02 0D 32 A1B2C3D4E5F60718 XX 03 |
二、XOR校验算法
计算公式:
Checksum = Length ⊕ CardType ⊕ Data[0] ⊕ Data[1] ⊕ ... ⊕ Data[n]
C语言实现:
uint8_t calc_xor_checksum(const uint8_t *data, uint8_t len) {
uint8_t checksum = 0;
for (uint8_t i = 0; i < len; i++) {
checksum ^= data[i];
}
return checksum;
}
// 验证完整帧
bool verify_frame(const uint8_t *frame, uint8_t frame_len) {
if (frame[0] != 0x02 || frame[frame_len-1] != 0x03)
return false;
uint8_t data_len = frame_len - 3; // 去掉头尾和校验字节本身
uint8_t calc_sum = calc_xor_checksum(frame + 1, data_len);
return calc_sum == frame[frame_len - 2];
}

三、完整SDK实现(C语言)
3.1 头文件定义 daic_mj_rw.h
#ifndef DAIC_MJ_RW_H
#define DAIC_MJ_RW_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ========== 卡类型定义 ========== */
typedef enum {
CARD_EM4100 = 0x01, // EM4100 (125KHz)
CARD_HID_PROX = 0x03, // HID Prox
CARD_IC_MIFARE = 0x10, // Mifare Classic
CARD_CID = 0x20, // CID
CARD_B_CARD = 0x21, // B卡
CARD_CPU_14443A = 0x25, // ISO14443A CPU卡
CARD_ISO15693 = 0x31, // ISO15693
CARD_FELICA_ICLASS = 0x32, // Felica / iCLASS
} CardType_t;
/* ========== 卡数据结构 ========== */
typedef struct {
CardType_t type; // 卡片类型
uint8_t sn[8]; // 序列号(最大8字节)
uint8_t sn_len; // 实际序列号长度
uint64_t card_id; // 转换为数值(便于存储)
uint32_t timestamp; // 读取时间戳
} CardInfo_t;
/* ========== 回调函数类型 ========== */
typedef void (*OnCardReadCallback)(const CardInfo_t *card);
/* ========== API接口 ========== */
// 初始化读卡器接口 (UART参数: 9600bps, 8N1)
int daic_reader_init(const char *uart_port);
// 关闭读卡器
void daic_reader_deinit(void);
// 注册卡片读取回调
void daic_register_callback(OnCardReadCallback cb);
// 手动轮询解析(非中断模式使用)
void daic_parse_byte(uint8_t byte);
// 发送蜂鸣器/LED控制指令(扩展功能)
int daic_send_control(uint8_t beep_ms, uint8_t led_color);
#ifdef __cplusplus
}
#endif
#endif /* DAIC_MJ_RW_H */
3.2 核心实现 daic_mj_rw.c
#include "daic_mj_rw.h"
#include <string.h>
#include <stdio.h>
/* ========== 帧解析状态机 ========== */
typedef enum {
STATE_IDLE, // 等待帧头
STATE_LENGTH, // 读取长度
STATE_TYPE, // 读取类型
STATE_DATA, // 读取数据
STATE_CHECK, // 读取校验
STATE_FOOTER // 等待帧尾
} ParseState_t;
static struct {
ParseState_t state;
uint8_t buffer[32]; // 帧缓冲
uint8_t idx; // 当前索引
uint8_t data_len; // 期望数据长度
uint8_t rec_len; // 接收到的长度
} parser = {0};
static OnCardReadCallback g_callback = NULL;
/* ========== 卡号数值转换 ========== */
static uint64_t bytes_to_uint64(const uint8_t *data, uint8_t len) {
uint64_t val = 0;
for (uint8_t i = 0; i < len; i++) {
val = (val << 8) | data[i];
}
return val;
}
/* ========== 获取卡类型名称 ========== */
const char* card_type_name(CardType_t type) {
switch(type) {
case CARD_EM4100: return "EM4100";
case CARD_HID_PROX: return "HID Prox";
case CARD_IC_MIFARE: return "IC Mifare";
case CARD_CID: return "CID";
case CARD_B_CARD: return "B Card";
case CARD_CPU_14443A: return "ISO14443A CPU";
case CARD_ISO15693: return "ISO15693";
case CARD_FELICA_ICLASS: return "Felica/iCLASS";
default: return "Unknown";
}
}
/* ========== 获取数据长度 ========== */
static uint8_t get_card_data_len(uint8_t frame_len) {
// Length字段值 = 1(Length) + 1(Type) + N(Data) + 1(Checksum)
// 所以 Data长度 = Length - 3
return frame_len - 3;
}
/* ========== 验证XOR校验 ========== */
static bool verify_xor(const uint8_t *frame, uint8_t frame_len) {
uint8_t checksum = 0;
// 计算从Length到Data末尾的异或
for (uint8_t i = 1; i < frame_len - 2; i++) {
checksum ^= frame[i];
}
return checksum == frame[frame_len - 2];
}
/* ========== 处理完整帧 ========== */
static void process_frame(const uint8_t *frame, uint8_t len) {
// 基础验证
if (frame[0] != 0x02 || frame[len-1] != 0x03) return;
if (!verify_xor(frame, len)) {
printf("[DAIC] XOR校验失败\n");
return;
}
CardInfo_t card = {0};
card.type = frame[2]; // Card Type字段
uint8_t data_len = get_card_data_len(frame[1]);
card.sn_len = data_len;
// 复制序列号
memcpy(card.sn, &frame[3], data_len);
// 转换为数值ID
card.card_id = bytes_to_uint64(card.sn, card.sn_len);
// 回调通知
if (g_callback) {
g_callback(&card);
} else {
// 默认输出
printf("[DAIC] 检测到 %s, ID: 0x%llX\n",
card_type_name(card.type), card.card_id);
}
}
/* ========== 字节流解析(状态机) ========== */
void daic_parse_byte(uint8_t byte) {
switch (parser.state) {
case STATE_IDLE:
if (byte == 0x02) {
parser.buffer[0] = byte;
parser.idx = 1;
parser.state = STATE_LENGTH;
}
break;
case STATE_LENGTH:
parser.buffer[parser.idx++] = byte;
parser.data_len = byte; // Length字段值
parser.rec_len = byte + 2; // 总长度 = Length + Header + Footer
parser.state = STATE_TYPE;
break;
case STATE_TYPE:
parser.buffer[parser.idx++] = byte;
parser.state = STATE_DATA;
break;
case STATE_DATA:
parser.buffer[parser.idx++] = byte;
// 检查是否收完数据部分(还需要2字节:Checksum + Footer)
if (parser.idx >= parser.rec_len - 1) {
parser.state = STATE_CHECK;
}
break;
case STATE_CHECK:
parser.buffer[parser.idx++] = byte;
parser.state = STATE_FOOTER;
break;
case STATE_FOOTER:
if (byte == 0x03) {
parser.buffer[parser.idx++] = byte;
process_frame(parser.buffer, parser.idx);
}
// 重置状态机
parser.state = STATE_IDLE;
parser.idx = 0;
break;
}
// 超时保护/长度溢出保护
if (parser.idx >= sizeof(parser.buffer)) {
parser.state = STATE_IDLE;
parser.idx = 0;
}
}
/* ========== 回调注册 ========== */
void daic_register_callback(OnCardReadCallback cb) {
g_callback = cb;
}
3.3 示例测试代码
C
#include "daic_mj_rw.h"
void on_card_read(const CardInfo_t *card) {
printf("================================\n");
printf("卡类型: %s (0x%02X)\n", card_type_name(card->type), card->type);
printf("序列号长度: %d 字节\n", card->sn_len);
printf("序列号(Hex): ");
for (int i = 0; i < card->sn_len; i++) {
printf("%02X ", card->sn[i]);
}
printf("\n卡号数值: %llu\n", card->card_id);
printf("================================\n\n");
}
int main() {
// 注册回调
daic_register_callback(on_card_read);
// 模拟接收数据:IC卡 卡号A1B2C3D4
uint8_t ic_card_frame[] = {0x02, 0x09, 0x10, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0x03};
// 计算校验:09^10^A1^B2^C3^D4 = E5
printf("模拟IC卡读取:\n");
for (int i = 0; i < sizeof(ic_card_frame); i++) {
daic_parse_byte(ic_card_frame[i]);
}
// 模拟Felica卡 8字节卡号
uint8_t felica_frame[] = {0x02, 0x0D, 0x32, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0xXX, 0x03};
// 需要重新计算校验值
return 0;
}

四、多设备集成方案
4.1 系统架构图
┌─────────────────────────────────────────────────────────┐
│ 统一管理平台 │
│ (门禁/考勤/消费/梯控/巡更) │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ 门禁控制器 │ │ 梯控主板 │ │ 消费机 │
│DAIC-MJ-RW │ │DAIC-MJ-RW │ │DAIC-MJ-RW │
└────────────┘ └────────────┘ └────────────┘
│ │ │
└───────────────┴───────────────┘
│
┌────────────┐
│ 读卡器总线 │
│(RS485/CAN) │
└────────────┘
4.2 多语言SDK封装
Python版本(适用于树莓派/Linux上位机):
import serial
import struct
from dataclasses import dataclass
from enum import IntEnum
from typing import Callable, Optional
class CardType(IntEnum):
EM4100 = 0x01
HID_PROX = 0x03
IC_MIFARE = 0x10
CID = 0x20
B_CARD = 0x21
CPU_14443A = 0x25
ISO15693 = 0x31
FELICA_ICLASS = 0x32
@dataclass
class CardInfo:
card_type: CardType
serial_number: bytes
card_id: int
def __repr__(self):
hex_sn = self.serial_number.hex().upper()
return f"CardInfo(type={self.card_type.name}, ID={hex_sn})"
class DAICReader:
HEADER = 0x02
FOOTER = 0x03
def __init__(self, port: str = '/dev/ttyUSB0', baudrate: int = 9600):
self.ser = serial.Serial(port, baudrate, timeout=0.1)
self.callback: Optional[Callable[[CardInfo], None]] = None
self._buffer = bytearray()
def set_callback(self, cb: Callable[[CardInfo], None]):
self.callback = cb
@staticmethod
def calc_xor(data: bytes) -> int:
checksum = 0
for b in data:
checksum ^= b
return checksum
def parse_frame(self, frame: bytes) -> Optional[CardInfo]:
if len(frame) < 6 or frame[0] != self.HEADER or frame[-1] != self.FOOTER:
return None
length = frame[1]
data_section = frame[1:-2] # Length到Data末尾
received_checksum = frame[-2]
if self.calc_xor(data_section) != received_checksum:
return None
card_type = CardType(frame[2])
data_len = length - 3 # Length - Type - Checksum字段
sn = frame[3:3+data_len]
card_id = int.from_bytes(sn, 'big')
return CardInfo(card_type, sn, card_id)
def run(self):
while True:
if self.ser.in_waiting:
data = self.ser.read(self.ser.in_waiting)
self._buffer.extend(data)
# 查找帧头帧尾
while len(self._buffer) >= 6:
try:
start = self._buffer.index(self.HEADER)
end = self._buffer.index(self.FOOTER, start)
frame = self._buffer[start:end+1]
card = self.parse_frame(frame)
if card and self.callback:
self.callback(card)
self._buffer = self._buffer[end+1:]
except ValueError:
break
# 使用示例
if __name__ == "__main__":
reader = DAICReader('/dev/ttyUSB0')
def on_card(card: CardInfo):
print(f"刷卡事件: {card}")
# 业务逻辑:开门、扣费、考勤记录等
reader.set_callback(on_card)
reader.run()
多卡种兼容读卡器支持多奥设备及一卡通系统,采用UART通信,支持IC/FeliCa卡,具备标准数据帧结构与XOR校验
五、常见问题排查
flowchart TD
A[用户刷卡] --> B[DAIC-MJ-RW读卡器]
B --> C{选择输出接口}
C -- 模式1: 即插即用 --> D[“韦根接口<br>(Wiegand 26/34)”]
C -- 模式2: 深度定制 --> E[“UART串口<br>(TTL电平)”]
D --> F[“标准门禁/梯控控制器<br>(如多奥DAIC-MJ-MB等)”]
D --> G[“第三方门禁/安防系统<br>(支持韦根输入)”]
E --> H[“微控制器<br>(如STM32, Arduino)”]
E --> I[“单板计算机<br>(如树莓派)”]
E --> J[“工业PLC/机器人控制器”]
E --> K[“自定义应用软件<br>(通过USB转串口)”]
F & G --> L[执行动作:开门/呼梯/通行]
H & I & J & K --> M[“获取原始数据包<br>(卡型 + 卡号)<br>进行自定义逻辑处理”]
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 收不到数据 | UART参数错误 | 检查波特率是否为9600,停止位1,无校验 |
| 校验失败 | 干扰/线缆过长 | 缩短RS232线缆,或改用RS485差分传输 |
| 卡号解析错误 | 大小端问题 | 确认序列号按大端模式(高位在前)解析 |
| 特定卡型不识别 | 未支持该卡型 | 记录Type值,添加到卡型表中 |
| 漏卡/重复读 | 状态机未重置 | 添加超时机制,超时后强制重置状态机 |
更多推荐









所有评论(0)