传统的单片机微控制器驱动GPIO引脚输出方式有两种,寄存器法和库函数法,其中库函数的底层也是操作寄存器,所以本质上都是直接操作寄存器。单片机资源和性能的限制,注定了操作寄存器不能有太多层的封装。而寄存器实际上也是特定的内存地址,比如STM32F103,在KEIL工程的stm32f10x.h里面,就将操作GPIO的寄存器的内存地址封装成为了一个结构体对象,比如一个名字叫GPIOA的对象,操作结构体对象的DR成员,即GPIOA->DR=???,也就是直接往GPIOA_DR寄存器所在的内存地址写数据,这个过程没有任何的物理地址到虚拟地址的映射。那么到了Linux这里,对可操作内存的封装就有更多层了,并且还通过MMU进行映射,用户可以通过芯片手册得知目标内存的实际地址即物理地址,但应用程序无法直接操作物理内存地址,要操作这个地址,需要通过mmap或ioremap等函数获得虚拟内存地址,其中mmap函数在用户程序即应用层可以调用,ioremap函数在内核代码的驱动层调用,实现的效果都是一样的。那么,在直接操作寄存器这一层之上,就有一层内核操作库函数,这个库函数类似单片机的库函数,由内核调用,用户层无法调用,一般是gpio_开头的几个库函数,如gpio_request(),gpio_set_value()等,值得注意的是,内核操作库函数是无法由用户程序直接调用的,只能编译成ko即内核输出驱动,加载并实例化成/dev目录下的某个设备,如/dev/gpioxxx,/dev/myled等,用户程序只能通过open(),close(),read(),write()等几个函数与/dev设备直接通信,这个叫做字符驱动。那么,将内核库函数和字符驱动封装到极致,用户程序可以直接调用的程度,就叫sysfs,如示例代码中操作/sys/class/gpio和/sys/class/leds/cpu,向特定文件读写即可进行通信。sysfs封装程度最高,实时性最低,三种驱动方式的前提都是必须先用ioctl初始化相应的GPIO引脚。
       那么,三种GPIO驱动的方式就显而易见了,分别是sysfs,字符驱动,mmap/ioremap直接读写寄存器,从刚刚的分析可得,从封装程度来看,由高到低依次是sysfs>字符驱动>mmap。
       本帖操作的GPIO为外扩引脚中的GPIO3_IO16,此引脚由英蓓特厂家从CPU中直接引出到外扩针脚,通过查询设备树源码文件dts,GPIO3_IO16已经事先在NXP官方evk开发板中的设备树源码由IOMUX控制器初始化,英蓓特厂家遵循NXP官方的分配方案将此作为GPIO引脚引出并直接基于官方的设备树来编写,我们开发者才可以如此方便地用sysfs或者内核驱动的方式来控制此引脚,按照IMX8引脚换算GPIO3_IO16=32*(3-1)+16=80,即操作gpio80。
12.jpg
13.jpg
       sysfs驱动方式是通过操作/sys/class/gpio下的文件进行,比如驱动gpio80,依次执行以下指令:
cd /sys/class/gpio
  • echo 80 > export
  • cd gpio80
  • echo out > direction
  • echo 1 > value
  • echo 0 > value
  • 复制代码
    -执行完echo 80 > export对export写入数据后,会生成gpio80文件夹,进入之后先操作direction文件后操作value文件,就可以手动操作GPIO,将上述文件系统操作封装成C语言函数就可以在代码中操作GPIO,此方式所操作GPIO响应时间非常慢,甚至无法操作DHT11这种单总线传感器,所以不进行深究,我这里放出之前使用别的IMX8开发板的操作波形抓取结果:
    然后就是内核代码驱动方案,我这边为了省事直接在内核源码中添加一个节点叫mygpio80,不在设备树中绑定GPIO引脚号,只是将其作为象征性节点来用:
    /{
  • mygpio80
  • {
  • compatible = "mygpio80";
  • pinctrl-names = "default";
  • };
  • };
  • 复制代码
    14.jpg

    编译内核dtb设备树文件:
    make dtbs
  • 复制代码
    15.jpg

    将生成的设备树文件em-sbc-imx8m.dtb放到开发板SD卡的内核信息分区mmcblk0p1中,替换同名文件:
    mount /dev/mmcblk0p1 /media
  • cd /media
  • 复制代码
    16.jpg

    那么这个修改后的dtb文件就可以将mygpio80节点暴露出来,给驱动源码使用了,但此时/dev设备节点还没生成,还需要驱动源码编译生成ko文件并插入到系统中。我为了省事直接在开发板上编译驱动源码,而为了做驱动源码本地编译工作,则需要将英蓓特厂商给出的内核源码拷到一个U盘上面,然后把U盘插到开发板上,并且U盘的文件系统格式必须是开发板可以直接识别的EXT格式:
    17.jpg IMG_20200817_003331.jpg
    mount /dev/sda1 /mnt
  • cd /mnt
  • cd imx8maaxksrc
  • make ARCH=arm64 -j4
  • 复制代码
    开发板本地编译时间很长,并且CPU会发热得很厉害,耐心等待一小时,内核源码编译完毕,那么就可以在开发板上直接编译各种驱动源码了,小到GPIO点灯,到传感器驱动,再到USB网卡,一条龙服务,不需要担心编译出来的ko文件无法被开发板识别,我想驱动开发者最不想看到的就是invalid module format报错了0w0。
    然后是编写mygpio80的专属drv即驱动源码:
    #include <linux/err.h>
  • #include <linux/gpio.h>
  • #include <linux/of_gpio.h>
  • #include <linux/gpio/consumer.h>
  • #include <linux/kernel.h>
  • #include <linux/module.h>
  • #include <linux/of.h>
  • #include <linux/platform_device.h>
  • #include <linux/property.h>
  • #include <linux/slab.h>
  • #include <linux/fs.h>
  • #include <linux/types.h>
  • #include <linux/delay.h>
  • #include <linux/device.h>
  • #include <asm/uaccess.h>
  • static int mygpio80_major = 0;
  • static int mygpio80_gpio = 80;
  • //GPIO号
  • static struct class *mygpio80_class;
  • //类
  • static struct device *mygpio80_dev;
  • //设备
  • static const char* mygpio80_name = "mygpio80";
  • //创建设备号
  • int MYGPIO80_open(struct inode *inode, struct file *flips)
  • {
  •         printk("--------------%s--------------\n",__FUNCTION__);
  •         return 0;
  • }
  • static ssize_t MYGPIO80_read(struct file *file, char __user *buf,
  •                     size_t nbytes, loff_t *ppos)
  • {       
  •     buf[0]=gpio_get_value(mygpio80_gpio);
  • }
  • static ssize_t MYGPIO80_write(struct file *file,const char __user *buf,
  •                     size_t nbytes, loff_t *ppos)
  • {
  •     if(buf[0]==1)
  •         gpio_set_value(mygpio80_gpio,1);
  •     else if(buf[0]==0)
  •         gpio_set_value(mygpio80_gpio,0);
  • }
  • static int MYGPIO80_close(struct inode *inode, struct file *flip)
  • {
  •     printk("--------------%s--------------\n",__FUNCTION__);
  •     return 0;
  • }
  • static struct file_operations mygpio80_fops = {
  •         .owner = THIS_MODULE,
  •         .read = MYGPIO80_read,
  •         .write = MYGPIO80_write,
  •         .open = MYGPIO80_open,
  •         .release = MYGPIO80_close,
  • };
  • static const struct of_device_id of_mygpio80_match[] =
  • {
  •         { .compatible = "mygpio80", },
  •         {},
  • };
  • MODULE_DEVICE_TABLE(of, of_mygpio80_match);
  • static int mygpio80_probe(struct platform_device *pdev)
  • {
  •     int ret;
  •     enum of_gpio_flags flag;
  •     printk("-------%s-------------\n", __FUNCTION__);
  •        
  •     //struct device_node *mygpio80_gpio_node = pdev->dev.of_node;
  •     //mygpio80_gpio = of_get_named_gpio_flags(mygpio80_gpio_node->child, "gpios", 0, &flag);
  •     if (!gpio_is_valid(mygpio80_gpio))
  •     {
  •         printk("mygpio80-gpio: %d is invalid\n", mygpio80_gpio);
  •         return -ENODEV;
  •     }
  •     else
  •         printk("mygpio80-gpio: %d is valid!\n", mygpio80_gpio);
  •     if (gpio_request(mygpio80_gpio, "mygpio80-gpio"))
  •     {
  •         printk("gpio %d request failed!\n", mygpio80_gpio);
  •         gpio_free(mygpio80_gpio);
  •         return -ENODEV;
  •     }
  •         else
  •                 printk("gpio %d request success!\n", mygpio80_gpio);
  •         //能够读到配置信息之后就可以开始创建设备节点
  •         mygpio80_major = register_chrdev(0, "mygpio80",&mygpio80_fops);
  •         if(mygpio80_major < 0)
  •         {
  •             printk(KERN_ERR "reg error!\n");
  •             goto err_0;
  •         }
  •         else
  •             printk("mygpio80_major = %d\n",mygpio80_major);
  •        
  •         mygpio80_class = class_create(THIS_MODULE,"mygpio80_class");//creat class
  •         if( IS_ERR(mygpio80_class))
  •         {
  •             printk(KERN_ERR "fail create class\n");
  •             ret = PTR_ERR(mygpio80_class);
  •             goto err_1;
  •         }
  •        
  •         //创建/dev/mygpio80
  •         mygpio80_dev = device_create(mygpio80_class, NULL,MKDEV(mygpio80_major,0), NULL, mygpio80_name);
  •         if(IS_ERR(mygpio80_dev))
  •         {
  •                 printk(KERN_ERR "fail create device!\n");
  •                 ret = PTR_ERR(mygpio80_dev);
  •                 goto err_2;               
  •         }
  •         gpio_direction_output(mygpio80_gpio, 1);
  •         gpio_set_value(mygpio80_gpio, 1);
  •         return 0;
  •        
  • err_2:
  •         device_destroy(mygpio80_class,MKDEV(mygpio80_major,0));
  • err_1:
  •         class_destroy(mygpio80_class);
  • err_0:
  •         unregister_chrdev(mygpio80_major,mygpio80_name);
  •         return -1;
  • }
  • static int mygpio80_remove(struct platform_device *pdev)
  • {
  •         printk("-------%s-------------\n", __FUNCTION__);
  •         device_destroy(mygpio80_class,MKDEV(mygpio80_major,0));
  •         class_destroy(mygpio80_class);
  •         unregister_chrdev(mygpio80_major,mygpio80_name);
  •         return 0;
  • }
  • static void mygpio80_shutdown(struct platform_device *pdev)
  • {
  •         printk("-------%s-------------\n", __FUNCTION__);
  • }
  • static struct platform_driver mygpio80_driver = {
  •         .probe                = mygpio80_probe,
  •         .remove                = mygpio80_remove,
  •         .shutdown        = mygpio80_shutdown,
  •         .driver                = {
  •                 .name        = "mygpio80_driver",
  •                 .of_match_table = of_mygpio80_match,
  •         },
  • };
  • module_platform_driver(mygpio80_driver);
  • MODULE_AUTHOR("donatello1996");
  • MODULE_DESCRIPTION("MYGPIO80");
  • MODULE_LICENSE("GPL");
  • <div>
  • 复制代码
    mygpio80的测试源码(main),一秒钟闪烁一次GPIO80的LED灯:
    1.gif
    #include <stdio.h>
  • #include <stdlib.h>
  • #include <unistd.h>
  • #include <sys/types.h>
  • #include <sys/stat.h>
  • #include <fcntl.h>
  • #include <errno.h>
  • int main ()
  • {
  •         int fd ;
  •         int retval ;
  •         unsigned char val;
  •         fd = open ( "/dev/mygpio80" , O_RDWR) ;
  •         if ( fd == -1 )
  •         {
  •                 perror ( "open /dev/mygpio80 error\n" ) ;
  •                 exit ( -1 ) ;
  •         }
  •         printf ( "open /dev/mygpio80 successfully\n" ) ;
  •         while (1)
  •         {
  •             val=1;
  •             write(fd,&val,1);
  •             sleep(1);
  •             val=0;
  •             write(fd,&val,1);
  •             sleep(1);
  •         }
  •         close ( fd ) ;
  • }
  • 复制代码
    Makefile:
    ROOTFS_DIR = /home
  • ifeq ($(KERNELRELEASE), )
  • KERNEL_DIR = /mnt/imx8maaxksrc
  • CUR_DIR = $(shell pwd)
  • APP_NAME = mygpio80_test
  • CC = aarch64-linux-gnu-gcc
  • all :
  •         make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules
  •         $(CC) $(APP_NAME).c  -o $(APP_NAME)
  •        
  • clean :
  •         make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean
  •         rm -rf $(APP_NAME)
  •        
  • install:
  •         cp -raf *.ko   $(ROOTFS_DIR)
  •         cp -raf  $(APP_NAME)  /usr/local/bin
  • else
  • obj-m += mygpio80_drv.o
  • #obj-m += math.o
  • endif
  • 复制代码
    生成的mygpio80dev.ko是需要指令进行插入的:
    insmod mygpio80_drv.ko
    复制代码
    18.jpg


    使用
    lsmod
    复制代码
    查询驱动设备:
    19.jpg


    这里看到,驱动源码Makefile里面指向的内核源码路径就是/mnt/imx8maaxksrc,即内核源码文件,将驱动源码和内核源码都放到板子上面本地编译,保证原材料新鲜,不会出现不兼容的问题。
    如果是要用示波器抓波形的话很简单,将延时去掉即可:
            while (1)
  •         {
  •             val=1;
  •             write(fd,&val,1);
  •             val=0;
  •             write(fd,&val,1);
  •         }
  • 复制代码
    示波器结果:
    IMG_20200817_000751.jpg
    上传驱动源码和ko文件:
    MYGPIO80.zip (18.56 KB, 下载次数: 0)
    全部回复 0
    暂无评论,快来抢沙发吧