当前位置: 首页 > 科技观察

Linux驱动实践:如何编写【GPIO】设备驱动?

时间:2023-03-19 18:48:21 科技观察

别人的经历就是我们的阶梯!大家好,我是刀哥。在之前的文章中,我们一起讨论过:在Linux系统中,编写字符设备驱动程序的基本框架主要是从代码流程和API函数两个方面来触发的。在本文中,我们将以此为基础编写一个具有实际应用功能的驱动程序:在驱动程序中,初始化GPIO设备并自动创建设备节点;在应用程序中,打开GPIO设备,发送控制命令设置GPIO端口状态;示例程序的目标是编写一个驱动模块:mygpio.ko。当加载这个驱动模块时,在系统中创建一个mygpio类设备,在/dev目录下创建4个设备节点:/dev/mygpio0/dev/mygpio1/dev/mygpio2/dev/mygpio3因为我们现在对GPIO控制操作是在x86平台上模拟的,没有实际的GPIO硬件设备。因此,在驱动代码中,与硬件相关的代码由宏MYGPIO_HW_ENABLE控制,通过printk输出打印信息来反映硬件的运行情况。在应用程序中,可以分别打开上述4个GPIO设备,通过发送控制命令来设置GPIO的状态。以下所有写驱动操作的工作目录与上一篇相同,即:~/tmp/linux-4.15/drivers/。创建驱动目录和驱动$cdlinux-4.15/drivers/$mkdirmygpio_driver$cdmygpio_driver$touchmygpio.cmygpio.c文件内容如下(不用手动敲,文末有代码下载链接):#include#include#include#include#include//GPIO硬件相关宏定义#defineMYGPIO_HW_ENABLE//设备名称#defineMYGPIO_NAME"mygpio"//一共4个GPIO口#defineMYGPIO_NUMBER4//设备类staticstructclass*gpio_class;//用于保存设备structcdevgpio_cdev[MYGPIO_NUMBER];//用于保存设备numberintgpio_major=0;intgpio_minor=0;#ifdefMYGPIO_HW_ENABLE//硬件初始化函数,当驱动加载时(gpio_driver_init)被调用staticvoidgpio_hw_init(intgpio){printk("gpio_hw_initiscalled:%d.\n",gpio);}//hardwarereleasestaticvoidgpio_hw_release(intgpio){printk("gpio_hw_releaseiscalled:%d.\n",gpio);}//设置硬件GPIO的状态,当控制GPIO(gpio_ioctl)被调查时staticvoidgpio_hw_set(unsignedlonggpio_no,unsignedintval){printk("gpio_hw_setiscalled.gpio_no=%ld,val=val=%d.\n",gpio_no,val);}#endif//应用打开时该设备,它被称为staticintgpio_open(structinode*inode,structfile*file){printk("gpio_openiscalled.\n");return0;}//应用程序控制GPIO时调用staticlonggpio_ioctl(structfile*file,unsignedintval,unsignedlonggpio_no){printk("gpio_ioctliscalled.\n");//检查设置的状态值是否合法if(0!=val&&1!=val){printk("valisNOTvalid!\n");return0;}//检查设备范围是否合法if(gpio_no>=MYGPIO_NUMBER){printk("dev_noisinvalid!\n");return0;}printk("setGPIO:%ldto%d.\n",gpio_no,val);#ifdefMYGPIO_HW_ENABLE//操作GPIO硬件gpio_hw_set(gpio_no,val);#endifreturn0;}staticconststructfile_operationsgpio_ops={.owner=THIS_MODULE,.open=gpio_open,.unlocked_ioctl=gpio_ioctl};staticint__initgpio_driver_init(void){inti,devno;dev_tnum_dev;printk("gpio_driver_initiscalled.\n");//动态应用如果所以,你应该检查函数返回值)alloc_chrdev_region(&num_dev,gpio_minor,MYGPIO_NUMBER,MYGPIO_NAME);//获取主设备号gpio_major=MAJOR(num_dev);printk("gpio_major=%d.\n",gpio_major);//创建吃了设备类gpio_class=class_create(THIS_MODULE,MYGPIO_NAME);//创建设备节点for(i=0;i#include#include#include#include#include#defineMY_GPIO_NUMBER4//4个设备节点chargpio_name[MY_GPIO_NUMBER][16]={"/dev/mygpio0","/dev/mygpio1","/dev/mygpio2","/dev/mygpio3"};intmain(intargc,char*argv[]){intfd,gpio_no,val;//检查参数个数if(3!=argc){printf("Usage:./app_gpiogpio_novalue\n");return-1;}gpio_no=atoi(argv[1]);val=atoi(argv[2]);//参数有效性校验assert(gpio_no/dev/mygpio0:opensuccess!如何确认/dev/mygpio0的GPIO状态确实设置为1呢?当然,再看看dmesg命令的打印信息:$dmesg通过上面的打印信息,可以看到确实执行了【设置mygpio0的状态为1】的动作。继续再次测试:设置mygpio0的状态为0:$sudo./app_mygpio00当然设置其他GPIO端口的状态也可以正确执行!卸载驱动模块卸载命令:$sudormmodmygpio此时,/proc/devices下主设备号为244的mygpio已经不存在了。再看dmesg的打印信息:可以看到:driver中的gpio_driver_exit()被调用并执行了。而且/dev目录下的4个设备节点也被函数device_destroy()自动删除了!本文转载自微信公众号《IOT物联网小镇》