• 02_LinuxLED驱动开发


    目录

    Linux下LED灯驱动原理

    地址映射

    ioremap函数

    iounmap函数

    I/O内存访问函数

    LED灯驱动程序编写

    编写测试APP

    编译驱动程序

    编译测试APP

    运行测试


    Linux下LED灯驱动原理

    Linux下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以LED灯驱动最终也是对I.MX6ULL的IO口进行配置,与裸机实验不同的是,在Linux下编写驱动要符合Linux的驱动框架。IMX6U-ALPHA开发板上的LED连接到I.MX6ULL的GPIO1_IO03这个引脚上,因此实验的重点就是编写Linux下I.MX6UL引脚控制驱动。

    地址映射

    在编写驱动之前,我们需要先简单了解一下MMU这个神器,MMU全称叫做 MemoryManage Unit,也就是内存管理单元。在老版本的Linux中要求处理器必须有MMU,但是现在Linux内核已经支持无MMU的处理器了。MMU主要完成的功能如下:

    1.完成虚拟空间到物理空间的映射。

    2.内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

    我们重点来看一下第1,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于32位的处理器来说,虚拟地址范围是2^32-4GB,我们的开发板上有512MB的DDR3,这512MB的内存就是物理内存,经过MMU可以将其映射到整个4GB的虚拟空间,如图所示:

     物理内存只有512MB,虚拟内存有4GB,那么肯定存在多个虚拟地址映射到同一个物理地,址上去,虚拟地址范围比物理地址范围大的问题处理器自会处理,这里我们不要去深究,因为MMU是很复杂的一个东西

    Linux内核启动的时候会初始化MMU,设置好内存映射,设置好以后CPU访问的都是虚拟地址。

    比如I.MX6ULL的GPIO1_I003引脚的复用寄存器IOMUXC_SW-MUX_CTL_PAD_GPIO1_IO03的地址为0X020E0068。如果没有开启MMU的话直接向0X020E0068这个寄存器地址写入数据就可以配置GPIO1_I003的复用功能。现在开启了MMU,并且设置了内存映射,因此就不能直接向0X020E0068这个地址写入数据了。我们必须得到0X020E0068这个物理地址在Linux系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap和iounmap

    ioremap函数

    ioremap函数用于获取指定物理地址空间对应的虚拟地址空间,定义在arch/arm/include/asm/io.h文件中,定义如下:

     ioremap是个宏,有两个参数: cookie 和 size,真正起作用的是函数_arm_ioremap,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:

    phys_addr:要映射的物理起始地址。

    size:要映射的内存空间大小。

    mtype: ioremap的类型,可以选择MT_DEVICE、MT_DEVICE_NONSHARED、MT DEVICE CACHED和MT DEVICE WC, ioremap函数选择MT DEVICE。

    返回值:_iomem类型的指针,指向映射后的虚拟空间首地址。

    假如我们要获取I.MX6ULL的IOMUXC_Sw_MUX_CTL_PAD_GPIO1_IO03寄存器对应的虚拟地址,使用如下代码即可:

     宏SW_MUX_GPIO1_1003_BASE是寄存器物理地址, SW_MUX_GPIO1_1O03是映射后的虚拟地址。对于I.MX6ULL来说一个寄存器是4字节(32位)的,因此映射的内存长度为4.映射完成以后直接对SW_MUX_GPIO1_I003进行读写操作即可。

    iounmap函数

    卸载驱动的时候需要使用iounmap函数释放掉ioremap函数所做的映射, iounmap函数原型如下:

     iounmap只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现在要取消掉IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的地址映射,使用如下代码即可:

    I/O内存访问函数

    这里说的I/O是输入/输出的意思,并不是我们学习单片机的时候讲的GPIO引脚。这里涉及到两个概念:I/O端口和I/O内存。当外部寄存器或内存映射到IO空间时,称为I/O端口。当外部寄存器或内存映射到内存空间时,称为I/O内存。但是对于ARM来说没有I/O空间这个概念,因此ARM体系下只有I/O内存(可以直接理解为内存)。使用ioremap函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是Linux内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

    读操作函数

    读操作函数有如下几个:

     readb, readw和readl这三个函数分别对应8bit, 16bit和32bit读操作,参数addr就是要读取写内存地址,返回值就是读取到的数据。

    写操作函数

    写操作函数有如下几个:

     writeb、writew和writel这三个函数分别对应8bit、16bit和32bit写操作,参数value是要写入的数值, addr是要写入的地址。

    LED灯驱动程序编写

    新建名为“2_led”文件夹,然后在2_led文件夹里面创建VSCode工程,工作区命名为“led”工程创建好以后新建led.c文件,此文件就是led的驱动文件,在led.c里面输入如下内容:

     第22~26行,定义了一些宏,包括主设备号、设备名字、LED 开/关宏。

    第29-33行,本实验要用到的寄存器宏定义。

    第36~40行,经过内存映射以后的寄存器地址指针。

    第47-59行, led_switch函数,用于控制开发板上的LED灯亮灭,当参数sta为LEDON(1)的时候打开LED灯, sta为LEDOFF(0)的时候关闭LED灯。

    第 68-71 行,led_open 函数,为空函数,可以自行在此函数中添加相关内容,一般在此函数中将设备结构体作为参数filp的私有数据(filp->private_data)。

    第81~84行,led_read函数,为空函数,如果想在应用程序中读取LED的状态,那么就可以在此函数中添加相应的代码,比如读取GPIO1_DR 寄存器的值,然后返回给应用程序。

    第94~114行,led_write函数,实现对LED灯的开关操作,当应用程序调用write函数向led 设备写数据的时候此函数就会执行。首先通过函数copy_from_user获取应用程序发送过来的操作信息(打开还是关闭LED),最后根据应用程序的操作信息来打开或关闭LED灯。

    第121-124行, led_release函数,为空函数,可以自行在此函数中添加相关内容,一般关·闭设备的时候会释放掉led_open函数中添加的私有数据。

    第127-133行,设备文件操作结构体led_fops的定义和初始化。

    第140-185行,驱动入口函数led_init,此函数实现了LED的初始化工作, 147~151行通过ioremap函数获取物理寄存器地址映射后的虚拟地址,得到寄存器对应的虚拟地址以后就可以完成相关初始化工作了。比如使能GPIO1时钟、设置GPIO1_I003复用功能、配置GPIO1_IO03的属性等等。最后,最重要的一步!使用register_chrdev函数注册led这个字符设备。

    第192-202行,驱动出口函数led_exit,首先使用函数iounmap取消内存映射,最后使用函数unregister_chrdev注销led这个字符设备。

    第205~206行,使用module_init和module_exit这两个函数指定led设备驱动加载和卸载函数。

    第207~208行,添加LICENSE和作者信息。

    第22~26行,定义了一些宏,包括主设备号、设备名字、LED 开/关宏。

    第29-33行,本实验要用到的寄存器宏定义。

    第36~40行,经过内存映射以后的寄存器地址指针。

    第47-59行, led_switch函数,用于控制开发板上的LED灯亮灭,当参数sta为LEDON(1)的时候打开LED灯, sta为LEDOFF(0)的时候关闭LED灯。

    第 68-71 行,led_open 函数,为空函数,可以自行在此函数中添加相关内容,一般在此函数中将设备结构体作为参数filp的私有数据(filp->private_data)。

    第81~84行,led_read函数,为空函数,如果想在应用程序中读取LED的状态,那么就可以在此函数中添加相应的代码,比如读取GPIO1_DR 寄存器的值,然后返回给应用程序。

    第94~114行,led_write函数,实现对LED灯的开关操作,当应用程序调用write函数向led 设备写数据的时候此函数就会执行。首先通过函数copy_from_user获取应用程序发送过来的操作信息(打开还是关闭LED),最后根据应用程序的操作信息来打开或关闭LED灯。

    第121-124行, led_release函数,为空函数,可以自行在此函数中添加相关内容,一般关·闭设备的时候会释放掉led_open函数中添加的私有数据。

    第127-133行,设备文件操作结构体led_fops的定义和初始化。

    第140-185行,驱动入口函数led_init,此函数实现了LED的初始化工作, 147~151行通过ioremap函数获取物理寄存器地址映射后的虚拟地址,得到寄存器对应的虚拟地址以后就可以完成相关初始化工作了。比如使能GPIO1时钟、设置GPIO1_I003复用功能、配置GPIO1_IO03的属性等等。最后,最重要的一步!使用register_chrdev函数注册led这个字符设备。

    第192-202行,驱动出口函数led_exit,首先使用函数iounmap取消内存映射,最后使用函数unregister_chrdev注销led这个字符设备。

    第205~206行,使用module_init和module_exit这两个函数指定led设备驱动加载和卸载函数。

    第207~208行,添加LICENSE和作者信息。

    编写测试APP

    编写测试APP, led驱动加载成功以后手动创建/dev/led节点,应用APP通过操作/dev/led文件来完成对LED设备的控制。向/dev/led 文件写0表示关闭LED灯,1表示打开LED灯。新建ledApp.c文件,在里面输入如下内容:

    编译驱动程序

    编写Makefile文件,本实验的Makefile文件上一篇基本一样,只是将obj-m变量的值改为led.o, Makefile内容如下所示:

    第4行,设置obj-m变量的值为led.o。

    输入如下命令编译出驱动模块文件:

    编译测试APP

    输入如下命令编译测试ledApp.c这个测试程序:

     编译成功以后就会生成ledApp这个应用程序。

    运行测试

     驱动加载成功以后创建“/dev/led”设备节点,命令如下:

     驱动节点创建成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开 LED灯:

     输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否点亮,如果点亮的话说明驱动工作正常。在输入如下命令关闭LED灯:

     输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否熄灭,如果熄灭的话说明我们编写的LED驱动工作完全正常!至此,我们成功编写了第一个真正的Linux驱动设备程序。

    如果要卸载驱动的话输入如下命令即可:

  • 相关阅读:
    解决Kafka新消费者组导致重复消费的问题
    阿里云操作日记
    git常用命令
    Python Web框架Django
    Shiro介绍及解析
    “因为内存泄漏,我的 M1 MacBook Pro 瘫痪了”
    ELK日志收集系统
    vue中el-dialog 中的内容没有预先加载,因此无法获得内部元素的ref 的解决方案 使用强制提前加载dialog方法
    每天五分钟机器学习:使用反向传播算法完成神经网络的参数更新
    React 函数式组件性能优化指南
  • 原文地址:https://blog.csdn.net/Yuanghxb/article/details/131146306