为了在Linux用户空间中对板上的硬件I/O进行控制,需要编写驱动程序。
尝试了一些驱动程序的编写,发现Linux的Gpiolib方便一些,能够实现GPIO管脚的输出、输入、中断功能,相对于自己再去写设备驱动更方便一些。Gpiolib是基于SysFs接口实现的GPIO管脚的操作,用起来虽然方便,但是针对于按键、LED这些特定功能的I/O口,我们还可以使用Linux内核中的gpio-keys, leds-gpios驱动去实现更方便的I/O口操作。
下面介绍gpio-keys, leds-gpio的驱动,而Gpiolib网上有很多例子,以后再来学习。
在上一节“EMIO Slice 自定义IP设计”中我们已经将Zynq SoC中PS的emio信号连接到了PL上的管脚,最终的bd图如下:
从EMIO(GPIO_0)中引出21个信号,分别连接到ZedBoard的BTN(5位),SW(8位)和LED(8位)上,他们的emio编号分别为:54~58,59~66,67,~74。在板子上还有一个LD9的LED,使用的是MIO 7的引脚。再加入管脚约束、综合、实现、生成bit和hdf后,我们再次进入petalinux的设计。
约束文件如下:
#btn
set_property PACKAGE_PIN N15 [get_ports {btn_tri_io[0]}]
set_property PACKAGE_PIN R18 [get_ports {btn_tri_io[1]}]
set_property PACKAGE_PIN T18 [get_ports {btn_tri_io[2]}]
set_property PACKAGE_PIN R16 [get_ports {btn_tri_io[3]}]
set_property PACKAGE_PIN P16 [get_ports {btn_tri_io[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {btn_tri_io[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {btn_tri_io[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {btn_tri_io[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {btn_tri_io[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {btn_tri_io[4]}]
#sw
set_property PACKAGE_PIN F22 [get_ports {sw_tri_io[0]}]
set_property PACKAGE_PIN G22 [get_ports {sw_tri_io[1]}]
set_property PACKAGE_PIN H22 [get_ports {sw_tri_io[2]}]
set_property PACKAGE_PIN F21 [get_ports {sw_tri_io[3]}]
set_property PACKAGE_PIN H19 [get_ports {sw_tri_io[4]}]
set_property PACKAGE_PIN H18 [get_ports {sw_tri_io[5]}]
set_property PACKAGE_PIN H17 [get_ports {sw_tri_io[6]}]
set_property PACKAGE_PIN M15 [get_ports {sw_tri_io[7]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[0]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[1]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[2]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[3]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[4]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[5]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[6]}]
set_property IOSTANDARD LVCMOS25 [get_ports {sw_tri_io[7]}]
#led
set_property PACKAGE_PIN T22 [get_ports {led_tri_io[0]}]
set_property PACKAGE_PIN T21 [get_ports {led_tri_io[1]}]
set_property PACKAGE_PIN U22 [get_ports {led_tri_io[2]}]
set_property PACKAGE_PIN U21 [get_ports {led_tri_io[3]}]
set_property PACKAGE_PIN V22 [get_ports {led_tri_io[4]}]
set_property PACKAGE_PIN W22 [get_ports {led_tri_io[5]}]
set_property PACKAGE_PIN U19 [get_ports {led_tri_io[6]}]
set_property PACKAGE_PIN U14 [get_ports {led_tri_io[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led_tri_io[7]}]
下面参考官方文档,网址:http://www.wiki.xilinx.com/Linux+GPIO+Driver
首先导入新的hdf和bit文件:
petalinux-config --get-hw-description=../hw_description/min_system_wrapper_hw_platform_1/
配置内核:
petalinux-config -c kernel
Device Drivers --->
-*- GPIO Support --->
<*> Xilinx GPIO support
<*> Xilinx Zynq GPIO support
[*] LED Support --->
<*> LED Class Support
<*> LED Support for GPIO connected LEDs
[*] LED Trigger support --->
<*> LED Timer Trigger
<*> LED One-shot Trigger
<*> LED Heartbeat Trigger
<*> LED backlight Trigger
[*] LED CPU Trigger
<*> LED GPIO Trigger
<*> LED Default ON Trigger
<*> LED Transient Trigger
Input device support --->
[*] Keyboards --->
<*> GPIO Buttons
<*> Polled GPIO buttons
编辑设备树文件:
gedit ./subsystems/linux/configs/device-tree/system-top.dts
修改system-top.dts,我把ZedBoard上的5个按键配置为键盘的左右上下回车功能,8个开关配置为F1~F8按键,8个EMIO连接的LED默认打开,1个MIO连接的LED配置为心跳模式。
其中设备树中的linux,code编号可以在下面的头文件中找到:
input-event-codes.h
input.h
我们使用了其中的若干按键:
#define KEY_ENTER 28
#define KEY_UP 103
#define KEY_LEFT 105
#define KEY_RIGHT 106
#define KEY_DOWN 108
#define KEY_F1 59
#define KEY_F2 60
#define KEY_F3 61
#define KEY_F4 62
#define KEY_F5 63
#define KEY_F6 64
#define KEY_F7 65
#define KEY_F8 66
/dts-v1/;
/include/ "system-conf.dtsi"
/ {
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
btn-left {
label = "btn-left";
gpios = <&gpio0 54 0>;
linux,code = <105>; /* KEY_LEFT */
wakeup-source;
autorepeat;
};
btn-right {
label = "btn-right";
gpios = <&gpio0 55 0>;
linux,code = <106>; /* KEY_RIGHT */
wakeup-source;
autorepeat;
};
btn-up {
label = "btn-up";
gpios = <&gpio0 57 0>;
linux,code = <103>; /* KEY_UP */
wakeup-source;
autorepeat;
};
btn-down {
label = "btn-down";
gpios = <&gpio0 56 0>;
linux,code = <108>; /* KEY_DOWN */
wakeup-source;
autorepeat;
};
btn-enter {
label = "btn-enter";
gpios = <&gpio0 58 0>;
linux,code = <28>; /* KEY_ENTER */
wakeup-source;
autorepeat;
};
sw-0 {
label = "sw-0";
gpios = <&gpio0 59 0>;
linux,code = <59>; /* KEY_F1 */
wakeup-source;
autorepeat;
};
sw-1 {
label = "sw-1";
gpios = <&gpio0 60 0>;
linux,code = <60>; /* KEY_F2 */
wakeup-source;
autorepeat;
};
sw-2 {
label = "sw-2";
gpios = <&gpio0 61 0>;
linux,code = <61>; /* KEY_F3 */
wakeup-source;
autorepeat;
};
sw-3 {
label = "sw-3";
gpios = <&gpio0 62 0>;
linux,code = <62>; /* KEY_F4 */
wakeup-source;
autorepeat;
};
sw-4 {
label = "sw-4";
gpios = <&gpio0 63 0>;
linux,code = <63>; /* KEY_F5 */
wakeup-source;
autorepeat;
};
sw-5 {
label = "sw-5";
gpios = <&gpio0 64 0>;
linux,code = <64>; /* KEY_F6 */
wakeup-source;
autorepeat;
};
sw-6 {
label = "sw-6";
gpios = <&gpio0 65 0>;
linux,code = <65>; /* KEY_F7 */
wakeup-source;
autorepeat;
};
sw-7 {
label = "sw-7";
gpios = <&gpio0 66 0>;
linux,code = <66>; /* KEY_F8 */
wakeup-source;
autorepeat;
};
};
gpio-leds {
compatible = "gpio-leds";
led-ld0 {
label = "led-ld0";
gpios = <&gpio0 67 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld1 {
label = "led-ld1";
gpios = <&gpio0 68 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld2 {
label = "led-ld2";
gpios = <&gpio0 69 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld3 {
label = "led-ld3";
gpios = <&gpio0 70 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld4 {
label = "led-ld4";
gpios = <&gpio0 71 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld5 {
label = "led-ld5";
gpios = <&gpio0 72 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld6 {
label = "led-ld6";
gpios = <&gpio0 73 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld7 {
label = "led-ld7";
gpios = <&gpio0 74 0>;
default-state = "on";
linux,default-trigger = "default-on";
};
led-ld9 {
label = "led-ld9";
gpios = <&gpio0 7 0>;
default-state = "on";
linux,default-trigger = "heartbeat";
};
};
};
新建测试程序
petalinux-create -t apps --name gpiolib_test --enable
gedit ./components/apps/gpiolib_test.c
复制下面的代码进去覆盖之前的:
#include
#include
#include
#include
#include
#include
#include
#define LED_BRIGHTNESS "/sys/class/leds/led-ld7/brightness"
#define LED_TRIGGER "/sys/class/leds/led-ld7/trigger"
#define INPUT_EVENT "/dev/input/event0"
#define LED_MAX_SPEED 10
#define PERIOD_COEFF 16000
unsigned int led_speed;
pthread_mutex_t lock;
/* Blink LED */
static void *LEDMod(void *dummy)
{
unsigned int led_period;
int tmp;
tmp = open(LED_BRIGHTNESS, O_WRONLY);
if (tmp < 0)
exit(1);
while (1) {
pthread_mutex_lock(&lock);
led_period = (LED_MAX_SPEED - led_speed) * PERIOD_COEFF;
pthread_mutex_unlock(&lock);
write(tmp, "1", 2);
usleep(led_period);
write(tmp, "0", 2);
usleep(led_period);
}
}
int main()
{
pthread_t pth;
struct input_event ev;
int tmp;
int key_code;
int size = sizeof(ev);
/* Configure LED */
led_speed = 5;
tmp = open(LED_TRIGGER, O_WRONLY);
if (tmp < 0)
return 1;
if (write(tmp, "default-on", 10) != 10) {
printf("Error writing trigger");
return 1;
}
close(tmp);
printf("Configured LED for use\n");
/* Create thread */
pthread_mutex_init(&lock, NULL);
pthread_create(&pth, NULL, LEDMod, "Blinking LED...");
/* Read event0 */
tmp = open(INPUT_EVENT, O_RDONLY);
if (tmp < 0) {
printf("\nOpen " INPUT_EVENT " failed!\n");
return 1;
}
/* Read and parse event, update global variable */
while (1) {
if (read(tmp, &ev, size) < size) {
printf("\nReading from " INPUT_EVENT " failed!\n");
return 1;
}
if (ev.value == 1 && ev.type == 1) { /* Down press only */
key_code = ev.code;
if (key_code == KEY_DOWN) { /* lower speed */
/* Protect from concurrent read/write */
pthread_mutex_lock(&lock);
if (led_speed > 0)
led_speed -= 1;
pthread_mutex_unlock(&lock);
} else if (key_code == KEY_UP) { /* raise speed */
pthread_mutex_lock(&lock);
if (led_speed < 9)
led_speed += 1;
pthread_mutex_unlock(&lock);
}
printf("Speed: %i\n", led_speed);
usleep(1000);
}
}
}
编译,打包:
petalinux-build
cd ./images/linux
petalinux-package --boot --fsbl zynq_fsbl.elf --fpga min_system_wrapper.bit --u-boot –force
复制BOOT.bin和image.ub到SD卡,插入ZedBoard的SD卡插槽,配置为SD卡启动,启动开发板。
启动后执行以下命令观察效果:
root@miz702:~# cd /sys/class/leds/
root@miz702:/sys/class/leds# ls
led-ld0 led-ld2 led-ld4 led-ld6 led-ld9
led-ld1 led-ld3 led-ld5 led-ld7 mmc0::
root@miz702:/sys/class/leds# cd led-ld5
root@miz702:/sys/class/leds/led-ld5# ls
brightness max_brightness subsystem uevent
device power trigger
root@miz702:/sys/class/leds/led-ld5# cat brightness
255
root@miz702:/sys/class/leds/led-ld5# echo 0 > brightness
root@miz702:/sys/class/leds/led-ld5# echo 1 > brightness
root@miz702:/sys/class/leds/led-ld5# cat trigger
[none] nand-disk mmc0 timer oneshot heartbeat backlight gpio cpu0 cpu1 default-on transient flash torch
root@miz702:/sys/class/leds/led-ld5# echo heartbeat > trigger
root@miz702:/sys/class/leds/led-ld5#
root@miz702:/sys/class/leds/led-ld5# cd /sys/class/input
root@miz702:/sys/class/input# ls
event0 input0 mice
root@miz702:/sys/class/input# gpiolib_test
测试过程:
0. 默认状态下ld0~ld7常亮,ld9闪烁
1. 查看了gpio-leds下的设备,包含了led-ld0到led-ld9
2. 进入led-ld5,进行了灭与亮的操作,然后配置为心跳模式,可以看到ld5与ld9同样的闪烁
3. 看到input0的设备,并运行gpiolib_test程序,可以看到ld7开始闪烁,按下up或down按键,可以调整闪烁频率。按下任何按键时,串口都有打印输出状态,说明btn和sw都工作OK,同时说明上一节做的EMIO Slice IP没有问题。
至此,gpio-leds和gpio-keys的驱动就测试完成了,参考官方的代码能够暂时满足我的需求,其他的驱动方式稍后再学习吧。