邝坚_北邮嵌入式实验报告

第一篇:邝坚_北邮嵌入式实验报告
嵌入式系统期末实验
一、实验要求
题目:支持消息驱动模式的实时软件框架
目的:在充分理解嵌入式处理器特点、RTOS 及强实时嵌入式系统软件设计规范的基础上,构建自己的实时系统软件框架基本功能,并在其上自拟应用(如部分模拟TCP 的C/S两端通信流程),测试软件框架的相关功能。
环境:VxWorks 的VxSim 仿真环境或2440(ARM920T)内容: 必选功能:
1.消息驱动的Task 统一框架,包含统一消息格式定义及使用规范; 2.支持消息驱动模式的软定时器的机制; 3.Task 启动同步功能;
4.体现前次实验中实现的自定义内存管理机制,最大限度降低外部碎片对系统可靠性的威胁。
可选功能(加分):
其它有利于实时处理的有效机制,如:无信号量(互斥)支持的临界资源访问方式,zero copy 等;
二、实现的功能
1.消息驱动的Task 统一框架,包含统一消息格式定义及使用规范; STATUS Task(){ Initialization(MBox, Data Structure, Timer, etc.)Forever{ MsgReceive If(…){ …… }else if(…){ …… } ……
} } typedef struct _MESSAGE { int mType;
/* 消息类型 0:timer->client *1:client->server 2:server->client*/ int mSendId;/* 发送任务的MESSAGE ID */ int mRecvId;/* 接收任务的MESSAGE ID */ int mData;/* 消息中传递的数据 */ }MESSAGE;2.支持消息驱动模式的软定时器的机制;
/* timer(id)向客户端消息队列定时发送的定时器*/ STATUS timer(int id){ MESSAGE* txMsg;/* 用于从消息队列中接收消息 */ int tick;/*创建一个定时,用于提醒发送者任务定时发送消息*/ tick=sysClkRateGet();semTake(semSynStart,WAIT_FOREVER);FOREVER {
taskDelay((int)(tick*DELAY_SECOND));txMsg =(MESSAGE*)memMalloc(MAX_MSG_LEN);txMsg->mType = 0;txMsg->mSendId = MID_TIMER(id);txMsg->mRecvId = MID_CLIENT(id);txMsg->mData = 0;printf(“tTimer%d send message to tClient%d!n”,id,id);if(msgQSend(msgQIdClient[id],(char*)&txMsg,MAX_MSG_LEN,WAIT_FOREVER,MSG_{ return(ERROR);} PRI_NORMAL)== ERROR)
} } 3.Task 启动同步功能;
由manager()创建的任务优先级最高,先创建timer()、server()、client()的任务,让他们都在等待信号量semSynStart而被阻塞,最后创建manager()的任务,占据CPU,等待其他所有任务都被阻塞,解锁所有等待信号量的任务,让它们同时启动。/* progStart()启动实例程序*/ STATUS progStart(void){
int id;/* 用来区分不同的定时器或者客户任务 */ mallocPtr=&sysMalloc;mallocPtr->frontBlock = 0;return(OK);
initialPtr = initial();tidServer = tidManager = 0;for(id = 0;id < NUM_CLIENT;id++){ tidClient[id] = 0;} for(id = 0;id < NUM_TIMER;id++){
} /* 创建消息队列 */ msgQIdServer = msgQCreate(MAX_MSGS, MAX_MSG_LEN, MSG_Q_FIFO|MSG_Q_EVENTSEND_ERR_NOTIFY);if(msgQIdServer == NULL){
} for(id = 0;id < NUM_CLIENT;id++){
} semSynStart = semBCreate(SEM_Q_FIFO | SEM_EVENTSEND_ERR_NOTIFY,SEM_EMPTY);semMalloc = semBCreate(SEM_Q_PRIORITY,SEM_FULL);semFree = semBCreate(SEM_Q_PRIORITY,SEM_FULL);/* 创建任务 */ tidServer = taskSpawn(“tServer”, 220, 0, STACK_SIZE,(FUNCPTR)server,0,0,0,0,0,0,0,0,0,0);for(id = 0;id < NUM_CLIENT;id++){
char tempName[20];sprintf(tempName, “tClient%d”, id);tidClient[id] = taskSpawn(tempName, 210, 0, STACK_SIZE, msgQIdClient[id] = msgQCreate(MAX_MSGS, MAX_MSG_LEN, if(msgQIdClient[id] == NULL){ return(ERROR);} MSG_Q_FIFO|MSG_Q_EVENTSEND_ERR_NOTIFY);return(ERROR);tidTimer[id] = 0;(FUNCPTR)client,id,0,0,0,0,0,0,0,0,0);} for(id = 0;id < NUM_TIMER;id++){
} tidManager = taskSpawn(“tMannager”, 200, 0, STACK_SIZE,(FUNCPTR)manager,0,0,0,0,0,0,0,0,0,0);printf(“programe start!n”);return(OK);} /* manager()管理进程,实现task同步*/ STATUS manager(){ int id;while(taskIsSuspended(tidServer)|| taskIsReady(tidServer))
{ while(taskIsSuspended(tidClient[id])|| taskDelay(10);taskIsReady(tidClient[id]))
} for(id = 0;id < NUM_TIMER;id++){
} semFlush(semSynStart);return(OK);} /* server()处理来自各个客户任务的消息*/ STATUS server(void){ …… while(taskIsSuspended(tidTimer[id])|| taskIsReady(tidTimer[id]))taskDelay(10);taskDelay(10);for(id = 0;id < NUM_CLIENT;id++)char tempName[20];sprintf(tempName, “tTimer%d”, id);tidTimer[id] = taskSpawn(tempName, 230, 0, STACK_SIZE,(FUNCPTR)timer,id,0,0,0,0,0,0,0,0,0);semTake(semSynStart,WAIT_FOREVER);FOREVER { } return(OK);} /* timer(id)向客户端定时发送的定时器*/ STATUS timer(int id){ ……
semTake(semSynStart,WAIT_FOREVER);FOREVER { } /*client(id)向服务器任务发请求消息*/ STATUS client(int id){ ……
semTake(semSynStart,WAIT_FOREVER);FOREVER { } return(OK);} 4.体现前次实验中实现的自定义内存管理机制,最大限度降低外部碎片对系统可靠性的威胁。
静态内存的数据结构为单链表,采用头插法,申请内存时,修改firstavailable另其指向第二块,将firstavailable指向的头块取出,回收内存时,将回收的块的frontBlock指向第一块,修改firstavailable另其指向回收的块,将回收的块作为第一块,数据结构如下所示: …… ……
} return(OK);…… poolpoolHeadnextfirstavailable blockHeadfrontBlockblockHeadfrontBlockblockHeadfrontBlockblockHeadpoolHeadfirstavailableblockHeadfrontBlockblockHeadfrontBlockblockHeadfrontBlockblockHead 静态分配了含有32个16B块的内存池和含有16个256B块的内存池,如果申请的内存大于256B,调用系统malloc。
/*initial()初始化内存池*/ pool* initial(void){ int i;pool* mem;pool* poolPtr;poolHead* poolHeadPtr;blockHead* blockHeadPtr;
mem=(pool*)malloc(6000);/*分配6000B内存作为内存池*/
/*初始化pool*/ poolPtr =(pool*)mem;poolPtr->poolNum = 2;poolPtr->pool =(poolHead*)((char*)mem + sizeof(pool));/*pool指向申请内存区尾*/
/*初始化pool 1 该内存池分配大小为16B的内存*/ poolHeadPtr =(poolHead*)((char*)mem + sizeof(pool));/*初始化内存池的首地址*/ poolHeadPtr->available = 32;/*初始化可用块数32*/ poolHeadPtr->blockSize = 16;/*块大小16B*/ blockHeadPtr =(blockHead*)((char*)poolHeadPtr+sizeof(poolHead));初始化块的首地址*/ poolHeadPtr->firstavailable = blockHeadPtr;/*初始化第一块可用块的地址*/ poolHeadPtr->next=(poolHead*)((char*)poolHeadPtr + sizeof(poolHeadPtr)
/*+ 32*(sizeof(blockHead)+16));/*next指向第二个内存池 */
blockHeadPtr->poolId =1;blockHeadPtr->frontBlock = 0;
for(i=1;i<32;i++)/*将该内存池划分为32个容量16B的内存块*/ {
址*/ } /*初始化pool 2 该内存池分配大小为256B的内存*/ poolHeadPtr = poolHeadPtr->next;poolHeadPtr->available = 16;/*初始化可用块数16*/ poolHeadPtr->blockSize = 256;/*块大小256*/
blockHeadPtr =(blockHead*)((char*)poolHeadPtr+sizeof(poolHead));
poolHeadPtr->firstavailable = blockHeadPtr;poolHeadPtr->next = 0;
blockHeadPtr->poolId =2;blockHeadPtr->frontBlock = 0;
for(i=1;i<16;i++)/*将该内存池划分为16个容量256B的内存块*/ {
} return(pool*)mem;} blockHeadPtr=(blockHead*)((char*)blockHeadPtr + blockHeadPtr->poolId = 2;/* pool号为2,表示他是256B容量的*/ blockHeadPtr->frontBlock = poolHeadPtr->firstavailable;poolHeadPtr->firstavailable = blockHeadPtr;(sizeof(blockHead)+256));
blockHeadPtr=(blockHead*)((char*)blockHeadPtr + blockHeadPtr->poolId = 1;/* pool号为1,表示他是16B容量的*/ blockHeadPtr->frontBlock = poolHeadPtr->firstavailable;/* 当前首poolHeadPtr->firstavailable = blockHeadPtr;/* 求下一首个可用块地(sizeof(blockHead)+16));/*块的首址移动16加结构体的开销长度*/ 个可用块地址赋给frontBlock */
/*memMalloc()分配内存*/ void* memMalloc(int Size){ void* mem;poolHead* poolHeadPtr;blockHead* blockHeadPtr;
semTake(semMalloc,WAIT_FOREVER);
poolHeadPtr = initialPtr->pool;
if((Size <= 16)&&(poolHeadPtr->available!= 0))/*长度小于16时,分配长度为16的内存空间*/ {
blockHeadPtr = poolHeadPtr->firstavailable;/*首个可用块地址赋给poolHeadPtr->firstavailable = blockHeadPtr->frontBlock;/*改变下poolHeadPtr->available--;/*可用块数减一*/ semGive(semMalloc);分配块的首地址*/ 一第一可用块的地址*/
return(void*)((char*)blockHeadPtr + sizeof(blockHead));/*分配内存时加入块头开销*/ } else if((Size <= 256)&&((poolHeadPtr->next)->available!= 0))
{
} else
{ printf(“n[Warning] : Too large for blocks or the blocks are /*其他情况用系统的内存分配函数malloc分配*/ blockHeadPtr =(poolHeadPtr->next)->firstavailable;(poolHeadPtr->next)->firstavailable = blockHeadPtr->frontBlock;(poolHeadPtr->next)->available--;semGive(semMalloc);return(void*)((char*)blockHeadPtr + sizeof(blockHead));/*长度大于16小于256时,分配长度为256的内存空间*/ exhausted n”);
} } /*memFree()释放内存空间*/ void memFree(void* dataPtr){ char* mem=(char*)dataPtr;poolHead* poolHeadPtr;blockHead* blockHeadPtr;
semTake(semFree,WAIT_FOREVER);
poolHeadPtr = initialPtr->pool;/*恢复内存池首址*/ blockHeadPtr =(blockHead*)((char*)memsizeof(blockHead));Timer0Timer1Timer9ClientMsgQ0ClientMsgQ1……ClientMsgQ9Client0Client1Client9ServerMsgQserver6.zero copy 消息队列存储的是指向消息的指针,从而实现了零拷贝。#define MAX_MSG_LEN sizeof(MESSAGE*)MESSAGE* rxMsg;/* 用于从消息队列中接收消息 */ MESSAGE* txMsg;/* 用于向消息队列中发送消息 */ msgQReceive(msgQIdServer,(char*)&rxMsg,MAX_MSG_LEN,WAIT_FOREVER);
msgQSend(msgQIdClient[mSendId],(char*)&txMsg,MAX_MSG_LEN,WAIT_FOREVER,MSG_PRI_NORMAL);
三、运行结果
在shell中输入progStart,观察VxSim,输入progStop结束。
四、心得
实验中遇到了各种各样的问题,特别是代码调试,对报错的分析,定位错误,但是通过不懈努力,完成了本次实验,让我对课堂上所讲的内容有了更深刻的认识,对嵌入式实时操作系统有了更深的理解。
由于正值期末,考试任务繁重,时间紧迫,自身水平有限,难免会有疏漏,请老师指正。
第二篇:北邮嵌入式实验报告
北京邮电大学
嵌入式系统开发实验报告
学院:
班级: 姓名: 学号:
序号:
目录
一、实验目的..............................................................................................1
二、实验设备..............................................................................................1
三、基础实验(实验一~实验七)............................................................1
1.实验五..................................................................................................1 2.实验六..................................................................................................1 3.实验七..................................................................................................1
四、驱动程序..............................................................................................5
1.设备驱动程序的概念..........................................................................5 2.驱动程序结构......................................................................................6 3.设备注册和初始化..............................................................................7 4.设备驱动程序的开发过程..................................................................8
五、基本接口实验......................................................................................8
1.实验十二简单设备驱动程序..............................................................9 2.实验十三 CPU GPIO驱动程序设计...................................................9 3.实验十四中断实验...........................................................................10 4.实验十五数码管显示实验................................................................12 5.实验十六 LED点阵驱动程序设计...................................................19 6.实验十七 AD驱动实验....................................................................23 7.实验十八 DA驱动实验....................................................................26
六、实验中遇到的问题及解决方法........................................................30
七、实验总结及心得................................................................................31
一、实验目的
通过实验熟悉Linux环境,并掌握一些基本接口驱动的写法和用C语言编写简单的实验程序。学习LINUX开发环境的搭建,通讯配置等。并熟练掌握LINUX驱动程序的编写及开发流程。对嵌入式系统有进一步的了解。
二、实验设备
1.一套PXA270EP嵌入式实验箱
2.安装Redhat9的宿主PC机,并且配置好ARM Linux的开发环境
三、基础实验(实验一~实验七)
实验一~七为基础实验,目的是为后续实验搭建好软、硬件环境,配置好相关的协议、服务,并通过编写最简单的HelloWorld程序进行测试。由于后面的实验都要依靠前面实验的配置,故本段只着重叙述实验七的具体实现。
1.实验五
实验五为宿主PC机配置了TFTP服务。TFTP(Trivial File Transfer Protocol)是简单文件传输协议,由于特定开发环境的制约,这一服务是必须的。在配置完成后,每次重启宿主PC机时,都须先输入命令:service xinetd restart,以启动TFTP服务。
2.实验六
实验六为宿主PC机配置了NFS服务。NFS(Network File System)指网络文件系统,它实现了文件在不同的系统间使用。当我们想用远端档案时,只需调用“mount”就可以远端系统挂接在自己的档案系统之下。每次重启宿主PC机时,都须先输入命令:service nfs restart,以启动nfs服务。
3.实验七
实验七通过用c语言编写的简单程序HelloWorld,测试前面几个实验是否成功配置好环境,从超级终端可以看到HelloWorld程序的运行结果。
实验步骤如下: 1)硬件连接:
连接宿主 PC 机和一台 PXA270-RP目标板。2)打开宿主PC 机电源,进入 Linux操作系统。
3)启动RedHat 9.0 的图形界面,如下图,若您是以 root 身份登陆在文本模式下,则输入命令startx启动图形界面。进入RedHat 9.0 图形界面后,打开一个终端窗(Terminal)。
4)输入minicom然后回车,minicim设置为115200 8NI无流控。
5)打开PXA270_RP目标板电源,按目标板上的BOOT键,在minicom中应该会看到如下图:
6)在minicom终端窗口中,如图,输入下列四条命令 root ifconfig eth 192.168.0.50 up mount-o nolock 192.168.0.100:/ /mnt cd /mnt 此时,先将该窗口最小化,在后面的第 10 操作步骤中还将会回到该窗口中进行操作。
7)宿主机上打开一个终端窗口(Terminal),点击【红帽/System Tools/Terminal】启动终端窗口,输入下列 4 条命令: ① cd /home
②mkdir HW
③ cd HW
④ vi
HelloWorld.c
/*请您输入程序 7.1 程序清单*/
此时会显示一个空白的屏幕,这条命令的含义是,使用 Vi 编辑器,对一个名叫HelloWorld.c的文件进行编辑,我们看到的空白窗口是对文件进行编辑的窗口,如下图。就像在 Windows系统下面使用写字板等一样道理。
在 vi 里面先单击键盘 A 键,然后左下角会变成—INSER。输入程序的时候和其他编辑器是一样的,如下图。
当输入程序完毕后,单击键盘 Esc 键,然后按“:”(冒号)此时左下角会出现冒号然后输入“wq”最后按“Enter”确认存盘退出 vi 编辑器,如下图。
8)在上面同一个终端窗口中,输入下列 2 条命令交叉编译HelloWorld.c源程序,并查看生成的.o 目标文件,如图 7-10,图7-11: ①
arm-linux-gcc–oHelloWorldHelloWorld.c ②ls 等到再次出现提示符,代表程序已经正确编译。如果此步出现错误信息,请查看错误信息,并且重新编辑原来的 C文件,修改错误。直到正确编译。
9)重新打开第 7 步最小化的开有minicom的终端窗口,即到 PXA270-RP 目标板的mnt目录下,请您输入下列 3 条命令,运行HelloWorld编译成功的HelloWorld目标程序:
① cd home/HW
/*回到minicom中目标板的/mnt/home/HW目录下*/ ②ls ③./ HelloWorld
/*此时会看到如下图*/
四、驱动程序
1.设备驱动程序的概念
设备驱动程序实际是处理和操作硬件控制器的软件,从本质上讲,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口,驱动程序屏蔽了硬件的细节,完成以下功能:
对设备初始化和释放;
对设备进行管理,包括实时参数设置,以及提供对设备的操作接口; 读取应用程序传送给设备文件的数据或者回送应用程序请求的数据; 检测和处理设备出现的错误。
Linux操作系统将所有的设备全部看成文件,并通过文件的操作界面进行操作。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说,是把设备映射为一个特殊的设备文件,用户程序可以像对其他文件一样对此设备文件进行操作。这意味着:
由于每一个设备至少由文件系统的一个文件代表,因而都有一个“文件名”。应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。
打开了代表着目标设备的文件,即建立起与设备的连接后,可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。
设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。
由于Linux 中将设备当做文件处理,所以对设备进行操作的调用格式与对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,会从用户态转到核心态,通过内核将open()这样的系统调用转换成对物理设备的操作。
2.驱动程序结构
一个设备驱动程序模块的基本框架
在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点也可以理解为设备的句柄,就是对设备进行操作的基本函数。字符型设备驱动程序提供如下几个入口点:
open入口点。打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。
close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。
read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。
write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。
ioctl入口点。执行读、写之外的操作。
select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。
3.设备注册和初始化
设备的驱动程序在加载的时候首先需要调用入口函数init_module(),该函数最重要的一个工作就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册。register_chrdev的定义为:intregister_chrdev(unsigned int major, const char *name, struct file_ operations *fops);其中,major是为设备驱动程序向系统申请的主设备号,如果为0,则系统为此驱动程序动态分配一个主设备号。name是设备名,fops是对各个调用的入口点说明。此函数返回0时表示成功;返回-EINVAL,表示申请的主设备号非法,主要原因是主设备号大于系统所允许的最大设备号;返回-EBUSY,表示所申请的主设备号正在被其他设备程序使用。如果动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev()操作成功,设备名就会出现在/proc/dvices文件中。
Linux在/dev目录中为每个设备建立一个文件,用ls–l命令列出函数返回值,若小于0,则表示注册失败;返回0或者大于0的值表示注册成功。注册以后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux通过请求访问的设备名得到主、次设备号,然后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不同的函数。
当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_ module()函数中调用unregister_chrdev()来完成设备的注销。unregister_chrdev()的定义为:intunregister_chrdev(unsigned int major, const char *name);包括设备注册在内,设备驱动的初始化函数主要完成的功能是有以下5项。(1)对驱动程序管理的硬件进行必要的初始化。
对硬件寄存器进行设置。比如,设置中断掩码,设置串口的工作方式、并口的数据方向等。
(2)初始化设备驱动相关的参数。
一般说来,每个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。
(3)在内核注册设备。
调用register_chrdev()函数来注册设备。(4)注册中断。
如果设备需要IRQ支持,则要使用request_irq()函数注册中断。(5)其他初始化工作。
初始化部分一般还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也可以在open子程序或者其他地方申请。这些资源不用时,应该释放,以利于资源的共享。
若驱动程序是内核的一部分,初始化函数则要按如下方式声明: int __initchr_driver_init(void);其中__init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。
当驱动程序是以模块的形式编写时,则要按照如下方式声明: intinit_module(void)当运行后面介绍的insmod命令插入模块时,会调用init_module函数完成初始化工作。
4.设备驱动程序的开发过程
由于嵌入式设备由于硬件种类非常丰富,在默认的内核发布版中不一定包括所有驱动程序。所以进行嵌入式Linux系统的开发,很大的工作量是为各种设备编写驱动程序。除非系统不使用操作系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。可以在硬件生产厂家或者Internet上寻找驱动程序,也可以根据相近的硬件驱动程序来改写,这样可以加快开发速度。实现一个嵌入式Linux设备驱动的大致流程如下。
(1)查看原理图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可以根据需要自行设计。
(2)定义设备号。设备由一个主设备号和一个次设备号来标识。主设备号惟一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。
(3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。(4)设计所要实现的文件操作,定义file_operations结构。(5)实现所需的文件操作调用,如read、write等。
(6)实现中断服务,并用request_irq向内核注册,中断并不是每个设备驱动所必需的。
(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。
五、基本接口实验
在完成了基本实验后,我们开始着手基本接口实验。在这些实验中,我们学习如何编写设备驱动程序,及如何用测试程序检验驱动程序是否正确,并通过改写测试程序正常地对驱动程序进行相关操作。
1.实验十二 简单设备驱动程序
本次实验的任务是编写一个字符型设备驱动程序,并学习在应用程序中调用驱动。考虑到我们初次接触驱动程序的编写,对此还十分陌生,因此指导书中提供了本次实验所要用到的程序源代码。虽然这样一个字符型设备驱动程序并没有任何实际作用,但是它让我们轻松掌握了嵌入式驱动的编写过程,因为复杂繁琐的驱动,其骨架都是相同的。因此,看懂本实验的源代码,学习并模仿其编写方法,对于后续实验有着非常重要的意义。
2.实验十三 CPU GPIO驱动程序设计
在本实验中,我们要编写第一个针对实际硬件的驱动程序。我们知道,凡是操作系统控制外部设备,即使是最简单的硬件电路,也是需要驱动的。本实验涉及的外部硬件只有电阻和发光二极管。我们使用自己编写的驱动程序与应用程序控制 GPIO96的电平,通过 LED 的亮灭来判断,是否 CPU 做出了正确的响应。
补充代码(1)
//-------------------WRITE-----------------------ssize_tSIMPLE_GPIO_LED_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_GPIO_LED_DEBUG printk(“SIMPLE_GPIO_LED_write [--kernel--]n”);
#endif
return count;}
补充代码(2)
//-------------------OPEN------------------------ssize_tSIMPLE_GPIO_LED_open(structinode * inode ,struct file * file){ #ifdef OURS_GPIO_LED_DEBUG printk(“SIMPLE_GPIO_LED_open [--kernel--]n”);
#endif
MOD_INC_USE_COUNT;
return 0;}
补充代码(3)
//------------------structfile_operationsGPIO_LED_ctl_ops ={ open:SIMPLE_GPIO_LED_open, read:SIMPLE_GPIO_LED_read, write:SIMPLE_GPIO_LED_write, ioctl:SIMPLE_GPIO_LED_ioctl, release:SIMPLE_GPIO_LED_release, };实验作业
要求在目标板上LED闪烁产生亮7秒,灭2秒的效果 在测试程序中有这样一段代码: while(1){ ioctl(fd,LED_OFF);sleep(1);
sleep(1);while(1){ ioctl(fd,LED_OFF);sleep(2);
sleep(7);} 3.实验十四
中断实验
// 灭2秒 // 亮7秒 ioctl(fd,LED_ON);}
// 休眠1秒
ioctl(fd,LED_ON);只需将上面的代码改为如下代码即可:
在理论课中,我们学习了许多中断方面的知识,包括中断向量、中断优先级、中断过程等。在PXA270系统里,中断控制器分外部设备和 PXA270X 处理器设备产生的两个层次的中断,前者是初级的中断源,后者是次级中断源,大量的次级中断源通常被映射为一个初级中断源。
补充代码1 voidshowversion(void){ printk(“*********************************************n”);
printk(“t %s tn”, VERSION);
printk(“*********************************************nn”);
} static intSimpleINT_temp_count = 0;补充代码2 //-------------------READ------------------------ssize_tSIMPLE_INT_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){
#ifdef OURS_INT_DEBUG
#endif return count;printk(“SIMPLE_INT_read [--kernel--]n”);} 补充代码3 //-------------------WRITE-----------------------ssize_tSIMPLE_INT_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){
#ifdef OURS_INT_DEBUG
} 补充代码4 //------------------structfile_operationsINT_ctl_ops ={ open: SIMPLE_INT_open, read: SIMPLE_INT_read, #endif return count;printk(“SIMPL_INT_write [--kernel--]n”);write:SIMPLE_INT_write, ioctl:SIMPLE_INT_ioctl, release:SIMPLE_INT_release, };
通过此实验,我了解了硬件中断管脚与中断号的对应关系,以及中断号与中断处理程序的对应关系,对于今后编写更为复杂的中断程序打下基础。
4.实验十五
数码管显示实验
在此实验中,我们要编写针对 74LV164 的驱动程序,并用其串并转换功能来控制八段LED数码管的显示。
补充代码1 voidshowversion(void){ printk(“*********************************************n”);
printk(“t %s tn”, VERSION);
printk(“*********************************************nn”);
} 补充代码2 //-------------------READ------------------------ssize_tSERIAL_LED_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_HELLO_DEBUG
} 补充代码3 //-------------------WRITE-----------------------ssize_tSERIAL_LED_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops)return count;printk(“SERIAL_LED_read [--kernel--]n”);#endif { #ifdef OURS_HELLO_DEBUG
} 补充代码4 //-------------------IOCTL-----------------------ssize_tSERIAL_LED_ioctl(structinode * inode ,struct file * file, unsigned intcmd, long data){ #ifdef OURS_HELLO_DEBUG
#endif
} 补充代码5 //-------------------OPEN------------------------ssize_tSERIAL_LED_open(structinode * inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG
#endif
return 0;} MOD_INC_USE_COUNT;printk(“SERIAL_LED_open [--kernel--]n”);return 0;printk(“SERIAL_LED_ioctl [--kernel--]n”);return count;#endif write_byte(* buf);printk(“SERIAL_LED_write [--kernel--]n”);补充代码6 //-------------------RELEASE/CLOSE---------------ssize_tSERIAL_LED_release(structinode *inode ,struct file * file){ #ifdef OURS_HELLO_DEBUG
printk(“SERIAL_LED_release [--kernel--]n”);
#endif MOD_DEC_USE_COUNT;return 0;} 补充代码7 //------------------structfile_operationsSERIAL_LED_ops ={ open: SERIAL_LED_open,read: SERIAL_LED_read,write:SERIAL_LED_write,ioctl:SERIAL_LED_ioctl,release:SERIAL_LED_release, };补充代码8 staticint __initHW_SERIAL_LED_init(void){ int ret =-ENODEV;
ret =
devfs_register_chrdev(SERIAL_LED_MAJOR, &SERIAL_LED_ops);
showversion();if(ret < 0)“serial_led_ctl”,} {
} else { } return ret;printk(“ pxa270 serial_led_driver register success!![--kernel--]n”);printk(“ pxa270 init_module failed with %dn [--kernel--]”, ret);return ret;补充代码9 staticint __init pxa270_SERIAL_LED_init(void){ int ret =-ENODEV;
printk(“pxa270_SERIAL_LED_init [--kernel--]n”);
#endif
ret = HW_SERIAL_LED_init();if(ret)return ret;return 0;} 补充代码10 static void __exit cleanup_SERIAL_LED(void){ #ifdef OURS_HELLO_DEBUG #ifdef OURS_HELLO_DEBUG
#endif }
补充代码11 MODULE_DESCRIPTION(“serial_led driver module”);
MODULE_AUTHOR(“liduo”);
MODULE_LICENSE(“GPL”);
module_init(pxa270_SERIAL_LED_init);module_exit(cleanup_SERIAL_LED);使用测试程序看到的测试结果是数码管按0-9显示输出。实验作业要求在上述基础上,分别实现一下两个功能:
①要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字9-0。
②要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字02468。
由于在测试程序中定义了数组buf[10]分别存储了0-9是个数,因此上述功能的实现方法是,分别对测试程序做如下修改:
原测试程序: while(1){ for(count=0;count<10;count++){ data[0] = buf[count];ret=write(fd,data,1);sleep(1);} } 实现功能①: while(1){ for(count=9;count>=0;count--)} } 结果显示
// 倒序显示数字
{ data[0] = buf[count];ret=write(fd,data,1);sleep(1);devfs_unregister_chrdev(SERIAL_LED_MAJOR, “serial_led”);printk(“cleanup_SERIAL_LED [--kernel--]n”);实现功能②: while(1){ for(count=0;count<9;count=count+2)} } 结果显示
// 更改显数顺序
{ data[0] = buf[count];ret=write(fd,data,1);sleep(1);
通过更改显数的顺序,很容易实现实验作业里要求的功能。
5.实验十六 LED点阵驱动程序设计
通过本实验的操作,我们将 8X8 的点阵 LED 驱动起来并通过编写测试程序,使其能够按照您的意图进行显示。要求您还编写更多的测试程序
补充代码1 voidshowversion(void){ printk(“*********************************************n”);printk(“t %s tn”, VERSION);printk(“*********************************************nn”);
} 补充代码2 //-------------------READ------------------------ssize_tSIMPLE_LED_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_LED_DEBUG
#endif return count;printk(“SIMPLE_LED_read [--kernel--]n”);} 补充代码3 //-------------------IOCTL-----------------------ssize_tSIMPLE_LED_ioctl(structinode * inode ,struct file * file, unsigned intcmd, long data){
#endif
} 补充代码4 //------------------structfile_operationsLED_ctl_ops ={ open: SIMPLE_LED_open, read:
SIMPLE_LED_read, write: SIMPLE_LED_write, ioctl: SIMPLE_LED_ioctl, release:SIMPLE_LED_release, };补充代码5 staticint __init pxa270_LED_CTL_init(void){ int ret =-ENODEV;
printk(“pxa270_LED_CTL_init [--kernel--]n”);
#endif
ret = HW_LED_CTL_init();if(ret)
return ret;#ifdef OURS_LED_DEBUG return 0;printk(“SIMPLE_LED_ioctl [--kernel--]n”);#ifdef OURS_LED_DEBUG return 0;} 补充代码6 static void __exit cleanup_LED_ctl(void){
#ifdef OURS_LED_DEBUG
#endif
} ①要求您再编写一个测试程序,实现按横的方向隔行顺序扫描 LED 点阵数码管。
②要求您再编写一个测试程序,实现按竖的方向顺序扫描 LED 点阵数码管。作业一,隔行扫描:
printk(“cleanup_LED_ctl [--kernel--]n”);outw(0x0000,ioremap_addr);
devfs_unregister_chrdev(SIMPLE_LED_MAJOR, “led_ary_ctl”);for(i=1;i<=8;i2++){
buf[0]=c;buf[1]=~r;// row for(j=1;j<=8;j++){
} r = 1;c = c<<1;
write(fd,buf,2);
printf(“buf[0],buf[1]: [%x,%x]n”,buf[0],buf[1]);usleep(200000);// sleep 0.2 second r=r<<1;
buf[1]=~r;// column
结果显示
作业二,竖向扫描:
for(i=1;i<=8;i++){
buf[0]=c;buf[1]=~r;// row for(j=1;j<=8;j++){
} r = 1;c = c<<1;
write(fd,buf,2);
printf(“buf[0],buf[1]: [%x,%x]n”,buf[0],buf[1]);usleep(200000);// sleep 0.2 second r=r<<1;
buf[1]=~r;// column
结果显示
6.实验十七 AD驱动实验
通过本实验的操作,我们将 AD 转换器驱动起来并通过编写测试程序,使其能够将模拟信号量按照我们的要求转换成数字信号量。为了更加清楚地理解 AD 转换器的工作过程,请您再编写一个测试程序,将 UCB_ADC_INP_AD0 换成其他通道,来观察其他 AD 通道情况。
补充代码1 voidshowversion(void){ printk(“%sn”,VERSION);} struct ucb1x00 *ad_ucb;
补充代码2 //-------------------READ------------------------staticssize_tadctl_read(struct file * file ,char *buf, size_t count, loff_t *offset){
} 补充代码3 //-------------------WRITE-----------------------ssize_tadctl_write(struct file * file ,const char *buf, size_t count, loff_t *offset){
#ifdef OURS_HELLO_DEBUG printk(“writen”);
#endif
} 补充代码4 //-------------------OPEN------------------------ssize_tadctl_open(structinode * inode ,struct file * file){
#ifdef OURS_HELLO_DEBUG printk(“openn”);
#endif
}
补充代码5 //-------------------RELEASE/CLOSE---------------ssize_tadctl_release(structinode *inode ,struct file * file){
#ifdef OURS_HELLO_DEBUG printk(“releasen”);
#endif return 0;return 0;return count;#ifdef OURS_HELLO_DEBUG printk(“readn”);#endif return count;} 补充代码6 staticstructfile_operationsadctl_ops = {
};补充代码7 //-------------------INIT------------------------staticint __initHW_AD_CTL_init(void){
return ret;}
补充代码8 staticint __init pxa270_AD_CTL_init(void){ int ret =-ENODEV;#ifdef OURS_HELLO_DEBUG int ret =-ENODEV;ret = devfs_register_chrdev(ADCTL_MAJOR, “adctl”, &adctl_ops);showversion();ad_ucb=ucb1x00_get();if(ret < 0){
} else { } adctl_dev_handle = devfs_register(NULL, “ad_ctl”, DEVFS_FL_DEFAULT, printk(“adctl driver register success!n”);printk(“fail %dn”,ret);return 0;read: ioctl: adctl_read, adctl_ioctl, write: adctl_write, open: adctl_open, release:adctl_release,ADCTL_MAJOR, 0, S_IFCHR, &adctl_ops, NULL);printk(“initn”);#endif ret=HW_AD_CTL_init();if(ret)}
补充代码9 static void __exit cleanup_AD_ctl(void){
}
7.实验十八 DA驱动实验
通过本实验的操作,我们使用示波器看到了通过DA转换而输出的波形。在此基础上,要求试写一个实现输出三角波的测试程序。
补充代码1 #include
} printk(“t %st n”,VERSION);printk(“*****************************n”);static long ioremap_addr;补充代码3 //-------------------READ------------------------ssize_tSIMPLE_DA_read(struct file * file ,char * buf, size_t count, loff_t * f_ops){ #ifdef OURS_DA_DEBUG
} 补充代码4 //-------------------WRITE-----------------------ssize_tSIMPLE_DA_write(struct file * file ,const char * buf, size_t count, loff_t * f_ops){
printk(“SIMPLE_DA_write[--kernel--]n”);
#endif
return count;} 补充代码5 //-------------------IOCTL-----------------------ssize_tSIMPLE_DA_ioctl(structinode * inode ,struct file * file, unsigned intcmd, outb(buf[0],ioremap_addr);#ifdef OURS_DA_DEBUG return count;#endif printk(“SIMPLE_DA_read[--kernel--]n”);long data){ #ifdef OURS_DA_DEBUG
printk(“SIMPLE_DA_ioctl[--kernel--]n”);
#endif return 0;} 补充代码6 //-------------------OPEN------------------------ssize_tSIMPLE_DA_open(structinode * inode ,struct file * file){
#ifdef OURS_DA_DEBUG printk(“SIMPLE_DA_open [--kernel--]n”);
MOD_INC_USE_COUNT;return 0;
#endif } 补充代码7 /------------------structfile_operationsDA_ctl_ops ={
read: SIMPLE_DA_read,};
补充代码8 release:
SIMPLE_DA_release, ioctl:
SIMPLE_DA_ioctl, write:
SIMPLE_DA_write, //-------------------INIT------------------------staticint __initHW_DA_CTL_init(void){ int ret =-ENODEV;
}
补充代码9 staticint __init pxa270_DA_CTL_init(void){ int ret =-ENODEV;
printk(“pxa270_DA_CTL_init [--kernel--]n”);
#endif #ifdef OURS_DA_DEBUG } printk(“ pxa270 led_driver register success!![--kernel--]n”);{ else } return ret;printk(“ pxa270: init_module failed with %dn [--kernel--]”, ret);{ if(ret < 0)showversion();ret = devfs_register_chrdev(SIMPLE_DA_MAJOR, “da_ctl”, &DA_ctl_ops);
ret = HW_DA_CTL_init();if(ret)
return ret;return 0;} 补充代码10 static void __exit cleanup_DA_ctl(void){
#endif } 补充代码11 MODULE_DESCRIPTION(“DA_ctl driver module”);MODULE_AUTHOR(“liduo”);MODULE_LICENSE(“GPL”);module_init(pxa270_DA_CTL_init);module_exit(cleanup_DA_ctl);printk(“cleanup_DA_ctl [--kernel--]n”);#ifdef OURS_DA_DEBUG
六、实验中遇到的问题及解决方法
每一次上课重新启动后,当需要将宿主PC机的根目录挂在到PXA270-EP目标板的mnt目录下(即在超级终端中输入命令“mount –o soft,timeo=100,rsize=1024 192.168.0.100:/ /mnt”)时,常显示无法挂载。
解决方法:在超级终端下的挂载命令应该用”mount –o nolock 192.168.0.100:/ /mnt”,如果依然不能挂载需要重启NFS服务,即在PC机终端中输入命令”service nfs restart”两遍后就可以挂载,当然有时候也可能是因为网线没插好。
在每次重启机器之后都需要将PC机终端的IP地址和开发板中的系统的IP地址设定正确,不然也无法挂载。
七、实验总结及心得
本学期的所有实验均在宿主PC机与PXA270-EP目标板上进行。在实验中,我们先建立硬件实验平台,又建立主机软件开发环境,接着为实验进行各项配置,最后完成了各个实验中的多种功能。值得注意的是,前期的硬件、软件准备必须完整无误地实现,后续的实验才能顺利进行。所以,打基础的工作一定要仔细谨慎。后续实验中虽然给出了驱动程序的框架,仍需要我们自己补充完整,并开动脑筋举一反三,在原代码的基础上进行一定修改以实现新的功能。
通过这学期的实验,我逐步完成了建立实验软件开发平台,搭建实验编译软件环境,在PC上编辑、编译一个应用程序,并且在嵌入式系统上运行和调试它的过程。在实验中,不难发现,编译驱动程序大体框架都是一样的,比如里面的读函数、写函数、ioctl函数、打开、关闭以及函数模块的初始化并且在超级终端上显示出等。但所不同的是,要根据不同的实验要求修改名称,并且对其中必要的部分进行修改。
除此之外,我认为很多基础知识对实验的进行也起着非常大的作用,例如数码管的显示原理。在掌握了基础知识之后,上机的过程会显得相对简单,尤其是代码框架已经给出,我们所以需要做的就是根据需要稍作改动来得到我们想要的结果。
在实验过程中常常会遇到各种各样的问题,刚开始时我不知如何是好,只能求助于老师和同学,后来随着实验的进行,我对实验的内容和虚拟机都有了一定的了解,遇到问题时也可以静下心来思考其原因,自己尝试各种方法去解决问题。整个实验让我了解了一套完整的嵌入式系统驱动程序开发的全过程,学到的内容非常丰富,相信在学习了这些内容后,在今后的学习工作中接触到类似内容,我不会感到无从下手,而是能够有条不紊。
感谢老师的辛勤指导!
第三篇:北邮嵌入式实验报告
嵌入式系统开发实验报告
班
级: 姓
名: 班内序号: 学
号: 日
期:
目录
一、实验目的..............................................................................................1
二、实验设备..............................................................................................1
三、基础实验(实验一~实验七)............................................................1
1.实验五..................................................................................................2 2.实验六..................................................................................................2 3.实验七..................................................................................................2
四、驱动程序..............................................................................................3
1.设备驱动程序的概念..........................................................................3 2.驱动程序结构......................................................................................3 3.设备注册和初始化..............................................................................4 4.设备驱动程序的开发过程..................................................................6
五、基本接口实验......................................................................................6
1.实验十二 简单设备驱动程序............................................................6 2.实验十三 CPU GPIO驱动程序设计...................................................7 3.实验十四 中断实验...........................................................................8 4.实验十五 数码管显示实验................................................................9 5.实验十六 LED点阵驱动程序设计...................................................11 6.实验十七 AD驱动实验....................................................................12 7.实验十八 DA驱动实验....................................................................13
六、实验中遇到的问题及解决方法........................................................15
七、实验总结及心得................................................................................15
一、实验目的
通过实验熟悉Linux环境,并掌握一些基本接口驱动的写法和用C语言编写简单的实验程序。学习LINUX开发环境的搭建,通讯配置等。并熟练掌握LINUX驱动程序的编写及开发流程。对嵌入式系统有进一步的了解。
二、实验设备
1.一套PXA270EP嵌入式实验箱
2.安装Redhat9的宿主PC机,并且配置好ARM Linux的开发环境
三、基础实验(实验一~实验七)
实验一到六为基础实验,主要是为了在熟悉实验操作平台的同时为后续实验搭建好软、硬件环境,配置好相关的协议、服务。
其中实验一是各个硬件的互联,搭建好了实验的硬件环境。实验二是在宿主PC端安装虚拟机,提供了实验需要的Linux操作系统。实验三是宿主PC端开发环境的安装与配置。
实验四是配置宿主PC机端的超级终端,使PC机与PXA270目标板之间可以通过串口通讯。在每次重启宿主PC机时,都需要重新将超级终端挂载到虚拟机上,挂载之前须通过ifconfig命令查看该机的IP地址,若其已经复位,须用命令:ifconfig eth0 192.168.0.100 up重置宿主PC机的IP地址。挂载虚拟机的代码为:
root ifconfig eth0 192.168.0.50 up mount –o nolock 192.168.0.100:/ /mnt 实验五是配置宿主PC机的TFTP服务。TFTP是简单文件传输协议。每次重启宿主PC机时,都要重启该服务,重启命令为:
service xinetd restart。
实验六是配置宿主PC机端NFS服务。NFS是指网络文件系统,它实现了文件在不同的系统间使用。当使用者想用远端档案时,只需调用“mount”就可以远端系统挂接在自己的档案系统之下。每次重启宿主PC机时,也都要重启该服务,重启命令为: service nfs restart service nfs restart
1.实验五
实验五为宿主PC机配置了TFTP服务。TFTP(Trivial File Transfer Protocol)是简单文件传输协议,由于特定开发环境的制约,这一服务是必须的。在配置完成后,每次重启宿主PC机时,都须先输入命令:service xinetd restart,以启动TFTP服务。
2.实验六
实验六为宿主PC机配置了NFS服务。NFS(Network File System)指网络文件系统,它实现了文件在不同的系统间使用。当我们想用远端档案时,只需调用“mount”就可以远端系统挂接在自己的档案系统之下。每次重启宿主PC机时,都须先输入命令:service nfs restart,以启动nfs服务。
3.实验七
实验七通过用c语言编写的简单程序HelloWorld,测试前面几个实验是否成功配置好环境,从超级终端可以看到HelloWorld程序的运行结果。
四、驱动程序
1.设备驱动程序的概念
设备驱动程序实际是处理和操作硬件控制器的软件,从本质上讲,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口,驱动程序屏蔽了硬件的细节,完成以下功能:
对设备初始化和释放;
对设备进行管理,包括实时参数设置,以及提供对设备的操作接口; 读取应用程序传送给设备文件的数据或者回送应用程序请求的数据; 检测和处理设备出现的错误。
Linux操作系统将所有的设备全部看成文件,并通过文件的操作界面进行操作。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说,是把设备映射为一个特殊的设备文件,用户程序可以像对其他文件一样对此设备文件进行操作。这意味着:
由于每一个设备至少由文件系统的一个文件代表,因而都有一个“文件名”。 应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。
打开了代表着目标设备的文件,即建立起与设备的连接后,可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。
设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。
由于Linux 中将设备当做文件处理,所以对设备进行操作的调用格式与对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,会从用户态转到核心态,通过内核将open()这样的系统调用转换成对物理设备的操作。
2.驱动程序结构
一个设备驱动程序模块的基本框架
在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点也可以理解为设备的句柄,就是对设备进行操作的基本函数。字符型设备驱动程序提供如下几个入口点:
open入口点。打开设备准备I/O操作。对字符设备文件进行打开操作,都会调用设备的open入口点。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。
close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。
read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。
write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。
ioctl入口点。执行读、写之外的操作。
select入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。
3.设备注册和初始化
设备的驱动程序在加载的时候首先需要调用入口函数init_module(),该函数最重要的一个工作就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册。register_chrdev 的定义为:int register_chrdev(unsigned int major, const char *name, struct file_ operations *fops);其中,major是为设备驱动程序向系统申请的主设备号,如果为0,则系统为此驱动程序动态分配一个主设备号。name是设备名,fops是对各个调用的入口点说明。此函数返回0时表示成功;返回-EINVAL,表示申请的主设备号非法,主要原因是主设备号大于系统所允许的最大设备号;返回-EBUSY,表示所申请的主设备号正在被其他设备程序使用。如果动态分配主设备号成功,此函数将返回所分配 的主设备号。如果register_chrdev()操作成功,设备名就会出现在/proc/dvices文件中。
Linux在/dev目录中为每个设备建立一个文件,用ls –l命令列出函数返回值,若小于0,则表示注册失败;返回0或者大于0的值表示注册成功。注册以后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux通过请求访问的设备名得到主、次设备号,然后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不同的函数。
当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_ module()函数中调用unregister_chrdev()来完成设备的注销。unregister_chrdev()的定义为:int unregister_chrdev(unsigned int major, const char *name);包括设备注册在内,设备驱动的初始化函数主要完成的功能是有以下5项。(1)对驱动程序管理的硬件进行必要的初始化。
对硬件寄存器进行设置。比如,设置中断掩码,设置串口的工作方式、并口的数据方向等。
(2)初始化设备驱动相关的参数。
一般说来,每个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。
(3)在内核注册设备。
调用register_chrdev()函数来注册设备。(4)注册中断。
如果设备需要IRQ支持,则要使用request_irq()函数注册中断。(5)其他初始化工作。
初始化部分一般还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也可以在open子程序或者其他地方申请。这些资源不用时,应该释放,以利于资源的共享。
若驱动程序是内核的一部分,初始化函数则要按如下方式声明: int __init chr_driver_init(void);其中__init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。
当驱动程序是以模块的形式编写时,则要按照如下方式声明: int init_module(void)当运行后面介绍的insmod命令插入模块时,会调用init_module函数完成初始化工作。
4.设备驱动程序的开发过程
由于嵌入式设备由于硬件种类非常丰富,在默认的内核发布版中不一定包括所有驱动程序。所以进行嵌入式Linux系统的开发,很大的工作量是为各种设备编写驱动程序。除非系统不使用操作系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。可以在硬件生产厂家或者Internet上寻找驱动程序,也可以根据相近的硬件驱动程序来改写,这样可以加快开发速度。实现一个嵌入式Linux设备驱动的大致流程如下。
(1)查看原理图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可以根据需要自行设计。
(2)定义设备号。设备由一个主设备号和一个次设备号来标识。主设备号惟一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一个设备驱动控制下的某个独立的设备。
(3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。(4)设计所要实现的文件操作,定义file_operations结构。(5)实现所需的文件操作调用,如read、write等。
(6)实现中断服务,并用request_irq向内核注册,中断并不是每个设备驱动所必需的。
(7)编译该驱动程序到内核中,或者用insmod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。
五、基本接口实验
在完成了基本实验后,我们开始着手基本接口实验。在这些实验中,我们学习如何编写设备驱动程序,及如何用测试程序检验驱动程序是否正确,并通过改写测试程序正常地对驱动程序进行相关操作。
1.实验十二 简单设备驱动程序
本次实验的任务是编写一个字符型设备驱动程序,并学习在应用程序中调用驱动。考虑到我们初次接触驱动程序的编写,对此还十分陌生,因此指导书中提供了本次实验所要用到的程序源代码。虽然这样一个字符型设备驱动程序并没有任何实际作用,但是它让我们轻松掌握了嵌入式驱动的编写过程,因为复杂繁琐的驱动,其骨架都是相同的。因此,看懂本实验的源代码,学习并模仿其编写方
法,对于后续实验有着非常重要的意义。
2.实验十三 CPU GPIO驱动程序设计
在本实验中,我们要编写第一个针对实际硬件的驱动程序。我们知道,凡是操作系统控制外部设备,即使是最简单的硬件电路,也是需要驱动的。本实验涉及的外部硬件只有电阻和发光二极管。我们使用自己编写的驱动程序与应用程序控制 GPIO96的电平,通过 LED 的亮灭来判断,是否 CPU 做出了正确的响应。
实验第一步是编写 PXA270 GPIO 驱动程序。驱动的写法参照实验十二,大体相同,主要区别如下:
//-------------------控制IO设备-----------------------switch(cmd){ case LED_ON : {GPCR3 |= 0x1;break;} 为1 case LED_OFF: {GPSR3 |= 0x1;break;} 为1
default :
{
//如果cmd=LED_OFF,那么GPSR3置//如果cmd=LED_ON,那么GPCR3置printk(“lcd control : no cmd run [--kernel--]n”);return(-EINVAL);} //-------------------驱动程序初始化--------------------------GPDR3 |= 0x00000001;//设置GPIO96输出模式:开灯 GPSR3 |= 0x00000001;// 关灯
对Makefile中的目标体和依赖文件也要做相应的修改,此处省略。在测试程序中有这样一段代码: while(1){ ioctl(fd, LED_OFF);sleep(1);
// 休眠1秒
ioctl(fd,LED_ON);sleep(1);}
实验作业要求在目标板上LED闪烁产生亮7秒,灭5秒的效果,很容易实现,只需将上面的代码改为如下代码即可:
while(1){ ioctl(fd, LED_OFF);sleep(5);
sleep(7);}
// 灭5秒 // 亮7秒 ioctl(fd,LED_ON);
3.实验十四
中断实验
在理论课中,我们学习了许多中断方面的知识,包括中断向量、中断优先级、中断过程等。在PXA270系统里,中断控制器分外部设备和 PXA270X 处理器设备产生的两个层次的中断,前者是初级的中断源,后者是次级中断源,大量的次级中断源通常被映射为一个初级中断源。
在此实验中,我们要编写一个中断程序,利用目标板上的按键SW2来产生中断,使得当每次按下此按键时,在超级终端上打印出响应的信息。编写中断程序与前两个实验的主要区别如下:
/*--------------------------初始化
request_irq申请硬件中断,参数包括申请的硬件中断号、设备id、中断处理的
一些属性(SA_INTERRUPT是快速处理程序,调用时屏蔽所有中断)等
-----------------*/ ret = request_irq(SIMPLE_INT_IRQ, &SIMPLE_INT_interrupt, SA_INTERRUPT, “int_ctl”, NULL);//--------------------卸载,对应request_irq释放中断---------------------free_irq(SIMPLE_INT_IRQ,NULL);通过此实验,我了解了硬件中断管脚与中断号的对应关系,以及中断号与中断处理程序的对应关系,对于今后编写更为复杂的中断程序打下基础。
4.实验十五
数码管显示实验
在此实验中,我们要编写针对 74LV164 的驱动程序,并用其串并转换功能来控制八段LED数码管的显示。
在编写驱动程序时,主要有以下需要注意的: // 按位写入
void write_bit(int data){
{ } else
{GPCR2 |=(0x1 << 26);
GPCR2 |=(0x1 << 27);GPSR2 |=(0x1 << 26);if((data & 0x80)== 0x80)
}
GPSR2 |=(0x1 << 27);} // 按字节写,一次写8位
void write_byte(int data){ { } } int i;
write_bit(data << i);for(i=0;i<8;i++)
使用测试程序看到的测试结果是数码管按0-9显示输出。实验作业要求在上述基础上,分别实现一下两个功能:
① 要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字9-0。
② 要求您再编写一个测试程序,实现 PXA270-EP 目标板上的 LED 数码管循环显示的数字02468。
由于在测试程序中定义了数组buf[10]分别存储了0-9是个数,因此上述功能的实现方法是,分别对测试程序做如下修改:
原测试程序:
while(1){ { } } for(count=0;count<10;count++)
data[0] = buf[count];ret=write(fd,data,1);sleep(1);
实现功能①:
while(1){ { } } for(count=9;count>=0;count--)// 倒序显示数字
data[0] = buf[count];ret=write(fd,data,1);sleep(1);
实现功能②:
while(1){ { } } for(count=0;count<9;count=count+2)// 更改显数顺序
data[0] = buf[count];ret=write(fd,data,1);sleep(1);
通过更改显数的顺序,很容易实现实验作业里要求的功能。
5.实验十六 LED点阵驱动程序设计
通过本实验的操作,我们将 8X8 的点阵 LED 驱动起来并通过编写测试程序,使其能够按照
邝坚_北邮嵌入式实验报告
本文2025-01-29 06:11:02发表“合同范文”栏目。
本文链接:https://www.wnwk.com/article/337011.html
- 二年级数学下册其中检测卷二年级数学下册其中检测卷附答案#期中测试卷.pdf
- 二年级数学下册期末质检卷(苏教版)二年级数学下册期末质检卷(苏教版)#期末复习 #期末测试卷 #二年级数学 #二年级数学下册#关注我持续更新小学知识.pdf
- 二年级数学下册期末混合运算专项练习二年级数学下册期末混合运算专项练习#二年级#二年级数学下册#关注我持续更新小学知识 #知识分享 #家长收藏孩子受益.pdf
- 二年级数学下册年月日三类周期问题解题方法二年级数学下册年月日三类周期问题解题方法#二年级#二年级数学下册#知识分享 #关注我持续更新小学知识 #家长收藏孩子受益.pdf
- 二年级数学下册解决问题专项训练二年级数学下册解决问题专项训练#专项训练#解决问题#二年级#二年级数学下册#知识分享.pdf
- 二年级数学下册还原问题二年级数学下册还原问题#二年级#二年级数学#关注我持续更新小学知识 #知识分享 #家长收藏孩子受益.pdf
- 二年级数学下册第六单元考试卷家长打印出来给孩子测试测试争取拿到高分!#小学二年级试卷分享 #二年级第六单考试数学 #第六单考试#二年级数学下册.pdf
- 二年级数学下册必背顺口溜口诀汇总二年级数学下册必背顺口溜口诀汇总#二年级#二年级数学下册 #知识分享 #家长收藏孩子受益 #关注我持续更新小学知识.pdf
- 二年级数学下册《重点难点思维题》两大问题解决技巧和方法巧算星期几解决周期问题还原问题强化思维训练老师精心整理家长可以打印出来给孩子练习#家长收藏孩子受益 #学霸秘籍 #思维训练 #二年级 #知识点总结.pdf
- 二年级数学下册 必背公式大全寒假提前背一背开学更轻松#二年级 #二年级数学 #二年级数学下册 #寒假充电计划 #公式.pdf


