利用SYSFS和SHELL脚本操作GPIO

linux下面有一个通用的GPIO操作接口,那就是 “/sys/class/gpio” 方式。
首先,看看系统中有没有“/sys/class/gpio”这个文件夹。如果没有请在编译内核的时候加入
Device Drivers —> GPIO Support —> /sys/class/gpio/… (sysfs interface)。

通过/sys/文件接口操作IO端口 GPIO到文件系统的映射
控制GPIO的目录位于/sys/class/gpio
/sys/class/gpio/export文件用于通知系统需要导出控制的GPIO引脚编号
/sys/class/gpio/unexport 用于通知系统取消导出
/sys/class/gpio/gpiochipXXX目录保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数 导出一个引脚的操作步骤
首先计算此引脚编号,引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数
向/sys/class/gpio/export写入此编号,比如12号引脚,在shell中可以通过以下命令实现,
echo 12 > /sys/class/gpio/export
(或者 echo “12” > /sys/class/gpio/export)
命令成功后生成/sys/class/gpio/gpio12目录,如果没有出现相应的目录,说明此引脚不可导出:

echo 12 > /sys/class/gpio/unexport
(或者 echo “12” > /sys/class/gpio/unexport)
取消导出偏移量12的引脚。

direction文件,定义输入输入方向,可以通过下面命令定义为输出
echo out > /sys/class/gpio/gpio12/direction
(或者echo “out” > /sys/class/gpio/gpio12/direction)
direction接受的参数:in, out, high, low。high/low设置方向为out,并将value设置为相应的1/0。

value文件是端口的数值,为1或0.例如
echo 1 >/sys/class/gpio/gpio12/value
(或者echo “1” >/sys/class/gpio/gpio12/value)

四、重温几个简单的例子

1、导出
# echo 44 > /sys/class/gpio/export
2、设置方向
# echo out > /sys/class/gpio/gpio44/direction
3、查看方向
# cat /sys/class/gpio/gpio44/direction
4、设置输出
# echo 1 > /sys/class/gpio/gpio44/value
5、查看输出值
# cat /sys/class/gpio/gpio44/value
6、取消导出
# echo 44 > /sys/class/gpio/unexport
除了上述例程的操作方法外,我们还可以有更简单地做法,就是编写 Shell 脚本。例如:
#!/bin/bash
echo 48 > /sys/class/gpio/gpio48/export
echo out > /sys/class/gpio/gpio48/direction
echo 1 > /sys/class/gpio/gpio48/value
usleep 1000
echo 0 > /sys/class/gpio/gpio48/value

gpiochip讲解
linux下对gpio是按照组来管理的,在zynq默认生成的内核中看到的gpiochip为gpiochip906 , 这个chip是怎么来的困扰了我好久,简单结说是按照驱动加载的顺序从上而下自动生成的,
内核版本默认是有1024个GPIO的,按照注册的先后自动生成gpiochip , zynq7000 PS端共有MIO EMIO共计118 个,所以这个906刚好是1024-118 = 906 ,906就是这么来的;
假如又增加了axi-gpio共计有4个gpio ,那么gpiochip902 中的902 = 906-4 ;后续有增加会相应的增加gpiochipXXX ;
这些信息可以在 /sys/class/gpio/gpiochipXXX下面的ngpio看到,命令为
#cd /sys/class/gpio/gpiochipxxx
#cat ngpio
#cat base

为什么会这样分配?
来看驱动代码。
里面有一个这样的定义

struct zynq_gpio {
	struct gpio_chip chip;
	void __iomem *base_addr;
	struct clk *clk;
	int irq;
	const struct zynq_platform_data *p_data;
	struct gpio_regs context;
};

它包含一个chip。

static int zynq_gpio_probe(struct platform_device *pdev)
{
	int ret, bank_num;
	struct zynq_gpio *gpio;
	struct gpio_chip *chip;
	struct resource *res;
	const struct of_device_id *match;

	。。。。。。

/* configure the gpio chip */
chip = &gpio->chip;
chip->label = gpio->p_data->label;
chip->owner = THIS_MODULE;
chip->parent = &pdev->dev;
chip->get = zynq_gpio_get_value;
chip->set = zynq_gpio_set_value;
chip->request = zynq_gpio_request;
chip->free = zynq_gpio_free;
chip->direction_input = zynq_gpio_dir_in;
chip->direction_output = zynq_gpio_dir_out;
chip->base = -1;
chip->ngpio = gpio->p_data->ngpio;
。。。。。。

/* report a bug if gpio chip registration fails */
ret = gpiochip_add_data(chip, gpio);
if (ret) {
	dev_err(&pdev->dev, "Failed to add gpio chip\n");
	goto err_pm_put;
}
。。。。。。

err_rm_gpiochip:
	gpiochip_remove(chip);
err_pm_put:
	pm_runtime_put(&pdev->dev);
err_pm_dis:
	pm_runtime_disable(&pdev->dev);
	clk_disable_unprepare(gpio->clk);

return ret;

我们可以看到,probe中把base设置成了-1,这是一个无效基地址。
probe调用了gpiochip_add_data(), 用来把chip注册到系统中。这个函数会调用gpiochip_find_base()。
这个函数中会判断base,如果base<0,那么开始系统自动分配base的过程。
会在系统内从最高到最低进行试探分配,遍历系统维护的资源链表,发现可用的GPIOBASE,就分配给新注册的gpiochip。

为了解决gpiochip906问题,我们有两种方法:
1)修改SHELL脚本,使用从906~1023的号码。
2)修改驱动,把base=-1修改为base=0

我们采用后者来实现。
修改驱动,重新编译内核。
下载运行,看到了gpiochip0。

我们来看看rootfs里面的两个脚本。
read_sw shell脚本:

#!/bin/sh
value=0;
for i in 0 1 2 3 4 5 6 7;
do
        sw=$((76-$i));
        sw_tmp=`cat /sys/class/gpio/gpio$sw/value`;
        value=$(($value*2));
        value=$(($value+$sw_tmp));
done;
printf "0x%x %d\n" $value $value;

SW的GPIO号是从69~76。基址69。
循环体中,每次获取一个开关的位号。
然后获取开关的值,并用乘2法实现左移位,然后加上LSB的值。
(注意用$((NUM))将一个NUM强制转换成int类型,而不是被识别成string类型。
例如:
sw=$((76));
最后给sw赋值为数值76,而不是字符串"76")

write_led shell脚本:

#!/bin/sh
value=$(($1));

if [ $value -ge 0 ]; then
        for i in 0 1 2 3 4 5 6 7;
        do
                led=$(($i+61));
                echo $(($value&0x01)) > /sys/class/gpio/gpio$led/value;
                value=$(($value/2));
        done;
fi;

先将传入的参数强制转换成整型数。
例如传入的0xff,被转换成255.
然后判断value是否为负数,负数是无效数,不执行。
循环体中,每次设置一个LED。
每次获取一个LED的位号,LED的位号61~68,基址61.
然后用逻辑位运算提取出LSB的值,并强制转换成INT,
然后echo输出,echo命令的destination文件重定向到GPIO的FILE中。
最后value利用除2法进行右移位,准备下一次循环操作。

load_oled shell脚本:

#!/bin/sh
insmod /lib/modules/`uname -r`/pmodoled-gpio.ko;

if [ -c /dev/zed_oled ]; then
        cat /root/logo.bin > /dev/zed_oled;
fi;

首先加载module。
然后判断是否是个字符设备,如果不是就不执行
然后用cat输入。cat命令的sourcefile是 logo.bin,但是destinationfile被重定向了,不再是stdin,而是/dev/zed_oled。
这样,cat执行的操作是,从logo.bin中,逐个char的读取数据,然后传递给/dev/zed_oled。

unload_oled shell脚本:

#!/bin/sh
lsmod
rmmod pmodoled-gpio
#rmmod pmodoled-gpio /lib/modules/`uname -r`/pmodoled-gpio.ko

先显示一下系统中注册的modules。
然后卸载注册到系统中的OLED设备驱动。

补充:
shell中各种替换符的基础知识。

(1)命令嵌套替换
在bash中,$( )与` `(反引号)都是用来作命令替换的。
命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。
在操作上,这两者都是达到相应的效果,但是不建议使用``,建议使用$( )。
但是$( )的弊端是,并不是所有的类unix系统都支持这种方式,但反引号是肯定支持的。
反引号的弊端是,不支持多层嵌套,因为反引号不像(),有左右两个限界符,反引号只有一个限界符,所以,shell在解释时,只能是顺序限界,即遇到第一个,认识是start,遇到第二个,认为是end。

(2)变量嵌套替换
一般情况下,$var与${var}是没有区别的,但是用${ }会比较精确的界定变量名称的范围

(3)数值运算。
$(( ))连续括号完成进制转换和整型转换。
其中(( )),不需要再将表达式里面的大小于符号转义,除了可以使用标准的数学运算符外,还增加了 逻辑运算符号:
bash中整数运算符号
符号 功能
+ - * / 分别为加、减、乘、除
% 余数运算
&| ^ ! 分别为“AND、OR、XOR、NOT”
在 $(( )) 中的变量名称,可于其前面加 $ 符号来替换。
example:
[root@localhost ~]# a=5;b=7;c=2
[root@localhost ~]# echo $(($a+$b*$c))
19

$(( ))可以将其他进制转成十进制数显示出来。用法如下:
echo $((N#xx))
其中,N为进制,xx为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值。
[root@localhost ~]# echo $((2#110))
6
[root@localhost ~]# echo $((16#2a))
42
[root@localhost ~]# echo $((8#11))
9

(())可以强制转换变量值为整型。
[root@localhost ~]# a=5;b=7
[root@localhost ~]# ((a++))
[root@localhost ~]# echo $a
6
[root@localhost ~]# ((a–));echo $a
5
[root@localhost ~]# ((a<b));echo $?
0
[root@localhost ~]# ((a>b));echo $?
1

(4)字符串界定。
单引号’’

双引号""
还有
反引号``
三者都是解决变量中间有空格的问题。
在bash中“空格”是一种很特殊的字符,比如在bash中这样定义str=this is String,这样就会报错,为了避免出错就得使用单引号’‘和双引号""。
单引号’’,双引号"“的区别是单引号’‘剥夺了所有字符的特殊含义,单引号’'内就变成了单纯的字符。双引号”“则对于双引号”"内的参数替换($)和命令替换(``)是个例外。
反引号``是命令替换,命令替换是指Shell可以先执行``中的命令,将输出结果暂时保存,在适当的地方输出。

(5)字符串处理
例如:
file=/dir1/dir2/dir3/my.file.txt
[root@localhost ~]# echo ${file#*/} // 拿掉第一条 / 及其左边的字符串
dir1/dir2/dir3/my.file.txt

[root@localhost ~]# echo ${file##*/} //拿掉最后一条 / 及其左边的字符串
my.file.txt

[root@localhost ~]# echo ${file#*.} //拿掉第一个 . 及其左边的字符串
file.txt

[root@localhost ~]# echo ${file##*.} //拿掉最后一个 . 及其左边的字符串
txt

[root@localhost ~]# echo ${file%/*} //拿掉最后一条 / 及其右边的字符串
/dir1/dir2/dir3

[root@localhost ~]# echo ${file%%/*} //拿掉第一条 / 及其右边的字符串
(空值)

[root@localhost ~]# echo ${file%.*} //拿掉最后一个 . 及其右边的字符串
/dir1/dir2/dir3/my.file

[root@localhost ~]# echo ${file%%.*} //拿掉第一个 . 及其右边的字符串
/dir1/dir2/dir3/my

注意:
# 是去掉左边(在键盘上 # 在 $ 之左边)
% 是去掉右边(在键盘上 % 在 $ 之右边)
单一符号是最小匹配;两个符号是最大匹配
*是用来匹配不要的字符,也就是想要去掉的那部分
还有指定字符分隔号,与*配合,决定取哪部分

#echo ${file:0:5} //提取最左边的 5 个字节     
/dir1
#echo ${file:5:5} //提取第 5 个字节右边的连续 5 个字节
/dir2
#echo ${file/dir/path} //将第一个 dir 提换为 path          
/path1/dir2/dir3/my.file.txt
#echo ${file//dir/path}     //将全部 dir 提换为 path      
/path1/path2/path3/my.file.txt
#echo ${#file}  // 获取变量长度       
27

${file-my.file.txt} //若 $file 没设定,则使用 my.file.txt 作传回值 空值及非空值不作处理

${file:-my.file.txt} //若 $file 没有设定或为空值,则使用 my.file.txt 作传回值 非空值时不作处理

${file+my.file.txt} //若$file 设为空值或非空值,均使用my.file.txt作传回值 没设定时不作处理

${file:+my.file.txt} //若 $file 为非空值,则使用 my.file.txt 作传回值 没设定及空值不作处理

${file=txt} //若 $file 没设定,则回传 txt ,并将 $file 赋值为 txt 空值及非空值不作处理

${file:=txt} //若 $file 没设定或空值,则回传 txt ,将 $file 赋值为txt 非空值时不作处理

${file?my.file.txt} //若 $file 没设定,则将 my.file.txt 输出至 STDERR 空值及非空值不作处理

${file:?my.file.txt} //若 $file没设定或空值,则将my.file.txt输出至STDERR 非空值时不作处理

(6)数组处理
例如:
A=“a b c def” # 定义字符串
A=(a b c def) # 定义字符数组

#echo ${A[@]} //返回数组全部数据
a b c def

#echo ${A[*]} //返回数组全部数据
a b c def

#echo ${A[0]} //返回数组第一个元素
a

#echo ${#A[@]} //返回数组元素总个数
4

#echo ${#A[*]} //返回数组元素总个数
4

#echo ${#A[3]} //返回第四个元素的长度即def的长度
3

# A[3]=xzy //将第四个组数重新定义为 xyz
#echo ${A[3]}
xyz

(7)条件测试
用[]形成一个条件表达式,测试后返回表达式的值。例如:
if [ “$a” eq 1 -o “$b” eq 2 ]
echo “$a $b”"
fi

常用的测试操作符如下:
[ -n str1 ]        当串的长度大于0时为真(串非空)
[ -z str1 ]        当串的长度为0时为真(空串)
[ str1 ]         当串str1为非空时为真
[ int1 -eq int2 ]    两数相等为真
[ int1 -ne int2 ]    两数不等为真
[ int1 -gt int2 ]    int1大于int2为真
[ int1 -ge int2 ]    int1大于等于int2为真
[ int1 -lt int2 ]   int1小于int2为真
[ int1 -le int2 ]    int1小于等于int2为真

[ -r file ]    用户可读为真
[ -w file ]    用户可写为真
[ -x file ]    用户可执行为真
[ -f file ]    文件为正规文件为真
[ -d file ]    文件为目录为真
[ -c file ]    文件为字符特殊文件为真
[ -b file ]    文件为块特殊文件为真
[ -s file ]    文件大小非0时为真
[ -t file ]    当文件描述符(默认为1)指定的设备为终端时为真

[ expr1 -a expr2 ]       与
[ expr1 -o expr2 ]       或
[ ! expr1 ]      非

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值