嵌入式系统实验报告(五)--IO接口驱动138352019陈霖坤一实验目的学习嵌入式Linux操作系统设备驱动的方法。
二实验内容与要求根据硬件接口资料,实现任意一个设备的基本控制功能,包括驱动程序和用户程序。
三从外设到用户空间1内核空间与用户空间Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。
Linux内核将这4G字节的空间分为两部分。
将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。
而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间”。
内核空间和用户空间都是指虚拟空间,也就是虚拟地址。
这个概念的由来,跟CPU的发展有很大关系,在目前CPU的保护模式下,系统需要对其赖以运行的资料进行保护,为了保证操作系统内核资料,我们把内存空间进行划分,一部分为操作系统内核运行的空间,另一部分是应用程序运行的空间,所谓空间就是内存的地址。
在386以前的CPU实模式下,操作系统内核与用户程序的内存空间是不做区分的,也就不存在内核空间和用户空间的说法了。
CPU的保护模式的一个重大特点,也就是硬件直接支持的内存访问模式,虚拟地址空间到物理地址空间的映射。
这种工作模式与内核空间用户空间在技术上的相辅相成,也是促成内存空间划分的原因。
操作系统为了保护自己不被普通程序的破坏,对内核空间进行了一些定义,比如访问权限,换入换出,优先级等等。
也就是说内核空间只允许内核访问,用户程序如果要访问内核空间就需要经过内核的审核。
2ioremap几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。
根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:(1)I/O映射方式(I/O-mapped)处理器(如X86)为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O 端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。
(2)内存映射方式(Memory-mapped)RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。
此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。
但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源。
一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。
但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。
Linux在io.h 头文件中声明了函数void*ioremap(unsigned long phys_addr,unsigned long size,unsigned long flags);用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中。
void iounmap(void*addr)函数用于取消ioremap()所做的映射。
在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM 那样直接读写I/O内存资源了。
四电路与寄存器介绍S5PV210有237只多功能引脚,大多数是功能复用的,可以通过初始化编程将它们设置为GPIO(General Purpose I/O,通用IO)或者某个具体功能。
本实验中利用GPIO寄存器实现对LED的控制。
实验板上一共有五只LED,一只是电源指示灯,一只由PWM控制,剩下三个分别对应GPJ0_3、GPJ0_4、GPJ0_5。
实验指导书中列出了一系列GPJ0的相关寄存器,本次用到的有GPJ0CON和GPJ0DAT,前者在0001时对应GPJ0的输出模式,后者一共有8位,对应写到IO口的数据,因为LED共阳连接,所以IO给出低电平时显示亮。
五实验步骤与现象有过在PC上编写设备驱动的经验之后,构建嵌入式系统上的IO驱动接口变得更为简洁。
1内核模块这是IO接口驱动的核心,负责将对寄存器的操作传递给用户。
先仿照实验指导书给出的例子,编写一个简单的模块,实现对IO的控制。
int init_module(){*pCon=ioremap(GPJ0CON,4);*pDat=ioremap(GPJ0DAT,4);*pCon&=0xff000fff;*pCon|=0x00111000;*pDat&=~(1<<3);int major=register_chrdev(LEDMAJOR,LEDNAME,&fops);printk(“MAJOR:%d\n”,major);}在函数定义之前,还需要定义KERNEL和MODULE标志,GPL授权,宏定义设备名、主设备号等,在此前的设备驱动实验报告中已有介绍。
需要特别提到的是,pCon和pDat 需声明为volatile int*型,volatile的本意是“易失的,易挥发的”,被它修饰的变量,会跳过优化,每一次被访问时,都会从其相对应的内存单元中取值,从而保证了读写的数据与硬件真实情况相符合。
一般说来,volatile用在下面的几个地方:1、中断服务程序中修改的供其它程序检测的变量需要加volatile;2、多任务环境下各任务间共享的标志应该加volatile;3、存储器映射的硬件寄存器通常也要加volatile说明。
此外程序最开始还要定义GPIO寄存器地址。
#define GPJ0CON0XE0200240#define GPJ0DAT0XE0200244模块初始化程序做的工作是,将I/O内存资源的物理地址映射到核心虚地址空间(3GB -4GB)中,进而访问IO资源。
上面的初始化函数中,可以不注册设备,因为是直接对GPIO地址操作,而注册设备是为了之后在用户空间调用做准备。
后来我将模块初始化函数修改为,点亮三只LED,延时400毫秒再全部熄灭,从而提示用户加载成功。
同样,也可以写出注销模块函数。
void cleanup_module(){*pDat|=(1<<3);unregister_chrdev(SPEAKERMAJOR,SPEAKERNAME);}仿照设备驱动试验中的Makefile编辑并编译,其中需要更改的地方是内核目录,因为编译时要先进入内核目录读Makefile。
ifneq($(KERNELRELEASE),)obj-m:=ledctl.oelseKDIR:=$KERNELPATH/buildPWD:=$(shell pwd)default:$(MAKE)-C$(KDIR)SUBDIRS=$(PWD)modulesendif编译为.ko文件后,在开发板上加载,可以看到LED1(GPJ0_3)亮,卸载模块则灯灭。
卸载时内核消息提醒在lib/下找不到目录,新建/lib/modules/2.6.35.7目录,卸载成功。
在开发板上加载模块,即便没有指明优先级也会打印出来,但是在PC机上,指定最高级别也不会在终端中自动打印,没找到原因。
继续编写file_operations相关函数,使用户程序能以设备读写的方式实现对LED的控制。
open函数的功能包括:内核消息确认设备打开;DeviceOpen计数,防止重复打开;write函数的功能包括:向pDat写入指定数据;close函数功能包括:DeviceOpen--;内核消息确认关闭。
2用户程序头文件头文件仅包括两个函数int led_on(int)int led_off(int)分别实现LED的点亮与熄灭。
自定义的这两个函数通过传递参数的方式调用write(),写报告时想到之前做图形接口实验时用到的函数mmap,可以将内核空间映射到用户空间,如果用这种方法应该也可以实现效果。
3用户主程序主程序比较简单,即通过对led_on和led_off的调用,实现LED的亮灭。
led_on(1);usleep(500000);led_on(3);usleep(500000);led_off(1);链接头文件编译后,按如下顺序操作:mknod设备节点,加载模块,执行main文件。
即可看到LED1亮,LED3亮,LED1灭的效果。
六实验体会、小结及建议这次实验主要完成的是对LED灯的控制,我将其与单片机控制IO口比较异同。
两者相似的地方在于,将对IO接口、寄存器等的控制看作对内存地址的读写,事实上IO接口的本质就是控制寄存器完成某一功能。
两者区别在于,单片机没有内核空间与用户空间的概念,就像自动取得最高权限,可以对硬件地址直接操作一样。
下面是MSP430F16X系列的头文件截图下面是同为ARM内核的STM32F10X系列头文件截图下面是同为三星公司的ARM9架构的S3C2440头文件截图所以一般设备驱动编写流程可以看作先将硬件资源映射到内核空间,然后就可以像用单片机写驱动一样操作,但要注意为用户空间提供接口。
我猜想,是否可以继续用mmap映射到用户空间,然后将单片机程序稍作修改完成设备控制?七参考文献《微处理器与嵌入式系统》南京大学《嵌入式系统X210V3实验指导书》南京大学。