https://blog.csdn.net/sinat_36184075/article/details/71305568
** 本文未附任何实例代码,基于目标板的不同操作不尽相同,网络资源针对比较成熟的开发板均可找到对应的成套实例代码
【开发环境构建】
1. 开发环境搭建:操作系统或虚拟机Ubuntu安装、网络服务配置、工具安装等
工具资源<云盘>: https://pan.baidu.com/s/1bpakJtP // env/嵌入式linux软件开发环境(不定期更新)
步骤整理<博客>: http://blog.csdn.net/sinat_36184075/article/details/71194832
2. 编程基础内容:交叉编译使用、Makefile规则、常用汇编指令
交叉编译工具链制作<博客>: http://blog.csdn.net/sinat_36184075/article/details/71195114
Makefile编写<博客>: http://blog.csdn.net/sinat_36184075/article/details/54917518
汇编指令快速查询<博客>: http://blog.csdn.net/sinat_36184075/article/details/55819869
3. 常用工具使用:
windows下工具的使用(SourceInsight、SecureCRT、keil、IAR等)
linux下工具的使用(tftp、nfs、vi、man手册、基本命令行命令及grep/find/tar/diff/patch等)
// 常用工具上手简单,可自查。
【ARM9嵌入式系统基础】
1. GPIO接口
1) 嵌入式开发步骤:
编程:主要以.c .h文件为主;
编译:make命令调用Makefile中写好的交叉编译规则来编译出.bin二进制文件;
烧写:PC并口与JTAG连接或PC的USB与串口连接,使用对应的工具和命令烧写;
运行测试:命令运行或复位开发板,观察运行效果。
2) 通过GPIO引脚实现软件如何控制硬件:
单个引脚的操作有3种:输出高低电平、检测引脚状态、中断;
对某个引脚的操作通过读、写寄存器来实现;
读写寄存器方法:通过编程程序,读写寄存器的地址;
// 需结合2点:目标板电路图(确定引脚)、ARM芯片手册Datasheet(操作说明)
2. 存储控制器
1)目标板地址空间布局:
32位CPU虚拟地址4G,其中1G为CPU内核空间,3G为用户空间;
// 1G:0x0000_0000 ~ 0x4000_0000
// 3G:0x4000_0000 ~ 0xFFFF_FFFF
// 表:目标板功能部件与寄存器地址对照表
// 表:存储控制器与所接外设的访问地址对照表
3. 内存管理单元MMU
1) 虚拟地址和物理地址关系
MMU:负责虚拟地址到物理地址的映射,提供硬件机制的内存访问权限检查。
通过MMU使得各个用户进程都有自己独立的地址空间。
ARM的CPU上地址转换有3个概念:虚拟地址VA、变换后的虚拟地址MVA、物理地址PA。
没启动MMU时,CPU、cache、MMU、外设等所有部件使用的是物理地址;
启动MMU之后,CPU对外发出虚拟地址VA,VA被转换为MVA供cache、MMU使用,MVA在这里被转换为PA,最后使用PA读写实际的硬件设备(内部寄存器或外接设备)。
CPU核看到的、用到的只是虚拟地址VA。实际设备看不到VA和MVA,读写他们时使用的是物理地址PA。
2) 通过设置MMU来控制虚拟地址到物理地址的转化
虚拟地址VA >> 物理地址PA 方法有2:用一个确定的数学公式转换、用表格存储虚拟地址对应的物理地址(表格:页表Page Table)。
页表:由一个个条目(Entry)组成,每个条目存储了一段VA对应的PA及其访问权限,或者下一页表的地址。
ARM的CPU核使用的是第二种方法:页表。
3) MMU的内存访问权限机制
内存的访问权限检查是MMU的主要功能之一,决定了一块内存是否允许被读或被写。
// 表:AP位、S、R位的访问权限对照表
4. Nand Flash控制器
1) Nand Flash芯片接口
类似于PC上的硬盘,掉电不丢失,保存系统运行所必须的操作系统、应用程序、用户数据、其他数据等。
容量:16M~512M
与Nor Flash相比优点:Nand Flash性能高即擦写速度快3ms、可擦写次数多、生命周期10倍以上、容量大、价格低。
与Nor Flash相比缺点:Nand Flash可靠性较低必须要有校验措施、易用性不如Nor Flash、不支持XIP(XIP:代码可以直接在Nor Flash上运行,无需加载到内存)。
Flash存储器件的可靠性要考虑3点:位反转、坏块、可擦除次数。
Nand Flash发生位反转的概率更高,推荐使用EDC/ECC进行错误检测和恢复。
嵌入式linux对两种Flash的支持都很成熟,在Nor Flash上常用jffs2文件系统,在Nand Flash上常用yaffs文件系统。在更底层,有MTD驱动程序实现了对他们的读、写、擦除操作,也实现了EDC/ECC校验。
2) 通过Nand Flash控制器访问Nand Flash的方法
对Nand Flash进行访问控制时,需要先发出命令,然后发出地址序列,最后读写数据;需要使用各个使能信号来分辨是命令、地址、还是数据。
5. 中断体系结构
1) ARM的CPU中7种工作模式
用户模式usr、快速中断模式fiq、中断模式irq、管理模式svc、数据访问终止模式abt、系统模式sys、未定义指令终止模式und。
大多数程序运行与用户模式usr,进入其他6种特权模式是为了处理中断、异常、或者访问被保护的系统资源。
ARM的CPU中有两种工作状态:32bit的ARM指令、16bit的Thumb指令。
ARM9中有31个32bit寄存器 + 6个状态寄存器 = 37个寄存器。
2) 中断服务程序编写方法
CPU运行过程中,知道各类外设发生了某些事件如串口接收到了新数据、USB接口插入了设备、按下了某个按键等,主要通过2种方法:
轮询:循环检查设备状态并作出相应反应。实现简单,常用在功能单一的系统中,如温控系统;缺点是占用CPU资源过高,不适用于多任务系统;
中断:当某时间发生时,硬件会设置某个寄存器,中断当前程序,跳转执行此事件,最后返回被中断的程序。实现相对复杂,但效率很高,是常用的方法。
使用中断的步骤:
①设置好中断模式和快速中断模式下的栈;
②准备好中断处理函数(ISR);
③设置中断优先级;
④进入、退出中断模式或快速中断模式时,需要保存、恢复被中断程序的运行环境;
⑤根据具体中断,设置相应外设;
⑥确定使用此中断的方式:FIQ或IRQ;
⑦使能中断。
6. 系统时钟和定时器
1) 系统时钟体系结构
时钟控制逻辑给整个芯片提供三种时钟,
FCLK:用于CPU核;
HCLK:用于AHB总线(用高性能模块的连接)上的设备,如CPU核、存储器控制、中断控制器、LCD控制器、DMA和USB主机模块等;
PCLK:用于APB总线(用于低带宽外设的连接)上的设备,如IIS、I2C、PWM定时器、MMC接口、ADC、UART、GPIO、RTC和SPI;
2) 通过设置MPLL改变系统时钟的方法
①设置分频/倍频
②启动MPLL
③设置存储控制器SDRAM
④初始化定时器0
⑤定时器0中断使能及中断服务程序
7. 通用异步收发器UART
1) UART原理
全双工方式串行双向数据收发。
通信管脚跳线接线方式:
RxD <=> TxD
TxD <=> RxD
GND <=> GND
RS232逻辑电平:高电平(3~12V)表示0,低电平(-3~-12V)表示1。
重要参数:
>>数据位个数: 5 ~ 8 bit (开发板那端定好的是 8 bit / 帧数据)
>>验证方式:奇校验、偶校验、无校验
>>停止位宽度:1~2bit
>>通信的速率:bps (bit per second - 每秒传输bit位,即波特率)
数据收发原理:
发送数据时,CPU先将数据写入发送FIFO中,然后UART会自动将FIFO中的数据复制到发送移位器(TS)中,发送移位器将数据一位一位的发送到TxDn数据线上。
接收数据时,接收移位器(RS)将RxDn数据线上的数据一位一位的接收进来,然后复制到接收FIFO中,CPU即可从中读取数据。
2) UART使用
①将所涉及的UART通道管脚设为UART功能
②设置波特率
③设置传输格式
④设置时钟源、中断方式(或DMA模式)
⑤发送数据:往寄存器写入;接收数据:从寄存器读取
⑥UTRSTATn状态寄存器检测数据是否发送完毕、是否接收到数据
结合PC上的串口工具windows下SecureCRT工具,linux下kermit(Page195)工具进行测试。
8. I2C接口
1) I2C总线协议
飞利浦公司开发的串行总线,连接微控制器及其外围设备。
两条总线:SDA数据线,SCL时钟线。
多主机总线。
主从设备通信方式。
主机收发均可。
多主机冲突仲裁。
8bit双向数据传输,100kbit/s,400kbit/s,3.4Mbit/s。
从设备地址唯一。
三种类型信号:开始(S)、响应(ACK-8bit后的第9个时钟周期拉低SDA电平)、结束(P)
数据传输格式:
master:--S--从addr|W---接ACK---0x0d---接ACK---SR---从地址|R---接ACK---收---NACK---T--
slave: --------接---------发ACK-----接---发ACK-----------接-------发ACK---
2) I2C接口使用
①I2C控制器初始化
②主机发送函数
③主机接收函数
④中断服务程序i2c_Handle:清中断、写操作、读操作
9. LCD控制器
1) LCD显示器的接口及时序
LCD,液晶显示器。
CPU或显卡发出的信号时TTL信号,LCD本身接收的也是TTL信号。
LCD控制器被用来传输LCD图像数据,并提供必要的控制信号。
1幅图像为1帧frame。显示器从屏幕左上角,一行行的取得每个像素的数据并显示,沿着Z字形的路线进行扫描。
分辨率:有效像素数据的 行数 × 列数。
2) LCD控制器的使用方法
①main.c 提供菜单以选择不同的显示模式来操作LCD
②lcdlib.c 条用以下两个文件的函数操作LCD
③lcddrv.c 设置LCD控制器、调色板
④framebuffer.c 画点、画线、画同心圆、清屏
10. ADC和触摸屏接口
1) ADC和触摸屏结构
ADC,数模转换。可以将模拟信号输入转换为二进制数据。
// 图:ADC和触摸屏接口结构图
2) ADC和触摸屏的使用方法
ADC启动方法:手工启动、读结果时就自动启动下一次转换;
是否已经结束:查询状态位、转换结束时发出中断。
ADC操作只涉及三个寄存器:ADCCON、ADCTSC、ADCDAT0。
①设置ADCCON寄存器,选择输入信号通道,设置A/D转换器时钟;
②设置ADCTSC寄存器,使用设为普通转换模式,不适用触摸屏功能;
③设置ADCCON寄存器,启动A/D转换;
④转换结束时,读取ADCDAT0寄存器获得数值。
// 拓展:电阻触摸屏原理(Page241)
【嵌入式linux系统移植】
1. 移植u-boot
1) bootloader作用及工作流程
作用:一小段程序,在系统上电时开始执行,初始化硬件设备、准备好软件环境、最后调用操作系统内核。
也可以增强bootloader的功能,如增加网络功能、从PC上通过串口或网络下载文件、烧写文件、将Flash上压缩的文件解压后再运行等,强大的bootloader也叫Monitor。
工作过程:仅对开发人员使用,在开发时通常需要各种命令操作bootloader,一般通过串口工具连接pc和目标板,可以在串口上输入各种命令,观察运行结果。
嵌入式linux系统通常分为4个层次:bootloader、kernel、filesystem、Application
bootloader启动流程分为两个阶段:
第一阶段:
1>硬件设备初始化
2>为bootloader第二阶段准备RAM空间
3>复制bootloader第二阶段代码到RAM中
4>设置好栈
5>跳转到第二阶段代码的C入口点
第二阶段:
1>初始化本阶段要使用到的硬件设备
2>检测系统内存映射
3>将内核映像和根文件系统映像从Flash上读到RAM空间中
4>为内核设置启动参数
5>调用内核
u-boot是通用的bootloader,支持平台包括X86、ARM、PowerPC。
2) u-boot代码结构及编译过程
u-boot开源源码官方ftp下载地址:ftp://ftp.denx.de/pub/u-boot/
以u-boot-1.1.6为例,根目录下有26个子目录,可以分为4类:
1>平台相关(cpu/ lib_i386/)或开发板相关(board/)的;
2>通用的函数(include/ lib_generic/ common/);
3>通用的设备驱动程序(disk/ drivers/ dtt/ fs/ nand_spl/ net/ post/ rtc/);
4>u-boot工具(tools/)、示例程序(examples/)、文档(doc/)。
从readme和Makefile入手,根据目标开发板的名字board_name,通常执行2步进行编译:
1> $:' make <board_name>_config
// 配置对应目标板的配置文件
2> $:' make all
// 编译,生成u-boot.bin、u-boot、u-boot.srec三个文件
注意:tools/目录下生成的mkimage文件,在编译内核时用来生成u-boot格式的内核映像文件。
3) 移植u-boot流程
使用网卡芯片硬件,tftp服务器软件进行移植:
1> 配置开发板ip地址,服务器地址的环境变量
2> 使用tftp命令进行指定起始地址位置下载u-boot.bin文件
3> 修改相关文件使bootloader支持内核烧写、yaffs根文件系统移植;
4> 修改默认配置参数方便使用,如linux启动参数、自动启动命令、默认网络设置
(Page302)
4) 常用u-boot命令
help:查看所有u-boot中的命令及其作用,也可以help [命令]查看单个命令;也可以用?代替help进行执行。
下载命令:loadb、loads、loadx、loady和tftpboot、nfs
如loadb [off] [baud]
如tftpboot [loadAddress] [bootfilename]
如nfs [loadAddress] [host ip addr:bootfilename]
内存操作命令:
查看内存命令:md,如md[.b,.w,.l] address [count]
修改内存命令:mm
填充内存命令:mw
复制内存命令:cp
Nor Flash操作命令:
查看Flash信息:flinfo
加/解写保护命令:protect
擦除命令:erase
Nand Flash操作命令:
nand命令,根据不同的参数进行不同的操作,如擦除、读取、烧写。
启动命令:
boot、bootm命令都是执行环境变量bootcmd所指定的命令。
2. 移植linux内核
1) 内核源码结构,内核启动过程
获取内核源码:https://www.kernel.org/
下载、解压得到linux内核源码文件。
// 表:linux内核子目录结构
Makefile:顶层的Makefile是所有Makefile文件的核心,总体控制内核的编译和连接。
.config:配置文件,配置内核时产生。所有Makefile文件都是根据.config来决定使用哪些文件来编译。
Linux内核启动过程:
开始→确定内核是否支持该架构→确定内核是否支持该单板→建立一级页表→禁止ICache等→使能MMU→设置栈指针并调用start_kernel→输出linux版本信息→设置与体系结构相关的环境→初始化控制台→启动init进程
2) 内核配置方法
$:' make menuconfig
System Type:系统类型子菜单,用来选择目标板类型。(Page324)
Device Drivers:设备驱动程序子菜单。(Page327)
3) 移植内核
配置内核→编译内核→烧写内核→启动内核
4) MTD设备分区方法
MTD,内存技术设备,是Linux中ROM、Nor Flash、Nand Flash等存储设备抽象出来的一个设备层。
分区方法:修改arch/arm/plat-s3c24xx/common-smdk.c文件中的smdk_default_nand_part结构即可。
5) yaffs根文件系统移植
yaffs,专门为Nand Flash设计的嵌入式文件系统,适用于大容量的存储设备。
移植(Page346)
3. 构建linux根文件系统
1) linux文件系统层次标准
FHS文件系统层次标准:http://www.pathname.com/fhs/
2) 根文件系统下各目录的作用
/bin 目录:存放所有用户都可以使用的、基本的命令。
/sbin 目录:存放系统命令,即只有管理员能够使用的命令,用于启动系统、修复系统等。
/dev 目录:存放设备文件(字符设备、块设备)。(Linux下一切皆文件)
/etc 目录:存放各种配置文件。
/lib 目录:存放共享库和可加载模块(即驱动程序)。
/home 目录:用户目录,是可选的。目录下是以用户名命名的子目录。
/root 目录:根用户(root)目录,普通用户即为/home下的子目录。
/usr 目录:存放共享、只读的程序和数据。
/var 目录:存放可变的数据,与/usr内容相反。
/proc 目录:空目录,常作为proc文件系统的挂载点,proc文件系统是个虚拟的文件系统。
/mnt 目录:空目录,用于临时挂载某个文件系统的挂载点,如U盘、光盘等。
/tmp 目录:空目录,用于存放临时文件。
3) 构建根文件系统:移植Busybox、构造各个目录/文件等
Busybox开源工具下载地址:https://busybox.net/downloads/
下载、解压、开始配置:
// 表:Busybox配置选项分类(Page362)
配置完成、编译:make
安装并拷贝glibc库。
构建根文件系统:(Page367)
1> 创建etc/initab文件
2> 创建etc/init.d/rcS脚本文件
3> 创建etc/fstab文件(控制mount命令行为)
4> 构建dev/目录
5> 构建其他目录,如proc/ mnt/ tmp/ sys/ root/等
4) 制作yaffs、jffs2文件系统映像文件
yaffs根文件系统:Page367
jffs2根文件系统:Page375
烧写...
4. linux内核调试技术
1) 几种调试内核的方法:printk、kgdb
【printk】调试驱动、内核最简单的方法,使用printk打印信息。与用户空间的printf函数格式完全相同。可在字符串内的头部加入<n>(0~7)表示记录级别。
printk信息常常用于串口输出,这时串口被称为串口控制台。
使用u-boot时,设置了命令行参数"console=ttySAC0",它使得printk的信息从串口0中输出。
系统启动后,查看printk信息,直接运行dmesg命令即可。
【kgdb】源码级别的linux内核调试器。使用kgdb时,需要结合gdb一起使用,使得调试内核就像调试应用程序一样,可在内核代码中设置断点、一步一步执行、观察变量的值。
内核需要添加kgdb补丁才可以使用kgdb调试。
kgdb补丁下载地址:http://kgdb.cvs.sourceforge.net/kgdb/kgdb-2/?pathrev=linux2_6_22_uprev
使用cvs工具下载:(Page382)
$:' cd /work/debug
$:' cvs -z3 -d:pserver:anonymous@kgdb.cvs.sourceforge.net:/cvsroot/kgdb co -p -r linux2_6_22_uprev kgdb-2
...
配置内核,使能kgdb功能。
集合可视化图形前端ddd和gdb来调试。
2) 调试工具:gdb、ddd
【ddd】ddd调用gdb来调试内核,可以在图形界面上完成调试工作。
安装ddd:$:' sudo apt-get install ddd
调试方法(Page388)
【嵌入式linux设备驱动开发】
1. 字符设备驱动程序
1) linux系统中驱动程序的地位和作用
从下到上,一个软件系统能够运行的结构:硬件 → 驱动程序 → 操作系统(内核) → 库 → 应用程序
linux驱动程序分类:字符设备、块设备、网络接口。
编写驱动程序注意点:
1> 驱动程序可能同时被多个程序使用,考虑并发问题;尽可能发挥硬件作用以提高性能。
2> 硬盘驱动程序中使用DMA也可以不用,使用DMA的程序比较复杂,但是可以提高效率。
3> 处理硬件的各种异常情况,即使概率很低,否则出错时可能导致整个系统崩溃。
2) 驱动程序开发一般流程
1> 查看原理图、数据手册,了解设备的操作方法;
2> 在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从0开始;
3> 实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序;
4> 设计所要实现的操作,比如open、close、read、write等函数;
5> 实现中断服务(中断并不是每个设备驱动所必须的);
6> 编译该驱动程序到内核中,或者使用insmod命令加载;
7> 测试驱动程序。
3) 简单字符设备驱动程序开发方法
字符设备驱动程序操作函数集合,在结构体: struct file_operations {...}
调用哪个驱动程序的操作函数集合中的函数呢?
1> 设备文件有主/次设备号;
2> 模块初始化时,将主设备号与file_operations结构一起向内核注册;
编写字符设备驱动程序的流程:
1> 编写驱动程序初始化函数;
2> 构造file_operations结构中要用到的各个成员函数。
常用的操作函数集合中的函数:open、ioctl、write、read...
驱动程序编译:将.c文件放入内核的driver/char目录下,在driver/char/Makefile中增加对应模块编译的一个语句(obj-m +=...),然后在内核的根目录下执行命令make modules,就可以生成对应的.ko驱动程序模块文件。
驱动程序测试(Page409)。
2. 异常处理体系结构
1) linux异常处理体系结构
init/main.c中内核在start_kernel函数中调用trap_init和init_IRQ两个函数来设置异常的处理函数。
trap_init() 函数(arch/arm/kernel/traps.c)被用来设置各种异常的处理向量,包括中断向量。
init_IRQ() 函数(arch/arm/kernel/irq.c)被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数做进一步处理。
常见异常分类5种:未定义指令异常、指令预取中止异常、数据访问中止异常、中断异常、swi异常。
2) 中断处理体系结构及重要的数据结构
linux内核将所有的中断统一编号,使用一个结构数组来描述这些中断: struct irq_desc {...}
其中结构体成员 handle_irq 是这个或这组中断的处理函数入口。
3) 中断处理函数的注册、处理、卸载流程
初始化中断处理体系结构函数: init_IRQ (void);
用户向内核注册中断处理函数的函数: request_irq (...);
中断的C语言总入口函数: asm_do_IRQ (...);
用户从内核注销中断处理函数的函数: free_irq (...);
4) 驱动程序中使用中断的方法
按键驱动使用中断方式实现的实例。
3. 扩展串口驱动程序移植
1) 串口终端设备驱动程序的层次结构
串口驱动程序从上到下分为4层:终端设备层、行规程、串口抽象层、串口芯片层、(硬件)。
2) 移植标准串口驱动程序的方法(Page438)
...更多内容详见《嵌入式linux应用开发完全手册》.pdf