对Raspberry Pi的GPIO在shell script里面可以直接用sysfs的方式进行操作,也可以调用gpio命令来进行操作,还可以用API来进行操作。今天做了个实验对比sysfs方式、第三方gpio命令以及C语言API操作对效率的影响。
昨天写的文章里面提到了用GPIO来控制LED灯的显示,今天其实是突发奇想,想看看用第三方gpio命令来操作和直接操作sysfs的效率到底有多大的区别。
物理连线
连线和昨天写的文章里面的连线方法基本一样,只是在GPIO管脚和GND之间跨接了一个可以测频率的万用表(UNI-T UT136D)。
Shell Script 响应速度测试
测试脚本
#!/bin/bash #Written by Kang Liu (http://www.liukang.com) #You have the freedom to use and/or modify the script, and the freedom to distribute modified and therefore derivative script. #Actually in this test, I will only use GPIO 17 (WiringIO 0) (Red LED) orgled=27 greenled=18 redled=17 init_led() { for i in $orgled $greenled $redled do if [ ! -d /sys/class/gpio/gpio$i ] then echo $i >/sys/class/gpio/export fi echo "out" >/sys/class/gpio/gpio$i/direction echo 0 >/sys/class/gpio/gpio$i/value done } cleanup() { init_led for i in $orgled $greenled $redled do if [ -d /sys/class/gpio/gpio$i ] then echo $i > /sys/class/gpio/unexport fi done exit 0 } gpio_ben() { echo 1 > /sys/class/gpio/gpio$1/value echo 0 > /sys/class/gpio/gpio$1/value #gpio write 0 1 #method 4 #gpio write 0 0 #method 4 } init_led trap cleanup INT TERM EXIT while : do #gpio_ben 17 #method 2, method 4 echo 1 > /sys/class/gpio/gpio17/value #method 1 echo 0 > /sys/class/gpio/gpio17/value #method 1 #gpio write 0 1 #method 3 #gpio write 0 0 #method 3 done
方法1:直接调用sysfs控制通断
脚本中默认没有注释掉的就是方法1,相当于用下面命令来操作gpio实现让电路通断:
while : do echo 1 > /sys/class/gpio/gpio17/value #method 1 echo 0 > /sys/class/gpio/gpio17/value #method 1 done
实测频率在1.1kHz左右。目测LED基本感觉不到闪烁。
测试结果照片如下,最后两位数字在不断跳动。
方法2:用shell脚本中的函数调用sysfs控制通断
相当于用下面命令来通过自定义函数gpio_ben操作gpio,实现电路通断:
gpio_ben() { echo 1 > /sys/class/gpio/gpio$1/value echo 0 > /sys/class/gpio/gpio$1/value } while : do gpio_ben 17 done
实测频率在750Hz左右。目测LED基本感觉不到闪烁。
方法3:用gpio外部命令来控制通断
相当于用下面的命令来通过gpio外部命令来操作gpio,实现电路的通断:
while : do gpio write 0 1 gpio write 0 0 done
实际频率在40Hz左右。目测LED能感觉到一些闪烁。
方法4:用通过函数调用gpio外部命令控制通断
相当于用里阿敏的命令来通过函数调用gpio外部命令来操作,实现电路的通断:
gpio_ben() { gpio write 0 1 gpio write 0 0 } while : do gpio_ben done
和方法3测到的结果基本一致40Hz左右,目测LED 能感觉到一些闪烁。
Shell Script测试结论
总结一下测试的结果:
方法 | 方法1:直接用shell内部命令调用sysfs | 方法2:使用函数通过shell的内部命令调用sysfs | 方法3:使用gpio外部命令控制 | 方法4:通过函数调用gpio外部命令 |
响应频率 | 约1.1 KHz | 约 750 Hz | 约 40 Hz | 约 40 Hz |
占空比 | 约 53-54% | 约 60%-61% | 约49% -51% | 约50%-51% |
和预期一致,使用shell的内部命令(echo和重定向符等)调用sysfs明显效率比使用第三方的命令效率要高。只不过效率高出的比例(27倍!)确实超乎意料。
有些超出预期的是在相对足够快的时候使用shell函数对效率产生的影响也能比较明显的表现出来,方法1和方法2使用的命令是一致的,只是方法2通过函数调用了那些命令。效率相差了几乎40%!
为了进一步验证,我又利用万用表测试占空比的功能进行了补充测试,得到的结果见上面表格。分析shell代码,我估计是因为shell脚本进入和退出函数时占用了相对不少的时间,以至于直接影响到了频率和占空比。这也说明在shell中使用函数其实是要消耗不少资源的。
用函数编写脚本逻辑上会清晰一些,但是就像当年上学时学过的一样,比较“面向机器”、“面向过程”、“面向对象” 编程逻辑上依次可能会一个比一个清晰,但是牺牲的是运行效率。
另外需要特别说明的是频率和占空比测试时使用的不是专业的示波器,只是一个带有频率测试功能的万用表,因此只写了大概的数值。而且在测试频率时不是空载的,电路中带着电阻和LED。但是测得数值的差别已经足够可以反映出来运行效率的差距了。
用C语言代码来测试gpio
其实本文主要想写的是shell脚本来做gpio操作的测试,临写完时觉得还是该试试看gpio到底能”跑多快”。参考例子写了一段C代码编译之后测试,跑出的结果真是甩出shell脚本N条街……要想追求速度还是要上C啊……不知道汇编去操作会不会更快些……
使用WiringPi C API测试
代码如下:
#include <wiringPi.h> int main (void) { wiringPiSetup () ; pinMode (0, OUTPUT) ; for (;;) { digitalWrite (0, HIGH) ; digitalWrite (0, LOW) ; } return 0 ; }
测试的频率达到了4.5-4.6 MHz,占空比46%-47%。测试结果的照片如下:
测试使用C语言代码通过文件操作调用sysfs
代码如下:
#include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int main (void) { FILE *fp; int gpiovaluefile; char off='0'; char on='1'; fp = fopen("/sys/class/gpio/export", "w"); fprintf( fp, "%d", 17 ); fclose (fp); fp = fopen("/sys/class/gpio/gpio17/direction", "rb+"); fprintf (fp,"out"); fclose (fp); gpiovaluefile = open("/sys/class/gpio/gpio17/value",O_SYNC|O_WRONLY); write(gpiovaluefile,&off,1); for (;;) { write(gpiovaluefile,&on,1); write(gpiovaluefile,&off,1); } close (gpiovaluefile); //will never reach here actually... }
其中
open("/sys/class/gpio/gpio17/value",O_SYNC|O_WRONLY);
这一行我试用了O_SYNC, O_DSYNC, O_ASYNC分别测试,占空比基本都保持在50%正负不到1%的接近理想状况。频率结果如下:
O_ASYNC 175-185 kHz
O_DSYNC 168-177 (偶尔有看到超过180 kHz的情况)
O_SYNC 168-177 (偶尔有看到超过180 kHz的情况)
实际测试时结果相对不稳定,数值跳动的比较厉害,由于手头没有示波器,看不出来实际输出的波形和频率。
C语言程序测试的结论
使用C语言WiringPi API来操作gpio看似是几种方法中响应时间最快的,但是占空比距离理想值的50%有3%-4%的差距。(和预期的一样) 使用C语言对sysfs操作速度仍远超过shell脚本;占空比最接近理想值的50%,但是没有使用Wiring Pi API调用gpio响应时间短。
对比Shell Script和C语言程序测试结果
使用语言 | C语言 | SHELL脚本 | ||||
---|---|---|---|---|---|---|
调用方法 | wiring Pi API | 直接文件操作sysfs | 直接文件操作sysfs | 使用函数操作sysfs | 外部gpio命令 | 通过函数调用外部gpio命令 |
测试响应频率 | 4.5-4.6 MHz | 168-185 KHz | 1.1 KHz | 750 Hz | 40 Hz | 40 Hz |
测试占空比 | 46%-46% | 非常接近50% | 53%-54% | 60%-61% | 49-51% | 50%-51% |
相对最慢方法的大致”速度倍数” | 11万倍以上 | 4千倍以上 | 27.5倍 | 18.75倍 | 1 *最慢 | 1 *最慢 |
所有测试时,肉眼都能感觉出来作为参照的LED灯要比常亮的状态下要略微暗一点点。在40Hz的时候能相对明显的感觉出来LED灯在快速闪烁。
目前看来要想追求速度,用C代码调用API写gpio是效率最高的。有机会试试汇编再做对比。
谨以此文对今日所作测试做一些记录。