1.写LED驱动程序
1.1第一步 先看原理图
1.2第二步 再看芯片手册:使能GPIO
1.3 第三步 设置引脚是GPIO功能
1.4 第四步 设置引脚是输出
1.5 第五步 设置输出电平
2.最简单的LED驱动程序
后面的LED驱动程序为了容易扩展,引入了很多数据结构。对C语言的要求有点高,所以我们基于Hello驱动程序先写出最简单的LED驱动程序。
2A.1 LED操作方法_基于IMX6ULL
视频中的文档,放在GIT仓库中:
01_all_series_quickstart\ 05_嵌入式Linux驱动开发基础知识\doc_pic\pic\6A.最简单的LED驱动程序\ 03_IMX6ULL的LED操作方法.pptx
2A.2 最简单的LED驱动程序编程_基于IMX6ULL
视频中的源码文档,放在GIT仓库中:
01_all_series_quickstart\ 05_嵌入式Linux驱动开发基础知识\source\ 02_led_drv\ 00_led_simple\imx6ull
2A.2.1 字符设备驱动程序框架
字符设备驱动程序的框架:
编写驱动程序的套路:
① 确定主设备号,也可以让内核分配
② 定义自己的file_operations结构体
③实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
④把file_operations结构体告诉内核:register_chrdev
⑤谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
⑦其他完善:提供设备信息,自动创建设备节点:class_create, device_create
驱动怎么操作硬件?通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
驱动怎么和APP传输数据?通过copy_to_user、copy_from_user这2个函数。
下面就是写驱动程序的模板!!
******************************************************** //这个是驱动程序的框架,以后可以直接套用,在上面添加和修改 #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/mutex.h> #include <linux/wait.h> #include <linux/uaccess.h> #include <asm/io.h> /*定义主设备号*/ static struct major; /*定义led class类*/ static struct class *led_class; /*结构体中led_open函数*/ static int led_open(struct inode *inode, struct file *file) { /* *enable gpio *configure pin as gpio *configure gpio as output */ return 0; } /*结构体中led_write函数*/ static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos) { char val;//val来自内核定义 /*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/ copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。 /*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/ if(val) { /*set gpio to let led on */ } else { /*set gpio to let led off */ } return 0; } /*定义结构体*/ static struct file_operations led_fops ={ .owner = THIS_MODULE, .open = led_open, .write = led_write, }; /*入口函数*/ static int __init led_init(void) { printk("%s %s %d \n",_File_,_FUNCTION_,_LINE_); major=register_chrdev(0, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led” /*ioremap*/ led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。 device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/ return 0; } /*出口函数*/ static void led_exit(void) { device_destroy(led_class, NULL); class_destroy(led_class); unregister_chrdev(major,"100ask_led" ) } /*完善入口函数*/ module_init(led_init); /*完善出口函数*/ module_exit(led_init); /*设置为GPL协议*/ MODULE_LICENSE("GPL");//指定GPL协议
2A.2.2 实现什么功能
先编写驱动程序:
实现led_open函数,在里面初始化LED引脚。 实现led_write函数,在里面根据APP传来的值控制LED。
#include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/mutex.h> #include <linux/wait.h> #include <linux/uaccess.h> #include <asm/io.h> /*定义主设备号*/ static struct major; /*定义led class类*/ static struct class *led_class; /*registers*/ // IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 static volatile unsigned int*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3; // GPIO5_GDIR地址:0x020AC004 static volatile unsigned int*GPIO5_GDIR; //GPIO5_DR地址:0x020AC000 static volatile unsigned int*GPIO5_DR; /*结构体中led_open函数*/ static int led_open(struct inode *inode, struct file *file) { /* *enable gpio *configure gpio5_3 as gpio *configure gpio5_3 as output */ //把引脚配置成gpio *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= ~0x05; //把引脚配置成输出 *GPIO5_GDIR |=(1<<3); return 0; } /*结构体中led_write函数*/ static ssize_t led_write(struct file *file ,const char __user * buf, size_t count, loff_t *ppos) { char val;//val来自内核定义 /*copy_from_user:get data from app,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/ copy_from_user(&val, buf, 1);//把app(buf)中的数据拷贝至内核空间(val),拷贝一个字节。 /*to set gpio register :out 1/0,会涉及到寄存器,那么涉及到物理层,需要用到虚拟地址*/ if(val) { /*set gpio to let led on */ *GPIO5_DR &=~(1<<3); } else { /*set gpio to let led off */ *GPIO5_DR &=(1<<3); } return 0; } /*定义结构体*/ static struct file_operations led_fops ={ .owner = THIS_MODULE, .open = led_open, .write = led_write, }; /*入口函数*/ static int __init led_init(void) { printk("%s %s %d \n",_File_,_FUNCTION_,_LINE_); major=register_chrdev(0, "100ask_led", &led_fops);//unsigned int major;设置为0.const char * name设置为“100ask_led” /*ioremap*/ // IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3=ioremap (0x02290000 + 0x14, 4); // GPIO5_GDIR地址:0x020AC004 GPIO5_GDIR=ioremap (0x020AC004 + 0x14, 4); //GPIO5_DR地址:0x020AC000 GPIO5_DR=ioremap (0x020AC000 + 0x14, 4); static inline void __iomem * ioremap (unsigned long offset, unsigned long size) { return __ioremap(offset, size, 0); } led_class=class_create(THIS_MODULE, "myled");//这个变量的结构体名字被称为class,给这个模块创造一个名字为class类的结构。 device_create(led_class, NULL, MKDEV(major, 0),NULL, "myled");/**设备就会给我们创建一个/dev/myled设备结点。*/ return 0; } /*出口函数*/ static void led_exit(void) { device_destroy(led_class, NULL) class_destroy(led_class); unregister_chrdev(major,"100ask_led" ) } /*完善入口函数*/ module_init(led_init); /*完善出口函数*/ module_exit(led_init); /*设置为GPL协议*/ MODULE_LICENSE("GPL");//指定GPL协议
再编写测试程序。
//ledtest /dev/myled on //ledtest /dev/myled off #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(int argc,char **argc) { int fd; char status = 0; if(argc != 3) { printf("Usage: %s <dev> <on|off>\n",argv[0]); printf(" eg: %s /dev/myled on\n",argv[0]); printf(" eg: %s /dev/myled off\n",argv[0]); return -1; } //open fd=open(argv[1],O_RDWR);//open函数怎么使用呢,打开虚拟机远程登录。man 2 open, 看参数和头文件 if(fd<0) { printf("can not open %s\n ",argv[0]); return -1; } // write if(strcmp(argv[2],"on")==0) { status =1; } write(fd,&status,1);//把status写入fd,写一个字节 return 0; //write }
最后编写makefile
KERN_DIR=/home/book/100ask_imx6ull-sdk/linux-4.9.88 all: make -C $(KERN_DIR) M= `pwd` modules $(CROSS_COMPILE)gcc -o ledtest ledtest.c clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order rm -f hello_drv_test obj -m +=led_drv.o
2A.2.3 上机实验
编译过程实践看韦东山老师的开发手册
先设置工具链,参考第2篇第八章的内容。 再编译程序,把代码上传代服务器后执行make命令。没有问题后。
接着在开发板上挂载NFS,参考第2篇第六章的内容。 最后在开发板上加载驱动程序,执行测试程序,如下: echo "7 4 1 7" > /proc/sys/kernel/printk // 打开内核的打印信息,有些板子默认打开了
insmod /mnt/led_drv.ko /mnt/ledtest /dev/myled on // 点灯 /mnt/ledtest /dev/myled off // 关灯