之前我写过FPGA串口控制led,Vitis使用MIO,EMIO,串口控制led,现在是Linux系统控制led,led系列实验是经典入门实验,不能不尝
本次我做了两个小实验,分别是 驱动控制按键控制led亮灭 和 驱动控制Linux命令行控制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的亮灭也发生变化
通过命令行方式控制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的亮灭情况
最后附上led亮灭照片
本文章使用limfx的vscode插件快速发布