《嵌入式linux应用开发完全手册》核心笔记(全)

2019-07-12 14:08发布

** 本文未附任何实例代码,基于目标板的不同操作不尽相同,网络资源针对比较成熟的开发板均可找到对应的成套实例代码

【开发环境构建】
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控制器、调 {MOD}板
    ④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 _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函数格式完全相同。可在字符串内的头部加入(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