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

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

安装依赖

安装WSL2

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

安装交叉编译链

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

sudo apt install gcc-arm-linux-gnueabihf

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

arm-linux-gnueabihf-gcc -v

编译linux内核源码

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

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

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

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

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文件,填写以下内容:

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编译即可:

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

复制文件

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

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

加载:

insmod led.ko

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

进行测试:

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

卸载驱动:

rmmod led.ko

使用CLion

首先注意一点,Clion使用CMake进行工程构建,而linux内核源码使用Make,不能直接使用,因此这里只能查看代码不能编译。
新版Clion支持了Makefile,可以自行查看,这里没有更新。

Clion连接WSL

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

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

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

创建CMake

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

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:

#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:

#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;
}

本次使用的设备树:

/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>;
};
posted @ 2026-08-22 12:20  LO_StacNet  阅读(0)  评论(0)    收藏  举报