计算机与信息工程学院
嵌入式操作系统课程设计
基于S3C2440的H.264远程视频监控系统设计
专 业:
班 级:
姓 名:
学 号:
指导教师:
20##年6月27日
摘要:传统的视频监控系统虽然功能齐全,但价格昂贵并操作复杂,不适于普遍应用。随着流媒体技术、无线网络技术以及视频压缩技术的不断进步,视频监控系统的应用日益广泛和深入,监控的规模和范围也不断扩大,基于嵌入式移动终端设备和网络的远程视频监控具有成本体,操作简单,低功耗等优点,具有广阔的应用前景。本文结合嵌入式监控系统的性能需求,提出一种基于S3C2440的嵌入式Linux视频监控系统。嵌入式监控系统硬件采用ARM架构的处理器S3C2440平台,主频为400MHZ,满足视频采集、视频压缩编码和网络传输的处理能力。操作系统采用开源的Linux系统,该系统易于对底层硬件设备访问控制,同时为上层应用软件提供API接口调用,并负责软硬件资源分配、任务调度、协调控制等工作为监控系统提供软件平台。
关键字:嵌入式;H264; 优化;CPU
0. 引言:H.264是新一代的视频压缩标准,具有压缩比高、图像质量好、容错能力和网络适应性强的特点,适合应用于视频监控领域。本文利用H.264视频压缩技术,提出了一种新型的视频监控系统。新的系统硬件由监控前端、总控中心和分控中心三部分组成;软件由应用层、会话层和数据处理层三部分组成,最后给出了系统各部分的详细功能介绍。H.264是新一代的视频压缩标准,具有压缩比高、图像质量好、容错能力和网络适应性强的特点,适合应用于视频监控领域。本文利用H.264视频压缩技术,提出了一种新型的视频监控系统。新的系统硬件由监控前端、总控中心和分控中心三部分组成;软件由应用层、会话层和数据处理层三部分组成,最后给出了系统各部分的详细功能介绍。
1. 方案论证
本课题设计的嵌入式视频监控系统,采用迄今为止最先进的H.264视频压缩标准压缩视频文件,为基于嵌入式的多媒体应用搭建一个应用的基础平台。基于此设计思想,提出系统的总体设计框架。
服务器端以RISC通用处理器为核心,由SDRAM、FLASH等存储器件和各种通信接口构成嵌入式硬件平台。通过 USBHOST接口接入摄像头外设,以捕获视频原始数据。同时设有RS232、JTAG、LAN等用于开发调试的接口。选用免费、开源的Linux操作系统以实现基本控制和网络连接功能;采用H.264对采集的视频进行压缩编码,采用UDP协议实现视频的网络传输。客户端采用H.264解码后用Directshow技术播放接收到的视频。
2. 硬件详细设计
2.1体统组成
2.2模块
USB视频采集模块,数据处理模块,图像显示模块。
(1)USB视频采集模块:USB摄像头, USB摄像头驱动程序。
(2)数据处理模块:H.264编码库和采集传输应用程序。
(3)图像显示模块:运行于叫Windows之上的解码显示程序
2.3体统工作流程
(1)USB摄像头采集图像数据。
(2)采集传输应用程序通过摄像头驱动从摄像头获取到采集的图像数据。
(3)采集传输应用程序调用H.264编码库对图像数据进行压缩。
(4)采集传输应用程序将压缩后的图像数据通过网络传输给
Windows PC上的显示程序。
(5)Windows上的显示程序对图像数据进行解码并显示。
3. 软件详细设计
3.1 USB视频采集模块应用
视频图像的获取一般有两种方法,一种比较简单的方法就是直接调用read(),一般来说,read()通过内核缓冲区来读取数据的。另一种方法就是用mmap()内存映像的方法获取视频[45]。本程序就是采用mmap()的方式实现的。mmap()通过系统调用使得进程之间映像到同一个普通文件实现共享内存。普通文件被映像到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。采用共享内存通信的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。视频采集的流程如图。
在获取设备和图像信息之后,调用函数mmap()函数将设备映像到内存,map()函数的原型如下:
vd->map=mmap(0,vd->mbuf.size,PROT_READ | PROT_WRITE,MAP_SHARED,vd->fd,0)
然后使用操作名为VIDEOCAMCAPTRE的ioctl()函数启动捕获过程,这样就完成一帧图像的捕获并将其存储到内存映像区。代码如下:
vd->mmap.frame = frame;
if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0)
{
perror("v4l_grab_frame"); return -1;
}
vd->frame_using[frame] = TRUE;
vd->frame_current = frame;
到此并没有真正的完成图像的捕获,这时使用操作名为VIDEOSYNC的ioctl()函数等待动作的完成。该调用返回则表示捕获的操作结束,然后开始下一帧的捕获。代码如下:
if (ioctl(vd->fd, VIDIOCSYNC, &(vd->frame_current)) < 0) {
perror("v4l_grab_sync");
}
vd->frame_using[vd->frame_current] = FALSE;
3.2 数据处理
对图像帧的编码是通过调用X264函数库的函数实现的。 首先是下载X264的源代码,解压之后进入源代码顶层目录,源代码集中在common/、decoder/、encoder/、dshow/这几个目录下面,在build/目录下面则包含了针对不同平台的构建文件——对于Linux,这里指的是makefile;对于Windows,这里指的是针对Visual Studio的solution文件。除了上述两种平台之外,x264已经被移植到了TI公司的一款DSP上——名字叫做DM642。在build/目录下可以看到有下面这样几个子目录,分别对应着不同的平台:我们是要在Linux平台下编译,进入到linux/目录下面。在该目录下面存在着三个文件:dependencies中存放的是各个源文件之间的依赖关系,makefile是管理整个项目的make文件,TAGS中存放着ctags程序生成的tag标记。为了能够定位源程序中的变量或者对象,需要先通过ctags(适用于vim)或者etags(适用于emacs)生成tag文件,然后才能在一大堆程序代码中自由地跳转。在这里我们修改makefile把ctags修改为etags。因为我的编译器里没有ctags这个命令, ctags和etags其实是同一工具在不同linux版本里的不同名字而已,它们的作用是相同的。
编译过程中,除了需要../obj/目录之外,还需要一个../bin/目录。
把当前目录下makefile文件中CFLAGS= $(INCLUDE) -D__GCC__ 将其修改为:CFLAGS= $(INCLUDE) -D__GCC__ -DUSE_DISPLAY。指定交叉编译工具链,CC=/usr/local/arm/4.3.2/bin/arm-linux-gcc如图4.9。编译整个项目,执行命令make。如果编译出现错误,则执行make distclean清除配置,再执行make clean清除前一次编译生成的文件,再make一次。
成功编译后,进入avc-src-0.14/avc/build/objs/ 下删除X264.o文件。否则编译服务器端程序(server_arm.c)时会报错,至此编码器编译工作完成,接着编写服务器,端的网络socket程序,实现对编码器接口的调用,真正完成视频帧的压缩编码工作。
3.3图像显示
由于采集的视频格式为RGB色彩空间,在传输前还要把它转换为YcbCr色彩空间,这样可以降低传输和存储开销,在终端显示之前,则还需要转换到RGB空间。通过调用ConvertRGB2YUV(w,h,cam_data,cam_yuv) 函数实现转换。 然后就可以编写UDP发送编码后的X264程序,部分关键代码如下:
/* 初始化编码 */
void init_encoder()
{
const char* paramfile = "config.txt";
init_param(&m_param, paramfile);
m_param.direct_flag = 1;
m_x264 = X264_open(&m_param);
m_lDstSize = m_param.height * m_param.width + (m_param.height * m_param.width >> 1);
m_pDst = (uint8_t*)X264_malloc(m_lDstSize, CACHE_SIZE);
m_pPoolData = malloc(m_param.width*m_param.height*3/2);
}
/* 采用udp传输经过X264压缩后的视频数据 */
void udp_transport(int sockfd,int w,int h)
{
struct sockaddr_in addrsrc;
struct sockaddr_in addrdst;
int addrlen,n;
int32_t iActualLen;
int raw_Pixels = w*h;
float rate;
/* 将sockaddr_in结构各个成员原有的信息清0 */
bzero(&addrdst,sizeof(struct sockaddr_in));
/* 指代协议族,在socket编程中只能是AF_INET */
addrdst.sin_family=AF_INET;
/* 按照网络字节顺序存储客户端IP地址 */ addrdst.sin_addr.s_addr=inet_addr("202.193.74.217");
/* 调用htons函数设置网络端口号,以确保发出去正确的数据的字节序 */ addrdst.sin_port=htons(SERVER_PORT);
while(1)
{
read_video(NULL,w,h);
/* 将摄像头采集到的RGB数据转换成亮度、色度差YUV格式的数据 */ ConvertRGB2YUV(w,h,cam_data,cam_yuv);
/* 调用x264库文件中的X264_encode()函数进行编码 */
iActualLen = X264_encode(m_x264, cam_yuv, m_pDst, raw_Pixels); rate=(float)(raw_Pixels-iActualLen)*100/raw_Pixels;
printf("encoded:%d, %d bytes, encoded rate:%.3f%\n",raw_Pixels,iActualLen,rate); /* 从m_x264所指的区域复制1字节的 数据到m_pPoolData所指的内存中 */ memcpy(m_pPoolData,&m_x264->frame_num,1);
memcpy(m_pPoolData+1, m_pDst, iActualLen);
iActualLen++;
/* 从m_pPoolData数据缓冲区发送iActualLen字节数据到到addrdst地址处,实现了数据的传输 */
sendto(sockfd,m_pPoolData,iActualLen,0,(struct sockaddr*)&addrdst,sizeof(struct sockaddr_in));
}
}
4.结论
研究了H.264视频压缩编码标准,针对该视频采集系统对开源的T.264编码器进行编译和代码级优化,达到实时应用要求。研究了基于UDP协议的网络传输方案,修改设计服务器端的采集程序和客户端的视频解码播放程序,最终实现嵌入式视频采集系统的运行。
5.课程设计体会
两周的课程设计结束了,在这次的课程设计中不仅检验了我所学习的知识,也培养了我如何去把握一件事情,如何去做一件事情,又如何完成一件事情。在设计过程中,与同学分工设计,和同学们相互探讨,相互学习,相互监督。学会了合作,学会了运筹帷幄,学会了宽容,学会了理解,也学会了做人与处世。
在这次设计过程中,体现出自己单独设计模具的能力以及综合运用知识的能力,体会了学以致用、突出自己劳动成果的喜悦心情,从中发现自己平时学习的不足和薄弱环节,从而加以弥补。
6.参考文献
【1】李宾、高平. H264 编码系统的特点及其应用前景[J].电视工程,2003
【2】杨水清. ARM 嵌入式 Linux 系统开发技术详解[ M ].北京: 电子工业出版社,2008
【3】王黎明.ARM 9嵌入式系统开发与实践[M].北京: 北京航空航天大学出版社,2008
【4】周立功.ARM嵌入式系统基础教程[M].北京: 北京航空航天大学出版社, 2004
【5】张永强, 赵永勇等.嵌入式远程视频采集系统的设计与实现[J].现代电子技术, 2006
【6】韦东山.嵌入式Linux应用开发完全手册[M]. 北京: 人民邮电出版社,2009
【7】杜春雷.ARM体系结构与编程[M].北京: 清华大学出版社,2009
第二篇:嵌入式系统课程设计
嵌入式系统课程设计
课题:嵌入式Linux操作系统的移植与研究
一、简介
操作系统是用于管理计算机资源和控制应用程序运行的计算机程序,充当计算机用户和计算机硬件之间的一个中介。本次课程学习主要了解嵌入式系统技术基本概念、特点、分类,掌握嵌入式系统软硬件设计的基本方法。
随着微电子技术的发展,嵌入式系统的硬件功能越来越强大,嵌入式软件开始使用C、c++等高级语言编写,调试手段也越来越多和成熟。在体系结构上,也由最初的单一控制流程,逐渐引入嵌入式操作系统等技术。嵌入式操作系统首先从技术上解决了嵌入式系统标准化、层次化的问题,其次基于嵌入式操作系统,我们可以形成有效的技术积累和资源积累,比如在嵌入式操作系统基础上的各种硬件驱动程序、专家库函数、行业库函数、产品库函数和通用性的应用程序等。嵌入式操作系统作为嵌入式系统的基础,是嵌入式系统重要的运行环境和开发平台,它的集成度和可用性直接关系到嵌入式系统的效率,历来是嵌入式系统设计和开发的重点。
二、系统总体设计
关于嵌入式Linux操作系统的移植主要包括移植环境的搭建,Bootloader的移植和内核的移植,构建linux根文件系统,驱动设备
的移植。在这里主要介绍Bootloader的移植和内核的移植,比较PC机上的Linux与嵌入式Linux的内核,安装过程及模块的区别。
三、原理概述
Linux是一个用于多种硬件体系结构的操作系统,目前可以在i386、MIPS、ARM等硬件体系结构上运行,但是实际工作到特定的硬件环境上方方面面都需要修改,这就是具体的移植。例如基本操作系统移植包括文件系统、存储管理、设备驱动等,在原有系统的基础上根据系统结构的特点进行如下修改或开发:修改系统引导和初始化;去掉引导过程中多余代码,如:解压缩、移动核心代码等,以加速系统的引导和系统存储空间的充分利用;去掉swap,优化文件系统的物理布局,将常修改的文件定位在SDRAM中,不常修改的文件定位Flash中:修改外围初始化,简化系统文件的配置和文件属性、访问模式的设置,以便于安全管理;修改存储管理,使特定程序能在位执行,并增强内存空间的保护机制;开发所需驱动程序、设备状态监视程序: 本章将从Bootloader移植、内核移植等方面来分析讲解一个完整的 Li FlUX移植和优化的过程。
四、系统实现:
4.1移植环境的搭建
(1)获取内核源码
一个的FTP地址:ftp:///pub/linux/kernel/
(2)构建交叉编译环境
搭建交叉编译环境是嵌入式开发的第一步,也是很关键的一步。不同的体系结构、不同的操作内容设置是不同的版本的内核,都会用到不同的交叉编译器。选择交叉编译器非常重要,有些交叉编译器经常会有部分的bug,都会导致最后的代码无法正常运行。建立交叉编译环境的目的在于使用普通的PC机作为宿主机来调试目标开发板。建立ARM的交叉编译环境主要用到的开发工具有: binutils、gcc、glibc。其中binu2tils 是二进制文件的处理工具, 它主要包含了一些辅助开发工具, 例如objdump 显示反汇编码、nm列出符号表、readelf显示elf 文件信息及段信息、strip 将不必要的代码去掉以减少可执行文件大小等。这些工具在嵌入式开发初期, 尤其硬件平台存储器的地址安排是移植调试操作系统时非常有用; gcc 是编译工具, 用来编译内核代码的工具, 使用它可以编译汇编语言和c 语言的程序, 生成ARM的代码; glibc 是链接和运行库, 它的编译需要指定编译器为刚才做好的ARM交叉编译器; 否则编译出的glibc 代码将会是同时有ARM和x86 代码的混和体。所有需要用到的工具可以下载源码自行编译, 然后在宿主机上进行安装, 就可以建立起ARM的交叉编译环境。
4.2 BootLoder引导程序的移植
在嵌入式系统中,BootLoader的作用与PC机上的BIOS类似,通过.BootlLoader可以完成对系统板上的主要部件如CPU,SDRAM,FLASH、串行口等进行初始化,也可以下载文件到系统板上,对FLASH进行擦除与编程。当运行操作系统时,它会在操作系统内核运行之前
运行,通过它,可以分配内存空间的映射,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统准备好正确的环境。因此,正确建立Linux移植的前提条件是具备一个与Linux配套、易于使用的Boot Loader,它能够正确完成硬件系统的初始化和Linux的引导。
系统使用的BootLoader是VIVI。VIVI是CPU加电后运行的第一段程序,其基本功能是初始化硬件设备,建立内存空问的映射图,从而为调用嵌入式Linux内核做好准备。为能够实现正确引导Linux系统的运行,以及当编译完内核后,快速下载内核和文件系统,VIVI首先通过串口下载内核和文件系统,当系统正常运行起来后,网络驱动正常运行后,VIVI就通过网口下载内核和文件系统。同时,它也具有功能较为完善的命令集,对系统的软硬件资源进行合理的配置与管理。
4.3 内核移植
Linux 内核主要由5 个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信,支持X86,ARM 等多种体系结构。要让Linux 可以在不同的硬件平台运行只需修改与体系结构平台相关的代码即可。内核源码一般在/usr/src/Linux-*目录下。内核源码arch 子目录包括了所有和平台体系结构相关的核心代码,它的每一个子目录都代表所支持的一种体系结构。本项目移植内核版本是Linux-2.4.18。内核移植包括以下工作:
(1)根目录/Makefile 文件修改
根目录Makefile 有2 个任务:产生vmlinux 文件和产生内核模
块。Makefile 还将递归进入各个子目录中,调用子目录Makefile。在此处要做的是:
指定目标平台ARCH:=$(shell uname –m |
sed–es/i.86/i386/.....
设置为ARCH:=arm
指定交叉编译器 CROSS_COMPILE=
设置为CROSS_COMPILE=arm-linux-
(2)/arch/arm/linux
启动代码的产生要通过此一文件,由于2.4 内核没有对S3C2410 的支持, 自行加入如下代码:
ifeq($(CONFIIG_ARCH_S3C2410),y),
TEXTADDR = xxx
MACHINE = s3c2410
endif
TEXTADDR 是内核的虚拟起始地址,也是内核最终运行地址,通常设为PAGE_OFFSET+0x8000,须根据实际情况相结合。
(3)/arch/arm/config.in
1) config.in 是配置文件, 决定了我们在配置菜单中看到的内容。自行加入$CONFIG_ARCH_S3C2410 子选项。
If[“$CONFIG_ARCH_S3C2410”=”y”]; then
dep_bool ‘ SMDK (MERI TECH BOARD) ’
CONFIG_S3C2410_SMDK//
$CONFIG_ARCH_S3C2410
//其他需要的选项
fi
2)其他选项
在if [ “ $CONFIG_FOOTBRIDGE_HOST ” = “ y “ –o \ ??//省略号代表其他SOC 的配置
“ $CONIFG_ARCH_SA1100” = “ y “]; then
define_bool CONFIG_ISA y
else
define_bool CONFIG_ISA n
fi
中依照其他SOC 的设置方式加入”$CONFIG_ARCH_s3c2410 “= “y” –o \
(4) /arch/arm/boot/Makefile
ZTEXTADDR 是自解压代码的起始地址,ZRELADDR 是内核解压后最终执行的位置。
ZRELADDR 与TEXTADDR 之间符合如下映射关系:
__virt_to_phys(TEXTADDR) == ZRELADDR
移植后:
Ifeq ($(CONFIG_ARCH_S3C2410), y)
ZTEXTADDR =-xxx
ZRELADDR = xxx
endif
(5) /arch/arm/boot/compressed/Makefile 依照其他加入:
ifeq (“ $(CONFIG_ARCH_S3C2410), y)
OBJS += head – s3c2410.o
endif
(6) /arch/.arm/boot/compressed/head-s3c2410.S 此处需要自己添家加入内核解压前处理器初始化文件head-s3c2410.S,
示例代码:
#include <linux/config.h>
#include <linux/linkage.h>
#include <asm/mach-types.h>
.section “.starr”, #alloc, #execinstr __S3C2410_start:
bic r2 , pc ,# 0x1f @清除pc 相关位,放于r2 add r3, r2, #0x4000 @r3=r2+16k
1: ldr r0, [r2] , #32
teq r2, r3 @比较2 个寄存器内容
bne 1b
mcr p15 , 0 , r0 , c7, c10 , 4 @写回WriteBuffer mcr p15 , 0, r0, c7, c7 , 0 @刷新I& D caches
#if 0 @禁用 MMU , caches
??.
#endif
mov r0 , #0x00200000
1: subs r0, r0 , #1
bne lb @暂停一段时间,等待主机启动终端
http://www.
- 5 -
(7) /arch/arm/kernel/Makefile
依样将$(CONFIG_ARCH_2400)$(CONFIG_ARCH_2410) \加入 no – irq – arch : = $(CONFIG_ARCH_INTEGRATOR) \$(CONFIG_ARCH_CLPS711X) \
??
中。并添加obj-$(CONFIG_MIZI) += event.o
obj-$(CONFIG_ARCH_APM) += apm2.o
(8) /arch/arm/kernel/entry-armv.S
此文件主要定义CPU 初始化时中断处理部分,可参考处理器使用手册,按处理器使用
要求设置。
(9) /arch/arm/kernel/debug-armv.S
此文件用于最基本的串口调试功能, 包括调试串口的地址初始化、发送、等待、忙状态定义等。使用此文件可以在启动过程中打印
出相关信息。
(10) /arch/arm/kernel/setup.c
在此文件中要根据使用的板子设置几个变量。nr_banks 指定了内存块的数量,bank 指定了每块内存块的范围,PAGE_OFFSET 是内存起始地址,MEM_SIZE 是内存的大小。
PAGE_OFFSET,MEM_SIZE 要在/include/asm-arm-arm/arch-s3c2410 中定义。
(11) /arch/arm/mm/mm-armv.c 此文件用于与硬件相关的内存管理,如初始化内存页表内存映射等。
将 init_maps->bufferable=0; 改为init_maps->bufferable=1
(12) /arch/arm/mach-s3c2410
建立相应目录并按照处理器使用要求编写irq.c, mm.c,
time.c, arch.c, Makefile,分别实现中断控制器的初始化,初始化,地址的虚实映射关系,时钟中断和实时时钟处理以及有关Ramdisk 使用参数等的设置。
(13) /include/asm-arm/arch-s3c2410
此目录下定义用到的头文件。至此移植工作基本完成,再进行如下编译过程,即可得到我们需要的映像文件。
make dep; make clean ; make zImage
4.4 PC机上的Linux与嵌入式Linux安装过程的区别:
共同点:使用相同的linux内核
不同点:在内核上加载的应用不同
4.5 设备驱动程序模块
Linux下的设备驱动程序可以按照两种方式进行编译,一种是直接静态编译成内核的一部分,另一种则是编译成可以动态加载的模块。如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态地卸载,不利于调试,所有推荐使用模块方式。 从本质上来讲,模块也是内核的一部分,它不同于普通的应用程序,不能调用位于用户态下的c或者c++库函数,而只能调用Linux内核提供的函数,在/proc/ksyms中可以查看到内核提供的所有函数。 在以模块方式编写驱动程序时,要实现两个必不可少的函数
ini t module()和cleanup module(),而且至少要包含<1 inux/krernel.h>和<linux/module.h>两个头文件。在用gcc编译内核模块时,需要加上一DMODULE—D EERNEL —DLINUX这几个参数,编译生成的模块(一般为.0文件)可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init—module()。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup—module()。任何时候都可以使用命令来ismod查看目前已经加载的模块以及正在使用该模块的用户数。
五、结果与分析
跟平台相关的代码在一级目录的arch之中,在这个目录中的每个 子目录都是一种硬件cpu的代码。现在应用与嵌入式最多的i386,ARM,MIPS这几种cpu。ARM的启动过程都在三级目录boot之中,如果编译压
缩内核又属compressed目录为最关键。
六、总结
在操作系统移植理论和Linux内核移植原理的基础上,通过 对Linux内核体系结构和Linnx源代码妁研究。分析了Linux内核在向新的硬件平台移植过程中需要处理的~些问题。在这些分析和研究的基础上。针对Enet.SHl目标板,对Linux内核移植全过程进行了完整的实践。首先,根据目标机和宿主机系统建立交叉开发环境:然后,根据目标硬件平台对Linux核心进行修改;最后,提供了使用GNU调 试工具GDB对新移植的Linux内核进行调试的方法。掌握这些移植的技术和流程, 对于开发嵌入式系统是十分重要的。
七、参考资料
1. 《ARM应用系统开发详解》,李驹光,清华大学出版社,20xx年;
2. 《嵌入式技术与系统——Intel Xscale 结构与开发》,陈章龙,北航出版社,2004。
3. 《AT91系列ARM核微控制器结构与开发》,马忠梅;
4. 《嵌入式linux应用开发详解》,刘峥嵘,机械工业出版社;
5. 《嵌入式linux设计与应用》,清华大学出版社。
6、《ARM嵌入式系统移植实战开发》韩少云,北京航天航空大学