Orangepi GPIO 驱动开发详解
最近新入手了一个OrangePi ,准备学习一下linux下的驱动开发,不过由于刚开始入门,踩到的坑有点多。
硬件环境:OrangePi PC Puls
开发工具:gcc
开发环境:vscode + Remote SSH ( 不得不说vscode真的香啊~)
1. wiringPi库驱动
在刷入linux镜像之后,第一个程序就是编写Blink程序!
安装wiringPi库
首先克隆orangepi的wiringpi库:
#获取wiringpi库
git clone https://github.com/orangepi-xunlong/wiringOP.git
#运行脚本自动编译wiringOP库
./wiringOP/build
#根据板卡型号进行选择,我是orangepi pc puls 选择3
安装完成后输入gpio readall
可以查看orangepi所有了引脚序号,在后面的程序中需要使用这个序号。
可以看到下面输出:
这个表是引出IO的信息:
GPIO:是指内核的序号。具体的计算方法为:(字母在字母表中的位置- 1) * 32 + 引脚序号
如PC07 引脚编号为:(3 - 1) * 32 + 7 = 71。
wPi:是在wiringpi函数中使用的引脚序号,
V:是当前IO的输出电平
命令行驱动GPIO
使用wiringpi命令控制IO状态:
# 配置GPIO12(对应wPi序号0)为为输出模式
gpio mode 0 out
# 控制IO输出高电平,格式为gpio read <pin> <value>
gpio write 0 1
#读取IO状态
gpio read 0
# 以500ms的频率翻转IO状态
gpio blink 0
C语言驱动GPIO
在使用命令行运行Blink程序之后开始通过C语言编程,点亮led
代码如下:
#include <stdio.h>
#include <wiringPi.h>
#define LED 0 //接到树莓派映射引脚0上,对应GPIO12
int main(void)
{
printf("OrangePi blink...\r\n");
wiringPiSetup(); //初始化GPIO配置
pinMode(LED, OUTPUT); //配置IO模式
while(1)
{
digitalWrite(LED, HIGH); //控制GPIO
delay(500);
digitalWrite(LED, LOW);
delay(500);
}
}
不过在这个过程中就踩到了坑:根据给的教程走时发现编译出错。
显示未定义引用,查找资料后发现这是由于未链接完整的库文件导致的。
正确编译参数应为
gcc led.c -L/usr/local/lib -lwiringPi -lpthread -lwiringPiDev -lm -l crypt -lrt -o LED
重新编译之后程序可以正常运行,注意:wiringPiSetup()
函数需要使用root权限
更多wiringpi的函数参考此链接
2. 内核(sysfs)GPIO驱动
在点亮第一个led之后,就开始准备折腾有关GPIO的更多操作了。
注意到在orangepi的开发板上有两个led,在原理图上显示一个是PWR_LED(PA15)和Status_LED(PL10)。PWR_LED是电源指示灯,那是不是可以不接线直接驱动板载LED?
然后经过一番尝试后使用wiringpi库无法驱动板载LED灯,在和群友进行交流时提到“板载LED这么重要的一个GPIO应该不会暴露到用户空间”,我顺着这个信息查下去,果然无法直接驱动!参考资料
命令行驱动板载led
板载的两个LED都是可以进行配置的,既可以作为状态指示灯使用,也可以作为普通GPIO使用。
#以下代码请使用root用户执行
#将板载状态指示灯配置为通用GPIO
echo none | sudo tee /sys/class/leds/orangepi:red:status/trigger
#控制板载led输出低电平
echo 0 > /sys/class/leds/orangepi:red:status/brightness
#控制板载led输出高电平
echo 0 > /sys/class/leds/orangepi:red:status/brightness
# 还原板载LED作为指示灯
echo cpu0 | sudo tee /sys/class/leds/orangepi:red:status/trigger
知道板载LED可以作为普通GPIO驱动,那普通GPIO怎么驱动?搜索了以下linux GPIO驱动的资料: [参考](https://www.cnblogs.com/lulipro/p/5992172.html)
在linux下,所有设备据看作为文件,GPIO也不例外,该方式就是对内核文件进行操作控制IO。不过由于GPIO在内核空间,用户无法直接对IO进行操作,需要先释放到用户空间,然后在进行读写。 有关GPIO的文件在/sys/class/gpio/目录下  ## 命令行驱动GPIO 控制 GPIO 的目录位于 /sys/class/gpio。 >/sys/class/gpio/export 文件用于通知系统需要导出控制的 GPIO 引脚编号。 >/sys/class/gpio/unexport 用于通知系统取消导出。 >/sys/class/gpio/gpiochipX >目录保存系统中 GPIO 寄存器的信息,包括每个寄存器控制引>脚的起始编号 base,寄存器名称,引脚总数
#暴露GPIO12到用户空间
echo 12 > /sys/class/gpio/export
#配置GPIO方向
echo "out" >/sys/class/gpio/gpio12/direction
#控制GPIO电平
echo 1 > /sys/class/gpio/gpio12/value
#隐藏GPIO到内核空间
echo 12 > /sys/class/gpio/unexport
C语言sysfs驱动GPIO
使用C语言通过sysfs方式控制GPIO状态如下:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
void delay(uint16_t ms)
{
usleep(ms*1000);
}
#define BUFF_SIZE 128
#define LED 12
int main(int argc, char *argv[])
{
char buffer[BUFF_SIZE],path[128];
int fd,len;
printf("oraangpi sysfs drive led !\n");
/*暴露GPIO到用户空间*/
fd = open("/sys/class/gpio/export", O_WRONLY);
len = snprintf(buffer, BUFF_SIZE, "%d", LED);
write(fd, buffer, len);
close(fd);
/*配置GPIO方向*/
snprintf(path, BUFF_SIZE, "/sys/class/gpio/gpio%d/direction", LED);
fd = open(path, O_WRONLY);
len = snprintf(buffer, BUFF_SIZE, "out");
write(fd, buffer, len);
close(fd);
while(1)
{
snprintf(path, BUFF_SIZE, "/sys/class/gpio/gpio%d/value", LED);
fd = open(path, O_WRONLY);
len = snprintf(buffer, BUFF_SIZE, "%d", 0);
write(fd, buffer, len);
close(fd);
delay(500);
snprintf(path, BUFF_SIZE, "/sys/class/gpio/gpio%d/value", LED);
fd = open(path, O_WRONLY);
len = snprintf(buffer, BUFF_SIZE, "%d", 1);
write(fd, buffer, len);
close(fd);
delay(500);
}
/*隐藏GPIO到内核空间*/
fd = open("/sys/class/gpio/unexport", O_WRONLY);
len = snprintf(buffer, BUFF_SIZE, "%d", LED);
write(fd, buffer, len);
close(fd);
}
3. 自行编写底层驱动
在上面的方式都是通过调用已有的驱动,然后对IO进行配置,从而进行控制IO。还有一种方式就是自己编写GPIO的底层驱动。
这种方式比较复杂,由于时间和能力原因不在详细描述,在网上也有很多类似的资料。