Zynq Linux控制led

之前我写过FPGA串口控制led,Vitis使用MIO,EMIO,串口控制led,现在是Linux系统控制led,led系列实验是经典入门实验,不能不尝

本次我做了两个小实验,分别是 驱动控制按键控制led亮灭 和 驱动控制Linux命令行控制led亮灭

按键控制led亮灭

将led的输出与按键输入相关联,按下button0时led0亮,放开button0时led0灭,同样button1控制led1

驱动代码 key_drv_v1.c

//1、添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>

#define ZYNQMP_GPIO_NR_GPIOS 174
//button0,button1
#define MIO_PIN_45 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 45)
#define MIO_PIN_44 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 44)
//led0,led1
#define MIO_PIN_17 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 17)
#define MIO_PIN_23 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 23)

static int key_major;
static int ret = 0;
static struct class *key_cls;

int key_open(struct inode *inode, struct file *filp)
{
	printk("----^v^-----key open\n");
	//将MIO45 44设置为输入,
	ret = gpio_direction_input(MIO_PIN_45);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_45 fail!\n");
	}
	ret = gpio_direction_input(MIO_PIN_44);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_44 fail!\n");
	}
	ret = gpio_direction_output(MIO_PIN_17,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_17 fail!\n");
	}
		ret = gpio_direction_output(MIO_PIN_23,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_23 fail!\n");
	}
	return 0;
}

ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
	int value[4] = {0};
	printk("----^v^-----key read\n");
	value[0] = gpio_get_value(MIO_PIN_45);
	value[1] = gpio_get_value(MIO_PIN_44);
	gpio_set_value(MIO_PIN_17,1-value[0]);
	gpio_set_value(MIO_PIN_23,1-value[1]);
	ret = copy_to_user(buf, value, count);
	if (ret < 0)
	{
		printk("gpio get value failed!\n");
		return ret;
	}

	return 0;
}


int key_close(struct inode *inode, struct file *filp)
{
	printk("----^v^-----key close\n");
	return 0;
}

const struct file_operations key_fops = {
	.open = key_open,
	.read = key_read,
	.release = key_close,
};

//4、实现装载入口函数和卸载入口函数
static __init int key_drv_v1_init(void)
{
	printk("----^v^-----key drv v1 init\n");
	//①、申请主设备号
	//参数1----需要的主设备号,>0静态分配, ==0自动分配
	//参数2----设备的描述 信息,体现在cat /proc/devices, 一般自定义
	//参数3----文件描述集合
	//返回值,小于0报错
	key_major = register_chrdev(0, "key_drv_v1", &key_fops);
	if (key_major < 0)
	{
		printk("register chrdev faile!\n");
		return key_major;
	}
	printk("register chrdev ok!\n");

	//②、自动创建设备节点
	//创建设备的类别
	//参数1----设备的拥有者,当前模块,直接填THIS_MODULE
	//参数2----设备类别的名字,自定义
	//返回值:类别结构体指针,其实就是分配了一个结构体空间
	key_cls = class_create(THIS_MODULE, "key_class");
	printk("class create ok!\n");

	//③、创建设备
	//参数1----设备对应的类别
	//参数2----当前设备的父类,直接填NULL
	//参数3----设备节点关联的设备号
	//参数4----私有数据直接填NULL
	//参数5----设备节点的名字
	device_create(key_cls, NULL, MKDEV(key_major, 0), NULL, "mykey%d", 0);
	printk("device create ok!\n");

	//④、MIO45 44申请GPIO口
	ret = gpio_request(MIO_PIN_45, "key1");
	if (ret < 0)
	{
		printk("gpio request key1 error!\n");
		return ret;
	}

	ret = gpio_request(MIO_PIN_44, "key2");
	if (ret < 0)
	{
		printk("gpio request key2 error!\n");
		return ret;
	}

	ret = gpio_request(MIO_PIN_17, "led0");
	if (ret < 0)
	{
		printk("gpio request led0 error!\n");
		return ret;
	}

	ret = gpio_request(MIO_PIN_23, "led1");
	if (ret < 0)
	{
		printk("gpio request led1 error!\n");
		return ret;
	}
	return 0;
}

static __exit void key_drv_v1_exit(void)
{
	printk("----^v^-----key drv v1 exit\n");

	//释放按键GPIO
	gpio_free(MIO_PIN_45);
	gpio_free(MIO_PIN_44);
	gpio_free(MIO_PIN_17);
	gpio_free(MIO_PIN_23);
	//删除设备
	device_destroy(key_cls, MKDEV(key_major, 0));
	//删除类
	class_destroy(key_cls);
	//注销主设备号
	unregister_chrdev(key_major, "key_drv_v1");
}

//2、申明装载入口函数和卸载入口函数
module_init(key_drv_v1_init);
module_exit(key_drv_v1_exit);

//3、添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("msxbo");

应用代码 key_app_v1.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
	int fd = 0;
	int i = 0;
	int a[4] = {0};

	fd = open("/dev/mykey0", O_RDWR);
	if (fd < 0)
	{
		perror("open fail!\n");
		exit(1);
	}

	while (1)
	{
		read(fd, a, sizeof(a));
		printf("but1 value %d\n", a[0]);//MIO45
		printf("but2 value %d\n", a[1]);//MIO44
		sleep(1);
	}

	close(fd);

	return 0;
}

Makefile

#已经编译过的内核源码路径
KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

#当前路径
CURRENT_DIR = $(shell pwd)

MODULE = key_drv_v1
APP = key_app_v1

all : 
#进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules 
	rm -rf *.mod.c *.mod.o *.symvers *.order *.o

ifneq ($(APP), )
	$(CROSS_COMPILE)gcc $(APP).c -o  $(APP)
endif

clean : 
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
	rm $(APP)

#指定编译哪个文件
obj-m += $(MODULE).o

代码编写完成后,传至虚拟机开发环境交叉编译,然后传至开发板执行测试,如下图所示,同时能观察到随着按键的值的改变,led的亮灭也发生变化

0

Linux命令行控制led

通过命令行方式控制led亮灭,有读写两种命令

读命令,获取两个led的状态,0代表熄灭,1代表点亮

./my_led_app r

写命令,因为只有两个led需要控制,所以w后面的参数只有四种,分别为00,01,10,11,同样0代表熄灭,1代表点亮

./my_led_app w 00 
./my_led_app w 01
./my_led_app w 10
./my_led_app w 11

驱动代码 my_led_app.c

//1、添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>

#define ZYNQMP_GPIO_NR_GPIOS 174

//led0,led1
#define MIO_PIN_17 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 17)
#define MIO_PIN_23 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 23)

static int key_major;
static int ret = 0;
static struct class *key_cls;
int value[4] = {0};

int key_open(struct inode *inode, struct file *filp)
{
	printk("----^v^-----key open\n");
	ret = gpio_direction_output(MIO_PIN_17,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_17 fail!\n");
	}
		ret = gpio_direction_output(MIO_PIN_23,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_23 fail!\n");
	}
	return 0;
}

ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
	ret = copy_to_user(buf, value, count);
	if (ret < 0)
	{
		printk("gpio get value failed!\n");
		return ret;
	}

	return 0;
}
ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *fops) //用户发送,内核读取信息并打印
{
	int flag = 0;
	char writebuf[2]="00";
	flag = copy_from_user(writebuf, buf, count); //使用copy_from_user读取用户态发送过来的数据
	if (flag == 0)
	{
		printk(KERN_CRIT "Kernel receive data: %s\n", writebuf);
	}
	else
	{
		printk("Kernel receive data failed!\n");
	}
	value[0]=writebuf[0]-'0';
	value[1]=writebuf[1]-'0';
	gpio_set_value(MIO_PIN_17,value[0]);
	gpio_set_value(MIO_PIN_23,value[1]);
	printk("-led write-\n");
	return 0;
}

int key_close(struct inode *inode, struct file *filp)
{
	printk("----^v^-----key close\n");
	return 0;
}

const struct file_operations key_fops = {
	.open = key_open,
	.read = key_read,
	.write = key_write,
	.release = key_close,
};

//4、实现装载入口函数和卸载入口函数
static __init int key_drv_v1_init(void)
{
	printk("----^v^-----key drv v1 init\n");
	//①、申请主设备号
	//参数1----需要的主设备号,>0静态分配, ==0自动分配
	//参数2----设备的描述 信息,体现在cat /proc/devices, 一般自定义
	//参数3----文件描述集合
	//返回值,小于0报错
	key_major = register_chrdev(0, "key_drv_v1", &key_fops);
	if (key_major < 0)
	{
		printk("register chrdev faile!\n");
		return key_major;
	}
	printk("register chrdev ok!\n");

	//②、自动创建设备节点
	//创建设备的类别
	//参数1----设备的拥有者,当前模块,直接填THIS_MODULE
	//参数2----设备类别的名字,自定义
	//返回值:类别结构体指针,其实就是分配了一个结构体空间
	key_cls = class_create(THIS_MODULE, "key_class");
	printk("class create ok!\n");

	//③、创建设备
	//参数1----设备对应的类别
	//参数2----当前设备的父类,直接填NULL
	//参数3----设备节点关联的设备号
	//参数4----私有数据直接填NULL
	//参数5----设备节点的名字
	device_create(key_cls, NULL, MKDEV(key_major, 0), NULL, "myled%d", 0);
	printk("device create ok!\n");

	//MIO17 23申请GPIO口
	ret = gpio_request(MIO_PIN_17, "led0");
	if (ret < 0)
	{
		printk("gpio request key1 error!\n");
		return ret;
	}

	ret = gpio_request(MIO_PIN_23, "led1");
	if (ret < 0)
	{
		printk("gpio request key2 error!\n");
		return ret;
	}

	return 0;
}

static __exit void key_drv_v1_exit(void)
{
	printk("----^v^-----key drv v1 exit\n");

	//释放按键GPIO
	gpio_free(MIO_PIN_17);
	gpio_free(MIO_PIN_23);

	//删除设备
	device_destroy(key_cls, MKDEV(key_major, 0));
	//删除类
	class_destroy(key_cls);
	//注销主设备号
	unregister_chrdev(key_major, "key_drv_v1");
}

//2、申明装载入口函数和卸载入口函数
module_init(key_drv_v1_init);
module_exit(key_drv_v1_exit);

//3、添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("msxbo");

应用代码 my_led_app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	int fd = 0;
	int retvalue = 0;
	int i = 0;
	int a[4] = {0};
	char writebuf[2]="00";
	fd = open("/dev/myled0", O_RDWR);
	if (fd < 0)
	{
		perror("open fail!\n");
		exit(1);
	}
	switch (*argv[1]) //对操作数进行解析
	{
	case 'r':
		if (argc != 2) //进行鲁棒性检查
		{
			printf("Unknow operation, use the formate: ./APPNAME /dev/DRIVENAME r to read date from kernel.\n");
			return -1;
		}
		read(fd, a, sizeof(a));
		printf("led0 value %d\n", a[0]);//MIO17
		printf("led1 value %d\n", a[1]);//MIO23
		break;
	case 'w':
		if (argc != 3) //进行鲁棒性检查
		{
			printf("Unknow operation, use the formate: ./APPNAME /dev/DRIVENAME w \"USERDATE\" to write date to kernel.\n");
			return -2;
		}
		memcpy(writebuf, argv[2], strlen(argv[2])); //将内容拷贝到缓冲区
		retvalue = write(fd, writebuf, 2);			//写数据
		if (retvalue < 0)
		{
			printf("Write led failed!\n");
		}
		else
		{
			printf("Write led success!\n");
		}
		break;
	default:
		printf("Unknow Operation: %d\n", *argv[1]);
		break;
	}

	close(fd);

	return 0;
}

Makefile

#已经编译过的内核源码路径
KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

#当前路径
CURRENT_DIR = $(shell pwd)
APP = my_led_app

all : 
#进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules 
	rm -rf *.o *.mod.c *.mod.o *.symvers *.order
ifneq ($(APP), )
	$(CROSS_COMPILE)gcc $(APP).c -o $(APP)
endif

clean : 
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean


#指定编译哪个文件
obj-m += my_led_drv.o

代码编写完成后,传至虚拟机开发环境交叉编译,然后传至开发板执行测试,如下图所示,通过输入不同命令,会改变led的亮灭情况

1

led

最后附上led亮灭照片

00

01

10

11


本文章使用limfx的vscode插件快速发布