一个刷卡开门装置的实现
这个计划其实已经酝酿很久了,从我开始接触NFC到现在已经一年了(在Windows下编译支持ACR122U的libnfc、Mifare Classic 1K智能卡介绍及nfc-tools的使用,感谢TUNA),但一直没有时间去搞,而且中间有一些细节一直没想清楚,如果没完全想明白的话,是很难下手去做的。经过详细的思考和与专业人士的讨论(感谢江江的耐心指导),终于在最近把详细的方案设计了出来并把它实现了。(拖了很久才写完)
虽然刷卡开门并没有什么卵用,毕竟研究这玩意的时间已经够我手动开锁开好几年了,但是为了秉承理工科生“善于解决问题,没有问题创造问题也要解决问题”的一贯作风,我还是把它实现了。最关键的是在这个过程中还是学到了不少东西的。
正因为我不是专业做这个的,所以有些设计和实现并不是很优雅。但即使是这样,记录下我的思考过程和实施过程也是很有意义的,下面就来详细介绍一下。
设计方案
我们的设计目标主要有两点,一是用最简单的方法完成任务,二是不改变原有的门锁结构。
经过对门锁的观察,我发现一个最容易开锁的方法就是在旋钮的上侧用绳子向左拉。
用什么把绳子固定在旋钮上呢?以我多年的经验,热熔胶是最好的选择,它效果好、使用简单,想去掉的话也很容易。
那怎么牵引这根绳子呢?自然而然我们可以想到使用电动机,例如电动小马达。我们只要给电机供电,让它转,就可以拽绳子了。但我就是在这里卡了很久,因为有几个问题没有解决:
- 怎么控制电动机通断电?因为我们不可能让它一直转,难道我们要再买个电磁继电器吗?
- 怎么控制电动机反向转?因为拉开锁之后我们必须让它转回去,否则在门内无法手动关锁。简单的做法是交换正负极,但这个要求我们设计电路。
- 怎么控制电动机转的时间?当然我们可以固定一个时间,但是我们必须要考虑一个问题,一旦电动机拉到底,转不动了,整个电路就相当于短路了,这很容易把设备烧掉。
- 电动机需要施加多大的力?
所以说,一个简单的问题里需要考虑的事情还是非常多的,我也正是因为没有完全解决这些问题才没开始做。后来经过跟江江的讨论后发现,我实在是太孤陋寡闻了,原来这世界上还有个叫舵机的东西,完美解决了上述问题。
现在通常使用数字舵机,它一般有三根线:正极、负极和控制。它通常工作在5V,通过在控制线上给一个矩形波,控制旋转的角度。买舵机的时候,主要有最大旋转角度和扭矩大小的区别。这个够用就行了,不然太贵不划算。
动力问题解决之后,我们要思考控制问题。综合考虑便捷程度和价格,我决定选用Raspberry Pi Zero W开发板。为什么选这个呢?因为它有无线网卡可用于联网;有Mini HDMI接口用于视频输出;有Micro USB可以接外设;GPIO齐全;可参考的资料多;Linux系统实现起来简单;而且——价格也比较便宜,只要100多块(不含存储卡)。具体实施的时候,我们只要用GPIO输出控制信号给舵机就可以了。
刷卡问题怎么解决呢?首先观察门的构造,发现中间有块玻璃使得内外距离变得很小,从而实现读卡器放在里面但在外面刷卡。然后我们要对NFC读卡器进行选择。我之前研究NFC的时候就买了个ACR122U,但是那个要100多块,太贵了,平常可能还会用到,粘门上不方便,于是准备买个NFC模块,实在不行的话就用ACR122U也行。结果还真的不太行,但不管怎样,有了ACR122U,刷卡的问题一定可以解决。
现在,我们还差最后一步:供电问题。由于开发板和舵机都是5V电压,所以我们用普通的手机电源适配器,拉导线过去供电就行了。但由于树莓派要求1.5A电流,又考虑到电阻损耗问题,所以决定使用5V 2A的电源适配器。
至此,所有问题都已经解决,只是在具体的材料选择和实施方案上会有差别,但这也只会影响到要花多少钱和多少时间。
总结一下:
- 用粘在旋钮上的绳子开锁
- 用舵机牵引绳子
- 用树莓派控制舵机
- 树莓派连接NFC读卡器获取卡片信息并认证
- 使用5V 2A电源适配器给整个系统供电
虽然说起来简单,但还是有不少细节需要处理。
工具和材料准备
工欲善其事,必先利其器。工具不齐全还是不行。准备好以下工具是很必要的。
如果有下面这些更好。
巧妇难为无米之炊。我们还需要准备以下材料。
简单解释一下这些工具和材料都是干嘛的。
首先,为什么要网线呢?因为——我没有导线啊!导线不够,网线来凑!由于距离实在是太远,考虑到电阻可能比较大,于是考虑两根线并联在一起。这样双绞线的8根线变成了正负正负4根线,正好可以为树莓派和舵机供电。关键是这网线质量非常好,后来才发现好几米的网线比我10cm的排线电阻还小,所以并联是多余的。
电烙铁用来焊导线和树莓派上的排针;网线不太吸焊,要用点松香助焊;热熔胶用来固定网线以及门上的树莓派、舵机等;万用表测一测有没有焊好;压线钳剥网线很有用;排针焊坏了可以用吸焊器;酒精可以用来去热熔胶,稍微喷一点等个几秒就可以一整块地拿下来了;读卡器用来装系统;Raspberry Pi Zero W的视频输出需要用Mini HDMI转接头。
关于材料基本上在前面都解释过了。电源适配器需要5V 2A的,最好是多口的,省心;读卡器要质量好一些的,不然半天读不出来卡还不如拿钥匙;开发板可以在够用的前提下买便宜的;舵机一样;家中常备网线很关键,质量稍好一些的超五类线即可;排线还是齐全一点比较好,我们把排线焊到导线上,这样连接设备会很方便;Raspberry Pi Zero W上的USB是Micro USB,因此需要OTG线,如果要同时接Mini HDMI的话还不能是那种大头的,不然会挡到Mini HDMI的接口;USB线是很重要的,至少需要两根,考虑到可能还有其他用途,就买了Micro USB线,质量也是比较好的那种快充线,可以跑2A电流,我是在1688上批了一盒,一块钱一根,运费8块,我们把USB线接到电源适配器上,另外一边剪断,焊上排线,给设备供电;黑胶带用来裹住导线焊点,防止触碰短路。
实施过程
开工!
准备线材
首先把排线焊到USB线上,由于舵机和树莓派是分开供电的,所以需要两根。
普通USB有4根线,两根电源线和两根数据线,一般是红黑绿白,分别是正极、负极、D+、D-,不过我这里是红黄绿白。由于我们只需要供电,因此只需要把焊红黄两根线。焊完之后用黑胶带裹一下,防止碰到短路。
同理,我们给网线也焊上排线,但要注意公母选择问题,并且记住哪个是正极哪个是负极。我这里选择橙绿为一对正负极,蓝棕为一对正负极。
准备树莓派
先装个系统。为了方便,我直接选Raspbian了,使用的是官方的NOOBS安装,装起来十分方便,只需要把SD卡格式化成FAT32然后把下载的zip解压到卡里,插上电源就可以进入系统安装界面了。
为了使用GPIO,还需要焊上排针。
编译安装ACR122U的驱动:
tar -jxvf acsccid-x.y.z.tar.bz2
cd acsccid-x.y.z
./configure
make
make install
不过貌似树莓派自带这个驱动?但这一点我没有确定,因为我是先装的驱动才用的。
安装libnfc:sudo apt-get install libnfc-dev libnfc-bin libnfc-examples libusb-1.0-0-dev
,顺便把一些其他的库也装了。
代码编写
很显然,python和bash是最适合做这项工作的。
(图片来自https://pinout.xyz/)
确定一个控制引脚后,我们需要树莓派GPIO定义确定它的编号。这里我使用的是GPIO 40,但在程序里要用BCM序号进行控制,也就是21。代码很简单,核心的就是通过控制占空比来控制转动的角度。
# -*- coding:utf-8 -*-
import RPi.GPIO as GPIO
import time
import signal
import atexit
import sys
atexit.register(GPIO.cleanup)
servopin = 21
f = 50
per = 2.5
ang = float(sys.argv[1]) # 设置舵机角度
GPIO.setmode(GPIO.BCM)
GPIO.setup(servopin, GPIO.OUT, initial=False)
p = GPIO.PWM(servopin,f)
p.start(0)
p.ChangeDutyCycle(per + per * 4 * ang / 180)
time.sleep(1.0/f)
假设舵机最大角度为180度,则执行ctrl.py 70
就可以让舵机转动70%*180度。
接着,我编写了一个bash脚本,来做整体的控制。读取uidlist.txt
里的信息,里面存放了所有放行卡片的UID,一行一个,每行是4个字节,用小写十六进制表示,字节之间用一个空格隔开。刷卡通过后会记录日志到log.txt
。
代码也很简单,通过不断读取nfc-list
的输出,查看其中有没有我们的UID。里面有个细节要注意一下,就是echo $nfcinfo
之后UID的字节之间的两个空格会被吃掉一个变成一个,因此uidlist.txt
里面字节之间只要一个空格即可。
#!/bin/sh
cd /home/pi/main
while true
do
nfcinfo=`nfc-list`
echo $nfcinfo
cat uidlist.txt | while read line
do
res=`echo $nfcinfo | grep "$line"`
if [ ! -n "$res" ]; then
echo "No such card"
else
log=`date "+%Y-%m-%d %H:%M:%S"`
log="$log, \"$line\""
echo $log >> log.txt
python ctrl.py 100 && sleep 2s && python ctrl.py 0
sleep 5s
fi
done
sleep 1s
done
修改启动项,sudo vi /etc/rc.local
开机启动/home/pi/main/main.sh >/dev/null
。
测试
把材料准备好,插入读卡器,测试一下能不能正常刷卡控制舵机,测试完毕后再进行后面的操作,不然都粘门上之后不好调试。
布线与安装
上热熔胶&网线!
细节与插曲
- USB和GPIO都可以给树莓派供电,用GPIO可以少焊个USB线。
- 舵机与树莓派共地。尤其是如果不是像我这样一个适配器多口的。
- OTG线不能是那种没有线只有头的(很胖的那种),不然会挡住旁边的Mini HDMI接口。当然,不用它的话自然是没什么问题了。
- 记得关万用表电源!电池倒是很便宜,但是浪费啊!而且得买新的啊!幸亏有一个备用的,不然淘宝买电池还得等两天。或者买带自动关机的万用表……
- 我买的这个USB线,红和黄的铜丝多一些,至于原因嘛,那是因为它俩是供电线,当然电阻要小一些更好。
- 排线的颜色要选择好,不然自己都分不清正负极。我一般都是红色或其他比较容易分辨的暖色调为正极,白色、黑色或灰色为负极。
- 网线质量太好,导热性太好,焊的时候把我手烫死了。
- 松香助焊剂是必不可少的,用电烙铁在松香盒里烫几下,拿导线沾一点松香就很容易粘住焊锡了。
- 搞的时候还是要小心一点,树莓派太脆弱了。我在测试的时候不小心拿万用表的一根表笔碰了一下,导通了3.3V和5V的Pin,树莓派就直接重启了……
- 在热熔胶侧面喷一点酒精,等5秒之后热熔胶与接触面就不怎么粘了,就可以比较轻松地一下把一整块都揭下了。
成果
附录
读卡器PN532的问题
我一开始是想用PN532的,毕竟便宜。但用了之后发现实在是太慢,对距离要求也太高了,于是放弃。不过还是把PN532相关的内容作为附录放在下面吧。
首先焊上排针。
我们以使用I2C接口为例,将PN532开关调至I2C模块,首先sudo raspi-config
开启树莓派的I2C接口。
接着sudo vi /etc/nfc/libnfc.conf
修改内容:
allow_autoscan = true
allow_intrusive_scan = false
log_level = 1
device.name = "fz"
###device.connstring = "pn532_uart:/dev/ttyAMA0" # for UART
device.connstring = "pn532_i2c:/dev/i2c-1"
安装一些库:sudo apt-get install libnfc-pn53x-examples libpcsclite-dev
使用lsmod | grep i2c
可查看系统I2C接口是否开启,根据GPIO定义图连接好I2C的线,使用sudo i2cdetect -y 1
查看是否检测到PN532。
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- 24 -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
显示出一个字节的信息即成功。
注意:如果更改PN532的模式后记得要给PN532断下电才能生效。如果是通过USB TO TTL连接到电脑,则PN532必须是HSU(UART)模式。
PN532对卡片质量要求也高,Type A的北京市公交卡读起来没问题,同为Type A的银联卡(用于闪付的NFC芯片),在尝试各种姿势之后,三张中有两张勉强可读,一张不可读。Type B的校园卡和身份证怎么变换姿势都不可读。
感觉自己买到了个假模块……
控制代码
这样的代码也可以:
# -*- coding: utf-8 -*-
import sys
import RPi.GPIO as GPIO
import time
pin=21
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.HIGH)
time.sleep(0.000001*float(sys.argv[1]))
GPIO.output(pin, GPIO.LOW)
GPIO.cleanup()
使用python ctrl.py 1300 && sleep 1s && python ctrl.py 150
控制,但是精确度不如上面那个高。
后续
2018-02-26 UPDATE: 在HHJ的建议下,我采取了并联电容的方法,但仍然没有解决。而今天我又再次尝试了一下,并联了5个电容上去,居然就真的好了!这说明我买的电容还是不够大啊,都1000uF了居然还不够!