查看: 1568|回复: 0
打印 上一主题 下一主题

基于ARM-Linux系统开发平台下嵌入式MP3的设计与实现

[复制链接]
跳转到指定楼层
沙发
发表于 2015-3-31 10:42:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

基于ARM-Linux系统开发平台下

嵌入式MP3的设计与实现

[摘  要] 随着计算机技术和微电子的的迅速发展,嵌入式系统己经被广泛地应用到许多领域,如科学研究、工程设计、军事技术以及各种商业应用等。嵌入式系统被定义为以应用为中心,以计算机技术为基础、软硬件可裁剪、适应于特定应用系统对功能、可靠性、成本、体积、功耗等严格要求的专业计算机系统。在目前的各种嵌入式处理器中,由于ARM芯片的低功耗、低成本等显著优点,因而获得众多的半导体厂商的大力支持,在32位嵌入式应用领域获得了巨大的成功。另一方面,Linux操作系统具有开放源代码、功能强大且易于移植等特点而成为嵌入式操作系统的首选,因此在ARM芯片上构建嵌入式Linux系统成为嵌入式领域的一个热点。

[关键词] 嵌入式系统;ARM;Linux操作系统;热点

Abstract

Zhuhandon

(Zhejiang Ocean University Donghai Science and Technology College,Zhuhandon 316004, China)

[Abstract] With the rapid development of computer technology and micro-electronics, embedded system is widely applied To many fields,such as scientific research, engineering design,military technology,and kinds of business applications.Embedded system is defmed as a professional computer system based on computer technology which aims at applications.Its software and hardware can bu cut down.It also meets the specifical requirement,such as func的Il’reliability,cost,volume,power dissipation.At the present time,because of lower power dissipation and lower cost of ARMts strong suit,many manufacturers use it.ARM is more successful than other embedded processor of 32-bits.On the other hand,linux is open—soufoed'easy to port and has powerful function SO that it turns into the first choice of embedded system.Therefore building embedded linux system is popular.

[Key words] micro-electronics,embedded system,ARM


前  言

嵌入式系统是一种专用的计算机系统,作为装置或设备的一部分。通常,嵌入式系统[1]是一个控制程序存储在ROM中的嵌入式处理器控制板。事实上,所有带有数字接口的设备,如手表、微波炉、录像机、汽车等,都使用嵌入式系统,有些嵌入式系统还包含操作系统,但大多数嵌入式系统都是由单个程序实现整个控制逻辑。从应用对象上加以定义,从右图中可以看出嵌入式系统是软件和硬件的综合体,还可以涵盖机械等附属装置。国内普遍认同的嵌入式系统定义为:以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。

第1章.嵌入式系统

1.1 嵌入式系统特点

(1)可裁剪性。支持开放性和可伸缩性的体系结构

(2)强实时性。EOS实时性一般较强,可用于各种设备控制中。

(3)统一的接口。提供设备统一的驱动接口。

(4)操作方便、简单、提供友好的图形GUI和图形界面,追求易学易用。

提供强大的网络功能,支持TCP/IP协议及其他协议,提供TCP/UDP/IP/PPP协议支持及统一的MAC访问层接口,为各种移动计算设备预留接口。

(5)强稳定性,弱交互性嵌入式系统一旦开始运行就不需要用户过多的干预、这就要负责系统管理的EOS具有较强的稳定性。嵌入式操作系统用户接口一般不提供操作命令,它通过系统的调用命令向用户程序提供服务。

(6)固化代码。在嵌入式系统中,嵌入式操作系统和应用软件被固化在嵌入式系统计算机的ROM中。

(7)更好的硬件适应性,也就是良好的移植性。[5]

(8)嵌入式系统和具体应用有机地结合在一起,它的升级换代也是和具体产品同步进行,因此嵌入式系统产品一旦进入市场,具有较长的生命周期。

1.2 嵌入式计算机系统同通用计算机系统区别

  (1)嵌入式系统通常是面向特定应用的,嵌入式CPU与通用型的最大不同就是嵌入式CPU大多工作在为特定用户群设计的系统中,它通常都具有功耗低、体积小、集成度高等特点,能够把通用CPU中许多由板卡完成的任务集成在芯片内部,从而有利于嵌入式系统设计小型化,移动能力大大增强,跟网络的耦合也越来越紧密。
  (2)嵌入式系统是将先进的计算机技术、半导体技术和电子技术与各个行业的具体应用相结合的产物。这一点就决定了它必然是一个技术密集、资金密集、高度分散、不断创新的知识集成系统。
  (3)嵌入式系统的硬件和软件都必须高效率地设计,量体裁衣、去除不需要的多余功能,力争在更小的硅片面积上实现同样的性能,这样才能在具体应用中更具有竞争力。
    (4)嵌入式系统和具体应用有机地结合在一起,它的升级换代也是和具体产品同步进行,因此嵌入式系统产品一旦进入市场,具有较长的生命周期。
    (5)为了提高执行速度和系统可靠性,嵌入式系统中的软件一般都固化在存储器芯片或单片机本身中,而不是存储于磁盘等载体中。
  (6)嵌入式系统本身不具备自主开发能力,即使设计完成以后用户通常也是不能对其中的程序功能进行修改的,必须有一套与通用计算机系统连接的开发工具和环境才能进行开发。

1.3系统组成:

一个嵌入式系统装置一般都由嵌入式计算机系统和执行装置组成,

嵌入式计算机系统是整个嵌入式系统的核心,由硬件层、中间层、系统软件层和应用软件层组成。执行装置也称为被控对象,它可以接受嵌入式计算机系统发出的控制命令,执行所规定的操作或任务

1硬件层

硬件层中包含嵌入式微处理器、存储器(SDRAM、ROM、Flash等)、通用设备接口和I/O接口(A/D、D/A、I/O等)。在一片嵌入式处理器基础上添加电源电路、时钟电路和存储器电路,就构成了一个嵌入式核心控制模块。其中操作系统和应用程序都可以固化在ROM中。

(1)嵌入式微处理器

嵌入式系统硬件层的核心是嵌入式微处理器,嵌入式微处理器与通用CPU最大的不同在于嵌入式微处理器大多工作在为特定用户群所专用设计的系统中,它将通用CPU许多由板卡完成的任务集成在芯片内部,从而有利于嵌入式系统在设计时趋于小型化,同时还具有很高的效率和可靠性。

嵌入式微处理器的体系结构可以采用冯·诺依曼体系或哈佛体系结构指令系统可以选用精简指令系统(Reduced Instruction Set Computer,RISC)和复杂指令系统CISC(Complex Instruction Set Computer,CISC)。RISC计算机在通道中只包含最有用的指令,确保数据通道快速执行每一条指令,从而提高了执行效率并使CPU硬件结构设计变得更为简单。

嵌入式微处理器有各种不同的体系,即使在同一体系中也可能具有不同的时钟频率数据总线宽度,或集成了不同的外设和接口。据不完全统计,全世界嵌入式微处理器已经超过1000多种,体系结构有30多个系列,其中主流的体系有ARM、MIPS、PowerPC、X86和SH等。但与全球PC市场不同的是,没有一种嵌入式微处理器可以主导市场,仅以32位的产品而言,就有100种以上的嵌入式微处理器。嵌入式微处理器的选择是根据具体的应用而决定的。

(2)存储器

嵌入式系统需要存储器来存放和执行代码。嵌入式系统的存储器包含Cache、主存和辅助存储器。

(3)主存

主存是嵌入式微处理器能直接访问的寄存器,用来存放系统和用户的程序及数据。它可以位于微处理器的内部或外部,其容量为256KB~1GB,根据具体的应用而定,一般片内存储器容量小,速度快,片外存储器容量大。

常用作主存的存储器有:

ROM类 NOR Flash、EPROM和PROM等。

RAM类 SRAM、DRAM和SDRAM等。

其中NOR Flash 凭借其可擦写次数多、存储速度快、存储容量大、价格便宜等优点,在嵌入式领域内得到了广泛应用。

(4)辅助存储器

辅助存储器用来存放大数据量的程序代码或信息,它的容量大、但读取速度与主存相比就慢的很多,用来长期保存用户的信息。

嵌入式系统中常用的外存有:硬盘、NAND Flash、CF卡、MMC和SD卡等。

(5)通用设备接口和I/O接口

嵌入式系统和外界交互需要一定形式的通用设备接口,如A/D、D/A、I/O等,外设通过和片外其他设备的或传感器的连接来实现微处理器的输入/输出功能。每个外设通常都只有单一的功能,它可以在芯片外也可以内置芯片中。嵌入式系统中常用的通用设备接口有A/D(模/数转换接口)、D/A(数/模转换接口),I/O接口有RS-232接口(串行通信接口)、Ethernet(以太网接口)、USB(通用串行总线接口)、音频接口、VGA视频输出接口、I2C(现场总线)、SPI(串行外围设备接口)和IrDA(红外线接口)等。

2.中间层

硬件层与软件层之间为中间层,也称为硬件抽象层(Hardware Abstract Layer,HAL)或板级支持包(Board Support Package,BSP),它将系统上层软件与底层硬件分离开来,使系统的底层驱动程序与硬件无关,上层软件开发人员无需关心底层硬件的具体情况,根据BSP 层提供的接口即可进行开发。该层一般包含相关底层硬件的初始化、数据的输入/输出操作和硬件设备的配置功能。BSP具有以下两个特点。

硬件相关性:因为嵌入式实时系统的硬件环境具有应用相关性,而作为上层软 件与硬件平台之间的接口,BSP需要为操作系统提供操作和控制具体硬件的方法。

操作系统相关性:不同的操作系统具有各自的软件层次结构,因此,不同的操作系统具有特定的硬件接口形式。

实际上,BSP是一个介于操作系统和底层硬件之间的软件层次,包括了系统中大部分与硬件联系紧密的软件模块。设计一个完整的BSP需要完成两部分工作:嵌入式系统的硬件初始化以及BSP功能,设计硬件相关的设备驱动

3 系统软件

系统软件层由实时多任务操作系统(Real-time Operation System,RTOS)、文件系统、图形用户接口(Graphic User Interface,GUI)、网络系统及通用组件模块组成。RTOS是嵌入式应用软件的基础和开发平台。

(1)嵌入式操作系统

嵌入式操作系统(Embedded Operation System,EOS)是一种用途广泛的系统软件,过去它主要应用与工业控制和国防系统领域。EOS负责嵌入系统的全部软、硬件资源的分配、任务调度,控制、协调并发活动。它必须体现其所在系统的特征,能够通过装卸某些模块来达到系统所要求的功能。已推出一些应用比较成功的EOS产品系列。随着Internet技术的发展、信息家电的普及应用及EOS的微型化和专业化,EOS开始从单一的弱功能向高专业化的强功能方向发展。嵌入式操作系统在系统实时高效性、硬件的相关依赖性、软件固化以及应用的专用性等方面具有较为突出的特点。EOS是相对于一般操作系统而言的,它除具有了一般操作系统最基本的功能,如任务调度、同步机制、中断处理、文件处理等外,还有以下

(2)文件系统

通用操作系统的文件系统通常具有以下功能:

提供用户对文件操作的命令。

提供用户共享文件的机制。

管理文件的存储介质。

提供文件的存取控制机制,保障文件及文件系统的安全性。

提供文件及文件系统的备份和恢复功能。

提供对文件的加密和解密功能。

嵌入式文件系统比较简单,主要提供文件存储、检索和更新等功能,一般不提供保护和加密等安全机制。它以系统调用和命令方式提供文件的各种操作,主要有:

设置、修改对文件和目录的存取权限。

提供建立、修改、改变和删除目录等服务。

提供创建、打开、读写、关闭和撤销文件等服务。

4 初始化

系统初始化过程可以分为3个主要环节,按照自底向上、从硬件到软件的次序依次为:片级初始化、板级初始化和系统级初始化。

(1)片级

完成嵌入式微处理器的初始化,包括设置嵌入式微处理器的核心寄存器控制寄存器、嵌入式微处理器核心工作模式和嵌入式微处理器的局部总线模式等。片级初始化把嵌入式微处理器从上电时的默认状态逐步设置成系统所要求的工作状态。这是一个纯硬件的初始化过程。

(2)板级

完成嵌入式微处理器以外的其他硬件设备的初始化。另外,还需设置某些软件的数据结构和参数,为随后的系统级初始化和应用程序的运行建立硬件和软件环境。这是一个同时包含软硬件两部分在内的初始化过程。

(3)系统

该初始化过程以软件初始化为主,主要进行操作系统的初始化。BSP将对嵌入式微处理器的控制权转交给嵌入式操作系统,由操作系统完成余下的初始化操作,包含加载和初始化与硬件无关的设备驱动程序,建立系统内存区,加载并初始化其他系统软件模块,如网络系统、文件系统等。最后,操作系统创建应用程序环境,并将控制权交给应用程序的并将控制权交给应用程序的入口。

第2章 U-boot启动代码分析

2.1.概述

嵌入式linux系统从软件角度看,一般可分为以下几个层次

.引导加载程序,其主要的工作包括固化在固件中的boot代码和bootloader两大部分然而在大部分嵌入式系统中没有固件。所以bootloader是上电后第一个执行的程序,U-boot为bootloader一种,运用最为广泛。

.Linux内核,bootloader最终目的就是为了启动内核,其是整个嵌入式系统的核心,掌管着对软硬件的调配控制着整个系统的运行,就像是人的大脑一样

.文件系统:文件系统包括根文件系统和建立于Flash内存设备之上的文件系统,其包含了Linux系统能够运行所必须的应用程序,库等,比如提供用户与系统交互的shell程序,通过shell程序可以让用户调控系统,还有动态链接库等

.用户应用程序:这个很容易理解,应用程序存储于文件系统中。

关于Flash上的存储内容


图2-1 Flash上的存储内容

常用的嵌入式系统Flash有norFlash和NANDFlash两种,一般使用NAND作为其存储Flash,其中的存放方式如上图,bootloader(引导加载)放在最前端,其后是boot parameters(启动参数)这个的作用是bootloader给内核传递的参数,原因是因为在bootloader引导内核运行后,其生命周期就已经结束,其所占用的空间都有可能被其他程序占用。但是内核启动需要具体的参数以便适应不同的开发环境,因此,在bootloader生命周期结束前需要将内核启动的参数存放在一个地方,而后内核在启动的时候就去读取这些参数,所以划分了这么一个存储空间,接下来就是kernel(内核),再之后就是Root filesystem(根文件系统)。

上电后执行流程

系统启动分为nor flash启动和nand flash启动,其其别在于nor flash可以像读内存一样读取,而nand flash不行,对nand的读取要有一定的时序,另外arm系统一上电就会在0地址处开始执行,就nor启动而言,如图可以看到当不使用nand flash启动时,当芯片上电后的0地址是在由片选引脚nGCS0选中的芯片的0地址处开始运行,再看下原理图,可以看到MX29LV800BBTV(nor flash芯片)其引脚26接了芯片的nGCS0片选引脚,所以当不使用nand flash启动时,芯片就从nor flash的0地址处运行。

然后我们再看下从nand启动的流程,由于nor flash价格很贵,而且容量太小,其擦除次数也远不

如nand flash,所以很多地方都以nand flash为   

图 2-2 nand flash原理图          主,nand启动时根据上图我们可以看到,其0地址就成了SRAM,由SRAM是芯片内部集成的,容量只有4k,而一个u-boot的大小要200多kb,所以这4k容量的   大小就承担了将bootloader移动到片外SDRAM中去执行,当开机时,芯片会自动将bootloader的前4k移动到内部SRAM中去执行,这个移动的代码是已经固化了的,不用修改,也不能修改,移动好后,bootloader的前4k代码在SRAM中运行其将会剩余部分的bootloader代码拷贝到片外的SDRAM中,然后bootloader就可以在片外SDRAM中运行了。

U-boot启动阶段

bootloader都分为 stage1和 stage2两部分, u-boot也不例外。依赖于 CPU体系结构的代码(如设备初始化代码等)通常都放在 stage1且可以用汇编语言来实现,而 Stage2则通常用 C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。u-boot的 stage1代码通常放在

start.S文件中,他用汇编语言写成,其主要代码部分如下:

(1)定义入口。由于一个可执行的

Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在 ROM(Flash)的 0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。

(2)设置异常向量( Exception Vector)。

(3)设置 CPU的速度、时钟频率及终端控制寄存器。

(4)初始化内存控制器。

(5)将 ROM中的程序复制到 RAM中。

(6)初始化堆栈。

(7)转到 RAM中执行,该工作可使用指令 ldr pc来完成。

Stage2 C语言代码部分 lib_arm/board.c中的 start arm boot是 C语言开始的函数也是整个启动代码中 C语言的主函数,同时还是整个 u-boot(armboot)的主函数,该函数只要完成如下操作:

(1)调用一系列的初始化函数。

(2)初始化 Flash设备。

(3)初始化系统内存分配函数。

(4)如果目标系统拥有 NAND设备,则初始化 NAND设备。

(5)如果目标系统有显示设备,则初始化该类设备。

(6)初始化相关网络设备,填写 IP、MAC地址等。

(7)进去命令循环(即整个 boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。

当然u-boot的最终目的是启动内核,不过在调用内核之前必须要满足下列要求

(1).CPU寄存器的设置

  -R0=0

-R1=机器类型ID,对于ARM架构的CPU,其机器类型ID在linux/arch/arm/tools/mach-types

-R2=启动参数标记列表在RAM中的起始基地址。

(2).CPU工作模式。

-必须禁止中断

-CPU必须为SVC模式(管理模式)

(3).Cache和mmu的设置。

-MMU必须关闭

-指令cache可以打开也可以关闭

-数据cache必须关闭

U-boot

/uboot-1.6/u-boot-1.1.6/board/smdk2410下的u-boot.lds链接脚本记录了代码的启动及存放流程


图2-3 u-boot链接脚本

2.2 U-boot启动代码第一阶段分析

1..globl _start

_start: b       reset

reset:

mrs r0,cpsr

bic r0,r0,#0x1f

orr r0,r0,#0xd3

msr cpsr,r0

设置CPU为SVC32模式,关闭看门狗

mov r1, #0xffffffff

ldr r0, =INTMSK

str r1, [r0]

从代码中可以找出INTMSK的地址

# define INTMSK 0x4A000008 在看芯片手册


图2-4 芯片手册说明

通过描述可以看出来,当相应位被置位1时,相应的中断就会被CPU屏蔽掉

U-boot启动第一阶段

(1).设置CPU为SVC模式

reset:

mrs r0,cpsr

bic r0,r0,#0x1f

orr r0,r0,#0xd3

msr cpsr,r0

设置CPU为SVC32模式


(2)关闭看门狗

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

ldr     r0, =pWTCON

mov     r1, #0x0

str     r1, [r0]


(3)屏蔽中断

mov r1, #0xffffffff

ldr r0, =INTMSK

str r1, [r0]


(4)初始化SDRAM

mov r0, #0

mcr p15, 0, r0, c7, c7, 0

mcr p15, 0, r0, c8, c7, 0


(5)禁止MMU和cache

mrc p15, 0, r0, c1, c0, 0

bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)

bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)

orr r0, r0, #0x00000002 @ set bit 2 (A) Align

orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache

mcr p15, 0, r0, c1, c0, 0


/*

* before relocating, we have to setup RAM timing

* because memory timing is board-dependend, you will

* find a lowlevel_init.S in your board directory.

*/

mov ip, lr

bl lowlevel_init

mov lr, ip

mov pc, lr

这段提示很重要,(在搬移代码之前,我们要组织RAM的时序,因为存储时序是板级支持的,你会找到一个lowlevel_init.S代码文件在你的开发板相关的目录里),如果要移植代码,这一步是必须的,因为不能保证每个开发板用的SDRAM都是相同的,要对应开发板做不同的修改

再来看下lowlevel_ini部分的代码,这段代码是SDRAM的初识化工作

lowlevel_init:

ldr     r0, =SMRDATA  //将SMRDATA段的基地址给R0

ldr r1, _TEXT_BASE   //_TEXT_BASE是整个u-boot的起始地址

sub r0, r0, r1      //用R0减去R1,得到是SMRDATA段相对于起始地址的偏                          //移,之所以这样是因为链接地址与现在的运行地址不同

ldr r1, =BWSCON

add     r2, r0, #13*4

0:

ldr     r3, [r0], #4     //将以(R0地址内存储的数据)为地址的内存

str     r3, [r1], #4

cmp     r2, r0

bne     0b


(6)设置栈

stack_setup:

ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot   */

英文提示:128kb的u-boot最上层,就是u-boot的开始部分

sub r0, r0, #CFG_MALLOC_LEN 动态内存分配区域

sub r0, r0, #CFG_GBL_DATA_SIZE (全局变量)


(7)设置时钟

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

bl clock_init


(8)代码搬移,从flash中搬移到SDRAM中

#ifndef CONFIG_SKIP_RELOCATE_UBOOT

relocate:

adr r0, _start

ldr r1, _TEXT_BASE

cmp     r0, r1           

beq     clear_bss


ldr r2, _armboot_start

ldr r3, _bss_start

sub r2, r3, r2

#if 1

bl  CopyCode2Ram

#else这里,bl  CopyCode2Ram中CopyCode2Ram对应的是一个可以判断是NOR还是nand flashc语言函数,这段代码实现了从nand启动的方式,因为U-boot原码只支持从NOR启动,对于CopyCode2Ram这个函数是用C语言实现的一个函数,其实现是这样的

int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)

但是关于这个参数传递的规则,这个是由ARM架构的ATPCS规则实现的,其参数的传递是靠寄存器R0-R3传递的,所以看上面汇编代码

r0, _start    

r1, _TEXT_BASE

sub r2, r3, r2

其中R0是当前代码的位置,R1是代码的链接地址,R2是代码段的大小正好对应于上面函数的参数


(9)清bss段

 clear_bss:

ldr r0, _bss_start

ldr r1, _bss_end

mov  r2, #0x00000000

clbss_l:str r2, [r0]

add r0, r0, #4

cmp r0, r1

ble clbss_l


(10)调用start_armboot


2.3 U-boot启动第二阶段的代码分析

第二阶段代码是从strat_armboot开始的

硬件的初始化

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)

{  

if ((*init_fnc_ptr)() != 0) //执行初始化函数

{

hang ();                //错误判断

}

其中init_sequence这个数组定义了一系列硬件的初始化,我们可以看下

init_fnc_t *init_sequence[] = {

cpu_init, //基本的cpu依赖的设置,是栈的设置,函数内只是用宏划分了大小

board_init,        //基本的单板设置,里面设置了芯片的ID号等等,还有定义了时钟的寄存器地址等等

interrupt_init, //定时器初始化

env_init, //初始化环境设置

init_baudrate, //波特率设置

serial_init, //设置串口通信

console_init_f, //阶段1的控制台

display_banner, //打印信息的函数

#if defined(CONFIG_DISPLAY_CPUINFO)

print_cpuinfo, //打印cpu的信息

#endif

#if defined(CONFIG_DISPLAY_BOARDINFO)

checkboard, //测试板子并打印信息

#endif

dram_init,     //设置RAM的大小

display_dram_config,//打印DRAM的配置信息

NULL,

};


接下来的是size = flash_init ();     //nor_flash 的初始化

addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);

size = lcd_setmem (addr);

gd->fb_base = addr;

nand_init(); //nand_flash 初始化

env_relocate ();//初始化环境设置

中间是一大部分的硬件初始化过程,先省略,最终U-boot阶段2调用函数

for (;;) {

main_loop ();

}

第3章 内核移植

3.1 准备工作

(1)下载 解压内核

从官网上下载linux-2.6.35的内核,

ftp://ftp.kernel.org/pub/linux/kernel/v2.6/ ,文件不大,约85M。

新建一个工作目录s3c2440,将内核源码包拷贝至工作目录下,再解压。

(2)移植yaffs2驱动

下载最新的驱动点击下载 解压在工作目录s3c2440下

进入yaffs2:  cd yaffs2

给内核打补丁: ./patch-ker.sh  c ../linux-2.6.35

(3)安装交叉编译环境下载arm-linux-gcc 4.3.2,然后安装并配置环境变量

最后执行arm-linux-gcc -v查看


3.2 移植

(1)修改机器码

开发板的bootloader默认的机器码是193,所以我们在使用smdk2440机器的时候,需要修改机器码。修改内核2.6.35.3中的arch/arm /tools/mach-types

删掉

s3c2410      ARCH_S3C2410    S3C2410    182

然后将

s3c2440      ARCH_S3C2440    S3C2440    362

修改为

s3c2440      ARCH_S3C2440    S3C2440    193

(2)指定目标板machine、编译器和编译器路径

修改linux-2.6.35.3/Makefile,将

ARCH                ?= $(SUBARCH)

CROSS_COMPILE        ?=

修改成ARCH                ?= arm

CROSS_COMPILE        ?= /opt/arm/4.3.2/bin/arm-linux-

CROSS_COMPILE是指交叉编译器的路径,该路径一定要完整,否则最后make zImage时提示文件不存在。

(3)增加devfs文件管理器的支持

我们所用的文件系统使用的是devfs文件管理器。修改fs/Kconfig,

找到

menu "seudo filesystems"

添加如下语句:

config DEVFS_FS

         bool "/dev file system support (OBSOLETE)"

         default y   

config DEVFS_MOUNT

bool "Automatically mount at boot"

default y

depends on DEVFS_FS

帮助理解:Kconfig就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码中,能够修改Kconfig,

这样就能够选择这个驱动,假如想使这个驱动被编译,要修改Makefile。

(4)修改晶振频率( 可解决打印信息乱码问题 )

文件:arch/arm/mach-s3c2440/mach-smdk2440.c

/*s3c24xx_init_clocks(16934400);*/  s3c24xx_init_clocks(12000000);


(5)关闭ECC校验

文件:drivers/mtd/nand/s3c2410.c

函数:s3c2410_nand_init_chip

/*chip->ecc.mode = NAND_ECC_SOFT; */  chip->ecc.mode = NAND_ECC_NONE;

问题:关于ECC:ECC是“Error Correcting Code”的简写,中文名称是“错误检查和纠正”。ECC是一种能够实现“错误检查和纠正”的技术,ECC内存就是应用了这种技术的内存,一般多应用在服务器及图形工作站上,这将使整个电脑系统在工作时更趋于安全稳定。此处为避免容易出错,将ECC关闭。

(6)修改nandflash驱动,支持K9F1G08的nandflash

    修改drivers/mtd/nand下面的nand_bbt.c 文件:

static struct nand_bbt_descr largepage_memorybased = {  

        .options = 0,  

        .offs = 0,  

        .len = 1,     // 原数值为2,支持2K每页的flash修改为1。K9F1G08,K9F2G08是2k每页的flash  

        .pattern = scan_ff_pattern  

};  

static struct nand_bbt_descr largepage_flashbased = {  

        .options = NAND_BBT_SCAN2NDPAGE,  

        .offs = 0,  

        .len = 1, //原数值为2,支持2K每页的flash修改为1。K9F1G08,K9F2G08是2k每页的flash  

        .pattern = scan_ff_pattern  

};  

(7)下面,开始配置内核。

进入linux-2.6.35目录,把s3c2410的默认配置写入config文件。

make s3c2410_defconfig

make menuconfig

配置内核特点使用ARM EABI编译

配置文件系统选项

配置yaffs2文件系统

修改配置如下:

File systems  --->

   

  • Miscellaneous filesystems  --->

            <*>   YAFFS2 file system support

                -*-     512 byte / page devices

                -*-     2048 byte (or larger) / page devices

                   

  •        Autoselect yaffs2 format

                   

  •      Cache short names in RAM

    配置cpu相关选项

    修改配置如下:

    System Type  --->

        S3C2440 Machines  --->

            

  • SMDK2440

            

  • SMDK2440 with S3C2440 CPU module

    去掉S3C2400 Machines、S3C2410 Machines、S3C2412 Machines、S3C2442 Machines的所有选项 ,

    否则会报错。如果现在编译内核,下载到开发板中,内核就可以正常启动了.有了雏形,继续移植设备驱动。

    这里,内核选项*代表编译至内核,M代表编译为模块 。

    (8)移植USB host驱动

    在这个版本的linux内核,已经对USB驱动进行来很好的支持,仅仅需要修改配置。

    Device Drivers  --->

       

  • USB support  --->

            {*}   Support for Host-side USB

            

  •      USB device filesystem (DEPRECATED)

            

  •      USB device class-devices (DEPRECATED)

            <*>     OHCI HCD support

            <*>   USB Mass Storage support

       

  • HID Devices  --->

            {*}   Generic HID support

            

  •      /dev/hidraw raw HID device support

        SCSI device support  --->

            <*> SCSI device support

            

  • legacy /proc/scsi/ support

            <*> SCSI disk support

            <*> SCSI tape support

    (9)移植RTC驱动

    在这个版本的linux内核,已经对RTC驱动进行来很好的支持,不需要修改配置。相应配置如下:

    Device Drivers  --->

        <*> Real Time Clock  --->

            

  •    Set system time from RTC on startup and resume

            (rtc0)  RTC used to set the system time

            [ ]   RTC debug support

                  *** RTC interfaces ***

            

  •    /sys/class/rtc/rtcN (sysfs)

            

  •    /proc/driver/rtc (procfs for rtc0)

            

  •    /dev/rtcN (character devices)

            <*>   Samsung S3C series SoC RTC

    然后添加对设备的支持

    打开arch/arm/mach-s3c2440/mach-smdk2440.c ,添加设备,代码如下:

    static struct platform_device *smdk2440_devices[] __initdata = {  

            &s3c_device_ohci,  

            &s3c_device_lcd,  

            &s3c_device_wdt,  

            &s3c_device_i2c0,  

            &s3c_device_iis,  

            &s3c_device_rtc,  

    };  

    (10)移植DM9000驱动

    a、修改 drivers/net/dm9000.c 文件:

    头文件增加:

    #include <mach/regs-gpio.h>

    #include <mach/irqs.h>

    #include <mach/hardware.h>

    在dm9000_probe 函数 开始增加:

    unsigned char ne_def_eth_mac_addr[]={0x00,0x12,0x34,0x56,0x80,0x49};  

        static void *bwscon;  

        static void *gpfcon;  

        static void *extint0;  

        static void *intmsk;  

        #define BWSCON           (0x48000000)  

        #define GPFCON           (0x56000050)  

        #define EXTINT0           (0x56000088)  

        #define INTMSK           (0x4A000008)  


            bwscon=ioremap_nocache(BWSCON,0x0000004);  

            gpfcon=ioremap_nocache(GPFCON,0x0000004);  

            extint0=ioremap_nocache(EXTINT0,0x0000004);  

            intmsk=ioremap_nocache(INTMSK,0x0000004);  


            writel(readl(bwscon)|0xc0000,bwscon);  

            writel( (readl(gpfcon) & ~(0x3 << 14)) | (0x2 << 14), gpfcon);   

            writel( readl(gpfcon) | (0x1 << 7), gpfcon); // Disable pull-up  

            writel( (readl(extint0) & ~(0xf << 28)) | (0x4 << 28), extint0); //rising edge  

            writel( (readl(intmsk))  & ~0x80, intmsk);      

    在这个函数的最后需要修改:

    if (!is_valid_ether_addr(ndev->dev_addr)) {  

                    /* try reading from mac */  


                    mac_src = "chip";  

                    for (i = 0; i < 6; i++)  

                            //ndev->dev_addr = ior(db, i+DM9000_PAR);   

                            ndev->dev_addr = ne_def_eth_mac_addr;  

            }  

    b、修改arch/arm/mach-s3c2440/mach-smdk2440.c ,添加设备[cpp] view plaincopy

    static struct platform_device *smdk2440_devices[] __initdata = {  

            &s3c_device_ohci,  

            &s3c_device_lcd,  

            &s3c_device_wdt,  

            &s3c_device_i2c0,  

            &s3c_device_iis,  

            &s3c_device_rtc,  

            &s3c24xx_uda134x,  

            &s3c_device_dm9000,  

    };  

    c、修改 arch/arm/plat-s3c24xx/devs.c  

    添加头文件

    #include <linux/dm9000.h>

    添加以下代码

    static struct resource s3c_dm9000_resource[] = {   

            [0] = {   

            .start = S3C24XX_PA_DM9000,   

            .end   = S3C24XX_PA_DM9000+ 0x3,   

            .flags = IORESOURCE_MEM   

            },   

            [1]={   

            .start = S3C24XX_PA_DM9000 + 0x4, //CMD pin is A2   

            .end = S3C24XX_PA_DM9000 + 0x4 + 0x7c,   

            .flags = IORESOURCE_MEM   

            },   

            [2] = {   

            .start = IRQ_EINT7,   

            .end   = IRQ_EINT7,   

            .flags = IORESOURCE_IRQ   

            },   

            };   

          static struct dm9000_plat_data s3c_device_dm9000_platdata = {   

            .flags= DM9000_PLATF_16BITONLY,   

            };   

            struct platform_device s3c_device_dm9000 = {   

            .name= "dm9000",   

            .id= 0,   

            .num_resources= ARRAY_SIZE(s3c_dm9000_resource),   

            .resource= s3c_dm9000_resource,   

              .dev= {   

            .platform_data = &s3c_device_dm9000_platdata,   

              }   

    };   

    140 EXPORT_SYMBOL(s3c_device_dm9000);  

    d、修改 arch/arm/plat-samsung/include/plat/devs.h    45行附近,添加

    141 extern struct platform_device s3c_device_dm9000;  

    e、修改arch/arm/mach-s3c2410/include/mach/map.h 文件

    #define   S3C24XX_PA_DM9000 0x20000300

    #define   S3C24XX_VA_DM9000 0xE0000000

    (11)启动画面显示小企鹅的方法

    配置内核 ,下面是必选项

    Device Drivers--->

        Graphics support  --->

                    <*> Support for frame buffer devices

                    <*> S3C2410 LCD framebuffer support ,multi support!

                    Console display driver support  --->

                            <*> Framebuffer Console support  

                    Logo configuration  --->  

                            

  • Bootup logo

                                    

  •    Standard 224-color Linux logo

    (12)修改uart2为普通串口以及测试程序

    修改arch/arm/mach-s3c2440/mach-smdk2440.c 中的uart2的配置,修改后如下:

    static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {  

            [0] = {  

                    .hwport             = 0,  

                    .flags             = 0,  

                    .ucon             = 0x3c5,  

                    .ulcon             = 0x03,  

                    .ufcon             = 0x51,  

            },  

            [1] = {  

                    .hwport             = 1,  

                    .flags             = 0,  

                    .ucon             = 0x3c5,  

                    .ulcon             = 0x03,  

                    .ufcon             = 0x51,  

            },  

            /* IR port */  

            [2] = {  

                    .hwport             = 2,  

                    .flags             = 0,  

                    .ucon             = 0x3c5,  

                    .ulcon             = 0x03,/*fatfish 0x43*/  

                    .ufcon             = 0x51,  

            }  

    };  

    在drivers/serial/samsung.c 中添加对uart2控制器的配置,配置为普通串口。

    添加头文件:

    #include <linux/gpio.h>

    #include <mach/regs-gpio.h>

    在static int s3c24xx_serial_startup(struct uart_port *port)函数中,添加

    if (port->line == 2) {   

                   s3c2410_gpio_cfgpin(S3C2410_GPH(6), S3C2410_GPH6_TXD2);   

                   s3c2410_gpio_pullup(S3C2410_GPH(6), 1);   

                   s3c2410_gpio_cfgpin(S3C2410_GPH(7), S3C2410_GPH7_RXD2);   

                   s3c2410_gpio_pullup(S3C2410_GPH(7), 1);   

           }   

    (13)移植看门狗

    修改配置

    Device Drivers --->

       

  • Watchdog Timer Support --->

             <*> S3C2410 Watchdog

    最后:make  zImage

    最后编译出来的zImage就2.1M左右。

    烧写内核,启动成功

    不过,此时,显示屏显示还有点问题,等下会进行LCD驱动的移植

    重新编译即可。

    至此,2.6.35.4内核移植成功。



    第4章 构建linux根文件系统

    类似于windows上的C,D,E盘,linux也可以将存储设备划分为不同的分区,以便存放不同的文件,同样的和windows的C盘类似,LINUX也要在一个分区中存放系统启动所需的文件,这些文件称谓根文件系统,其包含了系统启动所执行的第一个进程既INIT进程,windows我不是很了解,但是在linux中,所有进程都是由init进程拷贝而来的,可以说init进程是所有其他进程的父进程,这就是内核启动后要执行的第一件事,挂接根文件系统。

    最上面是根目录“/”,接下来依次是几个平行的子目录,关于各个目录的用途,我简要的说下,/bin目录是存放系统命令的,/sbin也是,两个不同点在于。Bin目录存放的命令是所有用户都可以使用的,而sbin不同,其包含的命令只能是由管理员使用的,/dev目录存放的是设备文件,通过这些设备文件可以达到操作设备的目的,具体也不详细展开了,/etc中存放的是系统的配置文件,/lib也就是系统的库文件,/home中则是用户目录,存放用户相关的配置文件,/root则是管理员超级用户的配置文件,/usr是存放系统资源的一个目录,/var同/usr目录类似,不过/usr存放的是不常变动的资源,而var则是常变动的资源,/proc中是存放proc虚拟文件系统的目录,/mnt挂载目录,用于挂载光盘,硬盘等。/tmp顾名思义,缓存目录存放临时文件

    在嵌入式领域,我们常用BUSYBOX来制作根文件系统,在内核起来后,它会去运行第一个进程init进程,而init进程在/sbin/init中,当然我们也可以再U-boot中指定自

    己的init程序,在嵌入式中一般用busybox集成的init,首先我们来看下busybox的配置过程,首先进入busybox的目录:


    图4-1 命令行显示(1)


    对于busybox的配置过程如果不熟悉的话可以先看目录中的INSTALL文件,其中详细说明了busybox的配置方法,我们可以看到如下


    图4-2 命令行显示(2)


    和内核的配置很相似,首先make menuconfig,然后执行make,不过在执行make之前要先指定编译器哦,可以再Makefile中直接修改,也可以执行

    make CROSS_COMPILE=arm-linux-

    最后执行的一步要千万注意,这里不能直接执行make install,这样会把你原先的系统配置文件覆盖掉,这的默认的情况下,所以我们必须要制定安装的位置,不然它就会把你原来系统的bin,和sbin目录覆盖掉,由于是交叉编译,它编译出来的是给arm用的,覆盖后电脑的芯片就处理不了了,结果就是电脑系统重启后就停留在logo标志处,后面的运行不起来了,结果只是运行了内核,没有根文件系统的话就不能做任何事,最后

    make CONFIG_PREFIX=/path/from/root

    install   

    其中/path/from/root是你指定生成根文件系统的目录,一般放在NFS服务目录下,通过NFS挂载到目标板中,安装完毕后进入安装的目录可以看到


    图4-3 命令行显示(3)


    出现了4个文件夹,我们可以进去看下,通过ls -la命令可以看到如下


    图4-4 命令行显示(4)


    这只是截取了其中一段,我们可以发现每个命令都是busybox的一个连接,可以说每个命令都是由busybox实现的,至此我们已经实现了一个根文件系统的大部分功能,不过一个根文件系统的功能也不应该只有命令的实现,比如我们要实现设备文件的管理等等,接下来,我们会一步一步实现其具体功能。

       为了能够实现软件的功能,就必须要有c库,接下来我们来制作glibc库首先要下载交叉编译工具制作工具crosstool-0.43.tar.gz压缩包,

    tar -xzf crosstool-0.43.tar.gz

    解压后就可以开始制作了,在制作前要将一个补丁glibc-2.3.6-version-info.h_err.patch文件放到crosstool-0.43/patches/glibc-2.3.6目录下,这个是一个修复错误的补丁,

    cp  glibc-2.3.6-version-info.h_err.patch   crosstool-0.43/patches/glibc-2.3.6

    接下来就可以开始配置了,

    修改crosstool-0.43目录下的demo-arm-softfloat.sh脚本具体修改如下,修改部分第一个是交叉编译工具所在的目录,第二个是我要存放编译好后文件的目录,如果要支持更多的语言还可以修改LANGUAGES这个选项,添加要支持的语言


    图4-5 命令行显示(5)


    然后在修改arm-softfloat.dat文件修改好后如下,就是修改编译选项


    图4-6 命令行显示(6)

    最后执行

    ./demo-arm-softfloat.sh

    生成文件,整个过程要持续2个多小时,在编译好后就可以制作我们自己根文件系统的lib库了,很简单

    首先进入编译好的工具目录下

    cd   /gcc-3.4.5-glibc-2.3.6/arm-linux/lib

    mkdir -p /work/nfs_root/MY_SEC/lib

    前面的/work/nfs_root/是我自己存放前面制作了一般的根文件目录,-p选项的作用可以用man cp观察其详细用法,在这里也可以不用,很简单,具体我不多说了,要学会看man帮助,这是一个想成为工程师的必要条件,就像我现在就在很努力的看内核的英文文档,虽然我英语不是很厉害,不过还是要练出来,万事开头难,以后就可以享受现在的成果了,基础要打扎实。

    最后

    cp *.so* /work/nfs_root/MY_SEC/ -d

    把所有库以动态链接的方式拷贝到制作的根文件系统lib目录下。

    接着制作etc目录,在这个目录下存放了根文件系统启动时的配置信息。

    首先mkdir etc 制作文件夹,然后创建inittab文件

    touch etc/inittab


    图4-7 命令行显示(7)

    其中要说的是对于mdev生成的串口终端名称不是通常的ttySAC0,ttySAC1,ttySAC2,而是s3cw2410_serial0,s3cw2410_serial1,s3cw2410_serial2,所以第二行是写s3cw2410_serial0的,如果你的开发板有多个串口可以连接,也加入s3cw2410_serial1,s3cw2410_serial2,一般情况下加一个就可以了,第3行是可有可无的,嫌麻烦也可以不加,这是相当于关机的快捷键,最后一个是必须要加的,在关机前要umount所有挂载的设备,当然我第一个没说,第一个自然是必须的,这个是一个启动脚本,它可以帮我们做很多事,我接下来会说明,至于这个inittab怎么写的格式,我就不多说了,学linux的人都应该知道,特别是学linux服务器管理的。

    接下来我们来创建rcS脚本

    mkdir   /etc/init.d

    touch  rcS  

    接下来来说明rcS文件中要写的东西


    图4-8 命令行显示(8)

    如上图第一个不说了配置IP可以和你在U-boot中设置的IP不同,这个无所谓,第二个很重要,mount -a 表示的按照/etc/fstab的内容挂载所有在其中列出的文件系统,我们可以通过

    man mount观察  

    -a     Mount all filesystems (of the given types) mentioned in fstab.

    里面是这样说的,讲的很明白稍后我会讲fstab的写法,其他的我不说了,详细请看/busy_box_text/busybox-1.7.0/docs/mdev.txt 按照里面说的加进去可以了,etc/fstab写法如下

    图4-9 命令行显示(9)

    最后一步了,创建dev目录,其中要做的工作我在前面创建etc目录的时候都已经写在里面了,最要就是通过mdev动态的挂载设备文件,工作已经做好了,还要做的就是在里面创建俩个设备文件。

    mknod console c 5 1

    mknod null c 1 3

    之所以这样是因为mdev是通过init进程来启动的,使用mdev构造dev目录之前,init进程至少需要用到这两个设备文件。

    最后在根目录下创建sys ,proc ,mnt ,tmp 等目录

    然后就是通过nfs服务挂载根文件系统了,我用的是u-boot,在u-boot界面可以做如下设置:

    bootargs noinitrd  root=/dev/nfs nfsroot=192.168.1.19:/work/nfs_root/MY_SEC ip=192.168.1.17:192.168.1.19:192.168.1.255:255.255.255.0::eth0ff /init-/linuxrc console=ttySAC0

    这条配置我讲下root=/dev/nfs 表面是用nfs挂载的方式nfsroot=主机IP:主机需要挂载的根文件系统路径 ip=目标机(开发板)Ip:主机IP:网关:子网掩码:主机名(省略):开发板网口:自动配置ip(off )   后面的不用改了,详情可以看linux源码目录下的/linux-2.6.22.6/Documentation/nfsroot.txt 文件,基本就是这样了。


    第5章 LCD驱动移植



                  图5-1 LCD原理图

    首先查看4.3寸TFT液晶显示屏的说明手册,看出来分辨率Resolution是480X3(RGB)X272

    另外此块液晶屏配有触摸屏接口,这里说明下,在LINUX2.6内核下对触摸屏的支持,而且本LCD屏幕采用的是普通的4线电阻触摸屏,内核对此有很好的支持,因而对触摸屏驱动并没有做修改,但是对于TFT的LCD显示屏,内核并不支持4.3寸的,因而对于LCD屏幕的移植其主要工作就是修改其显示参数,使其支持4.3寸的显示。

    对LCD驱动的分析:


    图5-2 LINUX的显示设备的框架模型


    上图是LINUX的显示设备的框架模型

    为了说明LINUX的显示框架,在这里说明一个概念。Linux中利用一种叫帧缓冲的概念:帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示,18.2.2小节将讲解显示缓冲区与显示点的对应关系。帧缓冲设备为标准字符设备,主设备号为29,对应于/dev/fb%d 设备文件。帧缓冲驱动的应用非常广泛,在Linux的桌面系统中,Xwindow服务器就是利用帧缓冲进行窗口的绘制。嵌入式系统中的Qt/Embedded等图形用户界面环境也基于帧缓冲而设计。另外,通过帧缓冲可支持汉字点阵的显示,因此帧缓冲也成为Linux汉化的可行方案。

    其中帧缓冲(framebuffer)的驱动就是上图的fbmem.c文件,这个文件中提供了上层应用的接口,在这个文件中包含了对LCD要显示的数据的处理流程,并且向下提供了对硬件的接口,做为驱动编写,就只要关系下层硬件相关部分就可以了,这样的框架大大提高了可移植性和简化了驱动的编写,因为fbmem.c文件是通用的,所以不用修改,另外下层的硬件层次,linux系统也将其划分为两个部分,就是将其注册为平台驱动,采用设备,驱动分离的思想,驱动部分就对应于上图的xxxfb.c,对于我的开发板就是s3c2410fb.c(内核并没有提供2440的驱动,因为2410与2440基本相同,2440就是比2410多几个寄存器),而设备资源部分在BSP板文件中,对应本块开发板就是Mach-smdk2440.c,其中标明了设备的硬件资源参数,对于s3c2410fb.c,具体不再多说明了,详细如上图,很清楚的表明了这个文件的构成,要做修改的就是在Mach-smdk2440.c的LCD设备资源部分。代码如下

    static struct s3c2410fb_mach_info smdk2440_lcd_cfg_ __initdata = {

        .regs   = {

            .lcdcon1 =  S3C2410_LCDCON1_TFT16BPP | \

                    S3C2410_LCDCON1_TFT | \

                    S3C2410_LCDCON1_CLKVAL(0x04),

            .lcdcon2 =  S3C2410_LCDCON2_VBPD(1) | \

                    S3C2410_LCDCON2_LINEVAL(239) | \

                    S3C2410_LCDCON2_VFPD(5) | \

                    S3C2410_LCDCON2_VSPW(1),

            .lcdcon3 =  S3C2410_LCDCON3_HBPD(36) | \

                    S3C2410_LCDCON3_HOZVAL(319) | \

                    S3C2410_LCDCON3_HFPD(19),

            .lcdcon4 =  S3C2410_LCDCON4_MVAL(13) | \

                    S3C2410_LCDCON4_HSPW(5),

            .lcdcon5 =  S3C2410_LCDCON5_FRM565 |

                    S3C2410_LCDCON5_INVVLINE |

                    S3C2410_LCDCON5_INVVFRAME |

    S3C2410_LCDCON5_INVVDEN |

                    S3C2410_LCDCON5_PWREN |

                    S3C2410_LCDCON5_HWSWP,

        },

        .gpccon      =  0xaaaa56aa,

        .gpccon_mask =  0xffffffff,

        .gpcup       =  0xffffffff,

        .gpcup_mask  =  0xffffffff,

        .gpdcon      =  0xaaaaaaaa,

        .gpdcon_mask =  0xffffffff,

        .gpdup       =  0xffffffff,

        .gpdup_mask  =  0xffffffff,

        .fixed_syncs =  1,

        .type        =  S3C2410_LCDCON1_TFT,

        .width       =  320,

        .height      =  240,

        .xres   = {

    .min = 320,

    .max = 320,

    .defval = 320,

    },

        .yres   = {

            .max    =   240,

            .min    =   240,

            .defval =   240,

        },

    .bpp = {

    .min = 16,

    .max = 16,

    .defval = 16,

    },

    };

    就只要修改这部分即可,其他无需修改,这部分代码对应于具体的硬件参数,其显示参数设置就在这里,在这里设置的结构体最后会匹配到相应的驱动中,就是在s3c2410fb.c会被引用,所以在s3c2410fb.c并没有对显示参数做具体的设置,用的是这里的参数,所以具体的显示参数就在这里设置。

      另外,通过代码看到最后在static void __init smdk2440_machine_init(void)加载函数中有如下函数 s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg); 观察代码内部了解到,最后是将结构体smdk2440_lcd_cfg设置成私有数据,这个结构体就是上面列出代码,存放的是LCD的显示参数和控制初始化。

    硬件:

       接下来是对LCD控制的分析,上上面结构体看到在smdk2440_lcd_cfg结构体中包含了对lcdcon1,lcdcon2, lcdcon3, lcdcon4, lcdcon5的设置,这几个寄存器是对s3c2440的LCD控制初始化,通过看芯片手册了解到

    首先查看S3C2440芯片手册,找到TFT屏的控制部分:

    TFT LCD CONTROLLER OPERATION

    The TIMEGEN generates the control signals for LCD driver, such as VSYNC, HSYNC, VCLK, VDEN, and LEND signal. These control signals are highly related with the configurations on the LCDCON1/2/3/4/5 registers in the REGBANK. Base on these programmable configurations on the LCD control registers in the REGBANK, theTIMEGEN can generate the programmable control signals suitable for the support of many different types of LCDdrivers.

    上面是TFT屏的控制简介,翻译的大意是这样的:时序产生的LCD控制信号,如VSYNC, HSYNC, VCLK, VDEN。 这些控制信号高度依赖LCD的控制寄存器组LCDCON1/2/3/4/5,依靠这些寄存器配置,芯片能产生LCD的控制信号以适应各种不同种类的LCD设备。

    因此,这些寄存器就是控制LCD的关键,接下来是了解这些寄存器的作用,在了解这些寄存器之前要找到S3C2440对TFT的LCD液晶显示屏的控制时序图,如下


    图5-3 LCD液晶显示屏的控制时序图

    其中VSYNC是垂直同步信号也就是帧同步信号代表的是一页视频数据的开始与结束,VSYNC为高,则表示一帧数据的开始或者结束,HSYNC是行同步信号代表的是一行视频数据的开始与结束,VDEN表示的是显示数据的有效性,只要在VDEN信号为高电平的时候,数据才是有效的,为低电平就是无效的数据,VCLK是要提供给LCD的时钟信号,这个要适应具体的LCD,VD则代表是数据,LEND表示一行数据的结束, 这里说明下这副时序图的意义,其中((VSPW+1)+(VBPD+1))表示每帧显示数据的开始有这些行是无效的,举个具体的实例,拿手机来说,手机的显示屏最上面是一定会有几行是不显示的,同样最下面也是有(VFPD+1)行是无法显示数据的,这个也是可以设置的。同理,也可以知道每行数据的开始有((HSPW+1)+(HBPD+1))个像数是无法显示的,每行最后有(HFPD+1)个像数是无法显示的,这也是为什么市面上所以的LCD显示设备四周都有黑框,这个是无法避免的,不过可以减小这个黑框的大小,具体就要设置LCD控制器的参数了

    其中的VSPW,VBPD,LINEVAL,HSPW,HBPD,HOZVAL,HFPD就是要根据具体的LCD屏幕的参数进行修改,具体的LCD屏幕参数要看对应这块LCD屏的说明手册,在LCD说明手册中找到具体的参数表如下图(由于全图太大,这里只是截取其中开头一部分)


    图5-3 LCD说明手册具体的参数表(截取部分)


    接下来看具体的硬件寄存器的设计,首先是LCDCON1观察S3C2440的芯片手册了解到LCDCON1的bit[27:18]控制的是行计数,这里并不需要,不用设置,bit[17:8]控制的是VCLK和CLKVAL的速率,其计算公式:VCLK(Hz) = HCLK/[(CLKVAL+1)x2] ,其中CLKVAL就是要计算出来的值,最后填入bit[17:8]的也是CLKVAL,具体的数据要根据具体的LCD说明书来设置。

    下面列出4.3寸显示屏的具体参数

    VFPD+1=2;

    LINEVAL+1=272;

    VBPD+1=2

    VSPW+1=10

    HBPD+1=2

    HSPW+1=40

    HOZVAL+1=480

    HFPD+1=2

    最后根据参数修改LCD1/2/3/4/5的值,得到新代码:(只列出了修改部分)

        .regs   = {

            .lcdcon1 =  S3C2410_LCDCON1_TFT16BPP | \

                    S3C2410_LCDCON1_TFT | \

                    S3C2410_LCDCON1_CLKVAL(0x04),

            .lcdcon2 =  S3C2410_LCDCON2_VBPD(1) | \

                    S3C2410_LCDCON2_LINEVAL(271) | \

                    S3C2410_LCDCON2_VFPD(1) | \

                    S3C2410_LCDCON2_VSPW(9),

            .lcdcon3 =  S3C2410_LCDCON3_HBPD(1) | \

                    S3C2410_LCDCON3_HOZVAL(479) | \

                    S3C2410_LCDCON3_HFPD(1),

        .lcdcon4= 40,


            .lcdcon5 =  S3C2410_LCDCON5_FRM565 |

                    S3C2410_LCDCON5_INVVLINE |

                    S3C2410_LCDCON5_INVVFRAME |

                    S3C2410_LCDCON5_PWREN |

                    S3C2410_LCDCON5_HWSWP,

        },

      移植结束,最后将新驱动替换老驱动,重新编译内核,make uImage,生成新内核后测试


    第6章 Wm8976声卡驱动移植


    6.1 wm8976声卡驱动分析


    图6-1 wm8976在开发板中的接线原理图



    图6-2 wm8976的引脚说明图

    根据原理图可知,wm8976的主要控制引脚是15,16,17以及引脚7,8,9,10,11

    根据原理图明显可以看出引脚7,8,9,10,是IIS音频总线的接口,根据linux内核的特性,内核将linux的声音处理系统分为了很多层次,如下图是2.4内核声音处理系统的驱动部分


    图6-3 Linux声音处理架构

    这是linux 2.4的内核的oss声音系统,相对简单,所以我选的是这个声音系统,对于2.6的内核,其声音系统是采用的最新的alsa系统,其结构要复杂很多,2.4内核的声音子系统大致上可以分为如图的两部分,相对简单很多,但是后续的产品测试发现声音的播放效果不是很理想,有很多杂音,芯片本身所能处理的效果应该不会很差,但是我在驱动中所处理的不是很好,导致了后期播放时在高音部分的声音处理出现了很多杂音,处理不是很理想。

    首先对于wm8976硬件原理的分析,对于IIS音频总线接口,linux2.4内核对声音的处理都是用的IIS总线,如上图,对硬件的处理和对声音数据的处理分为了两块,IIS对声音数据的处理对于不同芯片都是相同的,不同的就只有声卡硬件的结构。因此移植声卡的要点就是在于提供硬件对驱动的接口,及相关初始化。

    对于wm8976声卡芯片的移植,2.6内核并没有包含wm8976这块芯片的驱动,2.6内核包含的是UDA1341这块声卡芯片的驱动,由于开发板相近,所以决定在这块声卡芯片驱动的基础上改这个驱动

    对于芯片手册的分析:

       Wm8976的控制:


    图6-4 wm8976芯片手册时序图


    根据wm8976声卡芯片的设备介绍


    图6-5 wm8976芯片说明

    上图是截取芯片说明介绍的重点部分,这段话是介绍了wm8976芯片的控制方法:大意是这样的,wm8976提供两种控制方法,一种是两线模式,另外一种是三线模式,并且充分兼容和广泛地支持工业标准微控机和DSP信号处理


    图6-6 wm8976芯片说明


    上图是截取的芯片控制说明简介:这里说明了3线模式和2线模式的选择,当MODE引脚为高电平是选择的是3线模式,当为低电平时选择的是2线模式,根据芯片接线原理图可以看出,MODE接的是3.3V电压,是高电平,因而选择的是3线模式


    图6-7 wm8976芯片说明3线模式说明

    三线模式,如上图,CSB信号是控制数据的有效性的,SCLK提供时钟信号,SDIN提供对芯片的控制信号,这里说明下,SDIN会在CSB为高时发出信号,一共16位的信号,高位先发送,高位的8位为地址信号,通过这个信号提供的地址来访问声卡芯片相关的寄存器,SDIN数据的低8位为数据信号,提供对芯片相关寄存器的控制,也就是往地址中写入这些数据达到控制芯片的效果。最后数据传输完把CSB信号拉低表明数据传输结束。


    图6-8 wm8976芯片说明2线模式说明

    关于两线模式我大概说明下,因为我改的驱动是采用3线模式的,2线模式采用的是IIC的控制模式,先发wm8976芯片的设备地址,让后是读写信号位,当芯片应答后再发出要访问的芯片地址,然后是数据,最后是IIC的结束信号。

    另外值得一提的是wm8976支持主机模式和从机模式,主机模式就是wm8976做为主控芯片,类似于一块单片机用于处理声音数据的,而做为从机模式就是像这块开发板一样,wm8976由片上系统(SOC)也就是这块开发板的主控芯片S3C2440控制的,wm8976相当于外设芯片,帮助SOC处理声音数据,就像协处理器。因此在芯片的初始化中要设置wm8976工作在SLAVE(从机)模式。

    WM8976在linux2.6内核下的移植流程

    关于wm8976的芯片简介也描述的差不多了,现在来说明下wm8976在linux2.6内核下的移植流程,首先是参考另外一块芯片UDA1341声卡芯片的驱动,因为UDA1341芯片所集成的开发板和这块开发板用的芯片是一样的,其中对于IIS总线的声音处理流程也是相同的,不同点是在与芯片的控制不同,因而只要修改UDA1341芯片驱动对于硬件处理的流程即可



    图6-9 wm8976驱动框架说明

    如上图,是我画的一个图,其大致描述了在linux设备驱动中运用很广泛的虚拟平台技术,其中Device设备资源是设备的硬件描述,大多是体系相关的,如果换了一种SOC主控芯片但是声卡还是用的这款的话,就只要改动Device设备资源即可,如果是SOC主控芯片相同而声卡芯片换了的话就只要改Driver设备驱动程序即可,这种技术大大的提高了LINUX的可移植性,这也是linux为什么支持这么多芯片和这么受欢迎的原因之一。

    对于一个设备驱动,将其的设备资源和设备驱动都注册进platform总线后,平台总线会根据设备注册时提供的信息来匹配对应的设备资源和设备驱动,具体的匹配方法是根据注册时提供的设备名称,如果名称相同就匹配这个设备资源和设备驱动.。对于UDA1341芯片的驱动,其设备资源在内核的arch\arm\plat-s3c24xx\Devs.c中,其代码如下

    struct platform_device s3c_device_sdi = {

    .name   = "s3c2410-sdi",

    .id   = -1,

    .num_resources   = ARRAY_SIZE(s3c_sdi_resource),

    .resource   = s3c_sdi_resource,

    };

    可以看出来 .name   = "s3c2410-sdi", 这个就是匹配的条件,注册如内核后,平台总线会根据这个名字来匹配对应的驱动,其设备驱动在内核的\sound\soc\s3c24xx\s3c2410-uda1341.c中,在里面可以找到如下代码

    static struct device_driver s3c2410iis_driver = {

    .name = "s3c2410-iis",

    .bus = &platform_bus_type,

    .probe = s3c2410iis_probe,

    .remove = s3c2410iis_remove,

    };

    可以看懂 .name = "s3c2410-iis", 有这条信息,如此平台总线就可以根据这两个名字匹配设备和驱动,对于我要改的地方,是在\sound\soc\s3c24xx\s3c2410-uda1341.c这个文件,因为对于设备资源,可以找到其就是存放了SOC的IIS控制寄存器的地址,如果换了芯片而且其IIS控制寄存器不同的话就只要改arch\arm\plat-s3c24xx\Devs.c中的设备资源即可,对于\sound\soc\s3c24xx\s3c2410-uda1341.c这个UDA1341的设备驱动,我根据自己的想法大概分为了两个部分也就是开始的图


    图6-10 wm8976驱动说明


    其中的IIS声音处理部分两块芯片都是相同的,准确的来说所以声音的处理基本都是用的IIS总线,因此这部分不用改,对于硬件相关部分就是要修改的主要部分,因为这两款声卡的控制区别很大


    6.2 wm8976驱动的具体移植

    WM8976的控制寄存器有许多,那么设置的顺序如何确定呢?WM8976的芯片手册给了我们参考:

    图6-11 wm8976芯片操作说明


    除了以上初始化WM8976的代码需要修改之外,另外一个需要修改的地方就是音量控制。两款芯片,音量控制的方法肯定有所差别,我们需要修改使其适合于WM8976。

    首先我们看WM8976的芯片资料,从中发现寄存器52和寄存器53分别用于控制LOUT1和LOUT2的音量。而对于音量的控制,我们主要完成3点,分别是默认音量、设置音量和读出音量。下面我们分别来设置:

    设置之前我们要知道,UDA1341给的音量值越大,声音越小;而我们的WM8976则相反,给出的音量值越大,则声音越大!

    我们首先来设置默认值:

    在init_wm8976(void)函数里面加入:uda1341_volume = 57;

    在来设置读写音量,那么在哪里改呢?应该还记得,两个函数吧:

    audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);

    audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);

    前一个函数用于注册有关播放和录音的操作函数集,后一个用于注册有关音量控制的操作函数集!那么我们就从smdk2410_mixer_fops这个函数集下手了:

    static struct file_operations smdk2410_mixer_fops = {

    ioctl: smdk2410_mixer_ioctl,

    open: smdk2410_mixer_open,

    release: smdk2410_mixer_release

    };

    这个操作函数集里面有个ioctl函数,没错!找的就是它,我们做如下修改:

      case SOUND_MIXER_WRITE_VOLUME:

       ret = get_user(val, (long *) arg);

       if (ret)

        return ret;

       uda1341_volume =  (((val & 0xff) + 1) * 63) / 100;//设置值越大,音量越大

       wm8976_write_reg(52, (1<<8)|uda1341_volume);//寄存器52和53用于控制音量

       wm8976_write_reg(53, (1<<8)|uda1341_volume);

       break;


      case SOUND_MIXER_READ_VOLUME:

       val = ( uda1341_volume * 100) / 63;

       val |= val << 8;

       return put_user(val, (long *) arg);


    只需要对这两项进行修改就可以了!

    如此一来,代码修改完毕!不过基于本人脑子比较笨,所以还得再来总结一下:

    首先我们修改对控制寄存器的设置,这点很好理解,因为毕竟是两款芯片,寄存器肯定不会相同。而当设置寄存器的时候,方法也不相同,对于WM8976来说,是传输16位的数据,前7位表示寄存器地址,后9位表示要设置的值。而对于UDA1341来说,首先要在地址模式下设置好下面数据传输模式下传输的数据用于设定那些东东,然后就切换到数据传输模式下,传输相应的数据!

    然后我们修改有关音量控制的代码,主要是默认音量、设定音量和读出音量!对于WM8976来说,设定值越大,音量越大。而对于UDA1341来说,设定值越小,则音量越大。

    接下来是对声卡操作的修改部分,首先修改的\sound\soc\s3c24xx\s3c2410-uda1341.c中的

    static void init_uda1341(void)函数,这个是对于UDA1341芯片的初始化函数,对于wm8976的初始化和UDA1341完全不同,要做主要修改修改后代码如下

    static void init_wm8976(void)

    {

    uda1341_volume = 57; //音量控制,基本相同,没改

    uda1341_boost = 0;   //增益控制,相同,没改

    下面就是声卡硬件相关的初始化,是修改的主要部分,其控制函数wm8976_write_reg实现了对声卡芯片相应寄存器地址的控制,是根据原来的改的,不过两块芯片的读写流程很不一样

    wm8976_write_reg(0x3, 0x6f);

    wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b  

    wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable

    wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK  

    wm8976_write_reg(0x4, 0x10);//16bit

    wm8976_write_reg(0x2B,0x10);//BTL OUTPUT  

    wm8976_write_reg(0x9, 0x50);//Jack detect enable  

    wm8976_write_reg(0xD, 0x21);//Jack detect  

    wm8976_write_reg(0x7, 0x01);//Jack detect

    对于wm8976_write_reg函数的实现:

    static void wm8976_write_reg(unsigned char reg, unsigned int data)

    {

    int i;

    unsigned long flags;

    unsigned short val = (reg << 9) | (data & 0x1ff);

    s3c2410_gpio_setpin(S3C2410_GPB2,1);

    s3c2410_gpio_setpin(S3C2410_GPB3,1);

    s3c2410_gpio_setpin(S3C2410_GPB4,1);

    local_irq_save(flags);

    for (i = 0; i < 16; i++){

    if (val & (1<<15))

    {

    s3c2410_gpio_setpin(S3C2410_GPB4,0);

    s3c2410_gpio_setpin(S3C2410_GPB3,1);

    udelay(1);

    s3c2410_gpio_setpin(S3C2410_GPB4,1);

    }

    else

    {

    s3c2410_gpio_setpin(S3C2410_GPB4,0);

    s3c2410_gpio_setpin(S3C2410_GPB3,0);

    udelay(1);

    s3c2410_gpio_setpin(S3C2410_GPB4,1);

    }

    val = val << 1;

    }

    s3c2410_gpio_setpin(S3C2410_GPB2,0);

    udelay(1);

    s3c2410_gpio_setpin(S3C2410_GPB2,1);

    s3c2410_gpio_setpin(S3C2410_GPB3,1);

    s3c2410_gpio_setpin(S3C2410_GPB4,1);

    local_irq_restore(flags);

    }

    这个函数实现了对wm8976相应寄存器地址的读写,因为采用的是3线模式因此如上面所说的控制方式为如下的时序


    图6-12 wm8976芯片操作说明

    其中SDIN信号是由GPB3引脚发出的,具体如下图


    图6-13 wm8976原理


    上图为SOC(S3C2440)接口 下图为wm8976接口


    图6-14 wm8976原理图


    代码分析:

    wm8976_write_reg函数主要操作的是上图GPB2,GPB3,GPB4的引脚,可以看到原理图上的接线,这3个引脚是wm8976芯片的控制引脚,具体控制方法上图也已经给出,主要讲解的是static void init_wm8976(void)函数。首先是wm8976_write_reg(0, 0),其中参数中第一个参数代表的是wm8976芯片的地址,第二个参数代表的是要写入这个地址的值,首先写入的是0地址寄存器参数0,复位芯片,然后写0x3地址0x6f,使能左右声道DAC数模转换,寄存器的另外几位设置要参考下图,还要根据接线原理图,由于下图太小看不清楚,我再这里大概说下,0x03寄存器的第2,3位控制的是左右声道混音器使能,如果不使能的话其左右声道输出根据下图会是在OUT4,但是根据原理图发现OUT4并没有使用,因而必须使能第2,3两位。另外5,6两位根据原理图是扬声器输出,暂且使能,第7,8位是OUT3,OUT4的控制,没有使用这两个,设为0

    wm8976_write_reg(0, 0);  

    wm8976_write_reg(0x3, 0x6f);

    wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b  

    wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable

    wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK  

    wm8976_write_reg(0x4, 0x10);//16bit

    wm8976_write_reg(0x2B,0x10);//BTL OUTPUT  

    wm8976_write_reg(0x9, 0x50);//Jack detect enable  

    wm8976_write_reg(0xD, 0x21);//Jack detect  

    wm8976_write_reg(0x7, 0x01);//Jack detect


    图6-15 wm8976芯片说明手册

    其他具体不多说明了,根据寄存器的说明即可了解其作用,其中某些寄存器的设置要根据上图修改。这里也不一一说明,因为寄存器说明比较麻烦。

    第7章 Madplay移植说明

    Mp3文件是压缩的音频文件,所以对于Mp3文件的播放要有特殊的播放器,Madplay就是在Linux下用于播放Mp3音频文件的一个开源工具,接下来解释下对Madplay移植要点。

    (一).准备

    移植Madplay所需四个软件包分别为libid3tag-0.15.1b.tar.gz,

    libmad-0.15.1b.tar.gz,zlib-1.1.4.tar.gz,madplay-0.15.2b.tar.gz

    (二).解压

       1.mkdir /mp3 建立MP3目录

        2. tar -zxvf libid3tag-0.15.1b.tar.gz -C /mp3

        3. tar -zxvf ibmad-0.15.1b.tar.gz -C /mp3

        4. tar -zxvf zlib-1.1.4.tar.gz -C /mp3

        5. tar -zxvf madplay-0.15.2b.tar.gz -C /mp3

    (三).编译zlib

    #cd /mp3/zlib-1.1.4

    #./configure --prefix=/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/lib

    修改Makefile指定编译器以及库文件

    AR=/usr/local/arm/4.3.2/bin/arm-linux-ar

    CC=/usr/local/arm/4.3.2/bin/arm-linux-gcc

    RANLIB=/usr/local/arm/4.3.2/bin/arm-linux-ranlib

        最后执行  #make

                  #make install

    (四).编译libid3tag

    #cd /mp3/libid3tat-0.15.1d

    #./configure --host=arm-linux CC=arm-linux-gcc --disable-debugging --disable-shared --prefix=/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/lib

    #make

    #make install

    (五).编译libmad

    #cd /mp3/libmad-0.15.1b

    #./configure --enable-fpm=arm --host=arm-linux --disable-shared --disable-debugging --prefix=/usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/lib

    修改 Makefile 129行 去掉 –fforce-mem

    #make

    #make install

    (六).编译madplay

    #cd /mp3/madplay-0.15.2b

    #./configure --host=arm-linux CC=arm-linux-gcc --disable-debugging --disable-shared

    #make

    但是,这样得到的是动态连接的。

    #rm madplay

    拷贝make的最后一个连接的命令,在最后加上-static 和 -lz,然后运行,得到静态连接的程序 如

    arm-linux-gcc -Wall -O2 -fomit-frame-pointer -o madplay madplay.o getopt.o getopt1.o version.o resample.o filter.o tag.o crc.o rgain.o player.o audio.o audio_aiff.o audio_cdda.o audio_hex.o audio_null.o audio_raw.o audio_snd.o audio_wave.o audio_oss.o  -lmad -lid3tag -lm -lz -static

    最后把madplay拷贝到NFS挂载的文件系统目录下即可,移植结束。

    第8章 嵌入式MP3按键驱动编写

    下面来说明下对mp3按键驱动的移植流程。

         对按键驱动的实现是以模块形式,首先是用module_init(dev_init)注册模块,另外module_exit(dev_exit)卸载模块。MODULE_LICENSE("GPL")是必须的,

    首先模块是以混杂设备驱动的方式实现的,通过混杂设备结构体

    static struct miscdevice misc = {

    .minor = MISC_DYNAMIC_MINOR,

    .name = DEVICE_NAME,

    .fops = &dev_fops,

    };

    .minor表示的是设备的次设备号,所有注册为混杂设备的主设备号都是固定的,不同的是内核会分配不同的次设备号,.name代表的是设备的名字,到时候出现在内核的dev目录下时所显示的名字,另外.fops则是整个驱动的关键部分,可以看下这个结构体


    static struct file_operations dev_fops = {

        .owner   =   THIS_MODULE,

        .open  =   s3c24xx_buttons_open,

        .release=   s3c24xx_buttons_close,

        .read  =   s3c24xx_buttons_read,

        .poll   =   s3c24xx_buttons_poll,

    };

    这个结构体代表了具体的硬件操作方法,这里看图,简单说明下在Linux下提供应用程序操作具体硬件的方法,当应用程序调用库函数后,库函数会调用系统库函数,然后库函数通过内核的一系列处理最终调用的是硬件抽象层,这个硬件抽象层就是Linux的设备驱动,最终设备驱动函数会达到操作具体硬件的作用,在上面结构体中s3c24xx_buttons_open等函数会达到操作具体硬件的作用,而且这些函数是被系统调用库通过内核调用的,这就是linux应用工作的流程。

    分析到这里就发现Linux的设备驱动是很简单了,主要就是实现s3c24xx_buttons_open等函数,并向上层提供这些接口,具体的实现流程这里我不每个都说明了,因为学校不允许大幅用代码填充凑字数,所以这里我就讲下对s3c24xx_buttons_open,函数实现的过程,代码如下:

    static int s3c24xx_buttons_open(struct inode *inode, struct file *file)

    {

        int i;

        int err = 0;

        for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {

    if (button_irqs.irq < 0) {

    continue;

    }  

         err=request_irq(button_irqs.irq,buttons_interrupt,IRQ_TYPE_EDGE_RISING,

            button_irqs.name, (void *)&button_irqs);

            if (err)

                break;

        }

        if (err) {

            i--;

            for (; i >= 0; i--) {

        if (button_irqs.irq < 0) {

    continue;

        }

        disable_irq(button_irqs.irq);

                free_irq(button_irqs.irq, (void *)&button_irqs);

            }

            return -EBUSY;

        }

        ev_press = 0;

        return 0;

    }

    这里是在应用程序中调用open函数打开设备文件时会调用到这个函数,列如在这里.name = DEVICE_NAME,中的DEVICE_NAME宏代表的是buttons,那么到时候出现在Linux的/dev目录下就有/dev/button这个函数,如果在这个驱动已经装好后调用函数open("/dev/buttons", 0),最终会调用到s3c24xx_buttons_open,在这个函数中主要做了这些事情:给用到的几个按键设置中断,设置好后按这几个键就会产生中断,然后内核跳转到中断处理函数buttons_interrupt去执行,就是这么简单,只是把用到的几个按键设置了中断

    request_irq(button_irqs.irq,buttons_interrupt,IRQ_TYPE_EDGE_RISING,button_irqs.name, (void *)&button_irqs);

    这个是设置按键中断的函数,还有的就是申请中断失败的处理函数,最后一个ev_press = 0; 是设置标准位,到时候通过判断这个值来明确是否有按键按下。按键驱动相对简单,不多做解释了

    最后,因为这个驱动是以模块形式编写的,所以要写个Makefile来编译这个模块,编译模块的Makefile基本是相同的,这里就只要照着写个就行了,具体如下

    KERN_DIR = /work/system/linux-2.6.22.6(指定内核路径)

    all:

    make -C $(KERN_DIR) M=`pwd` modules  (利用内核的配置来编译模块)


    clean:

    make -C $(KERN_DIR) M=`pwd` modules clean (模块清理)

    rm -rf modules.order


    obj-m += s3c24xx_buttons.o (对于不同模块的编译,基本只要修改这个名字就行了)

    写好后执行make ,最后把生成的s3c24xx_buttons.ko文件拷到文件系统目录下(因为文件系统是以NFS形式挂载的)cp s3c24xx_buttons.ko /work/nfs_root/fs_qt  这个/work/nfs_root/fs_qt是NFS的服务目录,拷到这里后,当内核启动时就会把这里的内容挂载到开发板上,挂载好后执行insmod s3c24xx_buttons.ko加载模块


  • 回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入因仑

    本版积分规则

    快速回复 返回顶部 返回列表