ZYNQ学习笔记-Win下基于WSL的嵌入式Linux开发环境搭建

由于常规在虚拟机上编写嵌入式Linux驱动非常麻烦,而且还没有代码提示,这里给出一种在Win下利用WSL搭建的开发环境。

安装依赖

安装WSL2

首先需要开启WSL功能,并且需要是WSL2,网上教程很多,这里不再多说。

安装交叉编译链

然后需要安装Linux交叉编译器,使用如下命令安装:

1
sudo apt install gcc-arm-linux-gnueabihf

安装完成后使用如下命令检验:

1
arm-linux-gnueabihf-gcc -v

编译linux内核源码

驱动编写需要引用内核中的源码,这里需要下载xilinx-linux内核源码。对于petalinux来说,在build/tmp/work-shared/plnx-zynq7/kernel-source下,复制文件夹到便于使用的位置。

或者直接到github复制,注意版本需要与实际使用的一致:

1
git clone --depth 1 git@github.com:Xilinx/linux-xlnx.git

下载完成后,使用ZYNQ的配置编译源码。这里使用的是Xilinx 官方的 ZYNQ ZC702 EVK 开发板的配置,由于我们只需要一些BSP无关的生成文件,因此这里直接用就行。

1
2
make ARCH=arm xilinx_zynq_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

ARCH指定架构,CROSS_COMPILE指定编译器,-j12指定编译使用12核心。

编译测试

这里编译一个LED驱动进行测试。

源码准备

新建一个文件夹,创建并编写一个简单的字符程序myled.c,这里不多说了。

然后编写测试app。

源码在最后附录。

Makefile文件

这里需要使用make moudule功能,在上面的文件夹中创建一个Makefile文件,填写以下内容:

1
2
3
4
5
6
7
8
KDIR := /home/lo/kernel-source #内核源码目录

obj-m := led.o

all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KDIR) M=`pwd` modules
clean:
make -C $(KDIR) M=`pwd` clean

编写完成后直接make进行编译,得到.ko文件。

使用make clean清除编译产生的文件。

app编译即可:

1
arm-linux-gnueabihf-gcc ledapp.c -o ledapp

复制文件

使用scp命令将两个文件复制到开发板中,并进行测试。

1
scp led.ko ledapp root@192.168.2.138:/home/root/test

加载:

1
insmod led.ko

由于驱动中有自动创建节点的代码,因此不需要mknod。

进行测试:

1
2
./ledapp /dev/gpioled0 1 //点亮 LED 灯
./ledapp /dev/gpioled0 0 //关闭 LED 灯

卸载驱动:

1
rmmod led.ko

使用CLion

首先注意一点,Clion使用CMake进行工程构建,而linux内核源码使用Make,不能直接使用,因此这里只能查看代码不能编译。

Clion连接WSL

打开Clion,点击远程开发中的WSL。

点击连接到主机,选择使用的WSL实例(仅支持WSL2)。根据设置安装好对应WSl上的Clion版本,进行安装,Clion会自动进行,安装完成后选择项目目录打开。

此时应该会提示项目未设置。

创建CMake

创建CMakeLists.txt文件,并编写内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.26)# Clion自动生成
project(cliondev C)# Clion自动生成

set(CMAKE_C_STANDARD 99)# Clion自动生成,修改为C99
set(CMAKE_C_COMPILER "arm-linux-gnueabihf-gcc")
set(KERNEL_DIR "/home/lo/kernel-source") #设置内核目录

include_directories("${KERNEL_DIR}/include")
include_directories("${KERNEL_DIR}/include/uapi")
include_directories("${KERNEL_DIR}/include/generated")
include_directories("${KERNEL_DIR}/arch/arm/include")
include_directories("${KERNEL_DIR}/arch/arm/include/uapi")
include_directories("${KERNEL_DIR}/arch/arm/include/generated")
include_directories("${KERNEL_DIR}/arch/arm/include/generated/uapi")

add_definitions(-D__KERNEL__)
#add_compile_options("-includeautoconf.h")
add_executable(cliondev
led.c)# Clion自动生成

根据实际修改上面的内核目录。

之后重新加载目录,即可发现代码提示,可以愉快的开始编写了。

编译

Clion不能直接编译驱动,使用之前的Makefile脚本进行编译。

打开左下角终端,可以直接打开wsl的终端,在目录下输入make指令即可。

当然也可以使用CLion的脚本运行,在右上角配置中添加make脚本,直接运行即可(还可以将scp命令也添加到脚本中,实现编译后自动复制到开发板)。

使用Vscode

主要注意需要在设置文件中添加上面Cmake中写的头文件和定义,这里不多说了。

附录

led.c:

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled0"

/* dtsled 设备结构体 */
struct gpioled_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* LED 所使用的 GPIO 编号 */
};

static struct gpioled_dev gpioled; /* led 设备 */

static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
int ret;
char kern_buf[1];
struct gpioled_dev* pgpioled=(struct gpioled_dev*)filp->private_data;

ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据
if(0 > ret) {
printk(KERN_ERR "kernel write failed!\r\n");
return -EFAULT;
}

if (0 == kern_buf[0])
gpio_set_value(pgpioled->led_gpio, 0); // 如果传递过来的数据是 0 则关闭 led
else if (1 == kern_buf[0])
gpio_set_value(pgpioled->led_gpio, 1); // 如果传递过来的数据是 1 则点亮 led

return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};

static int __init led_init(void)
{
const char *str;
int ret;

/* 1.获取 led 设备节点 */
gpioled.nd = of_find_node_by_path("/leds/led0");
if(NULL == gpioled.nd) {
printk(KERN_ERR "gpioled: Failed to get /led node\n");
return -EINVAL;
}

/* 2.读取 status 属性 */
ret = of_property_read_string(gpioled.nd, "status", &str);
if(!ret) {
if (strcmp(str, "okay"))
return -EINVAL;
}

/* 2、获取 label 属性值并进行匹配 */
ret = of_property_read_string(gpioled.nd, "label", &str);
if(0 > ret) {
printk(KERN_ERR "gpioled: Failed to get compatible property\n");
return ret;
}

if (strcmp(str, "ps_led")) {
printk(KERN_ERR "gpioled: Compatible match failed\n");
return -EINVAL;
}

printk(KERN_INFO "gpioled: device matching successful!\r\n");

/* 4.获取设备树中的 led-gpio 属性,得到 LED 所使用的 GPIO 编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(!gpio_is_valid(gpioled.led_gpio)) {
printk(KERN_ERR "gpioled: Failed to get led-gpio\n");
return -EINVAL;
}

printk(KERN_INFO "gpioled: led-gpio num = %d\r\n", gpioled.led_gpio);

/* 5.向 gpio 子系统申请使用 GPIO */
ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
if (ret) {
printk(KERN_ERR "gpioled: Failed to request led-gpio\n");
return ret;
}

/* 6.将 led gpio 管脚设置为输出模式 */
gpio_direction_output(gpioled.led_gpio, 0);

/* 7.初始化 LED 的默认状态 */
ret = of_property_read_string(gpioled.nd, "default-state", &str);
if(!ret) {
if (!strcmp(str, "on"))
gpio_set_value(gpioled.led_gpio, 1);
else
gpio_set_value(gpioled.led_gpio, 0);
} else
gpio_set_value(gpioled.led_gpio, 0);

/* 8.注册字符设备驱动 */
/* 创建设备号 */
if (gpioled.major) {
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
if (ret)
goto out1;
} else {
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
if (ret)
goto out1;

gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}

printk("gpioled: major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);

/* 初始化 cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);

/* 添加一个 cdev */
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if (ret)
goto out2;

/* 创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
ret = PTR_ERR(gpioled.class);
goto out3;
}

/* 创建设备 */
gpioled.device = device_create(gpioled.class, NULL,
gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {
ret = PTR_ERR(gpioled.device);
goto out4;
}
return 0;

out4:
class_destroy(gpioled.class);

out3:
cdev_del(&gpioled.cdev);

out2:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

out1:
gpio_free(gpioled.led_gpio);

return ret;
}

static void __exit led_exit(void)
{
/* 注销设备 */
device_destroy(gpioled.class, gpioled.devid);

/* 注销类 */
class_destroy(gpioled.class);

/* 删除 cdev */
cdev_del(&gpioled.cdev);

/* 注销设备号 */
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);

/* 释放 GPIO */
gpio_free(gpioled.led_gpio);
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("LO");
MODULE_DESCRIPTION("LO ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

ledapp.c:

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
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
unsigned char buf[1];

if(3 != argc) {
printf("Usage:\n"
"\t./ledApp /dev/led 1 @ close LED\n"
"\t./ledApp /dev/led 0 @ open LED\n"
);
return -1;
}

/* 打开设备 */
fd = open(argv[1], O_RDWR);
if(0 > fd) {
printf("file %s open failed!\r\n", argv[1]);
return -1;
}

/* 将字符串转换为 int 型数据 */
buf[0] = atoi(argv[2]);

/* 向驱动写入数据 */
ret = write(fd, buf, sizeof(buf));
if(0 > ret){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}

/* 关闭设备 */
close(fd);
return 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
/include/ "system-conf.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include "dt-bindings/gpio/gpio.h"
#include "dt-bindings/input/linux-event-codes.h"
/{
model = "BX71 Development Board";

leds {
compatible = "gpio-leds";
led0 {
label = "ps_led";
led-gpio = <&gpio0 0 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
led1 {
label = "pl_led0";
led-gpio = <&gpio0 54 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};


keys {
compatible = "gpio-keys";
autorepeat;
key0 {
label = "ps_key";
gpios = <&gpio0 47 GPIO_ACTIVE_LOW>;
linux,code = <KEY_UP>;
debounce-interval = <15>;
};

key1 {
label = "pl_key";
gpios = <&gpio0 55 GPIO_ACTIVE_LOW>;
linux,code = <KEY_DOWN>;
debounce-interval = <15>;
};

};
usb_phy0: phy0@e0002000 {
compatible = "ulpi-phy";
#phy-cells = <0>;
reg = <0xe0002000 0x1000>;
view-port = <0x0170>;
drv-vbus;
};

};
&usb0 {
dr_mode = "otg";
usb-phy = <&usb_phy0>;
};
&gem0 {
local-mac-address = [00 0a 35 00 11 55];
};

&phy0 {
reg = <0x5>;
};