RFID工作原理
韋根協議
Wiegand協議是國際上統一的標准,是由摩托羅拉公司制定的一種通訊協議。它適用於涉及門禁控制系統的讀卡器和卡片的許多特性。 它有很多格式,標准的26-bit 應該是最常用的格式。此外,還有34-bit 、37-bit 等格式。 而標准26-bit 格式是一個開放式的格式,這就意味着任何人都可以購買某一特定格式的HID卡,並且這些特定格式的種類是公開可選的。26-Bit格式就是一個廣泛使用的工業標准,並且對所有HID的用戶開放。幾乎所有的門禁控制系統都接受標准的26-Bit格式。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
以上是RFID技術協議和韋根的簡略說明,不久之前,本人在某個項目中使用了RFID設備,在做解碼的時候感覺很有趣,現在分享出來,以供各位同僚參考,如果記述有錯誤或者疏漏,那完全是因為本人能力有限,還請各位勇士不吝指出,本人會感激不盡。
本篇文章的重點在於韋根協議的解碼,以及基於STM32單片機的C語言的具體實現。
首先看看韋根協議到底是什么。
韋根接口
基本概念

韋根協議常用的是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。
——————————————————————————————————————————————————————————————————————————————
以上。