基于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
- 通过串口发送指令:55 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF 03 FD D4 14 01 17 00
- 对PN532进行唤醒操作,如果唤醒成功则PN532返回数据:
00 00 FF | 02 | FE | D5 | 15 | 16 | 00 |
帧头 | 长度 | 长度校验 | DATA1 | DATA2 | 数据校验 | 帧尾 |
表1-3 唤醒返回数据
PN532返回以上数据表明唤醒成功,接下来进行第二步操作寻卡
-
二、PN532寻卡
- 在唤醒PN532的基础上通过串口再次发送寻卡指令:
00 00 FF | 04 | FC | D4 | 4A | 02 | 00 | E0 | 00 |
帧头 | 数据长度 | 长度校验 | DATA1 | DATA2 | DATA3 | DATA4 | 数据校验 | 帧尾 |
表1-4 寻卡指令
DATA1:传输方向为上位机STM32,或者串口助手等发送至PN532模块
DATA2:寻卡标识
DATA3:寻卡数量
- 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验证
- 在进行上面寻卡成功过后需要对卡片中的扇区块进行验证:
发送数据为: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 验证指令
- 验证成功返回数据为: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-4:
图1-4 操作错误演示
在途中接收数据中第三段返回的数据中是41 14 并不是41 00所以在这里出现错误
- 正确演示如图1-5:
图1-5 正确操作返回数据
可以明显看到在最后读卡返回数据中有我们所需返回数据。
以上操作我们成功通过PN532对卡片进行读数据操作,接下来讲述通过官方发的软件对M1卡进行写入数据操作。
二、NFC上位机对M1卡的读写操作
1. 打开上位机的文件夹
2. 注册上位机如图2-1所示
图2-1 上位机注册
- 右键点击NFC上位机管理员模式运行,运行成功如图2-2
图2-2 运行成功
其中运行成功后会发现NFC设备,PN532通过TTL与电脑相连,如果未发现可能出现了串口占用的情况。
- 点击读取卡片内容如图2-3所示
2-3 读取成功
读取过后会出现上面所示数据,其中有64个扇区,每个扇区有4个块。
我写入数据的位置为第一个扇区的第一块,地址也就是0x04,代表的从块号0数第4个块。
- 写卡操作如图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; //选择配置NVIC的USART1线 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教程 - 为敢技术 - 博客园
[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;//定义sum为8位数据,因为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]的理解和思考与您分享。
我诚实地认识到,文中可能存在疏漏和不足之处,这是因为个人的视角和经验总是有限的。我热切期望能够得到您的指正和反馈,因为每一位读者的见解对我来说都是宝贵的财富。正是通过不断的交流和学习,我们才能共同进步。
感谢您抽出宝贵的时间阅读我的文章。如果本文能够引起您的共鸣,或者激发您对[主题]更深层次的思考,那么我将感到无比荣幸。请不要吝啬您的意见和建议,让我们一起在这条求知的路上,相互启发,共同成长。
再次感谢您的关注和支持,它是我不断前行的动力。期待与您的下一次相遇,并希望那时我能为您提供更多有价值的内容。
[@电子鹅鹅鹅]