苏州高端网站建设机构,seo推广有哪些,app开发公司名字,网站开发技术文档使用新字符设备驱动的一般模板和“gpio子系统”#xff0c;以及设备树#xff0c;驱动KEY和LED。
1、在stm32mp157d-atk.dts文件中添加“gpio_led”和“key0”节点
打开虚拟机上“VSCode”#xff0c;点击“文件”#xff0c;点击“打开文件夹”#xff0c;点击“zgq”以及设备树驱动KEY和LED。
1、在stm32mp157d-atk.dts文件中添加“gpio_led”和“key0”节点
打开虚拟机上“VSCode”点击“文件”点击“打开文件夹”点击“zgq”点击“linux”点击“atk-mp1”点击“linux”点击“my_linux”点击“linux-5.4.31”点击“确定”点击“stm32mp157d-atk.dts”。
stm32mp157d-atk.dts文件如下
/dts-v1/; #include stm32mp157.dtsi
#include stm32mp15xd.dtsi
#include stm32mp15-pinctrl.dtsi
#include stm32mp15xxaa-pinctrl.dtsi
#include stm32mp157-m4-srm.dtsi
#include stm32mp157-m4-srm-pinctrl.dtsi
#include stm32mp157d-atk.dtsi / {
model STMicroelectronics STM32MP157D eval daughter;
/*model属性用于描述开发板的名字或设备模块的信息*/
compatible st,stm32mp157d-ed1, st,stm32mp157;
/*compatible属性用于将设备和驱动绑定起来*/ chosen { /*chosen子节点*/
stdout-path serial0:115200n8;
}; aliases { /*aliases子节点*/
serial0 uart4; /*给uart4起个别名叫“serial0”*/
}; reserved-memory {
gpu_reserved: gpuf6000000 { /*gpu节点标签为gpu_reserved*/
reg 0xf6000000 0x8000000;
no-map;
}; optee_memory: opteefe000000 {
reg 0xfe000000 0x02000000;
no-map;
};
}; stm32mp1_led {
compatible atkstm32mp1-led;
/*compatible属性用于将设备stm32mp1_led和驱动“.ko”绑定起来*/
status okay;
reg 0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */
0X5000A000 0X04 /* GPIOI_MODER */
0X5000A004 0X04 /* GPIOI_OTYPER */
0X5000A008 0X04 /* GPIOI_OSPEEDR */
0X5000A00C 0X04 /* GPIOI_PUPDR */
0X5000A018 0X04 ; /* GPIOI_BSRR */
};
}; cpu1{
cpu-supply vddcore;
}; gpu {
contiguous-area gpu_reserved;
status okay;
}; optee {
status okay;
}; 修改后的stm32mp157d-atk.dts文件如下
/dts-v1/; #include stm32mp157.dtsi
#include stm32mp15xd.dtsi
#include stm32mp15-pinctrl.dtsi
#include stm32mp15xxaa-pinctrl.dtsi
#include stm32mp157-m4-srm.dtsi
#include stm32mp157-m4-srm-pinctrl.dtsi
#include stm32mp157d-atk.dtsi / {
model STMicroelectronics STM32MP157D eval daughter;
/*model属性用于描述开发板的名字或设备模块的信息*/
compatible st,stm32mp157d-ed1, st,stm32mp157;
/*compatible属性用于将设备和驱动绑定起来*/ chosen { /*chosen子节点*/
stdout-path serial0:115200n8;
}; aliases { /*aliases子节点*/
serial0 uart4; /*给uart4起个别名叫“serial0”*/
}; reserved-memory {
gpu_reserved: gpuf6000000 { /*gpu节点标签为gpu_reserved*/
reg 0xf6000000 0x8000000;
no-map;
}; optee_memory: opteefe000000 {
reg 0xfe000000 0x02000000;
no-map;
};
}; stm32mp1_led {
compatible atkstm32mp1-led;
/*compatible属性用于将设备stm32mp1_led和驱动“.ko”绑定起来*/
status okay;
reg 0X50000A28 0X04 /* RCC_MP_AHB4ENSETR */
0X5000A000 0X04 /* GPIOI_MODER */
0X5000A004 0X04 /* GPIOI_OTYPER */
0X5000A008 0X04 /* GPIOI_OSPEEDR */
0X5000A00C 0X04 /* GPIOI_PUPDR */
0X5000A018 0X04 ; /* GPIOI_BSRR */
}; gpio_led {
compatible zgq,led;
status okay;
led-gpio gpioi 0 GPIO_ACTIVE_LOW;
}; key0 {
compatible zgq,key;
status okay;
key-gpio gpiog 3 GPIO_ACTIVE_LOW;
};
}; cpu1{
cpu-supply vddcore;
}; gpu {
contiguous-area gpu_reserved;
status okay;
}; optee {
status okay;
};
见下图 2、编译设备树
1)、在VSCode终端输入“make dtbs回车”执行编译设备树
2)、输入“ls arch/arm/boot/uImage -l”
查看是否生成了新的“uImage”文件
3)、输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l”
查看是否生成了新的“stm32mp157d-atk.dtb”文件 拷贝输出的文件
4)、输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”执行文件拷贝准备烧录到EMMC
5)、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”执行文件拷贝准备烧录到EMMC 6)、输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”执行文件拷贝准备从tftp下载
7)、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”执行文件拷贝准备从tftp下载 8)、输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹 9)、输入“ls -l /home/zgq/linux/tftpboot/回车”查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹
输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车”
给“stm32mp157d-atk.dtb”文件赋予可执行权限
输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车” ,给“uImage”文件赋予可执行权限
输入“ls /home/zgq/linux/tftpboot/回车”查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹 3、查看“gpio_led”节点和“key0”节点
启动开发板从网络下载程序
输入“root”
输入“cd /proc/device-tree/回车”
切换到“/sys/firmware/devicetree/base”目录
输入“ls”查看“gpio_led”节点和“key0”节点设备是否存在
输入“cd gpio_led/回车”
输入“ls”查看“/sys/firmware/devicetree/base/gpio_led”目录下的文件和文件夹
输入“cat compatible回车”
输入“cat led-gpio回车”
输入“cat name回车”
输入“cat status回车”
输入“cd ../回车”返回到“/sys/firmware/devicetree/base”目录
输入“cd key0/回车”
输入“ls”查看“/sys/firmware/devicetree/base/key0”目录下的文件和文件夹
输入“cat compatible回车”
输入“cat key-gpio回车”
输入“cat name回车”
输入“cat status回车” 4、创建Button_LED目录
输入“cd /home/zgq/linux/Linux_Drivers/回车”
切换到“/home/zgq/linux/Linux_Drivers/”
输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”
输入“mkdir Button_LED回车”创建“Button_LED”目录
输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/” 5、添加“Button.c”
#include Button.h
#include linux/gpio.h
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include linux/of_gpio.h
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()
#include linux/delay.h
//Linux内核中用到的延时函数
//使能ndelay()udelay()mdelay() struct Button_dev strButton; int Get_gpio_Button_num(void);
int Button_gpio_request(void);
void Button_free(void);
int Read_Button(void); int Get_gpio_Button_num(void)
{ int ret 0; const char *str; /* 设置Button所使用的GPIO */ /* 1、获取设备节点strButton */ strButton.nd of_find_node_by_path(/key0); //path/key0使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“key0” //返回值:返回找到的节点如果为NULL表示查找失败。 if(strButton.nd NULL) { printk(key0 device node not find!\r\n); return -EINVAL; } /* 2.读取status属性 */ ret of_property_read_string(strButton.nd, status, str); //在key0节点中status okay; //指定的设备节点strButton.nd //pronamestatus给定要读取的属性名字 //out_stringstr:返回读取到的属性值 //返回值:0读取成功负值读取失败。 if(ret 0) return -EINVAL; if(strcmp(str, okay)) return -EINVAL; //strcmp(s1,s2),当s1s2时返回值为负数 //strcmp(s1,s2),当s12时返回值为正数 //strcmp(s1,s2),当s1s2时返回值为0 /* 3、获取compatible属性值并进行匹配 */ ret of_property_read_string(strButton.nd, compatible, str); //在key0节点中compatible zgq,key; //指定的设备节点strButton.nd //pronamecompatible给定要读取的属性名字 //out_stringstr:返回读取到的属性值 //返回值:0读取成功负值读取失败。 if(ret 0) { printk(key0 node: Failed to get compatible property\n); return -EINVAL; } if (strcmp(str, zgq,key)) { printk(key0 node: Compatible match failed\n); return -EINVAL; } /* 4、 根据设备树中的key-gpio属性得到key0所使用的key0编号 */ strButton.button_gpio_number of_get_named_gpio(strButton.nd, key-gpio, 0); //在key0节点中key-gpio gpiog 3 GPIO_ACTIVE_LOW //npstrButton.nd,指定的“设备节点” //propnamekey-gpio给定要读取的属性名字 //Index0,给定的GPIO索引为0 //返回值正值获取到的GPIO编号负值失败。 if(strButton.button_gpio_number 0) { printk(cant get key-gpio); return -EINVAL; } printk(key-gpio num %d\r\n, strButton.button_gpio_number); //打印结果为“key-gpio num 99“ //因为GPIO编号是从0开始的GPIOG端口的序号是6每个端口有16个IO口因此GPIOI0的编号为6*16 3 99 return 0;
} int Button_gpio_request(void)
{ int ret 0; /* 5.向gpio子系统申请使用“gpio编号” */ ret gpio_request(strButton.button_gpio_number, Button0); //gpiostrButton.button_gpio_number指定要申请的“gpio编号” //labelButton给这个gpio引脚设置个名字为Button //返回值0申请“gpio编号”成功;其他值申请“gpio编号”失败 if (ret) { printk(KERN_ERR Button: Failed to request key0\n); return ret; } /* 6、设置PG3为输入模式*/ ret gpio_direction_input(strButton.button_gpio_number); //gpiostrButton.button_gpio_number,指定的“gpio编号” if(ret 0) { printk(cant set gpio!\r\n); } return 0;
} //函数功能释放Button的gpio
void Button_free(void)
{ gpio_free(strButton.button_gpio_number);
} //读取按键的值
int Read_Button(void)
{ int value; u8 ch; ch0; if (gpio_get_value(strButton.button_gpio_number) 0) ch1; mdelay(150);//延时150毫秒 if (gpio_get_value(strButton.button_gpio_number) 0) ch(u8)(ch1); if(ch0x02) { /* Button按下 */ valueBUTTON_VALUE; /* 有效的按键值 */ } else { valueINVABUTTON; /* 无效的按键值 */ } return value;
} 6、添加“Button.h”
#ifndef __BUTTON_H
#define __BUTTON_H #include linux/types.h
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include linux/of.h //使能device_node结构 #define BUTTON_VALUE 1 /* 按键值 */
#define INVABUTTON 0 /* 无效的按键值 */ struct Button_dev{ struct device_node *nd; /*设备节点*/ int button_gpio_number; /*Button所使用的GPIO编号*/
};
extern struct Button_dev strButton; extern int Get_gpio_Button_num(void);
extern int Button_gpio_request(void);
extern void Button_free(void);
extern int Read_Button(void); #endif 7、添加“Button_drv.c”
#include Button_drv.h
#include Button.h
#include linux/types.h
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include linux/ide.h
//使能copy_from_user(),copy_to_user()
#include linux/module.h
//使能ButtonDriver_init(),ButtonDriver_exit()
#include linux/gpio.h
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include linux/spinlock.h
//使能DEFINE_SPINLOCK()spin_lock_init(),spin_lock(),spin_trylock(),spin_is_locked()
//spin_lock_irq(),spin_unlock_irq(),spin_lock_irqsave(),spin_unlock_irqrestore() #define ButtonDriver_CNT 1 //定义设备数量为1
#define ButtonDriver_NAME ButtonDriver //定义设备的名字 struct ButtonDriver_dev strButtonDriver; /* 打开设备 */
static int ButtonDriver_open(struct inode *inode, struct file *filp)
{ /*通过判断原子变量的值来检查Button有没有被别的应用使用*/ if (!atomic_dec_and_test(strButtonDriver.lock)) { //当strButtonDriver.lock.counter1时atomic_dec_and_test()返回1 //从strButtonDriver.lock.counter减1如果结果为0就返回1否则返回0; atomic_inc(strButtonDriver.lock);/*小于0的话就加1,使其原子变量等于0*/ return -EBUSY; /* Button被使用返回忙*/ } filp-private_data strButtonDriver; /*设置私有数据*/ printk(ButtonDriver_open!\r\n); return 0;
} /* 从设备读取数据保存到首地址为buf的数据块中长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t ButtonDriver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{ int ret 0; int value; valueRead_Button();//读取按键的值 ret copy_to_user(buf, value, sizeof(value)); //将value拷贝到buf[]中 return ret;
} /* 向设备写数据将数据块首地址为buf的数据长度为cnt个字节发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t ButtonDriver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{ return 0;
} /* 关闭/释放设备 */
static int ButtonDriver_release(struct inode *inode, struct file *filp)
{ struct ButtonDriver_dev *dev filp-private_data; atomic_inc(dev-lock); /*关闭驱动文件的时候释放原子变量便于其它线程使用*/ printk(ButtonDriver_release!\r\n); return 0;
} /*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations ButtonDriver_fops { .owner THIS_MODULE, .open ButtonDriver_open, .read ButtonDriver_read, .write ButtonDriver_write, .release ButtonDriver_release,
}; /*驱动入口函数 */
static int __init ButtonDriver_init(void)
{ int ret; strButtonDriver.lock (atomic_t)ATOMIC_INIT(0); /*初始化原子变量*/ atomic_set(strButtonDriver.lock, 1); /*原子变量初始值strButtonDriver.lock.counter1*/ retGet_gpio_Button_num();//读引脚编号 if(ret 0) return ret; /* 1、申请“gpio编号”*/ retButton_gpio_request();//申请“gpio编号” if(ret 0) return ret;//向gpio子系统申请使用“gpio编号” 失败 /*2、申请设备号*/ strButtonDriver.major0; if(strButtonDriver.major)/*如果指定了主设备号*/ { strButtonDriver.devid MKDEV(strButtonDriver.major, 0); //输入参数strButtonDriver.major为“主设备号” //输入参数0为“次设备号”大部分驱动次设备号都选择0 //将strButtonDriver.major左移20位再与0相或就得到“Linux设备号” retregister_chrdev_region( strButtonDriver.devid,\ ButtonDriver_CNT, \ ButtonDriver_NAME ); //strButtonDriver.devid表示起始设备号 //ButtonDriver_CNT表示次设备号的数量 //ButtonDriver_NAME表示设备名 if(ret 0) goto free_gpio; } else { /* 没有定义设备号 */ retalloc_chrdev_region( strButtonDriver.devid,\ 0, \ ButtonDriver_CNT,\ ButtonDriver_NAME); /* 申请设备号 */ //strButtonDriver.devid保存申请到的设备号 //0次设备号的起始地址 //ButtonDriver_CNT要申请的次设备号数量 //ButtonDriver_NAME表示“设备名字” if(ret 0) goto free_gpio; strButtonDriver.major MAJOR(strButtonDriver.devid); /* 获取分配号的主设备号 */ //输入参数strButtonDriver.devid为“Linux设备号” //将strButtonDriver.devid右移20位得到“主设备号” strButtonDriver.minor MINOR(strButtonDriver.devid); /* 获取分配号的次设备号 */ //输入参数strButtonDriver.devid为“Linux设备号” //将strButtonDriver.devid与0xFFFFF相与后得到“次设备号” } /*3、注册字符设备*/ strButtonDriver.cdev.owner THIS_MODULE; //使用THIS_MODULE将owner指针指向当前这个模块 cdev_init(strButtonDriver.cdev,ButtonDriver_fops); //注册字符设备初始化“字符设备结构变量strButtonDriver.cdev” //strButtonDriver.cdev是等待初始化的结构体变量 //ButtonDriver_fops就是字符设备文件操作函数集合 /*4、添加字符设备*/ retcdev_add(strButtonDriver.cdev,strButtonDriver.devid,ButtonDriver_CNT); //添加字符设备 /*strButtonDriver.cdev表示指向要添加的字符设备即字符设备结构strButtonDriver.cdev变量*/ //strButtonDriver.devid表示设备号 //ButtonDriver_CNT表示需要添加的设备数量 if(ret 0 ) //添加字符设备失败 goto del_register; printk(dev id major %d,minor %d\r\n, strButtonDriver.major, strButtonDriver.minor); printk(ButtonDriver_init is ok!!!\r\n); /*5、自动创建设备节点 */ strButtonDriver.class class_create(THIS_MODULE, ButtonDriver_NAME); if (IS_ERR(strButtonDriver.class)){ goto del_cdev; } /*6、创建设备 */ strButtonDriver.device device_create(strButtonDriver.class, NULL, strButtonDriver.devid, NULL, ButtonDriver_NAME); //创建设备 //设备要创建在strButtonDriver.class类下面 //NULL表示没有父设备 //strButtonDriver.devid是设备号; //参数drvdataNULL设备没有使用数据 //ButtonDriver_NAME是设备名字 //如果设置fmtButtonDriver_NAME 的话就会生成/dev/ButtonDriver_NAME设备文件。 //返回值就是创建好的设备。 if (IS_ERR(strButtonDriver.device)){ goto destroy_class; } return 0; destroy_class: class_destroy(strButtonDriver.class); //删除类 //strButtonDriver.class就是要删除的类 del_cdev: cdev_del(strButtonDriver.cdev); //删除字符设备 //strButtonDriver.cdev表示指向需要删除的字符设备即字符设备结构strButtonDriver.cdev变量 del_register: unregister_chrdev_region(strButtonDriver.devid, ButtonDriver_CNT); /* 释放设备号 */ //strButtonDriver.devid需要释放的起始设备号 //ButtonDriver_CNT需要释放的次设备号数量 free_gpio://申请设备号失败 /*释放gpio编号*/ Button_free(); return -EIO;
} /*驱动出口函数 */
static void __exit ButtonDriver_exit(void)
{ /*1、删除字符设备*/ cdev_del(strButtonDriver.cdev); /*删除字符设备*/ /*strButtonDriver.cdev表示指向需要删除的字符设备即字符设备结构strButtonDriver.cdev变量*/ /*2、 释放设备号 */ unregister_chrdev_region(strButtonDriver.devid, ButtonDriver_CNT); /*释放设备号 */ //strButtonDriver.devid需要释放的起始设备号 //ButtonDriver_CNT需要释放的次设备号数; /*3、 删除设备 */ device_destroy(strButtonDriver.class, strButtonDriver.devid); //删除创建的设备 //strButtonDriver.class是要删除的设备所处的类 //strButtonDriver.devid是要删除的设备号 /*4、删除类*/ class_destroy(strButtonDriver.class); //删除类 //strButtonDriver.class就是要删除的类 /*5、释放gpio编号*/ Button_free();
} module_init(ButtonDriver_init);
//指定ButtonDriver_init()为驱动入口函数
module_exit(ButtonDriver_exit);
//指定ButtonDriver_exit()为驱动出口函数 MODULE_AUTHOR(Zhanggong);//添加作者名字
MODULE_LICENSE(GPL);//LICENSE采用“GPL协议”
MODULE_INFO(intree,Y);
//去除显示“loading out-of-tree module taints kernel.” 8、添加“Button_drv.h”
#ifndef __BUTTON_DRIVER_H
#define __BUTTON_DRIVER_H #include linux/types.h
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include linux/cdev.h //使能cdev结构
#include linux/cdev.h //使能class结构和device结构 struct ButtonDriver_dev{ dev_t devid; /*声明32位变量devid用来给保存设备号*/ int major; /*主设备号*/ int minor; /*次设备号*/ struct cdev cdev; /*字符设备结构变量cdev */ struct class *class; /*类*/ struct device *device; /*设备*/ atomic_t lock; /*原子变量*/
};
extern struct ButtonDriver_dev strButtonDriver; #endif 9、添加“LED.c”
#include LED.h
#include linux/gpio.h
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include linux/of_gpio.h
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio() struct MyLED_dev strMyLED; int Get_gpio_led_num(void);
int led_GPIO_request(void);
void MyLED_free(void);
void led_switch(u8 sta); int Get_gpio_led_num(void)
{ int ret 0; const char *str; /* 设置LED所使用的GPIO */ /* 1、获取设备节点strMyLED */ strMyLED.nd of_find_node_by_path(/gpio_led); //path/gpio_led使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“gpio_led” //返回值:返回找到的节点如果为NULL表示查找失败。 if(strMyLED.nd NULL) { printk(gpio_led node not find!\r\n); return -EINVAL; } /* 2.读取status属性 */ ret of_property_read_string(strMyLED.nd, status, str); //在gpio_led节点中status okay; //指定的设备节点strMyLED.nd //pronamestatus给定要读取的属性名字 //out_stringstr:返回读取到的属性值 //返回值:0读取成功负值读取失败。 if(ret 0) return -EINVAL; if (strcmp(str, okay)) return -EINVAL; //strcmp(s1,s2),当s1s2时返回值为负数 //strcmp(s1,s2),当s12时返回值为正数 //strcmp(s1,s2),当s1s2时返回值为0 /* 3、获取compatible属性值并进行匹配 */ ret of_property_read_string(strMyLED.nd, compatible, str); //在gpio_led节点中compatible zgq,led; //指定的设备节点strMyLED.nd //pronamecompatible给定要读取的属性名字 //out_stringstr:返回读取到的属性值 //返回值:0读取成功负值读取失败。 if(ret 0) { printk(gpio_led node: Failed to get compatible property\n); return -EINVAL; } if (strcmp(str, zgq,led)) { printk(gpio_led node: Compatible match failed\n); return -EINVAL; } /* 4、 根据设备树中的led-gpio属性得到LED所使用的LED编号 */ strMyLED.led_gpio_number of_get_named_gpio(strMyLED.nd, led-gpio, 0); //在gpio_led节点中led-gpio gpioi 0 GPIO_ACTIVE_LOW //npstrMyLED.nd,指定的“设备节点” //propnameled-gpio给定要读取的属性名字 //Index0,给定的GPIO索引为0 //返回值正值获取到的GPIO编号负值失败。 if(strMyLED.led_gpio_number 0) { printk(cant get led-gpio); return -EINVAL; } printk(led-gpio num %d\r\n, strMyLED.led_gpio_number); //打印结果为“led-gpio num 128“ //因为GPIO编号是从0开始的GPIOI端口的序号是8每个端口有16个IO口因此GPIOI0的编号为8*16128 return 0;
} int led_GPIO_request(void)
{ int ret 0; /* 5.向gpio子系统申请使用“gpio编号” */ ret gpio_request(strMyLED.led_gpio_number, LED-GPIO); //gpiostrMyLED.led_gpio_number指定要申请的“gpio编号” //IabelLED-GPIO给这个gpio引脚设置个名字为LED-GPIO //返回值0申请“gpio编号”成功;其他值申请“gpio编号”失败 if (ret) { printk(KERN_ERR strMyLED: Failed to request led-gpio\n); return ret; } /* 6、设置PI0为输出并且输出高电平默认关闭LED灯 */ ret gpio_direction_output(strMyLED.led_gpio_number, 1); //gpiostrMyLED.led_gpio_number,指定的“gpio编号”这里是128对应的是GI0引脚 //value1设置引脚输出高电平 //返回值0设置“引脚输出为vakued的值”成功;负值设置“引脚输出为vakued的值”失败。 if(ret 0) { printk(cant set gpio!\r\n); } return 0;
} //函数功能释放MyLED的gpio
void MyLED_free(void)
{ gpio_free(strMyLED.led_gpio_number);
} void led_switch(u8 sta)
{
if(sta LEDON) { gpio_set_value(strMyLED.led_gpio_number, 0); /* 打开LED灯 */
}
else if(sta LEDOFF) { gpio_set_value(strMyLED.led_gpio_number, 1); /* 关闭LED灯 */
}
} 10、添加“LED.h”
#ifndef __LED_H
#define __LED_H #include linux/types.h
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include linux/of.h //使能device_node结构 #define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */ struct MyLED_dev{ struct device_node *nd; /*设备节点*/ int led_gpio_number; /*led所使用的GPIO编号*/
};
extern struct MyLED_dev strMyLED; extern int Get_gpio_led_num(void);
extern int led_GPIO_request(void);
extern void MyLED_free(void);
extern void led_switch(u8 sta); #endif 11、添加“LED_drv.c”
#include LED_drv.h
#include LED.h
#include linux/types.h
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include linux/ide.h
//使能copy_from_user(),copy_to_user()
#include linux/module.h
//使能LEDDriver_init(),LEDDriver_exit()
#include linux/gpio.h
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//gpio_direction_output(),gpio_get_value(),gpio_set_value() #define LEDDriver_CNT 1 //定义设备数量为1
#define LEDDriver_NAME LEDDriver //定义设备的名字 struct LEDDriver_dev strLEDDriver; /* 打开设备 */
static int LEDDriver_open(struct inode *inode, struct file *filp)
{ /*通过判断原子变量的值来检查LED有没有被别的应用使用*/ if (!atomic_dec_and_test(strLEDDriver.lock)) { //当strLEDDriver.lock.counter1时atomic_dec_and_test()返回1 //从strLEDDriver.lock.counter减1如果结果为0就返回1否则返回0; atomic_inc(strLEDDriver.lock);/*小于0的话就加1,使其原子变量等于0*/ return -EBUSY; /* LED被使用返回忙*/ } filp-private_data strLEDDriver; /*设置私有数据*/ printk(LEDDriver_open!\r\n); return 0;
} /* 从设备读取数据保存到首地址为buf的数据块中长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t LEDDriver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{ return 0;
} /* 向设备写数据将数据块首地址为buf的数据长度为cnt个字节发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t LEDDriver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{ int ret 0; unsigned char databuf[1]; unsigned char ledstat; ret copy_from_user(databuf, buf, cnt);
//将buf[]中的前cnt个字节拷贝到databuf[]中 if(ret 0){ printk(kernel write failed!\r\n); ret -EFAULT; } ledstat databuf[0];/*获取到应用传递进来的开关灯状态*/ led_switch(ledstat);/*执行开灯或执行关灯*/ return ret;
} /* 关闭/释放设备 */
static int LEDDriver_release(struct inode *inode, struct file *filp)
{ struct LEDDriver_dev *dev filp-private_data; atomic_inc(dev-lock); /*关闭驱动文件的时候释放原子变量便于其它线程使用*/ printk(LEDDriver_release!\r\n); return 0;
} /*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations LEDDriver_fops { .owner THIS_MODULE, .open LEDDriver_open, .read LEDDriver_read, .write LEDDriver_write, .release LEDDriver_release,
}; /*驱动入口函数 */
static int __init LEDDriver_init(void)
{ int ret; strLEDDriver.lock (atomic_t)ATOMIC_INIT(0); /*初始化原子变量*/ atomic_set(strLEDDriver.lock, 1); /*原子变量初始值strLEDDriver.lock.counter1*/ retGet_gpio_led_num();//读引脚编号 if(ret 0) return ret; /* 1、申请“gpio编号”*/ retled_GPIO_request();//申请“gpio编号” if(ret 0) return ret;//向gpio子系统申请使用“gpio编号” 失败 /*2、申请设备号*/ strLEDDriver.major0; if(strLEDDriver.major)/*如果指定了主设备号*/ { strLEDDriver.devid MKDEV(strLEDDriver.major, 0); //输入参数strLEDDriver.major为“主设备号” //输入参数0为“次设备号”大部分驱动次设备号都选择0 //将strLEDDriver.major左移20位再与0相或就得到“Linux设备号”
retregister_chrdev_region( strLEDDriver.devid,\ LEDDriver_CNT, \ LEDDriver_NAME ); //strLEDDriver.devid表示起始设备号 //LEDDriver_CNT表示次设备号的数量 //LEDDriver_NAME表示设备名 if(ret 0) goto free_gpio; } else { /* 没有定义设备号 */
retalloc_chrdev_region( strLEDDriver.devid,\ 0, \ LEDDriver_CNT,\ LEDDriver_NAME); /* 申请设备号 */ //strLEDDriver.devid保存申请到的设备号 //0次设备号的起始地址 //LEDDriver_CNT要申请的次设备号数量 //LEDDriver_NAME表示“设备名字” if(ret 0) goto free_gpio; strLEDDriver.major MAJOR(strLEDDriver.devid); /* 获取分配号的主设备号 */ //输入参数strLEDDriver.devid为“Linux设备号” //将strLEDDriver.devid右移20位得到“主设备号” strLEDDriver.minor MINOR(strLEDDriver.devid); /* 获取分配号的次设备号 */ //输入参数strLEDDriver.devid为“Linux设备号” //将strLEDDriver.devid与0xFFFFF相与后得到“次设备号” } /*3、注册字符设备*/ strLEDDriver.cdev.owner THIS_MODULE; //使用THIS_MODULE将owner指针指向当前这个模块 cdev_init(strLEDDriver.cdev,LEDDriver_fops); //注册字符设备初始化“字符设备结构变量strLEDDriver.cdev” //strLEDDriver.cdev是等待初始化的结构体变量 //LEDDriver_fops就是字符设备文件操作函数集合 /*4、添加字符设备*/ retcdev_add(strLEDDriver.cdev,strLEDDriver.devid,LEDDriver_CNT); //添加字符设备 /*strLEDDriver.cdev表示指向要添加的字符设备即字符设备结构strLEDDriver.cdev变量*/ //strLEDDriver.devid表示设备号 //LEDDriver_CNT表示需要添加的设备数量 if(ret 0 ) //添加字符设备失败 goto del_register; printk(dev id major %d,minor %d\r\n, strLEDDriver.major, strLEDDriver.minor); printk(LEDDriver_init is ok!!!\r\n); /*5、自动创建设备节点 */ strLEDDriver.class class_create(THIS_MODULE, LEDDriver_NAME); if (IS_ERR(strLEDDriver.class)){ goto del_cdev; } /*6、创建设备 */ strLEDDriver.device device_create(strLEDDriver.class, NULL, strLEDDriver.devid, NULL, LEDDriver_NAME); //创建设备 //设备要创建在strLEDDriver.class类下面 //NULL表示没有父设备 //strLEDDriver.devid是设备号; //参数drvdataNULL设备没有使用数据 //LEDDriver_NAME是设备名字 //如果设置fmtLEDDriver_NAME 的话就会生成/dev/LEDDriver_NAME设备文件。 //返回值就是创建好的设备。 if (IS_ERR(strLEDDriver.device)){ goto destroy_class; } return 0; destroy_class: class_destroy(strLEDDriver.class); //删除类 //strLEDDriver.class就是要删除的类 del_cdev: cdev_del(strLEDDriver.cdev); //删除字符设备 //strLEDDriver.cdev表示指向需要删除的字符设备即字符设备结构strLEDDriver.cdev变量 del_register: unregister_chrdev_region(strLEDDriver.devid, LEDDriver_CNT); /* 释放设备号 */ //strLEDDriver.devid需要释放的起始设备号 //LEDDriver_CNT需要释放的次设备号数量 free_gpio://申请设备号失败 /*释放gpio编号*/ MyLED_free(); return -EIO;
} /*驱动出口函数 */
static void __exit LEDDriver_exit(void)
{ /*1、删除字符设备*/ cdev_del(strLEDDriver.cdev); /*删除字符设备*/ /*strLEDDriver.cdev表示指向需要删除的字符设备即字符设备结构strLEDDriver.cdev变量*/ /*2、 释放设备号 */ unregister_chrdev_region(strLEDDriver.devid, LEDDriver_CNT); /*释放设备号 */ //strLEDDriver.devid需要释放的起始设备号 //LEDDriver_CNT需要释放的次设备号数; /*3、 删除设备 */ device_destroy(strLEDDriver.class, strLEDDriver.devid); //删除创建的设备 //strLEDDriver.class是要删除的设备所处的类 //strLEDDriver.devid是要删除的设备号 /*4、删除类*/ class_destroy(strLEDDriver.class); //删除类 //strLEDDriver.class就是要删除的类 /*5、释放gpio编号*/ MyLED_free();
} module_init(LEDDriver_init);
//指定LEDDriver_init()为驱动入口函数
module_exit(LEDDriver_exit);
//指定LEDDriver_exit()为驱动出口函数 MODULE_AUTHOR(Zhanggong);//添加作者名字
MODULE_LICENSE(GPL);//LICENSE采用“GPL协议”
MODULE_INFO(intree,Y);
//去除显示“loading out-of-tree module taints kernel.” 12、添加“LED_drv.h”
#ifndef __LED_DRIVER_H
#define __LED_DRIVER_H #include linux/types.h
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include linux/cdev.h
//使能cdev结构
//使能class结构和device结构 struct LEDDriver_dev{ dev_t devid; /*声明32位变量devid用来给保存设备号*/ int major; /*主设备号*/ int minor; /*次设备号*/ struct cdev cdev; /*字符设备结构变量cdev */ struct class *class; /*类*/ struct device *device; /*设备*/ atomic_t lock; /*原子变量*/
};
extern struct LEDDriver_dev strLEDDriver; #endif 13、添加ButtonLED_APP.c
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include string.h #include unistd.h
//Linux系统编程下用到的延时函数
//使能usleep(),sleep() //#include delay.h
//Linux内核中用到的延时函数
//使能ndelay()udelay()mdelay() #define KEY0VALUE 1 /* 按键值 */
#define INVAKEY 0 /* 无效的按键值 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */ /*
参数argc: argv[]数组元素个数
参数argv[]:是一个指针数组
返回值: 0 成功;其他 失败
./ButtonLED_APP /dev/MyButton /dev/MyLED
*/
int main(int argc, char *argv[])
{ int fd_button; int fd_led; int retvalue; int keyvalue; int status; /* 1. 判断参数 */ if(argc ! 3) { printf(Error Usage!\r\n); return -1; } //argv[]是指向输入参数“./ButtonLED_APP /dev/MyButton /dev/MyLED” /* 2. 打开文件 */ fd_button open(argv[1], O_RDWR); //如果打开“/dev/MyButton”文件成功则fd_button为“文件描述符”,argv[1]/dev/MyButton if(fd_button 0) { printf(Cant open file %s\r\n, argv[1]); return -1; } fd_led open(argv[2], O_RDWR); //如果打开“/dev/MyLED”文件成功则fd_led为“文件描述符”,argv[2]“/dev/MyLED” if(fd_led 0) { printf(Cant open file %s\r\n, argv[2]); return -1; } /* 3. 读文件 */ while(1) { read(fd_button, keyvalue, sizeof(keyvalue));
if (keyvalue KEY0VALUE) { //如果按键按下 printf(KEY0 Press, value %#X\r\n, keyvalue);/* 按下 */ status LEDON; write(fd_led, status, 1); } else { //如果按键松开 status LEDOFF; write(fd_led, status, 1); //file结构指针变量fd_led表示要打开的设备文件 //bufstatus表示用户数据块的首地址 //cnt1表示用户数据的长度单位为字节
} } /* 关闭设备 */ retvalue close(fd_button); //fd_button表示要关闭的“文件描述符” //返回值等于0表示关闭成功 //返回值小于0表示关闭失败 if(retvalue 0) { printf(Cant close file %s\r\n, argv[1]); return -1; } retvalue close(fd_led); //fd_led表示要关闭的“文件描述符” //返回值等于0表示关闭成功 //返回值小于0表示关闭失败 if(retvalue 0) { printf(Cant close file %s\r\n, argv[2]); return -1; } return 0;
} 12、添加“Makefile”
#Linux一个Makefile编译多个内核驱动
KERNELDIR : /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH : $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
MyAPP : ButtonLED_APP obj-m : MyButton.o MyLED.o MyButton-y : Button_drv.o Button.o
MyLED-y : LED_drv.o LED.o CC : arm-none-linux-gnueabihf-gcc drv:
$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modules app:
$(CC) $(MyAPP).c -o $(MyAPP) clean:
$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean
rm $(MyAPP) install:
sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f 13、创建“c_cpp_properties.json” 的文件
按下“CtrlShiftP”,打开VSCode控制台然后输入“C/C:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件修改c_cpp_properties.json内容如下所示:
{ configurations: [ { name: Linux, includePath: [ ${workspaceFolder}/**, /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31, /home/zgq/linux/Linux_Drivers/Button_LED, /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include, /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include, /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated ], defines: [], compilerPath: /usr/bin/gcc, cStandard: gnu11, cppStandard: gnu14, intelliSenseMode: gcc-x64 } ], version: 4
} 14、编译
输入“make clean回车”
输入“make drv回车”
输入“make app回车”
输入“make install回车”
输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”查看是存在“ButtonLED_APP,MyButton.ko和MyLED.ko” 15、测试
启动开发板从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/回车”
切换到“/lib/modules/5.4.31/”目录
注意“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下但在开发板中却是位于根目录中。
输入“ls -l”查看“ButtonLED_APP,MyButton.ko和MyLED.ko”是否存在
输入“depmod”,驱动在第一次执行时需要运行“depmod”
输入“modprobe MyButton.ko”加载“MyButton.ko”模块
输入“modprobe MyLED.ko”加载“MyLED.ko”模块
输入“lsmod”查看有哪些驱动在工作 输入“ls /dev/ButtonDriver -l回车”发现节点文件“/dev/ButtonDriver”
输入“ls /dev/LEDDriver -l回车”发现节点文件“/dev/LEDDriver” 输入“./ButtonLED_APP /dev/ButtonDriver /dev/LEDDriver回车”
按下按钮等待串口输出“KEY0 Press, value 0X1”同时LED会亮。