前言 上回 说到,对于 arm64 的系统来说,目前并没有一个库可以支持对 GPIO 的调用。那么接下来摆在我面前的就三条路:
修改 setup.c,让 cpuinfo 中包含相应的 Hardware 信息,随后重新编译内核
修改 wiringPi 源码
放弃使用库
可行的方法 上面三个选项中,1 和 2 显然是极其不现实的,因此我决定采用第三条路。在之前那个issue 中,我看到了这样一种用法:
At least as root GPIO works in bash on low level: echo “23” > /sys/class/gpio/export echo “out” > /sys/class/gpio/gpio23/direction echo “1” > /sys/class/gpio/gpio23/value … and can be verified by LED.
根据网上的资料,/sys/class/gpio
是 linux 通用的 GPIO 控制方法,看样子是好好地贯彻了 Unix“一切皆文件”的思想。那么接下来简单地测试一下:
1 2 3 4 5 6 # root @ rasp in /sys/class/gpio [19:07:22] $ echo 18>export # root @ rasp in /sys/class/gpio [19:07:33] $ ls export gpio18 gpiochip0 gpiochip100 gpiochip128 unexport
可以看到系统自动生成了相关的 GPIO 目录,进入之后可以看到相关的文件
1 2 3 # root @ rasp in /sys/class/gpio/gpio18 [19:10:36] $ ls active_low device direction edge power subsystem uevent value
相关的命名还是非常简单直观的,向direction
中写入信息控制 GPIO 的输入输出方向,value
则控制的是输出值,那么就拿我们先前选定的 18 号针脚来测试一下吧:
1 2 3 4 5 # root @ rasp in /sys/class/gpio/gpio18 [19:11:31] $ echo out>direction # root @ rasp in /sys/class/gpio/gpio18 [19:14:50] $ echo "1">value
接下来用万用表检测 3.28v,确实是 1,取消输出不知道为什么向value
写入 0 并不管用,因此只有直接向unexport
写入端口号:
1 2 3 4 5 6 # root @ rasp in /sys/class/gpio [19:29:18] $ echo 18>unexport # root @ rasp in /sys/class/gpio [19:29:22] $ ls export gpiochip0 gpiochip100 gpiochip128 unexport
可以看到相关的目录自动被移除了,电压也变成了 0
代码编写 实际上逻辑并不复杂,但是要考虑到程序退出之后风扇依旧会旋转,因此要做好信号的捕捉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include <cstdio> #include <unistd.h> #include <stdlib.h> #include <sys/stat.h> #include <malloc.h> #include <time.h> #include <sys/types.h> #include <iostream> #include <string> #include <cstring> #include <fcntl.h> #include <signal.h> using namespace std ;#define TEMP_PATH "/sys/class/thermal/thermal_zone0/temp" #define MAX_SIZE 20 #define GPIO_PIN 18 float getTemp () ;bool status = false ;char *timenow;char *getTime () { free (timenow); timenow = (char *)calloc (40 , sizeof (char )); time_t rawtime; struct tm *timeinfo ; time(&rawtime); timeinfo = localtime(&rawtime); *timenow = '[' ; strcat (timenow, asctime(timeinfo)); timenow[strlen (timenow) - 1 ] = '\0' ; strcat (timenow, "] " ); return timenow; } void setHigh (int gpio_num) { string str1 = "echo " ; string str2 = "> /sys/class/gpio" ; system((str1 + to_string(gpio_num) + str2 + "/export" ).c_str()); system((str1 + "high" + str2 + "/gpio" + to_string(gpio_num) + "/direction" ).c_str()); printf (" Fan started.\n" ); } void removeGPIO (int gpio_num) { string str1 = "echo " ; string str2 = "> /sys/class/gpio" ; system((str1 + to_string(gpio_num) + str2 + "/unexport" ).c_str()); printf (" Fan stopped.\n" ); } void sigroutine (int sig) { if (sig == SIGINT) printf ("%sGet SIGINT, quiting..." , getTime()); else if (sig == SIGTERM) printf ("%sGet SIGTERM, quiting..." , getTime()); if (status) removeGPIO(GPIO_PIN); exit (-1 ); } int main (int argc, char *argv[]) { signal(SIGINT, sigroutine); signal(SIGTERM, sigroutine); if (argc != 1 ) { if (strcmp (argv[1 ], "stop" ) == 0 ) { removeGPIO(GPIO_PIN); return 0 ; } } float temp; while (true ) { temp = getTemp(); printf ("%sTemperature is %.2f," , getTime(), temp); if (temp > 50 && status == false ) { setHigh(GPIO_PIN); status = true ; } else if (temp < 45 && status) { removeGPIO(GPIO_PIN); status = false ; } else if (!status) printf (" nothing to do...\n" ); else if (status) printf (" keep fan working...\n" ); sleep(5 ); } return 0 ; } float getTemp (void ) { int fd; float temp = 0 ; char buf[MAX_SIZE]; fd = open (TEMP_PATH, O_RDONLY); if (fd < 0 ) { fprintf (stderr , "failed to open thermal_zone0/temp\n" ); return -1 ; } if (read (fd, buf, MAX_SIZE) < 0 ) { fprintf (stderr , "failed to read temp\n" ); return -1 ; } temp = atoi(buf) / 1000.0 ; close (fd); return temp; }
请原谅我极其不优雅不简洁的实现方式和混乱的代码风格,毕竟有太久没碰过了。但是我要说的还是那句话:
又不是不能用
再次碰壁 我按照上面的方法接入了风扇,随后 echo……嗯,看起来一切正常,也没有报错什么的,除了风扇*纹丝不动 以外。 纹丝不动…… 纹丝不动…… 不动…… 动…… …… Why?为什么会变成这样呢……第一次有了能调用 GPIO 的方法。有了能控制风扇开关的代码。两件快乐事情重合在一起。而这两份快乐,又给我带来更多的快乐。得到的,本该是像梦境一般幸福的时间……但是,为什么,会变成这样呢……
原因分析及解决方法 咳咳,请不要打我。其实仔细想想原因不难想到,GPIO 是一种数字电路,而数字电路的电阻通常大得惊人,电流则是 mA 级别的,所能做的也仅仅就是点亮 LED 而已,想要让它驱动风扇实在是强人所难。因此不可避免地,我们就需要对电流进行放大。目前手头只有 A42 A331 的三极管,β 值大约在 320 左右,勉强可以使用,就就地使用了。电路图大致如下(手残请忽略): 基极连接 GPIO 并调至高电平之后,测得发射极和和集电极之间电压为 4.66v,带动风扇应该没问题 接入风扇之后,风扇正常工作,至此,温控风扇连接完毕。
自启动 写了一个程序,当然要做成自启动服务了,得益于systemd
,编写系统服务变得非常简单,在/etc/systemd/system
目录下新建一个autofan.service
文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 [Unit] Description=Temperature controled fan daemon [Service] Type=simple PIDFile=/var/run/autofan.pid User=root ExecStart=/usr/bin/autoFan [Install] WantedBy=multi-user.target
随后运行systemctl daemon-reload
重新加载服务,systemctl start autofan.service
启动服务即可。
后文 虽然成功地实现了风扇的温度控制,但是文中的操控 GPIO 的方法实在太不优雅了,仅仅是个临时之策,无法大规模运用。当然,也不是没有将之封装成一个库的想法,但时目前并没有太多的需要以及动力,就先这样吧。又不是不能用
不要重复造轮子 ——忘了谁说的了
では、諸君は。