查看: 3098|回复: 0

【EVB-335X-II】基于寄存器重映射的GPIO驱动程序

[复制链接]
  • TA的每日心情
    难过
    2021-2-27 22:16
  • 签到天数: 1568 天

    连续签到: 1 天

    [LV.Master]伴坛终老

    发表于 2018-1-16 21:37:05 | 显示全部楼层 |阅读模式
    分享到:
    【EVB-335X-II】基于寄存器重映射的GPIO驱动程序设计(含源码)

    对于从单片机过度到ARM9以上的SOC底层开发人员,一开始总是想以单片机的开发思路来开发ARM9等高端处理的底层驱动。由于ARM9以上的SOC处理器,基本上都集成了MMU,并且在Linux系统下有严格的用户空间和内核空间存储区,我们无法按照原来操作单片机的方法来实现对片上外设寄存器的操作。
    但是如果我们能够以开发单片机的外设驱动的思路,来设计一些专用的Linux驱动,可以给我带来很大的方便,尤其当我们不太熟悉那些已经成熟但是又太复杂的外设驱动框架,我们可以用简单的几个函数调用来完成专用驱动程序的开发。
    通过对AM3354处理的用户手册以及数据手册研究,结合物理内存重映射机制,我自己简单实现了一下基于裸机编程思路开发GPIO驱动程序的小例子,本篇试用报告就简要的介绍一下使用过程。
    1.编译内核
    在上一篇的试用报告中,详细介绍开发环境的搭建,由于我手头上的这块EVB335X_II开发板为EMMC类型的核心板,我们需要重新配置和编译基于EMMC版本的内核。将上一篇报告中,拷贝到samba共享文件夹内的emmc内核源码解压缩,如图所示:

    1.png

    执行如下命令:
    cd linux-3.14.43-evb335x-ii
    sudo make ARCH=arm evb335x_defconfig
    sudo make ARCH=arm menuconfig
    根据需要配置内核后,退出保存配置文件
    sudo make ARCH=arm CROSS_COMPILE=交叉编译路径/bin/arm-linux-gnueabihf-LOADADDR=0x80008000 uImage
    2.驱动编写要解决的问题
    2.1硬件电路
    由于开发板上有一个用户可编程的LED灯,即USER LED,其电路图如下:

    2.png

    LED驱动电气原理图如下图所示:

    3.png

    从电路图上可以找到LED灯室友GPIO3_16控制,有一个NPN三极管驱动,所以GPIO3_16输出高电平LED点亮,输出低电平LED熄灭。
    2.1配置引脚功能
    尽管AM335X集成很多功能,但是不是每一个功能都可以用到,需要我们根据自己的实际需要进行取舍。CPU的每一个管脚都有一个以上的不同功能,我们可以通过引脚配置寄存器来实现多某一引脚的功能选择。
    引脚MUX寄存器在AM3354参考手册的第9章。每一个引脚的配置寄存器以该引脚默认的第一个功能命名。比如我们要控制的GPIO3_16,该引脚我们在数据手册发现,他属于SPI0_AXR0引脚的复用引脚,如图所示:

    4.png

    要想配置GPIO3_16为普通的I/O功能,我们需要到AM3354的参考手册查看该引脚的功能配置寄存器,如图所示:

    5.png

    从图中我们可以发现,该寄存器在引脚控制寄存器组中的偏移量为0x998h,我们还需要找到该寄存器组的基地址才能完成操作,通过搜索手册,如图所示:

    6.png

    引脚配置寄存器的基地址为0x44E1_0000。
    我们需要根据功能配置寄存器的表格设置寄存器的内容,如图所示:

    7.png

    由于我们用GPIO3_16作为输出点亮LED灯,所以我们设置[0:2]为0x07,[3]为1即可。
    2.2时钟配置
    要想让GPIO正常工作,需要为配置时钟,根据参考手册的说明,GPIO正常工作需要配置两种时钟:debouncing clock(消抖时钟或滤波时钟)和interface clock。
    1)debouncing clock
    时钟在参考手册的第8章配置,GPIO3的deboucing clock的配置如图所示:

    8.png

    CM_PER_L4LS_CLKSTCTRL在时钟配置寄出器组中的偏移为0x00h,基地址如图所示:

    9.png

    基地址为0x44E0_0000h。
    CM_PER_L4LS_CLKSTCTRL消抖寄存器设置如下:

    10.png

    把该寄存器的第21位设置为1即可。
    2)interface clock
    interface clock寄存器的偏移地址如图所示:

    11.png

    在时钟配置寄存器组中的偏移量为0xB4h.
    寄存器安装下表设置:

    12.png

    3.驱动编写
    3.1头文件
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>
    #include <linux/interrupt.h>
    #include <asm/io.h>
    #include <linux/irq.h>
    #include <linux/gpio.h>
    3.2全局变量
    static struct class *led_class;         //LED驱动类
    static struct class_device *led_class_dev;   //LED驱动设备
    volatile unsigned long *gpmod = NULL;             //引脚功能配置寄存器地址,基地址为:0x44E1_0000h,GPIO3_16在功能配置寄存器中的偏移地址为0x998h
    volatile unsigned long *gpoe = NULL;         //GPIO输出使能配置寄存器,GPIO3的基地址为:0x481A_E000h,系统上电后默认设置GPIO为输入,我们需要将GPIO3-16对应为的设置为输出,偏移地址为0x134h
    volatile unsigned long *gpdataout = NULL; //GPIO输出数据设置寄存器,对于GPIO3_16,我们设置该寄存器的第07位为1,输出高电平,否则输出低电平,偏移地址0x13Ch
    volatile unsigned long *gpclk = NULL;         //GPIO3对应的时钟控制寄存器
    volatile unsigned long *gpclkl4 = NULL;             //GPIO3对应的关于debouncing clock控制寄存器
    volatile unsigned long *gpsysctrl = NULL;   //GPIO的系统配置寄存器,用设置GPIO的Idle,软件复位等功能
    3.3文件操作函数
    1)open
    /*应用层文件所对应的内核驱动Open函数*/
    static int led_drv_open(struct inode*inode, struct file *file)
    {
          printk("opencmd!\n");            /*每次打开文件时输出一条open信息,主要便于调试*/
          /*使能GPIO3的GPIO工作模式,并使能GPIO时钟*/
          *gpclk&= ~((0x3<<0) | (0x03<<16) | (0x01<<18));
          *gpclk|= ((0x02<<0) | (0x01<<18));
          /*使能GPIO3在debouncing clock控制寄存器中相应位*/
          *gpclkl4|= (0x01<<21);
          /*配置GPIO3_16对应的引脚为输出,即选择MUX功能 */
          *gpmod&= ~(0x78<<0);          //清零0-6位
          *gpmod|= ((0x07<<0));          //选择mod7,在AM335X系列的处理器中某个引脚的GPIO功能对应于mod7 禁止上拉、下拉电阻、功能引脚为输入或输出
          /*设置好GPIO的引脚功能后,对GPIO3进行一次软件复位,复位完成后,系统自动清理softrest标志位*/
          *gpsysctrl|= (0x01<<1);

          /*配置GPIO3_16为输出功能*/
          *gpoe&= ~(0x01<<16);           //默认为输入,清零为输出,所以清零第16位
          /*初始状态设置LED灯灭,即GPIO3_16输出低电平,对于EVB335X_II开发板,该LED灯由一个NPN型的三极管控制,所以输出高电平灯亮,低电平灯灭*/
          *gpdataout&= ~(1<<16);

          return0;
    }
    2)write函数
    /*led驱动wirte函数*/
    static ssize_t led_drv_write(struct file*file, const char __user *buf, size_t count, loff_t * ppos)
    {
          intval;
          copy_from_user(&val,buf, count); //将用户空间传递过来的数据拷贝内核空间定义的变量中
          if(val == 1)
          {
                 //点灯
                 *gpdataout|= (1<<16);
                 printk("Ledon!\n");
          }
          else
          {
                 //灭灯
                 *gpdataout&= ~(1<<16);
                 printk("Ledoff!\n");
          }

          return0;
    }
    3)file_operations结构体
    static struct file_operations led_drv_fops= {
       .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
       .open   =   led_drv_open,     
       .write  =    led_drv_write,      
    };
    4)模块初始化函数
    int major;
    static int led_drv_init(void)
    {
          major= register_chrdev(0, "led_drv", &led_drv_fops); // 申请主设备号
          led_class= class_create(THIS_MODULE, "leddrv");
          led_class_dev= device_create(led_class, NULL, MKDEV(major, 0), NULL,"led_register"); /* /dev/led_register */
          /*将物理寄存器所在的地址映射到内核空间*/
          gpmod= (volatile unsigned long *)ioremap((0x44E10000+0x998), 4);        /*引脚PAD功能寄存器映射*/
          gpsysctrl= (volatile unsigned long *)ioremap((0x481AE000+0x10), 4);      /*GPIO配置寄存器映射*/
          gpoe= (volatile unsigned long *)ioremap((0x481AE000+0x134), 4);           /*GPIO输出方向寄存器映射*/
          gpdataout= (volatile unsigned long *)ioremap((0x481AE000+0x13C), 4);  /*GPIO输出数据写入寄存器映射*/
          gpclk      = (volatile unsigned long*)ioremap((0x44E00000+0xB4), 4);        /*GPIO时钟控制寄存器映射*/
          gpclkl4= (volatile unsigned long *)ioremap((0x44E00000+0x00), 4);         /*GPIO debouncing clock时钟控制寄存器映射*/

          printk("led_registerdriver load completed!\n");       /*模块加载成功后打印提示信息*/
          return0;
    }
    5)模块卸载函数
    static void led_drv_exit(void)
    {
          unregister_chrdev(major,"led_drv"); // 卸载
          device_unregister(led_class_dev);
          class_destroy(led_class);
          iounmap(gpmod);
          iounmap(gpoe);
          iounmap(gpdataout);
          iounmap(gpclk);
          iounmap(gpclkl4);
          iounmap(gpsysctrl);

          printk("led_registerdriver unload completed!\n");   /*模块卸载成功后打印提示信息*/
    }
    6)其他
    module_init(led_drv_init);
    module_exit(led_drv_exit);
    MODULE_LICENSE("GPL");
    4.Makefile
    KERN_DIR = /share/linux/linux-3.14.43-evb335x-ii
    all:
          make-C $(KERN_DIR) M=`pwd` modules
    clean:
          make-C $(KERN_DIR) M=`pwd` modules clean
          rm-rf modules.order
    obj-m    +=led_register.o
    注意上面红色的字体代表你自己放在的Linux内核路径
    5.编译测试
    5.1编译
    在驱动源码目录下执行如下命令:
    sudo make ARCH=arm CROSS_COMPILE=/交叉编译器路径/bin/arm_linux_gnueabihf-
    编译结果如图所示:

    13.png

    5.2编写测试程序
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    int main(int argc, char **argv)
    {
          intfd;
          intval = 1;
          fd= open("/dev/led_register", O_RDWR);
          if(fd < 0)
          {
                 printf("can'topen!\n");
          }
          if(argc != 2)
          {
                 printf("Usage:\n");
                 printf("%s<on|off>\n", argv[0]);
                 return0;
          }
          if(strcmp(argv[1], "on") == 0)
          {
                 val  = 1;
          }
          else
          {
                 val= 0;
          }

          write(fd,&val, 4);
          return0;
    }
    编译测试程序:
    sudo /交叉编译路径/bin/arm-linux-gnueabihf-gcc -o led_drv_testled_drv_test.c
    编译结果如所示:

    14.png

    5.3拷贝到NFS
    执行命令,将驱动程序和测试程序拷贝到NFS网络文件系统,供EVB_335X_II开发板挂载使用。
    sudo cp led_register.ko  /nfsshare
    sudo cp led_drv_test        /nfsshare
    5.4 EVB_335X_II上电测试
    1)挂载NFS
    ifconfig eth0 192.168.1.211
    mount -t nfs 192.168.1.103:/nfsshare /mnt-o nolock
    2)加载驱动
    insmod led_register.ko
    系统提示如图所示:

    15.png

    出现上图所示的信息,说明驱动加载成功。
    3)运行测试程序
    开发板上的USER LED灯初始状态下是不亮的,如图所示:

    16.png

    执行命令:
    ./led_drv_test on
    命令终端如图所示:

    17.png

    开发板上LED被点亮,如图所示:

    18.png

    根据命令终端提示的信息,以及开发板的LED灯显示效果,表明我们通过操作寄存器编写驱动程序成功了。
    6 小结
    经过一天的反复看手册、修改程序、编译内核,这个简单的LED驱动程序总算完成了,看似简单的工作,其内涵的知识点还是非常多的,需要我们具备较扎实的基本功才行。编写驱动程序本身就是一项很难的工作,在设计、调试过程中会出现很多我们细节上很容易忽略的知识点,须非常细心才行,希望这一篇试用报告能够起到抛砖引玉的效果,大家共进步!

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 05:33 , Processed in 0.111054 second(s), 16 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.