RFID工作原理
RFID技术的基本工作原理并不复杂:标签进入磁场后,接收解读器发出的射频信号,凭借感应电流所获得的能量发送出存储在芯片中的产品信息(无源标签或被动标签),或者由标签主动发送某一频率的信号(Active Tag,有源标签或主动标签),解读器读取信息并解码后,送至中央信息系统进行有关数据处理。
一套完整的RFID系统, 是由阅读器与电子标签也就是所谓的应答器及应用软件系统三个部份所组成,其工作原理是Reader发射一特定频率的无线电波能量,用以驱动电路将内部的数据送出,此时Reader便依序接收解读数据, 送给应用程序做相应的处理。
以RFID 卡片阅读器及电子标签之间的通讯及能量感应方式来看大致上可以分成:感应耦合及后向散射耦合两种。一般低频的RFID大都采用第一种式,而较高频大多采用第二种方式。
阅读器根据使用的结构和技术不同可以是读或读/写装置,是RFID系统信息控制和处理中心。阅读器通常由耦合模块、收发模块、控制模块和接口单元组成。阅读器和应答器之间一般采用半双工通信方式进行信息交换,同时阅读器通过耦合给无源应答器提供能量和时序。在实际应用中,可进一步通过Ethernet或WLAN等实现对物体识别信息的采集、处理及远程传送等管理功能。应答器是RFID系统的信息载体,应答器大多是由耦合原件(线圈、微带天线等)和微芯片组成无源单元。
韦根协议
Wiegand协议是国际上统一的标准,是由摩托罗拉公司制定的一种通讯协议。它适用于涉及门禁控制系统的读卡器和卡片的许多特性。 它有很多格式,标准的26-bit 应该是最常用的格式。此外,还有34-bit 、37-bit 等格式。 而标准26-bit 格式是一个开放式的格式,这就意味着任何人都可以购买某一特定格式的HID卡,并且这些特定格式的种类是公开可选的。26-Bit格式就是一个广泛使用的工业标准,并且对所有HID的用户开放。几乎所有的门禁控制系统都接受标准的26-Bit格式。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
以上是RFID技术协议和韦根的简略说明,不久之前,本人在某个项目中使用了RFID设备,在做解码的时候感觉很有趣,现在分享出来,以供各位同僚参考,如果记述有错误或者疏漏,那完全是因为本人能力有限,还请各位勇士不吝指出,本人会感激不尽。
本篇文章的重点在于韦根协议的解码,以及基于STM32单片机的C语言的具体实现。
首先看看韦根协议到底是什么。
韦根接口
Wiegand接口通常由3根线组成,它们是:数据0(Data0),数据1(Data1)和 Data return。这3条线负责传输Wiegand信号。D0,D1在没有数据输出时都保持+5V高电平。若输出为0,则D0拉低一段时间,若输出为1,则D1拉低一段时间。
基本概念
由上面可以知道,韦根数据输出由二根线组成,分别是DATA0 和 DATA1 ;二根线分别为‘0’或‘1’输出。
输出‘0’时:DATA0线上出现负脉冲;
输出‘1’时:DATA1线上出现负脉冲;
负脉冲宽度TP=100微秒;周期TW=1600微秒;
韦根协议常用的是26bit的协议,不过bit的多少对写代码的关系,完全可以做一个通用的代码。
现在我以韦根34为例子,做一下协议分析。
通讯协议
Wiegand 34格式:
各数据位的含义:
第 1 位: 为输出第2—17位的偶校验位
第 2-17 位: ID卡的HID码
第18-33位: ID卡的PID号码
第 34 位: 为输出第18-33位的奇校验位
数据输出顺序:
HID码和PID码均为高位在前,低位在后
方案分析
由以上的信息我们可以获悉,所谓的韦根协议十分简单,完全就只有两根线,平时两根线都保持高电平,等有信号的时候就突然来一个下降沿,
DATA0的下降沿表示二进制信号0,DATA1的下降沿表示二进制信号1,我们只需要将着一些信号收集起来,然后在组合成一个完整的数,
最后做一下奇偶校验就能得到正确的结果了。(如果不懂奇偶校验,请自行百度)
韦根信号的每一bit的持续时间都极端,只有100微秒左右,如果在代码中用轮询的方式明显是不可行的,自然而然,我们想到用外部中断的方式进行捕获信号。
代码讲解
首先是初始化函数,
1 /******************************************************************************* 2 * 函数名 : wiegand_init 3 * 描述 : 韦根机能初期化 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 void wiegand_init(void) 9 { 10 EXTI_Config_B0(); // 外部中断0初始化 韦根0 11 EXTI_Config_B1(); // 外部中断1初始化 韦根1 12 Timer_Config(); // 定时器初始化 13 NVIC_Config(); // 定时器中断配置 14 15 return; 16 }
由上面的信息得知,解码韦根协议需要用到两个外部中断,分别采集两根线的信号,但是在第12,13行,我还加入了定时器的初始化,
定时器是用来判断信号的有效性,比如说,当其中的某一个引脚上发生了下降沿中断,这时我们就应该启动定时器了,在规定时间内,
第二个中断信号没有来临,那么我们就可以把第一个中断信号当做干扰,从而提高了系统的抗干扰性。
本次代码是基于ARM STM32F103 这一款单片机的,中断的内部如下:
1 /******************************************************************************* 2 * 函数名 : EXTI_Config_B0 3 * 描述 : 外部中断0初期化 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 static void EXTI_Config_B0(void) 9 { 10 EXTI_InitTypeDef EXTI_InitStructure; 11 NVIC_InitTypeDef NVIC_InitStructure; 12 GPIO_InitTypeDef GPIO_InitStructure; 13 14 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource0); 15 16 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 17 18 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 19 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 20 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 21 GPIO_Init(GPIOB, &GPIO_InitStructure); 22 23 EXTI_InitStructure.EXTI_Line = EXTI_Line0; 24 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 25 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 26 EXTI_InitStructure.EXTI_LineCmd = ENABLE; 27 EXTI_Init(&EXTI_InitStructure); 28 29 NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; 30 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 31 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 32 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 33 NVIC_Init(&NVIC_InitStructure); 34 }
1 /******************************************************************************* 2 * 函数名 : EXTI_Config_B1 3 * 描述 : 外部中断1初期化 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 static void EXTI_Config_B1(void) 9 { 10 EXTI_InitTypeDef EXTI_InitStructure; 11 NVIC_InitTypeDef NVIC_InitStructure; 12 GPIO_InitTypeDef GPIO_InitStructure; 13 14 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1); 15 16 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; 17 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 19 20 GPIO_Init(GPIOB, &GPIO_InitStructure); 21 EXTI_InitStructure.EXTI_Line = EXTI_Line1; 22 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 23 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 24 EXTI_InitStructure.EXTI_LineCmd = ENABLE; 25 EXTI_Init(&EXTI_InitStructure); 26 27 NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; 28 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 29 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; 30 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 31 NVIC_Init(&NVIC_InitStructure); 32 }
以上的设置就是将某两个引脚设为捕获下降沿的外部中断功能,具体讲解网上的资料很多,请自行百度。
接下来是定时器的初始化:
1 /*============================================================================== 2 *名 称: Timer_Config(); 3 *功 能: 定时器中断初始化 4 *入口 参数: 5 *说 明: 放入主函数里初始化 6 *范 例: 7 *编者 时 间: Ye.FuYao 2012-9-23 8 9 公式为: 10 Period / (72M / (Prescaler+1) )=____ 秒 11 1000 / (72 M/ (35999+1) ) = 0.5 秒 12 *============================================================================*/ 13 void Timer_Config(void) 14 { 15 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIM结构体变量 16 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2外设 17 TIM_DeInit(TIM2); //复位时钟TIM2,恢复到初始状态 18 TIM_TimeBaseStructure.TIM_Period=200; 19 TIM_TimeBaseStructure.TIM_Prescaler=36000-1; 20 TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //TIM2时钟分频 21 TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //计数方式 22 // 定时时间T计算公式: 23 24 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化 25 TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除标志 26 // 中断方式下,使能中断源 27 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能中断源 28 TIM_Cmd(TIM2,ENABLE); //使能TIM2 29 }
定时器中断嵌套配置函数:
1 /*============================================================================== 2 *名 称: NVIC_Config(); 3 *功 能: 定时器嵌套控制 4 *入口 参数: 5 *说 明: 6 *范 例: 7 *编者 时 间: 8 *============================================================================*/ 9 void NVIC_Config(void) 10 { 11 NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量 12 // 设置优先分级组 13 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //0组,全副优先级 14 NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //选择中断通道,库 15 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级0 16 NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; //响应优先级0 17 NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //启动此通道的中断 18 NVIC_Init(&NVIC_InitStructure); //结构体初始化 19 }
接下来就是用来解码的外部中断服务函数了:
1 /******************************************************************************* 2 * 函数名 : EXTI0_IRQHandler 3 * 描述 : 外部中断服务函数 接收韦根a DATA断数据 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 void EXTI0_IRQHandler(void) 9 { 10 /* 外部中断 0 是韦根协议的 DATA0 */ 11 UID <<= 1; 12 /* 关闭定时器,初始化计数值为0后,再开启定时器 */ 13 TIM_Cmd(TIM2,DISABLE); 14 TIM2->CNT = 0; 15 TIM_Cmd(TIM2,ENABLE); 16 BitCount_a++; 17 EXTI_ClearITPendingBit(EXTI_Line0); 18 }
以上是韦根0的外部中断函数,当发生了外部中断下降沿信号后,数据采集变量UID左移,表示收到一个0,并且在这是重新初始化定时器,开始检测干扰。
然后韦根计数变量BitCount_a++,表示受到一个bit,当这个变量等于34的时候,就表示韦根信号接收完毕。
韦根1的外部中断也差不多,只有微小的区别:
1 /******************************************************************************* 2 * 函数名 : EXTI1_IRQHandler 3 * 描述 : 外部中断服务函数 接收韦根a DATA1中断数据 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 void EXTI1_IRQHandler(void) 9 { 10 /* 外部中断 1 是韦根协议的 DATA1 */ 11 UID <<= 1; 12 UID |= 1; 13 /* 关闭定时器,初始化计数值为0后,再开启定时器 */ 14 TIM_Cmd(TIM2,DISABLE); 15 TIM2->CNT = 0; 16 TIM_Cmd(TIM2,ENABLE); 17 BitCount_a++; 18 EXTI_ClearITPendingBit(EXTI_Line1); 19 }
当韦根1 发生了下降沿中断,表示接受到一个1bit,这是将数据采集变量UID加上一个1,剩下的和韦根0一毛一样。
下面是定时器服务函数,其中最重要功能是用来确定韦根信号是否接受完毕:
1 /******************************************************************************* 2 * 函数名 : TIM2_IRQHandler 3 * 描述 : 定时器中断服务函数,用于判定韦根解码失败 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 void TIM2_IRQHandler(void) 9 { 10 if(BitCount_a == WIEGAND34_DATA_LEN) 11 { 12 ReceiveFlag_a = 1; 13 } 14 BitCount_a = 0; 15 16 TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清标志 17 }
如果韦根计数变量BitCount_a达到了我们想要接收的位数,比如34,那么表示韦根信号已经接受完毕,设定好flag,接下来就可以效验了。
如果没有到达我们想要的计数量,那么就表示我们遇见了干扰,这时将计数变量清零,什么都不做,等待下一个信号来到。
等韦根信号的flag立起来后,我们就可以尝试着去效验数据的正确性了:
1 /******************************************************************************* 2 * 函数名 : wiegand_decode 3 * 描述 : 韦根解码效验函数 4 * 输出 : 无 5 * 返回 : 无 6 * 说明 : 无 7 *******************************************************************************/ 8 void wiegand_decode(void) 9 { 10 u16 i; 11 12 if(ReceiveFlag_a == 0x01) 13 { 14 /*收到第一位偶校验 */ 15 Parity = 0; 16 for(i = 0; i < 17; i ++) 17 { 18 Parity ^= ((UID >> (33 - i)) & 0x01); 19 } 20 if(Parity == 0) 21 { 22 /* 收到最后一位奇校验 */ 23 Parity = 0; 24 for(i = 0; i < 17; i ++) 25 { 26 Parity ^= ((UID >> (16 - i)) & 0x01); 27 } 28 PID = 0; 29 HID = 0; 30 if(Parity != 0) 31 { 32 /* 分离校验位,取出HID和PID码 */ 33 UID = (UID >> 1) & 0xFFFFFFFF; 34 35 PID = UID & 0x000000FF; 36 PID = (PID << 8); 37 PID = PID | ((UID >> 8) & 0x00FF); 38 39 UID = UID >> 16; 40 HID = UID & 0x000000FF; 41 HID = (HID << 8); 42 HID = HID | ((UID >> 8) & 0x00FF); 43 44 } 45 UID = 0; 46 } 47 ReceiveFlag_a = 0; 48 } 49 50 return; 51 }
等奇偶校验成功,然后就可以取出对应的PID和HID卡号了。
当自己校对数据的时候,一定记得要将两个奇偶校验位给去除,如果解码出来的数据和卡上的编码不一样:
第一,确认一下自己的韦根0和韦根1是否接反了。
第二,确认自己在解码后是否去掉了奇偶校验bit
如果自己刷卡以后,程序无法跑进定时器中断函数里面的那个if判定,那么就要看看自己韦根卡是否是设置为相应的bit,
如果卡是26bit,你用34bit去解码,当然收不全数据,然后还有一个比较重要,看看自己是否在外部中断服务函数中处理了太多的东西,
因为两个韦根信号来的很快,如果在中断里处理了太多的东西,那等第二个中断信号来临的时候,第一个处理还没有完成,那自然就会丢掉某个bit。
中断函数中的处理一定要尽可能的少,因为我在调试的时候发现,只要在外部中断里面再加入一些中断标志位的判定,那么就会丢掉bit。
——————————————————————————————————————————————————————————————————————————————
以上。