
【实验番外篇】任务四:基于树莓派的网络考勤系统及刷卡门禁 - 第一阶段
背景:公司的打卡机目前还是比较低端的,虽然是指纹识别的,但是每月统计打卡或临时查看记录的时候都要摘下来连接到电脑上,非常的不方便,而且刷卡门禁与考勤系统是分开的两套系统,又不方便统一管理。
虽然手中没有指纹识别模组,但是我们有物美价廉的RFID模块啊~~抱着把手中的RFID模块和树莓派充分利用起来以及Impossible is nothing的信念,决定自己做一套基于RFID模块和树莓派的网络版考勤系统,同时将刷卡门禁功能集成进来,方便统一管理。
任务阶段划分:
第一阶段:驱动RFID模块(RC522)
第二阶段:搭建RPi网络服务器
第三阶段:完善刷卡门禁功能及终端显示功能
第四阶段:搭建后台管理系统
第五阶段:完善后台管理系统与硬件接口的衔接
第六阶段:软、硬件功能系统测试
~~本次任务不同于之前几次(任务做完,过来发帖),由于内容比较多,所以需要分阶段完成~~
第一阶段:驱动RFID模块(RC522)
工具:RFID模块 -- RC522(通讯接口:SPI)
非接触式IC卡 -- Mifare One卡
1、启用SPI接口
从黑名单中将SPI接口释放出来,之前在I2C实验中做过类似的操作
sudo nano /etc/modprobe.d/raspi-blacklist.conf # blacklist spi and i2c by default (many users don't need them) #blacklist spi-bcm2708 #blacklist i2c-bcm2708
确认内核支持
pi@raspberrypi ~ $ ls -la /dev/spi* crw-rw---T 1 root spi 153, 0 Jan 1 1970 /dev/spidev0.0 crw-rw---T 1 root spi 153, 1 Jan 1 1970 /dev/spidev0.1
2、这次使用的编程语言是比较熟悉的C,安装wiringPi,方便调用SPI接口
使用GIT工具下载wiringPi,如果你的RPi还没有GIT,可以使用下面的指令获取
sudo apt-get install git-core通过GIT获得wiringPi的源代码
git clone git://git.drogon.net/wiringPi
进入wiringPi目录并安装wiringPi
cd wiringPi ./build测试wiringPi是否安装成功
gpio -v gpio readall
注意:需要用下面的指令将SPI驱动程序加载到内核
gpio load spi3、接线方式
4、驱动编写
RC522 Pin Name
RPi Header Pin Num
wiringPi Pin Num
VCC
#17(3.3v)
RST
#12(GPIO.1)
1
GND
#25(0v)
MISO
#21(MISO)
13
MOSI
#19(MOSI)
12
SCK(CLK)
#23(SCLK)
14
SDA(NSS)
#24(CE0)
10
(1)首先,需要初始化SPI接口,并了解其函数的使用方法,下面是一些比较重要的设置与代码举例:
#include <wiringPi.h>
#include <wiringPiSPI.h>
#define RST 1 #define channel 0 #define speed 1000000 unsigned char ucAddr; unsigned char data[2] = {0, 0}; wiringPiSetup(); //wiringPi初始化 wiringPiSPISetup(channel, speed); //设置SPI通道 0,速率 1000000 - 1Mbit/s wiringPiSPIDataRW(channel, &ucAddr, 1); wiringPiSPIDataRW(channel, data, 2);
我将RC522的片选(NSS)接到了CE0上,所以这里要设置为通道0,从RC522的DATASHEET中我们可以了解到,模块的SPI 接口可处理高达10Mbit/s的数据速率,这里我仅使用了1Mbit/s的数据速率,已经足够我的需求了。
根据官网的介绍RPi的SPI接口是全双工的工作方式,通过wiringPiSPI接口进行数据读写的时候,SPI总线会自动将返回的数据覆盖到之前发送的变量上。
"This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus."
(2)解决通讯基础函数
这里其实遇到了一些问题,导致很多次实验没有成功。首先,我们来看一下DATASHEET中的介绍。
地址字节格式:
读数据字节顺序:
写数据字节顺序:
从中我们可以发现读写的命令是包含在地址字节格式中的,而地址是通过主机发送出去的字节0。
读数据过程中字节0是没有真正返回寄存器的实际值的,而真正返回是从下一个字节开始的,这里我们直接发送0x00地址,获取真实数据。
另外,我在调试的过程中,在运行到if(!(ReadRawRC(ErrorReg)&0x1B))这句时,读ErrorReg寄存器返回值是0x45,然后直接跳转到MI_ERR,正是由于读取寄存器方式错误引起的。
///////////////////////////////////////////////////////////////////// //功 能:读RC522寄存器 //参数说明:Address[IN]:寄存器地址 //返 回:读出的值 ///////////////////////////////////////////////////////////////////// unsigned char ReadRawRC(unsigned char Address) { unsigned char ucAddr; unsigned char data = 0x00; ucAddr = ((Address<<1)&0x7E)|0x80; //address format:1XXXXXX0 wiringPiSPIDataRW(channel, &ucAddr, 1); wiringPiSPIDataRW(channel, &data, 1); //读两次才能拿到真正的RC522的返回值,非常重要 return data; } ///////////////////////////////////////////////////////////////////// //功 能:写RC522寄存器 //参数说明:Address[IN]:寄存器地址 // value[IN]:写入的值 ///////////////////////////////////////////////////////////////////// void WriteRawRC(unsigned char Address, unsigned char value) { unsigned char ucAddr; unsigned char data[2] = {0, 0}; ucAddr = ((Address<<1)&0x7E); //address format:0XXXXXX0 data[0] = ucAddr; data[1] = value; wiringPiSPIDataRW(channel, data, 2); }
当我们建立起通讯基础函数后,就可以放心的根据程序流程开始编写功能函数了。
上传完整代码:
https://github.com/chronosMaker/RPiRFID.git
看一下实际运行效果,试了一张MF1 S50的空白卡,读写都正常
下面是刚才读的扇区0的具体信息

Mifare 1 卡内部有 1 个 EEPROM,分成 0~15 共 16 个扇区,每个扇区分成 0~3 共 4 块,每块 16 字节。
1、扇区 0 的块 0 是厂商标志字节,保存着只读的卡信息及厂商信息。
22 8B BE B5 A2 08 04 00 69 73 73 69 35 36 34 30
前面四个字节 22 8B BE B5 是卡序列号,08 是卡容量,04 00 是卡类型,后面是厂商自定义的一些信息。
2、每个扇区的块 0 保存着该块的密钥 A 与密钥 B 及该块的访问条件,每个扇区都有自己的一套密钥及访问条件,其中,4 个字节的访问条件是对每个扇区 4 个块的读写定义。比如块 3 的 16 字节如下:
00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF
前面 6 个字节是密钥 A,因为 Read 永远为 Never,所以读到的都是 0x00,最后的 6 字节是密钥 B,其值为 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF,中间的 4 个字节是访问条件。对应DATASHEET相关表格,可得出对该扇区块的存取控制条件。
试了一张北京地铁车票
又试了一下公司的门禁卡
以上两张卡都由于密钥错误导致无法读取内部数据,如果能够获取对应密钥,那么。。。。。。^_^
tips:原装的 Philps S50 芯片在出厂时设置每个分区的的第四块 A 密钥是“FFFFFFFFFFFF”,控制字是:“FF078069”,B 密钥是:“FFFFFFFFFFFF”,A 密钥是供用户读写操作的,利用 A 密钥可对对除 0 区外其它所有扇区块进行读写操作。 B 密钥不可操作,这些用的都是逻辑加密算法加密,而且密钥都是不可见的,我们在读的时候能看到的 A 密钥都是显示为“000000000000”,B 密钥显示:“FFFFFFFFFFFF”,这些都是出厂时厂家设定的默认值。

【实验番外篇】任务四:基于树莓派的网络考勤系统及刷卡门禁 - 第二阶段:搭建RPi网络服务器
这部分的搭建我用的还是相对比较热门的LAMP(Linux Apache Mysql PHP)
搭建过程非常简单,网上也有好多教程,我这里就不重复啦~~分享一个教程链接吧!
简单介绍一下流程吧~~
安装Apache --> 安装mysql --> 安装PHP
安装完成后,在浏览器中输入RPi的IP,就可以访问部署在RPi上的网站了~~
当前这个页面只显示了一行“It works”,没有其它内容。。
当然我们也可以注册一个域名来实现通过域名访问。
我在花生壳上注册了一个免费的域名,通过设置路由器的动态DNS及虚拟服务器实现了局域网内通过域名访问,无奈免费的域名目前还无法实现外网访问,域名指向总是不对。。。。如果哪位小伙伴试验成功了,还望不吝赐教 :)
所以,目前先在局域网内实现我的任务吧,对于实用性来说还是没有什么影响的!
这里我简单的移植了一个留言板用来测试PHP,首先要感谢一下“夏日”做出来这么好的留言板~~
这部分内容将来可以作为“未打卡说明”结合审批过程添加到考勤系统中去!
在RPi中网站的目录在: /var/www
来看一下页面效果:

【实验番外篇】任务四:基于树莓派的网络考勤系统及刷卡门禁 - 第三阶段:完善刷卡门禁功能及终端显示功能
这部分内容主要是这次任务的硬件完善及其驱动,是第一阶段的延续,之所以安排到了第三阶段是由于某些元件需要购买,在等待到货的时间里完成了目前的第二阶段 :)
硬件部分除了第一阶段中的RFID模块之外,目前还需要添加驱动门禁电磁锁的继电器模块以及用于显示信息的显示屏。
继电器模块在【基础实验】五中已经用到过了,操作非常简单,单引脚控制,低电平触发
RELAY Pin Name
RPi Header Pin Num
wiringPi Pin Num
VCC
#1(3.3v)
IN
#7(GPIO.7)
7
GND
#9(0v)
显示屏部分这次不打算用之前的那块OLED屏幕了,看上去始终都显得有点儿小气,这次改用常规尺寸的LCD12864
这块屏幕是总工给我的,已经有段时间了,一直没用,说是军工企业生产的,看上去用料和做工真的很不错!!布局很紧凑的说~~
背面丝印显示是2005年生产的,有年头了啊,呵呵~~到厂家的网站上找找看,没想到还能找到配套的资料,不容易啊!!
有了引脚定义和时序图,驱动起来应该就不成问题了~~
为了节省RPi本来就不富裕的IO口,还是使用串行方式驱动吧
量了一下屏幕的FFC接口尺寸,应该是标准的1.0mm 20P的,马上到某宝去拍一些元器件回来,搞定接口问题~~
元器件名称
数量
金额
FC压线头2.54mm DC-20P
2个
¥0.48
牛角座2.54mm DC-20P
1个
¥0.20
FFC扁平电缆连接器下接1.0mm 20P
1个
¥0.48
贴片3*3单圈电位器10K
1个
¥0.25
FFC转接板1.0mm 20P to 2.54mm 20P
1个
¥1.20
2651灰排线1.27mm 20P
1米
¥1.50
共计:¥4.11
三天漫长的等待啊~~~~期间完成了第二阶段的内容 :)
到货后马上装起来试试!感觉还不错,挺合适的~~吐槽一下MX2近距离拍照畸变效果让我的梦想碎了一地啊~~
接口问题解决了,接下来就是如何让LCD显示出文字啦!
按照DATASHEET的范例接了一下电路,调整了S/P电阻到串行端,满心欢喜的按照DATASHEET码了一遍时序,结果没有显示。。。。
DATASHEET上说模块有两种电压规格,5V和3.3V的,并且没有说明是否通用,模块上也没有明显标识,所以怀疑是RPi的3.3V与模块不匹配!遂致电厂家客服咨询,一番描述后,确定手上拿的是5V屏,根据对方攻城狮的介绍,可以将模块背面的R9 3.3K分压电阻换成0Ω电阻,从而解决电平问题。不过,背光由于设计原因仍需使用5V驱动,这点问题不大~~
苦于手中没有0Ω电阻,又不想直接用锡跨接,只好借用一下S/P电阻(0Ω)啦~~然后将PSB引脚接地(S/P电阻拿掉后,由PSB引脚的高低状态确定模块的串行/并行模式)
再次运行程序,总算能看到文字啦!
可是新的问题又来了。。。显示对比度非常低,文字颜色很浅,即使将可变电阻调成0Ω,效果依然很浅。。。(正常情况下VO与VR端电阻如果调到0,基本上就是黑屏了)
量了一下VO和VR对地的电压,只有4.2V。。。非常低!!这两个引脚是内部升压电路的输出端,通常倍压输出,利用可调电阻控制LCD驱动电压、调节对比度,一般高于5V后正常显示,当前情况明显是电压不够!!由于这块LCD是由5V屏转过来的,虽然兼容了数据电平,其他电路依然还是按照5V设计的,所以导致驱动电压不足!!
为了提高电压,只好临时调整一下电路了,将负压输出VR悬空,VO直接接到5V上,可调电阻就下岗啦~~显示效果终于正常啦~~
清晰度与可视角度都非常不错~~
列一下接线方式:
上传一下LCD驱动部分的代码:
LCD Pin Num
LCD Pin Name
RPi Header Pin Num
wiringPi Pin Num
说明
1
GND
#34(0v)
地
2
VCC
#17(3.3v)
逻辑电压
3
VO
#4(5V)
LCD亮度调整,外接电阻端
4
RS(CS)
#38(GPIO.28)
28
片选(串行) 0:禁止 1:允许
5
R/W(SID)
#40(GPIO.29)
29
输入串行数据(串行)
6
E(SCLK)
#36(GPIO.27)
27
输入串行脉冲(串行)
15
PSB
#34(0v)
控制界面 0:串行,1:并行8/4位
19
LK
#34(0v)
背光负极
20
LA
#4(5V)
背光正极
///////////////////////////////////////////////////////////////////// //LCD SPI WIRE LCD CMD ///////////////////////////////////////////////////////////////////// #define LCD_CLK 27 #define LCD_RS 28 #define LCD_SID 29 #define PAGE0 0x80 #define PAGE1 0x90 #define PAGE2 0x88 #define PAGE3 0x98 #define DispClr 0x01 #define DispON 0x0C #define FuncSet 0x30 #define ModeSet 0x06 ///////////////////////////////////////////////////////////////////// //功 能:发送数据到LCD //参数说明:zdata[IN]:数据 ///////////////////////////////////////////////////////////////////// void sendbyte(unsigned char zdata) { unsigned int i; for(i=0; i<8; i++) { if((zdata << i) & 0x80) { digitalWrite(LCD_SID, HIGH); } else { digitalWrite(LCD_SID, LOW ); } digitalWrite(LCD_CLK, LOW ); digitalWrite(LCD_CLK, HIGH); } delay(0.01); //延时,实测不加延时容易丢帧 } ///////////////////////////////////////////////////////////////////// //功 能:写LCD指令 //参数说明:cmd_dat[IN]:指令 ///////////////////////////////////////////////////////////////////// void writeCmd(unsigned char cmd_dat) { sendbyte(0xF8); sendbyte(cmd_dat & 0xF0); sendbyte((cmd_dat << 4) & 0xF0); } ///////////////////////////////////////////////////////////////////// //功 能:写LCD数据 //参数说明:dat_dat[IN]:显示数据 ///////////////////////////////////////////////////////////////////// void writeData(unsigned char dat_dat) { sendbyte(0xFA); sendbyte(dat_dat & 0xF0); sendbyte((dat_dat << 4) & 0xF0); } ///////////////////////////////////////////////////////////////////// //功 能:初始化LCD //参数说明:无 ///////////////////////////////////////////////////////////////////// void lcdInit() { pinMode (LCD_RS , OUTPUT); pinMode (LCD_SID, OUTPUT); pinMode (LCD_CLK, OUTPUT); digitalWrite(LCD_RS, HIGH); writeCmd(FuncSet); //Function Set delay(1); writeCmd(FuncSet); //Function Set delay(1); writeCmd(DispON); //Display ON delay(1); writeCmd(DispClr); //Display Clear delay(50); writeCmd(ModeSet); //Enter mode set delay(50); } ///////////////////////////////////////////////////////////////////// //功 能:显示字符串 //参数说明:page[IN]:起始页 //参数说明:column[IN]:起始列 - 0,1,2,3,4,5,6,7 //参数说明:*s[IN]:字符串 ///////////////////////////////////////////////////////////////////// void strDisp(unsigned char page, unsigned char column, unsigned char *s) { writeCmd(page + column); while(*s > 0) { writeData(*s); s++; } }
按照DATASHEET中的时序图观察,模块应该也支持RPi的SPI接口,用SPI实际驱动了一下果然也没问题,显示什么的都正常。但是!!接上RC522后,LCD就开始显示乱码,CE0与CE1对调也是同样的效果,貌似片选引脚对LCD已经不起作用了,一屏一屏的乱码显示地非常快,然而RC522却能正常工作。。。无奈下将引脚接回了原来的地方,世界终于安静了~~
不知道大家有没有过类似的遭遇呢。。。
初步怀疑SPI双通道应用中片选(或时序)错误导致的数据传送问题(单独驱动时每个通道都能正常使用),目前不排除LCD使用的3线SPI与4线SPI(全双工)混用时存在的兼容性问题。
上一张完整的照片!!

不好意思啊,最近老婆生孩子,又加上创业初期,这个帖子基本上搁置起来了。。。希望上面的内容能够帮到你~~
香蕉派+罗技C270的配置确实要比树莓派的高,流畅度一定不错嘛,人脸识别这块儿我只接触过opencv,这里提供了很好的接口和实例,以及特征库,用起来很方便。目前我做的这部分引伸出来就像是相机的人脸追焦,或是社区网站的照片自动人脸框图,如果做类似于考勤或门禁方向的特征识别,我的思路是可以利用opencv提供的特征库将目标人脸的五官等特征部位截取下来作为备份,用于重新识别过程中的分步细节比较,这样做起来难度应该可以降低~~
以上内容只是头脑中的临时想法,没有实验验证的结论作为支持,不过希望可以帮你拓展一下思路。
回复
有奖活动 | |
---|---|
发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
【EEPW在线】E起听工程师的声音! | |
“我踩过的那些坑”主题活动——第001期 | |
高校联络员开始招募啦!有惊喜!! | |
【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
送您一块开发板,2025年“我要开发板活动”又开始了! | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
【我踩过的那些坑】工作那些年踩过的记忆深刻的坑被打赏10分 | |
【我踩过的那些坑】DRC使用位置错误导致的问题被打赏100分 | |
我踩过的那些坑之混合OTL功放与落地音箱被打赏50分 | |
汽车电子中巡航控制系统的使用被打赏10分 | |
【我踩过的那些坑】工作那些年踩过的记忆深刻的坑被打赏100分 | |
分享汽车电子中巡航控制系统知识被打赏10分 | |
分享安全气囊系统的检修注意事项被打赏10分 | |
分享电子控制安全气囊计算机知识点被打赏10分 | |
【分享开发笔记,赚取电动螺丝刀】【OZONE】使用方法总结被打赏20分 | |
【分享开发笔记,赚取电动螺丝刀】【S32K314】芯片启动流程分析被打赏40分 |