正点原子嵌入式linux驱动开发——LED驱动开发

在上一篇笔记中,详细的讲解了字符设备驱动开发步骤,并且用一个虚拟的chrdevbase设备为例完成了第一个字符设备驱动的开发。本章就开始编写第一个真正的Linux字符设备驱动。在正点原子STM32MP157开发板上有一个LED灯,本章就学习一下如何编写Linux下的LED灯驱动

Linux下LED灯驱动原理

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

地址映射

先简单了解一下MMU,MMU全称叫做Memory
Manage Unit,也就是内存管理单元
。在老版本的Linux中要求处理器必须有MMU,但是现在Linux内核已经支持无MMU的处理器了。MMU 主要完成的功能如下:

  1. 完成虚拟空间到物理空间的映射。
  2. 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。对于32位的处理器来说,虚拟地址范围2^32=4GB,开发板上有1GB的DDR3,这1GB的内存就是物理内存,经过MMU可以将其映射到整个4GB的虚拟空间,如下图所示:
内存映射
Linux内核启动的时候会初始化MMU,设置好内存映射,设置好以后CPU访问的都是虚拟地址。比如STM32MP157的PI0引脚的端口模式寄存器GPIOI_MODER物理地址为0x5000A000。开启了 MMU,并且设置了内存映射,因此不能直接向0x5000A000这个地址写入数据,必须得到0x5000A000这个物理地址在Linux系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数:ioremap和iounmap

ioremap函数

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

void __iomem *ioremap(resource_size_t res_cookie, size_t size);

一共有两个参数,而函数内部实际是调用了arch_ioremap_caller,并且有一个返回值:

  • res_cookie:要映射的物理起始地址。
  • size:映射的内存空间大小。
  • 返回值:__iomem类型指针,指向映射后虚拟空间首地址。

iounmap函数

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

void iounmap (volatile void __iomem *addr);

只有一个参数,就是要取消映射的虚拟地址空间首地址

I/O内存访问函数

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

读操作函数

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

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

写操作函数

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

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

硬件原理图分析

正点原子的STM32MP157开发班上有一个LED0,如下图所示:
LED原理图
可以看出, LED0接到了PI0上,当PI0输出低电平LED0就会导通点亮,当PI0输出高电平LED0不会导通,因此LED0也就不会点亮。

实验程序编写

LED灯驱动

首先定义一些方便操作的宏,比如设备号、设备名字以及LED开/官状态宏;之后需要定义寄存器宏定义;定义映射后的寄存器地址指针,指针是__iomem*类型;

之后定义led_switch函数,通过readl和writel函数来操作LED的状态;之后定义led_unmap函数,其中就是iounmap来取消各个寄存器的映射;

设备的read和open以及release都直接return 0就可以了,没什么操作;write函数就需要把数据拷贝过来之后,接收到的消息来判断具体的操作,在其中调用led_switch改变LED状态;然后file_operations把这几个设备操作函数封装一下;

最后是init函数,在这其中需要通过配置寄存器的方式来配置LED:首先通过ioremap读取物理寄存器映射后虚拟地址,然后就是寄存器的方法配置,最后需要register_chrdev注册led这个字符设备;注册失败需要回收,通过led_unmap来搞定;

最后还有exit函数,就是直接led_unmap取消映射,然后注销unregister_chrdev;

最后module_init和module_exit以及添加license和author就可以了,最后在表示这个驱动是intree模块驱动。

编写测试APP

就是最基础的打开、关闭以及写操作。

先判断argc是否为3,然后把设备名字argv[1]传入自己的char* filename,之后int fd通过open接住打开的设备,fd<0说明没有打开就直接return -1;之后把读取的数据传入databuf[0],这也是自己定义的unsigned char数组,大小为1,通过atoi(argv[2])传进去;最后调用write写进int retvalue;关闭文件同样调用close传给retvalue。

运行测试

编译驱动模块和测试APP

驱动程序就直接写一个Makefile,把obj-m设置为led.o就好了,然后make就能得到“led.ko”驱动模块文件。

测试APP则通过如下命令编译生成:

arm-none-linux-gnueabihf-gcc ledApp.c -o ledApp

运行测试

把编译生成的两个文件拷贝到rootfs/lib/modules/5.4.31中,然后重启开发板,进入lib/modules/5.4.31然后加载驱动:

depmod //第一次加载驱动的时候需要运行此命令
modprobe led //加载驱动

成功后就创建“/dev/led”设备节点:

mknod /dev/led.c 200 0

然后通过ledAPP测试驱动:

./ledApp /dev/led 1 //打开 LED灯
./ledApp /dev/led 0 //关闭 LED灯

如果成功,之后可卸载驱动:

rmmod led.ko

总结

本篇主要就是通过Linux的地址映射,把物理寄存器地址映射到虚拟内存中,然后通过字符设备的操作方式,操作LED的亮灭。这里面主要还要学习一下寄存器配置的方式,翻翻手册记一下就好了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值