二、(正点原子)Linux LED驱动开发实验

一、Linux下LED灯驱动原理

        我们知道,在Linux下的任何外设驱动,最终由要配置相对应的硬件寄存器。所以这次的LED灯驱动最终也是对I.MX6ULL阿尔法开发板上的IO进行配置,于裸机不同的是,在Linux下编写驱动要符合Linux的驱动框架。所以我们需要编写一个LED灯的驱动,通过这个驱动连接到LED灯的硬件,也就是连接到LED寄存器,操作寄存器控制LED灯。

二、Linux下LED灯驱动程序编写

        创建vscode工程,将我们第一次实验的工程拷贝一份。第一次的字符设备驱动中,由我们写好的Makefile和.vscode目录(里面是对Linux源码路径的包含)。

       1、LED驱动的加载和卸载

         打开vscode工程,我们只需要一个Makefile,一个LED驱动的.c文件和一个应用程序测试LED驱动。将前面我们编写的字符设备驱动改成LED驱动:

         首先驱动文件我们先得注册驱动模块,和注销驱动模块(在Linux中,一般这种类似的创建函数都与销毁时成对存在的),添加上模块的作者和协议:

        实例化这个模块:

         注意:实例化模块的格式必须严格按照要求,并且我们加上了打印提示信息,当我们这个模块加载时,会调用一次Led_Init(),模块卸载时也会调用一次Led_Exit()。

         我们就可以在Linux中加载我们创建的Led驱动,检查驱动是否成功,编译驱动文件,我们还得将Makefile文件中的编译名称改一下:

        编译我们的led驱动模块,并将驱动模块拷贝到nfs的根文件系统下的驱动模块目录下:

        在serialCRT中,我们加载我们的LED驱动:

        可以看出我们的驱动没有问题,驱动模块创建好后,我们需要在驱动加载时,创建一个字符设备,可以LED属于字符设备,所以我们得注册LED的字符设备。

        2、LED字符设备注册和注销

        字符设备的注册使用函数,在第一篇文章中已经介绍过了所以不在介绍了:

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)

         创建一个变量接收返回值,并判断返回值是否错误。第一个参数时主设备号,第二个参数位设备的名字,所以我们使用宏定义,定义一下LED驱动的主设备号和名字,宏定义的目的是为了代码的可读性和便以修改。第三个参数file_operations也就是LED驱动的操作集合,是一个结构体,我们选则需要的功能具体实现出来函数:

        在LED驱动的操作集合结构体中,我们选着的功能要实现出具体的函数,我们可以查看 file_operations这个结构体的功能函数模板,比如说open()函数:

        其他的功能我们也能这样仿照出来,LED驱动我们只需要打开关闭和写功能:

        注:每次我们写完可以先make编译一下看看我们写的代码有没有错,如果能编译证明我们写的带妹没有错误。 

        由创建就有销毁,所以在注销驱动函数中,我们还得添加LED字符设备注销函数。函数原型:

static inline void unregister_chrdev(unsigned int major, const char *name)

         这样,我们整体的框架就基本上搭好了。

3、LED初始化

        我使用的是正点原子的阿尔法开发板,LED灯连接到的是GPIO1_IO03口上,所以我们只需要初始化这个引脚时钟,配置这个引脚和电器属性,我们就可以通过寄存器对LED进行控制。

        这里有一个非常重要的一点:

         地址映射!!

        在说映射之前,我们先简单了解一下MMU,MMU全称:MemoryManage Unit,内存管理单元。在老版本的Linux中,为什么有些芯片不能再Linux下运行,比如说STM32,因为STM32没有MMU,虽然现在Linux内核也支持无MMU处理器,但是也没有多少人拿来Linux运行也就是这个主要的原因。MMU具体的功能:

        1、完成虚拟地址到物理地址的映射。

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

        地址映射也就是虚拟地址到物理地址的映射,虚拟地址(VA,Virtual Address):简单理解为一段没有真正的内存的地址。物理地址(PA, Physcical Address):实际的内存地址,比如我们说的512MB的DDR3等等。通过MMU可以将物理地址映射到虚拟地址上,但是不是一一对应的关系。比如说:物理内存只有 512MB,虚拟内存有 4GB,那么肯定存在多个虚拟地址映射到同一个物理地址上去,虚拟地址范围比物理地址范围大的问题处理器自会处理。这就叫做地址映射。

        为什么要地址映射呢?因为再Linux系统中,每个进程都拥有自己独立的虚拟空间,这样能保证进程之间的内存空间相互隔离,提高系统的安全性和稳定性

        再地址映射中我们就需要两个函数:

        在Linux内核源码arch/arm/include/asm/io.h中定义:

        1、ioremap():将指定的物理地址空间映射为虚拟地址空间。

        函数原型:

#define ioremap(cookie,size)		__arm_ioremap((cookie), (size), MT_DEVICE)

void __iomem *__arm_ioremap(phys_addr_t, size_t size, unsigned int mtype);

         ioremap是个宏定义,真正的函数是__arm_ioremap

        phys_addr_t:要映射的物理地址。

        size:要映射的内存大小。

        mtype:ioremap的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。

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

        2、iounmap():释放地址映射

        函数原型:

#define iounmap				__arm_iounmap

void __arm_iounmap(volatile void __iomem *addr)

        iounmap也是个宏定义,真正的函数是 __arm_iounmap

        addr:要取消地址映射的虚拟地址指针。

        在LED驱动中我门需要用到的寄存器有:

        CCM_CCGR1:GPIO1 clock。地址:0x020C406c。bit27~26

        IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03:复用为GPIO1_IO03。地址:0x020E0068。bit3~0:设置为0101为GPIO1_IO03模式。

        IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03:设置GPIO_IO03的电气属性。我们配置为0x1b。详细的可以看I.MX6ULL的参考手册。地址:0x020E02F4。

        GPIO1_GDIR:设置GPIO1_IO03的输入输出模式(1:output)。地址:0x0209c004。bit3

        GPIO1_DR:GPIO1_IO03的数据。地址:0X0209C000。bit3

        地址映射可以在open()中映射,也可以在注册驱动函数中映射,我选择在注册驱动函数中映射。但是取消映射都是在注销驱动中进行。

        地址映射后我们就可以对寄存器进行操作从而控制LED灯。

        在初始化之前我们先了解一下在LINUX下I/O内存访问函数 :

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

        1、读操作函数

        头文件:Linux内核源码arch/arm/include/asm/io.h

#define readb(c)		({ u8  __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c)		({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c)		({ u32 __v = readl_relaxed(c); __iormb(); __v; })

#define readb_relaxed(c) ({ u8  __r = __raw_readb(c); __r; })
#define readw_relaxed(c) ({ u16 __r = le16_to_cpu((__force __le16) \
					__raw_readw(c)); __r; })
#define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) \
					__raw_readl(c)); __r; })

        最终读操作函数形式:

 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

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

        2、写操作函数

#define writeb(v,c)		({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c)		({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c)		({ __iowmb(); writel_relaxed(v,c); })


#define writeb_relaxed(v,c)	__raw_writeb(v,c)
#define writew_relaxed(v,c)	__raw_writew((__force u16) cpu_to_le16(v),c)
#define writel_relaxed(v,c)	__raw_writel((__force u32) cpu_to_le32(v),c)

        最终写操作函数形式: 

 void writeb(u8 value, volatile void __iomem *addr)
 void writew(u16 value, volatile void __iomem *addr)
 void writel(u32 value, volatile void __iomem *addr)

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

        通过读写操作函数我们就可以读写寄存器,对寄存器操作了。

 

        编译驱动,然后我们验证一下当加载驱动时,灯会不会被点亮:当我们加载时,灯成功被我们点亮。证明我们地址映射成功,能过通过操作寄存器来控制LED灯。

        我们可以写一个函数来控制LED灯,方便用户断向设备写数据时候,控制LED灯的亮灭,我们使用宏定义来申明LED开关。

         4、控制LED

        在向设备写操作中我们来控制LED亮灭,当用户空间向设备写1表示开灯,写0表示关灯。

                 到这里我们的LED灯的驱动程序就写好了,下面需要编写一下应用程序来对我们的驱动程序进行验证。

三、Linux下LED灯应用程序编写

        和第一次的字符驱动一样,创建ledAPP.c的驱动文件,:
 

        在应用程序中我们都是操作文件,所以需要接收文件的操作符,文件名,和返回值:

        在判断一下用户使用应用程序时,传递过来的参数是否正确:

         然后将输入控制LED命令提取出来,发给设备:

         编译应用程序将应用程序拷贝到nfs根文件目录的驱动下:

 四、验证

        使用serialCRT在终端中,将LED模块加载进来:

         注:设备第一次使用modprobe命令加载时,需要先使用depmod命令添加依赖。

        创建设备结点,也就是在/dev下创建驱动文件,用户通过操作这个驱动文件来对驱动进行打开关闭、读写功能。

        成功能打开和关闭灯,说明LED驱动我们已经完成。 

  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tofu_Cabbage

你的打赏是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值