【.NET 与树莓派】使用 GPIO 库
上回老周在说准备工作的时候,提到过树莓派用金属盒散热的事情。有朋友会说,加了金属盒子接线不方便,就算用了“T”形板,毕竟是把导线延长了的。其实扩展板就是把原有的引脚引出(类似于延长),有的引出后使用并联电路来“复制”出几套接口。所以你买一块插孔多的面包板,自己也可以并联出许多扩展接口来,何况树莓派好一点的扩展板也比较贵。
如果你不运行桌面,不看岛国片,只是执行命令和运行代码,完全不用散热措施。老周直接用裸板试验过,只是运行程序的话,温度平均在 40 度上下,受室温影响不是很大,夏天的时候会高2度。这个温度问题不大,挂不了。70 度以上才要考虑散热。
最简单的散热方案是贴散热片,不过你不要贴歪了,想撕下来再贴一次好像不那么容易,弄不好可能会把芯片扯坏。曾经还听说有人把处理器扯下来的,不知道是真是假。另外,加个小风扇的方式应该挺好,有点噪音也没关系的(不要像轰炸机就行)。
好,现在说正题。我们知道,树莓派有许多 GPIO 引脚,通用方式是发出,或者接收电平信号。信号就是两种——高电平 or 低电平。我们可以简单地认为,高电平是 3.3 V 电压,低电平是 0V 电压,更深层的电路原理你可以不理会,不影响你编程。
为什么是高电平和低电平两个信号呢?嗯,问得真TM地好。你想一下啊,计算机只认识两个数字,哪两个呢?0 和 1,你给计算机下命令时,其实只要 0 和 1 组合就行。1 开灯,0 关灯;1 向左转,0 向右转;0 FQ,1 撞墙;0 引爆火药,1 引爆自己……
哟西,这么一来,两个电平就可以对应两个二进制数字:高电平—— 1,低电平—— 0,完事。
有了两个明确的值,还需要明确方向。树莓派自身把电平拉高或者拉低,以此来告诉连接的电子模块要干什么活,这是发送信号;当一个触摸开关被你的娇美的小手触碰后,模块把电平拉高(高电平状态),告诉树莓派有人按了按钮,这时候树莓派的接口就是接收状态,然后树莓派可以控制另一个设备去做其他事情(比如,冲马桶)。
由于树莓派带有 Linux 操作系统,所以,GPIO 接口的操作方法很多,可以直接调用相关的 lib,也可以用一些封装好的库。不过,系统集成了一种简单的操作方法,类似于文件的方式,你只需要读写对应的路径就能控制 IO 接口。
启动系统后,你可以进入以下目录:
cd /sys/class/gpio
然后执行 ls 命令,你会看到目录下有两个文件:
1、export:向这个文件写入 GPIO 口的编号,就可以打开相关接口;
2、unexport:向这个文件写入 GPIO 口的编号,关闭对应的接口。
你如果看到这两个文件,就可以试验一下了。不过在试之前,你还要做这件事:
sudo raspi-config
sudo 是为了防止权限不够,raspi-config,是树莓派系统专用的配置程序,在命令模式下也是图形化操作的。
选第3项,接口选项,回车。
用上下箭头键移到最下面的 P8,回车。
用 Tab 键选中 Yes,默认会选中,然后回车确认,收工。按 Esc 可以退出配置程序(或者选 Finish)。
Remote GPIO 选项必须打开的,不然你连接的电子模块是没有反应的。
接下来,老周用这个激光模块来做演示。
这个模块上面有一个激光头,跟我们小时候在路边买的装钮扣电池的差不多,发出的激光比小时候买的玩具还差。那这个模块有什么鸟用。其实这个模块真没什么鸟用,不过用来学习演示挺不错,现实用途可以拿来逗猫,不过注意不要让猫碰到单片机。
这个模块有三个引脚,PCB板上只标注了两侧的引脚。
为了提升拍照效果,我在下面垫了一张厕纸。最左边的引脚旁标注了“-”,意思是接电源负极,即接树莓派上的 GND 引脚。树莓派有八个 GND 引脚,你可以随便接;最右边的引脚标注了“S”,表示这是信号脚,接树莓派的 GPIO 脚,这个有很多了,当然了,最好选默认没有分配功能的 GPIO 接口。比如下图中画了蓝线的。
左右两边的引脚都知道用处了,所以,中间那个引脚就是接电源正极的,可以接树莓派的 5V,上面的图上你也看到了,树莓派两个 5V 脚都放在了一起(第2、4脚)。
下面开始接线,由于树莓派没有在板子印有引脚符号(无丝印),所以在接线时要小心,不要接错。
信号线老周选用了 GPIO 17,在树莓派上是 11 号引脚,也就是左边一排,往下顺数第六个脚。
接好线之后,我们回到终端,还记得前面提到的那两个文件吗?先用 cd 命令定位到 /sys/class/gpio 目录,然后向 export 文件输入文本“17”。
cd /sys/class/gpio echo 17 > export
然后用 ls 列出一下 gpio 下的子项,你是不是发现多了个子目录?叫 gpio17。
这说明 GPIO 的第17口已经准备就绪,可以通信了。
注意一下,这里的 GPIO 编号不是树莓派板子上的顺序,而是BCM编号,刚刚接线时接的是左排的第六脚,可以上 pinout.xyz 查看,这个脚的编号是17。
然后,cd gpio17,进入这个目录,看看里面有什么。
这里面有两个文件我们需要用到的。
1、direction:配置 GPIO 口的通信方向。向其中写入“in”为输入,写入“out”为输出。
2、value:GPIO 口输出的值,0 为低电平,1 为高电平。
激光头模块是收到高电平时发射激光的,所以这里要把通信方向设为 out。
echo out > direction
向 value 写入“1”,输出高电平,发射激光,开始逗猫。
echo 1 > value
逗久了猫也觉得不好玩了,你手也累了,这时向 value 写入“0”,输出低电平,关闭激光发射。
echo 0 > value
最后,向 /sys/class/gpio/unexport 写入GPIO口编号 17,就可以关闭这个接口了。这时候,gpio17 子目录就不见了。
有了上面的试验,我们可以开始使用微软封装的 GPIO 库了。
这个 Nuget 库叫 System.Device.Gpio,它已经封装好了GPIO操作常用的类型。
1、对于Windows平台,用的是 UWP 的库;
2、对于类 Unix 平台,封装了以下几种版本:
1) LibGpio;
2)SysFsDriver,这个就是上面举例用的方式,以文件方式操作 /sys/class/gpio 路径下的文件。
3)Raspberry Pi 专版。
另外,还有 HummingBoard 版本。
我们在使用时,其实并不需要去思考使用哪个平台的类型,在实例化 GpioController 类的时候,会自动选用合适的类型。
使用以下命令,在.NET项目中添加 System.Device.gpio 库的引用。
dotnet add package system.device.gpio
在VS2019 中,你也可以用非常熟悉的 Nuget 包管理器来添加,操作方法省略,老周相信你会用的了。
在代码中引入 System.Device.Gpio 命名空间,然后就可以欢畅地撸代码了。
using System; using static System.Threading.Thread; using static System.Console; using System.Device.Gpio; namespace testA { class Program { static void Main(string[] args) { int pin = 17; GpioController gpio = new GpioController(); // 打开接口 gpio.OpenPin(pin); // 设置通信方向 gpio.SetPinMode(pin, PinMode.Output); gpio.Write(pin, PinValue.Low); //先置低电平 // 也可以这样写 //gpio.Write(pin, 0); // 发送信号 int x = 20; while (x-- > 0) { WriteLine("打开激光头"); gpio.Write(pin, 1); Sleep(1000); WriteLine("关闭激光头"); gpio.Write(pin, 0); Sleep(1000); } // 关闭接口 gpio.ClosePin(pin); gpio.Dispose(); // 退出 WriteLine("3166"); } } }
变量 pin 的值是17,前面接线的时候接的是 GPIO 17 引脚。
dotnet publish -r linux-arm -c release --no-self-contained
-r linux-arm,就是指定目标的运行时为 arm (32)的 Linux 系统。
-c 指定生成方案,有 Debug 和 Release,这个老周就不多解释了,.NET 程序习惯模式。
--no-self-contained 表示不包含运行时库,因为老周已在树莓派的系统上装了 dotnet 运行时;如果你不想在上面安装运行时,可以去掉 --on-self-contained 选项,这样默认会包含运行时库。上传到树莓派上面,执行 chmod u+x xxxx 提升权限,然后就可以直接运行了。
要在树莓派上安装.NET 运行时也很简单。
1、执行 cd /tmp 转到临时目录。
2、下载时可以选择 .NET runtime,或 ASP.NET Core Runtime。后者包含运行Web应用的库。使用 wget 下载压缩包(选择 arm32 的 Linux 包)。
wget https://download.visualstudio.microsoft.com/download/pr/7aa18d1a-1c9a-4571-9668-3d76b5cda367/41a75d18282fa815c548be4dbc9dd55f/aspnetcore-runtime-5.0.2-linux-arm.tar.gz
3、在 /usr/local 目录下创建一个目录,叫 dotnet。
sudo mkdir /usr/local/dotnet
这个地方要加上 sudo ,不然权限不够。/usr/local 这个目录很适合,它就是让我们安装自己需要的第三方软件用的。
4、解压刚刚下载的压缩包,放到 /usr/local/dotnet 目录下面。
sudo tar -zxf aspnetcore-runtime-5.0.2-linux-arm.tar.gz -C /usr/local/dotnet
-z 参数表示使用 gz 算法,-x 表示解压,-f 表示要解压的文件,三个参数合起来就是 -zxf。注意 -C 参数是大写的,表示工作目录,也就是你要解压到哪个目录,此处解压到 /usr/local/dotnet 目录下。
5、在 /etc/profile 文件中加入以下内容。
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...). if [ "`id -u`" -eq 0 ]; then PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" else PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games" fi # .NET 的运行路径 if [ -d /usr/local/dotnet ]; then PATH=$PATH:/usr/local/dotnet fi export PATH ……
位置是在 PATH 环境变量导出之前,将 dotnet 运行时的路径追加上去。即在 export 命令之前。保存并退出就OK了。
设置 PATH 变量放在 /etc/profile 中比放在 bash 的资源文件中好一些。放在bash资源中需要你登录终端时才执行,而且只在当前会话期间可用。放在 /etc/profile 中就成了“全局”了,只要顺利登入系统都有效。
在任意路径下执行 dotnet --info,如果有以下输出,那就成功了。
好了,现在可以把我们已经发布的程序上传到树莓派了。Win 10 自带 scp 命令,不需要安装传输工具。
在树莓派上 cd 到 pi 用户目录下(如果你改了用户名,可能是其他名字,可以用“~”代替)。
cd ~
创建一个新目录,叫 app
mkdir app
回到 Windows 上,打开命令提示符窗口,cd 到程序发布的目录。
cd C:\Users\<用户名>\...\testA\bin\release\net5.0\linux-arm\publish
执行 scp 命令上传文件。
scp ** pi@192.168.200.15:/home/pi/app
其中,**(用一个星号也可以,通配符)表示把发布目录下的所有文件上传到树莓派;冒号后面是树莓派上的路径,把文件放到前面创建的 app 目录下面,注意要用绝对路径,不能用“~”。
用终端登入树莓派,cd 到app下,能看到刚上传的文件。
无比紧张的时刻来临,执行我们写好的程序。
dotnet testA.dll
如果没有出其他问题的话,你会看到激光头一开一关(其间暂停1秒)。
示例的源代码可以点这里下载:拼命点这里
这个 System.device.gpio 库还封装了 i2c、SPI 等常见的硬件通信方式,后续文章中老周还会介绍。