基于STM32与PN532的宿舍NFC门禁

基于STM32与PN532的宿舍NFC门禁

理论实现:通过PN532与STM32进行通信密码匹配,成功过后带动舵机运动控制门锁。流程如图1-1:

图1-1  理论框图

  • 一、PN532NFC模块串口通信

一、PN532串口硬件配置

通过PN532官方自带PDF中可以查阅到PN532硬件,上面通过调整可以改变串口通信方式,如图1-1 其中模块上由0欧电阻充当开关选择串口通信方式,如串口选择“00”、I2C选择“10”、SPI选择“01”。

图1-2  串口选择

通过硬件调整选择适合项目的模式,PN532硬件选择可能会由开关的方式,只需要调整开关键即可选择合适的功能如图1-2形式,在这里我们选择“00”。

选择串口模式与单片机通信。

图1-3 开关形式选择通信方式

  • 二、PN532串口通信指令

  • 一、通信数据帧格式

PN532通信数据帧格式如表1-1:

00

00 FF

LEN

LCS

DATA

DCS

00

帧头

开始数据

长度

长度校验

数据

数据校验

帧尾

表1-1    数据帧基本格式

00  :发送数据帧头

00 FF:属于数据帧开头固定格式

LEN  :数据长度,也就是DATA的长度

LCS  :长度校验位,其中校验位计算式通过长度的十六进制的补码,例如:长度为15也就是十六进制的0F,那么LCS = (FF-0F) + 1 = F1,这里F1以为就是所需要的长度校验位。

DATA:为你所想要发送的数据,其中也有固定的格式

DCS :数据校验位  

求DCS过程如下:

①通过求数据的和

②对数据的和求低八位

③再对低八位求补码

例如:数据为 D4 4A 02 00 那么DSC = FF- (D4 + 4A + 02 + 00)低八位+1=FF- (120)低八位+1=E0

 ,所以DCS最终也就等于E0

00 :帧位,数据帧末尾固定数据

二、数据帧DATA的格式

上表格为数据帧基本格式,在实际运用过程中因为收发,所以DATA在格式上会有所差别,如下表1-2:

TFI

CC/CC+1

DATA1

DATA2

···

DATAN

表1-2    DATA

TFI :D5/D4通常在数据DATA中的开头用于区分模块发送给我们,还是我们发送给模块,D5就是模块发送给我们的数据,D4代表的是我们发送给模块的数据。

例如:

1、我们发送给模块的数据:00 00 FF 04 FC D4 4A 02 00 E0 00,其中D4也作为我们发送数据的标识,也是数据的开始位。

 2、模块发送给我们的数据:00 00 FF 0C F4 D5 4B 01 01 00 04 08 04 C5 B4 21 03 31 00。其中D5也就式用于区分与上面相同。

CC/CC+1 :例如:

我们发送给模块的数据:00 00 FF 04 FC D4 4A 02 00 E0 00

模块发送给我们的数据:00 00 FF 0C F4 D5 4B 01 01 00 04 08 04 C5 B4 21 03 31 00。

其中在D4后4A与D5后面4B相差1,也就是CC+1

DATAN:就是想要发送的数据,通常发送使用芯片所带的指令集。

注:在上面指令的编写过程中注意校验码,如果校验码错误是无法与PN532进行交互。

  • 三、PN532读卡步骤及指令

  • 一、唤醒PN532
  1. 通过串口发送指令:55 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 03 FD D4 14 01 17 00
  2. 对PN532进行唤醒操作,如果唤醒成功则PN532返回数据:

00 00 FF

02

FE

D5

15

16

00

帧头

长度

长度校验

DATA1

DATA2

数据校验

帧尾

表1-3  唤醒返回数据

PN532返回以上数据表明唤醒成功,接下来进行第二步操作寻卡

  • 二、PN532寻卡
  1. 在唤醒PN532的基础上通过串口再次发送寻卡指令:

00 00 FF

04

FC

D4

4A

02

00

E0

00

帧头

数据长度

长度校验

DATA1

DATA2

DATA3

DATA4

数据校验

帧尾

表1-4   寻卡指令

DATA1:传输方向为上位机STM32,或者串口助手等发送至PN532模块

DATA2:寻卡标识

DATA3:寻卡数量

  1. PN532接受到寻卡指令之后会处于寻卡状态,当卡片靠近PN532时就会返回数据: 00 00 FF 0C F4 D5 4B 01 01 00 04 08 04 C5 B4 21 03 31 00

00 00 FF

帧头

0C

长度

F4

长度校验

D5 4B 01 01 00 04 08 04

数据

C5 B4 21 03

卡片的UID

31

数据校验

00

帧尾

表1-5   寻卡返回数据

  • 三、PN532验证
  1. 在进行上面寻卡成功过后需要对卡片中的扇区块进行验证:

发送数据为:00 00 FF 0F F1 D4 40 01 60 04 FF FF FF FF FF FF C5 B4 21 03 F0 00

00 00 FF

帧头

0F

长度

0F

长度校验

D4 40 

命令格式

01

数据字节长度

60

密钥认证

04

选择读取卡片的那一块,这里选择的第4块

FF FF FF FF FF FF

密码

C5 B4 21 03

卡片UID

F0

数据校验

00

帧尾

表1-6  验证指令

  1. 验证成功返回数据为:00 00 FF 00 FF 00 00 00 FF 03 FD D5 41 00 EA 00

00 00 FF

03

FD

D5

41

00

EA

00

帧头

长度

长度校验

传送方向

CC+1

验证成功

数据校验

包尾

表1-7   验证返回数据

四、PN532读卡

1.    在上面步骤成功之后进行最后一步操作,通过PN532读取卡片中的数据,发送指令:00 00 FF 05 FB D4 40 01 30 04 B7 00

00 00 FF

帧头

05

长度

FB

长度校验

D4

传送方向

40

命令格式

01

数据长度大于6个字节为1

30

30 16字节读,A0 表示16字节写,A2表示4字节写,C1表示增加,C0表示删除,B0表示转换

04

读取卡片的第四块(读取地址)

B7

数据校验

00

帧尾

表1-8   读卡指令

2.   读取成功返回数据,其中含有卡片里面所写数据我的数据如下:

00 00 FF 00 FF 00 00 00 FF 13 ED D5 41 00 20 22 05 02 02 26 00 00 00 00 00 00 00 00 00 00 79 00

00 00 FF

帧头

13

数据长度

ED

长度校验

D5

传送方向

41 00

41 00 代表成功,如果是其他就代表失败

20 22 05 02 02 26 00 00 00 00 00 00 00 00 00 00

卡片在第4块中所存储的数据

79

数据校验

00

帧尾

表1-9  读卡返回数据

串口操作截图如下:

  1. 操作过程卡片没有靠近出现错误,错误演示如图1-4:

                                                  图1-4  操作错误演示

在途中接收数据中第三段返回的数据中是41 14 并不是41 00所以在这里出现错误

  1. 正确演示如图1-5:

                                              图1-5     正确操作返回数据

可以明显看到在最后读卡返回数据中有我们所需返回数据。

以上操作我们成功通过PN532对卡片进行读数据操作,接下来讲述通过官方发的软件对M1卡进行写入数据操作。

二、NFC上位机对M1卡的读写操作

1.   打开上位机的文件夹

2.   注册上位机如图2-1所示

图2-1  上位机注册

  1. 右键点击NFC上位机管理员模式运行,运行成功如图2-2

图2-2  运行成功

其中运行成功后会发现NFC设备,PN532通过TTL与电脑相连,如果未发现可能出现了串口占用的情况。

  1. 点击读取卡片内容如图2-3所示

2-3  读取成功

读取过后会出现上面所示数据,其中有64个扇区,每个扇区有4个块。

我写入数据的位置为第一个扇区的第一块,地址也就是0x04,代表的从块号0数第4个块。

  1. 写卡操作如图2-4

图2-4  改写卡片数据

点击你想要改变的位置,输入数据即可。

改写成功过后,点击写整卡,等待一会儿就会写入卡片,成功过后再次读取就会显示改写位置的数据。

三、STM32F103单片机与PN532串口通信实现控制舵机操作

图3-1  逻辑实现框图

通过PN532与STM32进行数据交互,最终控制舵机实现开锁动作或关锁动作。

一、STM32F103与PN532串口通信

根据第一部分了解到PN532基本串口通信操作原理,结合STM32串口通信可设计串口通信模块

一、STM32串口初始化配置

表3-1表示STM32串口初始化配置等

#include "stm32f10x.h"                  // Device header

#include <stdio.h>                      

#include <stdarg.h>

#include "LED.h"

uint8_t Serial_TxPacket[16];                    //定义发送数据包数组

uint8_t Serial_RxPacket[16];                    //定义接收数据包数组

uint8_t Serial_RxFlag;                             //定义接收数据包标志位

uint8_t length;

/**

  *     数:串口初始化

  *     数:无

  * 值:无

  */

void Serial_Init(void)

{

     /*开启时钟*/

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    

     //开启USART1的时钟

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    

     //开启GPIOA的时钟

     /*GPIO初始化*/

     GPIO_InitTypeDef GPIO_InitStructure;

     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_Init(GPIOA, &GPIO_InitStructure);                          

     //PA9引脚初始化为复用推挽输出TXD

     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     GPIO_Init(GPIOA, &GPIO_InitStructure);                                           //PA10引脚初始化为上拉输入RXD

     /*USART初始化*/

     USART_InitTypeDef USART_InitStructure;                                       //定义结构体变量

     USART_InitStructure.USART_BaudRate = 115200; //波特率

       USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;     

        //硬件流控制,不需要

     USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;   

        //模式,发送模式和接收模式均选择

     USART_InitStructure.USART_Parity = USART_Parity_No;

     //奇偶校验,不需要

     USART_InitStructure.USART_StopBits = USART_StopBits_1;

     //停止位,选择1

     USART_InitStructure.USART_WordLength = USART_WordLength_8b;     

     //字长,选择8

     USART_Init(USART1, &USART_InitStructure);                   

     //将结构体变量交给USART_Init,配置USART1

     /*中断输出配置*/

     USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);            

     //开启串口接收数据的中断

     /*NVIC中断分组*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);     //配置NVIC为分组2

     /*NVIC配置*/

     NVIC_InitTypeDef NVIC_InitStructure;                        //定义结构体变量

     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;           

     //选择配置NVICUSART1线

     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            

     //指定NVIC线路使能

     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     

     //指定NVIC线路的抢占优先级为1

     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;      

     //指定NVIC线路的响应优先级为1

     NVIC_Init(&NVIC_InitStructure);                                     

     //将结构体变量交给NVIC_Init,配置NVIC外设

     /*USART使能*/

     USART_Cmd(USART1, ENABLE);                                           

     //使能USART1,串口开始运行

}

表3-1  串口初始化

在其中用到一些串口发送数据函数可根据>>>>>>哔哩哔哩江协串口章节所设计。

二、STM32串口中断函数

在接收数据部分所用到状态机原理,实现代码如下表3-2串口中断:

void USART1_IRQHandler(void)

{

     static uint8_t RxState = 0;           //定义表示当前状态机状态的静态变量

     static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量

     static uint8_t len = 0x00,lsc=0x00 ,dsc=0x00;

     if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)            //判断是否是USART1的接收事件触发的中断

     {

           uint8_t RxData = USART_ReceiveData(USART1);                    

           //读取数据寄存器,存放在接收的数据变量

           /*使用状态机的思路,依次处理数据包的不同部分*/

           /*当前状态为0,接收数据包包头*/

           if (RxState == 0)

           {

                 if (RxData == 0x00)             //如果数据确实是包头0x00

                 {

                       RxState = 1;            //置下一个状态

                 }

           }else if(RxState == 1)

           {

                 if (RxData == 0x00)             //如果数据确实是开始位1  0x00

                 {

                       RxState = 2;            //置下一个状态

                 }

           }else if(RxState == 2)

           {

                 if (RxData == 0xFF)              //如果数据确实是开始位2  0xFF

                 {

                       RxState = 3;            //置下一个状态

                 }

           }else if(RxState == 3)                 //读取数据长度

           {

                       len = RxData;

                       length = len;

                       RxState = 4;                                         

           }else if(RxState == 4)                 //读取长度检验

           {

                 lsc = RxData;

                 if(lsc == 0xff)

                 {

                       RxState = 7;

                 }else

                 {

                       RxState = 5;

                       pRxPacket = 0;              //数据包的位置归零

                 }

           }

           /*当前状态为5,接收数据包数据*/

           else if (RxState == 5)

           {

     Serial_RxPacket[pRxPacket] = RxData;//将数据存入数据包数组的指定位置

                 pRxPacket ++;                           //数据包的位置自增

                 if (pRxPacket == length)            //如果收够数据

                 {

                       RxState = 6;                        //置下一个状态

                 }

           }

           else if(RxState == 6)//读取数据长度检验为

           {

                       dsc = RxData;

                       RxState = 7;

           }

           /*当前状态为7,接收数据包包尾*/

           else if (RxState == 7)

           {

                 if (RxData == 0x00&&lsc == 0xff)           //如果数据确实是包尾部

                 {

                       RxState = 0;                 //状态归0

                 } else if(RxData == 0x00&&lsc != 0xff)

                       {  

                           RxState = 0;              //状态归0

                           Serial_RxFlag = 1;    //接收数据包标志位置1,成功接收一个数据包     

                       }  

                 }

           USART_ClearITPendingBit(USART1, USART_IT_RXNE);     //清除标志位

     }

}

表3-2  串口中断函数

三、STM32串口通信主程序

在主程序中就需要对串口接受到的数据进行处理主要程序如下表3-3:

           /*初始化模块*/

           KeyNum = Key_GetNum();                                        //获取按键键码

           if (KeyNum == 1)                                                    //按键1按下

           {

                 Serial_SendArray(initCommand,24);  

                 see = 0;

                 OLED_ShowString(1,1,"back");                         //测试显示

                 OLED_ShowString(2,1,"DOOR_OFF");                   //测试显示

           }

           /*串口接收到数据包处理模块*/

           if (Serial_GetRxFlag() == 1) //如果接收到PN532返回的唤醒数据包

           {

                 if(compare(Serial_RxPacket,Rdata_1,length) == 1&& see == 0)                    //比对是否正确唤醒

                 {                                        

                       Delay_ms(50);

                       Serial_SendArray(findcard,11);                       //发送寻卡指令

                       see = 1;                                                      //标志位    

                       OLED_ShowString(1,1,"back0");                     //测试显示                                                 

                 }

                 if(compare(Serial_RxPacket,Rdata_2,length-4) == 1 && see == 1)                   //唤醒之后进入寻卡状态

                 {

                       UID[0] = Serial_RxPacket[8];

                       UID[1] = Serial_RxPacket[9];

                       UID[2] = Serial_RxPacket[10];

                       UID[3] = Serial_RxPacket[11];

                       Delay_ms(50);

                       verify_send(verify_star,ADpath,UID);              //发送验证指令

                       see = 2;                                                      //标志位

                       OLED_ShowString(1,1,"back1");                     //测试显示

                 }    

     if(compare(Serial_RxPacket,Rdata_3,length) && see == 2)//进入验证状态

                 {

                       Delay_ms(50);

                       Serial_SendArray(readcard,12);                      //发送读卡指令

                       see = 3;                                                      //标志位

                       OLED_ShowString(1,1,"back2");                     //测试显示

                 }

                 if(compare(Serial_RxPacket,password,9)&& see == 3)

                 {

                       OLED_ShowString(1,1,"back3");               //测试密码匹配成功

                       see = 4;                                                //串口标志位

                 }    

           }

表3-3  串口主程序处理

当see标志位等于4的时候 进入舵机操作模块

二、舵机执行模块

一、舵机初始化程序

舵机初始化配置用PWM波实现控制舵机运转如表3-4所示:

#include "stm32f10x.h"                  // Device header

void  PWM_Init(void)

{

     RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2,ENABLE);//开启时钟

     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

     GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

     //选择复用推挽输出,因为这样才可以将控制权交给外设

     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init(GPIOA, &GPIO_InitStructure);

     TIM_InternalClockConfig(TIM2);

     //选择内部时钟//上电之后32自动选择内部时钟

     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

     TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//选择分频

     TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;

     //计数方式

     TIM_TimeBaseInitStructure.TIM_Period=10000-1;//ARR自动重装

     TIM_TimeBaseInitStructure.TIM_Prescaler=72-1;//PSC预分频器

     TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器

     TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);//配置时基单元

     TIM_OCInitTypeDef TIM_OCInitStructure;

     TIM_OCStructInit(&TIM_OCInitStructure);

     //给结构体变量赋初始值,防止出现一些道路不通

     TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//设置模式

     TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

     //设置输出比较的极性

     TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable ;

     //设置输出使能

     TIM_OCInitStructure.TIM_Pulse = 0;//设置CCR

     TIM_OC2Init(TIM2,& TIM_OCInitStructure);

     TIM_Cmd(TIM2,ENABLE);

}

void PWM_SetCompare2(uint16_t compare)

{

     TIM_SetCompare2(TIM2,compare);

}

表3-4  PWM初始化

舵机模块初始化见下表3-5:

#include "stm32f10x.h"                  // Device header

#include "PWM.h"

void Servo_Init(void)

{

     PWM_Init();

}

void Servo_SetAngle(float Angle)

{

     PWM_SetCompare2(Angle / 180 * 2000 + 500);

}

表3-5  舵机初始化

舵机初始化基本是PWM的初始化,在模块中封装了一个角度控置函数,来控制舵机旋转的角度。

三、定时器模块

定时器模块作用与在开锁之后指定时间恢复门锁状态

定时器初始化代码如下表3-6:

#include "stm32f10x.h"                  // Device header

void Timer_init(void )

{

     RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3,ENABLE);//开启时钟

     TIM_InternalClockConfig(TIM3);

     //选择内部时钟//上电之后32自动选择内部时钟

    

     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

     TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//选择分频

     TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;

     //计数方式

     TIM_TimeBaseInitStructure.TIM_Period=40000-1;//自动重装

     TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//PSC预分频器

     TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器

     TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);//配置时基单元

     TIM_ClearFlag(TIM3, TIM_IT_Update );

     //手动清除更新标志位,防止上电初始化之后就产生计次

     TIM_ITConfig(TIM3, TIM_IT_Update , ENABLE );//选择中断

     NVIC_InitTypeDef NVIC_Initstructure;

     NVIC_Initstructure.NVIC_IRQChannel=TIM3_IRQn;

     NVIC_Initstructure.NVIC_IRQChannelCmd=ENABLE;

     NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级

     NVIC_Initstructure.NVIC_IRQChannelSubPriority=1;//响应优先级

     NVIC_Init (&NVIC_Initstructure);

}

表3-6  定时器初始化

定时器模块中并没有直接开启定时器,而是在主程序中串口通信模块,NFC密码匹配成功过后打开定时器。

定时器中断函数如下表3-7:

void TIM3_IRQHandler (void)

{

     if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)

           {

                 Angle = 0;                                                   //舵机标志位置0

                 TIM_Cmd(TIM3,DISABLE);                                   //关闭定时器

                 TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除定时器标志位

           }

}

表3-7  定时器中断函数

在中断中证明计时结束,所以分别执行了舵机标志位清零,以及关闭定时器等操作。

四、过程问题总结

一、STM32与PN532串口通信

①初步通信时发现单片机与PN532无法正常通信?

答:原来是串口波特率设置于PN532波特率设置不匹配导致。

②串口通信测试阶段可能出现空数据包怎么处理?

答:在状态机接受时长度校验位出现FF则就说明出现的是空数据包,直接跳过接收数据部分,进入包尾并且返回状态及等待部分,不改变数据标志位。

③在接受数据包过后主程序如何处理呢?

答:在主程序当中我们想要实现匹配与发送操作,所以只需要根据PN532串口通信原理编写所对应的匹配和发送模块即可。

④在串口模块中读取UID的时候问什么出现for循环无法执行了?

答:因为使用for循环之后将串口卡死了,所以改成一步一步操作。

二、STM32与舵机之间交互

①舵机在何时才执行开锁动作?

答:舵机在STM32与PN532读卡数据匹配成功过后执行开锁操作。

②在何时执行关锁操作?

答:通过定时器计时,到达设定时间过后舵机执行关锁动作。

③问什么在测试阶段舵机与定时器单独可以执行,放在一块会出现问题?

答:因为最初PWM配置与定时器配置都用的TIM2,在程序中出现冲突所以无法执行,所以将定时器的TIM2变成TIM3就可以了。

④将舵机的旋转角度函数放在中断中无法执行怎么办?

答:可以通过标志位的方法,将函数放在主程序中执行。

参考文献

[1]  CSDN: NFC芯片--PN532的使用

链接:NFC芯片--PN532的使用_pn532说明-CSDN博客

[2]  CSDN: PN532NFC模块串口通信使用教程

链接:PN532NFC模块串口通信使用教程_pn532命令-CSDN博客

[3]  CSDN: PN532 NFC 读卡器的串口通讯实现

链接:PN532 NFC 读卡器的串口通讯实现_pn532串口通信-CSDN博客

[4] 博客园:PN532教程 - 为敢技术 - 博客园

链接:PN532教程 - 为敢技术 - 博客园

[5]  博客园:PN532握手数据流详解 - 为敢技术 - 博客园

链接:四、PN532握手数据流详解 - 为敢技术 - 博客园

[6]  哔哩哔哩:江协STM32教程

[7]  官方文档:PN532 NFC RFID Module Manual.pdf

[8]  官方文档:PN532 User Manual.pdf

附录

#include "stm32f10x.h"                 

#include "Delay.h"

#include "OLED.h"

#include "Serial.h"

#include "Key.h"

#include "LED.h"

#include "PWM.h"

#include "Servo.h"

#include "Timer.h"

#define ADpath 0x04     //定义卡片扇区块

uint8_t KeyNum;                 //定义用于接收按键键码的变量

uint8_t Angle = 1 ;        //定义舵机标志位

static uint8_t see = 0; //定义读卡标志位

uint8_tinitCommand[24]={0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0xFD,0xD4,0x14,0x01,0x17,0x00};

//唤醒指令

uint8_tfindcard[11]={0x00 ,0x00 ,0xFF ,0x04 ,0xFC ,0xD4 ,0x4A ,0x02 ,0x00 ,0xE0 ,0x00};

//寻卡指令

uint8_tverify_star[16]={0x00 ,0x00 ,0xFF ,0x0F ,0xF1 ,0xD4 ,0x40 ,0x01 ,0x60 ,ADpath ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF };//验证头指令

uint8_treadcard[12]={0x00 ,0x00 ,0xFF ,0x05 ,0xFB ,0xD4 ,0x40 ,0x01 ,0x30 ,ADpath ,0xB7 ,0x00};//读卡指令

uint8_t Rdata_1[2] = {0xD5,0x15};//比对数组1

uint8_t Rdata_2[8] = {0xD5 ,0x4B,0x01 ,0x01 ,0x00 ,0x04 ,0x08 ,0x04};

//比对数组二

uint8_t Rdata_3[3] = {0xD5,0x41,0x00};//比对数组三

uint8_t UID[4] ;//卡片UID接受数组

uint8_t password[9] = {0xD5 ,0x41 ,0x00 ,0x20 ,0x22 ,0x05 ,0x02 ,0x02 ,0x26};

//设置的密码

/**

  *     数:数组比对

  *     数:array   要比对数组的首地址

  *     数:array_c 要比对的固定数组的首地址

  *     数:len     数组的长度

  * 值:number  1为比对成功0为比对失败

  */

uint8_t compare(uint8_t *array,uint8_t *array_c ,uint8_t len)

{

     uint8_t i =0 ;

     uint8_t number = 1;

     for(i=0;i<len;i++)

     {

           if(array[i] != array_c[i])

           {    

                 number = 0;

                 break ; 

           }    

     }

     return number;

}

/**

  *     数:DSC求和函数

  *     数:adpath  搜索的块区

  *     数:UID     提取的卡片UID数组的首地址

  * 值:sum     计算过后的校验值

  */

uint8_t DSC(uint8_t adpath,uint8_t *UID)

{

     uint8_t i = 0;//定义sum8位数据,因为DSC只需要求和后的低八位

     uint16_t sum = 0x76f;

     uint8_t all;

     sum +=UID[0];

     sum +=UID[1];

     sum +=UID[2];

     sum +=UID[3];

     all =(0xFF-(sum+adpath)&0xFF)+0x01 ;//

     return all;

}

/**

  *     数:发送验证指令函数

  *     数:*verify_star  验证指令固定片段

  *     数:adpath        搜索的块区

  *     数:UID           提取的卡片UID数组的首地址

  * 值:*verify       和成之后的验证指令首地址

  */

void verify_send(uint8_t *verify_star,uint8_t adpath,uint8_t *UID)

{

     Serial_SendArray(verify_star,16);  //发送固定头

     Serial_SendArray(UID,4);             //发送UID

     Serial_SendByte(DSC(adpath,UID));    //发送校验位

     Serial_SendByte(0x00);                     //发送包尾

}

int main(void)

{

     /*模块初始化*/

     OLED_Init();       //OLED初始化

     Key_Init();                //按键初始化

     Serial_Init();        //串口初始化

     Servo_Init();       //舵机初始化

     Timer_init();       //定时器初始化

     while (1)

     {

           /*初始化模块*/

           KeyNum = Key_GetNum();                                                                      //获取按键键码

           if (KeyNum == 1)                                                                            //按键1按下

           {

                 Serial_SendArray(initCommand,24);  

                 see = 0;

                 OLED_ShowString(1,1,"back");                                                    //测试显示

                 OLED_ShowString(2,1,"DOOR_OFF");                                           //测试显示

           }

           /*串口接收到数据包处理模块*/

           if (Serial_GetRxFlag() == 1)                                                        

     //如果接收到PN532返回的唤醒数据包

           {

                 if(compare(Serial_RxPacket,Rdata_1,length) == 1&& see == 0)           //比对是否正确唤醒

                 {                                        

                       Delay_ms(50);

                       Serial_SendArray(findcard,11);                       //发送寻卡指令

                       see = 1;                                                           //标志位    

                       OLED_ShowString(1,1,"back0");                    //测试显示       

                 }

                 if(compare(Serial_RxPacket,Rdata_2,length-4) == 1 && see == 1)                   //唤醒之后进入寻卡状态

                 {

                       UID[0] = Serial_RxPacket[8];

                       UID[1] = Serial_RxPacket[9];

                       UID[2] = Serial_RxPacket[10];

                       UID[3] = Serial_RxPacket[11];

                       Delay_ms(50);

                       verify_send(verify_star,ADpath,UID);              //发送验证指令

                       see = 2;                                                      //标志位

                       OLED_ShowString(1,1,"back1");                     //测试显示

                 }    

                 if(compare(Serial_RxPacket,Rdata_3,length) && see == 2)                              //进入验证状态

                 {

                       Delay_ms(50);

                       Serial_SendArray(readcard,12);                //发送读卡指令

                       see = 3;                                                //标志位

                       OLED_ShowString(1,1,"back2");               //测试显示

                 }

                 if(compare(Serial_RxPacket,password,9)&& see == 3)

                 {

                       OLED_ShowString(1,1,"back3");         //测试密码匹配成功

                       see = 4;                                          //串口标志位

                 }    

           }    

           /*舵机模块*/

           if(see == 4 && Angle == 1) //保证串口匹配成功并且舵机标志位为1

           {

                 Servo_SetAngle(0);                           //舵机旋转为0

                 TIM_Cmd(TIM3,ENABLE);                  //开启定时器

                 see = 0;                                          //初始化串口标志位

           }

           if(Angle == 0)                           //舵机标志位为0,也就是定时结束

           {

                 Servo_SetAngle(180);                 //舵机旋转为180

                 OLED_ShowString(2,1,"DOOR_ ON");//测试显示

                 Angle = 1;                                 //初始化舵机标志位

           }

     }

}

void TIM3_IRQHandler (void)

{

     if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)

           {

                 Angle = 0;                                                           //舵机标志位置0

                 TIM_Cmd(TIM3,DISABLE);                                //关闭定时器

                 TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除定时器标志位

           }

}

结语

随着本文的发布,我心中充满了对各位读者的感激之情。在这篇文字中,我试图将自己对[STM32与PN532]的理解和思考与您分享。

我诚实地认识到,文中可能存在疏漏和不足之处,这是因为个人的视角和经验总是有限的。我热切期望能够得到您的指正和反馈,因为每一位读者的见解对我来说都是宝贵的财富。正是通过不断的交流和学习,我们才能共同进步。

感谢您抽出宝贵的时间阅读我的文章。如果本文能够引起您的共鸣,或者激发您对[主题]更深层次的思考,那么我将感到无比荣幸。请不要吝啬您的意见和建议,让我们一起在这条求知的路上,相互启发,共同成长。

再次感谢您的关注和支持,它是我不断前行的动力。期待与您的下一次相遇,并希望那时我能为您提供更多有价值的内容。

[@电子鹅鹅鹅]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值