分析这个多卡种兼容读卡器(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值,添加到卡型表中
漏卡/重复读 状态机未重置 添加超时机制,超时后强制重置状态机
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐