Raspberry Pi 实现刷卡就亮灯
先说一下本文用到的硬件:Raspberry Pi(Model A或Model B都可以),来自SeeedStudio的NFC开发板(PN532)和彩色LED灯(P9813),可选配件为:单色LED光源,蜂鸣器。
软件的话就是wiringPi的库(便于控制针脚),还有spi-dev, spi-bcm的内核模块需要被加载(否则设备无法被发现),gcc什么的肯定是必备啦
硬件连接
为了描述方便,这里使用wiringPi的针脚来进行说明,而图片当中则使用原始针脚来进行连接。
PN532:将Raspberry Pi的SPI的MOSI和MISO,CLK三个链接到PN532的开发板对应针脚上面,5V和GND可以使用外接电源或者使用Pi上面的针脚。ChipSelect则使用了wiringPi的11针(具体针脚是要和你代码当中的针脚一致的)这里不得不说SeeedStudio网页的例子了,感觉完全是照搬的,没有按照自己的板子更改。它的2*3是SPI端口,但是只有MOSI,MISO,CLK是需要连接树莓派对应SPI端口的;而5V,GND则是由下部的6Pin针脚来获取的。ChipSelect则是通过上面标号为10的针脚来进行的。
P9813:虽然它使用的是SPI通信协议,但它仅有4根线,除去5V和GND之外,只有CLK和MOSI了。由于它没有ChipSelect针脚,它一直接受总线信号。当它也接入SPI总线的时候会导致随机闪烁,因此我们需要解决方案:1.想办法让它只能“听到”针对自己的信号,别人的信号听不到;2.用GPIO模拟SPI的MOSI和CLK,那么就分离了它。本文使用方案2来进行。同时由于购买的板子不是2.54mm的杜邦线插座,但是幸好NFC的板子上面有对应接口,它们可以通过下面6Pin的4,5号针脚从外部引入信号。
上图的地线(黑色的那个)在20140308的重新连接电路并测试当中,发现该位置安装后不能正常SPI通讯,而切换到旁边的(上图黑色线与方块之间,也就是黑线右邻)之后则可以正常使用了。也许当初我没焊接好?
图错了啊!!!!!!上图的MISO(粉色)应该连接SCK上方的那个针,MOSI(紫色)应该连接SCK右侧的针。20140308重新搭建的时候发现该错误……
简单LED和蜂鸣器:GND连接通用的就可以,然后每个LED占用一个GPIO针脚,Beeper也是一样。因为它们比较简单,GPIO口处于高电压时就亮/发声;反之不亮/没有声音。操作起来只需设置他们的电位即可。
连接好之后,你可以通过wiringPi当中的gpio命令来测试LED和蜂鸣器是否可用:
gpio write {针脚编号,比如0,2,3,6} {1代表开,0代表关}
软件部分
(这个草稿早就开始写了,前一部分还好,这一部分写的时候就不知道当时想写哪些了…………那就把github上面的说明一下好了……)
思路:
软件读取NFC的数据(目前只会读取ID,而且是4ByteID,7Byte的应该没法读取),根据数据决定是否亮灯/提示音。如果遇到特殊的卡,则退出程序。
由于我希望的是在临睡前自动关灯,所以设定了个延迟来让灯逐渐变暗然后灭掉。
伪代码:
初始化硬件 获取卡ID IF 卡ID符合规则1 THEN 设置灯泡亮,蜂鸣器发音 ENDIF IF 卡ID符合规则2 THEN 关灯,提示音 退出程序 ENDIF ---------------------------- // 控制灯亮的线程(在main文件当中) 一直循环{ 获取当前颜色 与目标颜色比较得到增量(如果为0则此次循环等待过后跳过) 设置彩灯为增量过的颜色 更新当前颜色 } // 但是我发现这个线程并不是真正的并行运行了。比如在main当中有sleep的时候它也不执行了(具体表现为,如果颜色是一直递减变为0的情况下,会出现1s保持恒定,1s变暗的状态) -----------------------------
这里存在两个问题:1. 如何获取卡ID;2. 如何设置灯
获取卡ID(PN532.c)
这个直接使用http://blog.iteadstudio.com/to-drive-itead-pn532-nfc-module-with-raspberry-pi/的源代码作为参考。因为实际使用过程当中不需要NFC直接进行P2P传输消息,所以我把P2P的部分给忽略掉了。
原始文件当中使用了部分wiringPi的库,但是自己重写了不少wiringPi本来的东西,经过我的对比,发现在wiringPiSPI当中的wiringPiSPISetup函数有不同。我对仅使用wiringPi的和使用它给出的库运行了一下,对比发现wiringPi不能写入东西,而它给出的库可以读出来数据。这是因为wiringPiSPISetup当中他加入了以下函数来达到增加读取的功能:
if (ioctl (fd, SPI_IOC_RD_MODE, &spiMode) < 0) return -1 ; if (ioctl (fd, SPI_IOC_RD_BITS_PER_WORD, &spiBPW) < 0) return -1 ; if (ioctl (fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) return -1 ; i = ioctl(fd, SPI_IOC_RD_LSB_FIRST,&spiLSB);
(绿色字体的内容可以跳过)
难道原来的wiringPi不能这么做么?
究其原因,我又去projects.drogon.net的网站看了看,如果你是从git.drogon.net下载的话,这个站点当中的SPI源文件是没有RD相关的函数的,也就是没有给SPI读取的权限,而另外一个站点 github.com/WiringPi/ 下载的话则是有相关函数的。所以如果你遇到SPI相关问题,从github直接拖源码或许是个不错的选择。
因为我们要抛弃掉附加的SPI库,转向使用wiringPi里边带的库,因此需要把上面链接当中的nfc代码略作修改。主要集中在wiringPiSPIDataRW函数上面。原来的代码当中感觉很莫名其妙,设置了多个变量但是用到的只有一个……查看github上面的wiringPi源代码:
int wiringPiSPIDataRW (int channel, unsigned char *data, int len) { struct spi_ioc_transfer spi ; channel &= 1 ; spi.tx_buf = (unsigned long)data ; spi.rx_buf = (unsigned long)data ; spi.len = len ; spi.delay_usecs = spiDelay ; spi.speed_hz = spiSpeeds [channel] ; spi.bits_per_word = spiBPW ; return ioctl (spiFds [channel], SPI_IOC_MESSAGE(1), &spi) ; }
与原来的对比,就可以知道在low level SPI当中,传入的字符指针只需要一个即可,它既充当tx的buf又充当rx的buf,运行完之后data当中的数据就是读取到的数据。
小小疑问:难道我们不能直接传输整个字符串么?非得一个字符一个字符的传递?
其他的话都是小修小改,比如:
将delay改为usleep,因为我发现delay运行的情况下CPU负载20%以上,换做usleep则好很多。
添加了_chipSelect,并且初始化之后将chipSelect置于高位,也就是此时如果没有操作的话PN532默认是忽略信号的。
重命名了write,read,begin。感觉这些函数起名容易让人误解,尤其是在初始化当中蹦出来个begin();让人觉得莫名其妙。
添加了PN532DEBUG的宏,如果它被定义了,那么编译结果就会输出好多信息。
设置灯(P9813GPIO.c)
由于普通单色LED和蜂鸣器都是可以直接通过wiringPi的digitalWrite来操作的,一行程序即可解决问题,所以这些内容不再赘述。不要忘了在初始化的时候将这些端口设置为OUT就可以了。
下面说说P9813这个三色LED。根据文档的描述,它是用SPI通讯的,正巧PN532的板子上面有4pin的接口,接上去发现4pin对着的是SDA/SCL,查了一下这两个端口,它俩的意思是需要在写完8bit之后再写入一个bit;而P9813则是不接受冗余bit的,所以我觉得不可行就没有测试了。而是用SPI的话是可以的。但是问题在于如果将PN532和P9813同时接入的话,P9813会不时的闪一下。查了P9813的文档,猜想到是PN532通讯的时候,P9813没有忽略这些通讯信号(SPI共享总线的)因此误操作了。但是树莓派没有第二个SPI引出,而且添加一个chipSelect给P9813又不知道如何下手,所以索性就用GPIO bit-bang了一个SPI。
你也可以从第一个图看到,我把SCL/SDA接入到了GPIO23/24当中。在wiringPi对应的是4,5两个。之后就需要进行逐bit的发信号了。这里涉及到以下5个函数:
void sendByte(unsigned char b); //这个是逐个bit的发送b,也就是发送8次。根据datasheet,每一次发送都得让CLK升降一次。 void sendColor(unsigned char r, unsigned char g, unsigned char b); //调用sendByte发送RGB颜色,注意到datasheet当中提示发送每个RGB都需要带校验。 void setColorRGB(unsigned char r, unsigned char g, unsigned char b); //调用sendColor来发送,这里需要添加前缀。 void setColorRGBs(unsigned char* r, unsigned char* g, unsigned char* b, int count); //调用setColorRGB来设置多个彩灯的RGB,因为条件限制没有做测试,理论上是对的。datasheet说最多可以1024个。 void setColorRGBbuffered(unsigned char r, unsigned char g, unsigned char b); //由于发现调用颜色设置函数过于频繁的时候CPU可能占用超过15%,是由于大量digitalWrite造成的,所以决定使用缓冲来减少这种情况。代码当中设计了上一次设置的时候的颜色,如果此次设置颜色与上一次相符则什么都不操作,否则就写入颜色。为了防止不同步,设置了每隔一段时间强制写入(即使与上一次相符)
另外谁能告诉我怎么在C当中设置函数的访问权限?我不想让sendByte和sendColor从外部访问。别的都应只调用setColorRGB系列函数。
总结
按照上面的思路,你就可以使用NFC来操控灯了。我这里只是一个极其简单的实现,NFC怎么读卡内容,P9813设置不同颜色我都没有实现。不过现在就可以每天晚上刷卡亮灯,睡觉前把卡拿走,灯就会慢慢暗下来(最开始感觉不到,最后快要灭掉的时候才能觉得亮度明显降低了),省得之前躺被窝里之后还得拿手机登录ssh设置gpio write 0 0来灭普通LED了……
如果你有兴趣,我把代码放在了github上面,你可以去看看:https://github.com/DC-Shi/PN532SPI-P9813GPIO 因为我首次使用 github,许多地方都不了解,尽请谅解。
(2014038)为什么用GPIO去bit-bang一个SPI去控制P9813?
最开始是因为,直接把SCL,SDA接入到SPI上的话会导致在与PN532的交互过程中,P9813也接受信号从而导致出现莫名其妙的颜色。
而现在我有了一个4053的模拟开关,可以做到控制是否接通,也就是把SPI引出来,接到模拟开关的输入,其输出再接P9813。经过测试,通路状态下一切正常,但是在将通路切换为断路的时候,灯会略微延迟(保持最后的颜色),之后自动切换到白色。这一点非常不爽。所以我现在还是得用bitbang……
话说Pi有三条SPI的,但是只引出来一个,太可惜了……
题外话
这其实也是个最简单的应用,我在看完RPi的摄像头模块可以红外摄像之后,觉得能不能通过摄像头来捕捉红外信息,如果获取的图像是我躺着的图像,确定过后就灭灯,如果看见我不是睡觉的话就亮灯?或者进行其他的设置?
还是预告一下吧,我好久没写东西了(上篇还是在10月份的……)。一个是年末总结,但是这个是技术博客,因此发发技术上今年都干了啥;一个是DS18B20的,我居然之前没有写过!这个东西我9月14号就开始用了;还有一个是Surface的TF卡装Fedora20,上次装18的时候是安装到硬盘了,也尝试过TF卡,但一直没成功过,这次搞明白原理了弄成功了但是又遇到了新问题。