嵌入式
概论
- 世界上第一台现代电子计算机“埃尼阿克”(ENIAC),诞生于1946年2月14日的美国宾夕法尼亚大学
- 嵌入式系统: 以应用为中心,以计算机技术为基础,软件硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、重量、功耗严格要求的专用计算机系统. 简而言之,是含有处理器的专用软硬件系统, 具有自主的信息处理能力.
- 嵌入式系统是以嵌入式应用为目的的计算机系统,可分为系统级、模块级、芯片级
- 系统级:各种类型的工控机
- 模块级:各种类型的带CPU的模块
- 芯片级:各种以微控制器、DSP、嵌入式微处理器为核心的产品
- 嵌入式系统的体系结构
- 特点
- 嵌入专用: 嵌入式处理器与通用处理器的最大不同就是嵌入式处理器嵌入在针对特定应用设计的系统中
- 综合性强: 嵌入式系统是将计算机技术、半导体技术和电子技术与各个行业的具体应用相结合后的产物
- 设计高效: 可裁剪——嵌入式系统的硬件和软件都必须高效率地设计,量体裁衣、去除冗余
- 程序固化: 软件一般都固化在存储器芯片或处理器本身中,这样提高了系统的执行速度和可靠性
- 需要独立的开发系统: 嵌入式系统本身不具备自举开发能力,必须有一套开发工具和环境才能进行开发
- 生命周期长: 嵌入式系统和具体应用结合在一起,其升级换代一般与具体产品同步进行,因此具有较长的生命周期
- 可靠性高、成本低、资源受限、功耗低等等
ARM体系
ARM简介
- 特点
- 体积小、低功耗、低成本、高性能。
- 支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8/16位器件。
- 大量使用寄存器,指令执行速度更快。
- 大多数数据操作都在寄存器中完成。
- 寻址方式灵活简单,执行效率高。
- 指令长度固定。
- 多核
- 进行多任务处理时,能够很好的提升产品的性能或者提升多线程应用程序的使用。
- 应该可以增加电池使用寿命,延长续航时间
- 从单核处理器升级到双核处理器所带来性能提升为50%,而将双核处理器升级为四核处理器,所带来的提升仅为25%。
- 体系架构分类
- 复杂指令集和精简指令集:
- 复杂指令集(Complex Instruction Set Computer,CISC), CISC架构采用庞大的指令集,可以减少编程所需要的代码行数,减轻程式师的负担,
- 精简指令集(Reduced Instruction Set Computer,RISC),RISC采用精简指令集,包含了简单、基本的指令,透过这些简单、基本的指令,就可以组合成复杂指令
- 二者各有优缺点。ARM系列的芯片全部基于RISC技术。
- 普林斯顿结构和哈佛结构:
- 微处理器根据存储器结构可以分为哈佛(Harvard)结构和普林斯顿(Princeton)结构。ARM内核中ARM7系列基于普林斯顿结构,ARM9系列之后基本都为哈佛结构
- 普林斯顿结构也称冯·诺伊曼结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此,程序指令和数据的宽度相同。
- 哈佛结构是一种将程序指令存储和数据存储分开的存储器结构。中央处理器首先到程序指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据,并进行下一步的操作(通常是执行)。程序指令存储和数据存储分开,可以使指令和数据有不同的数据宽度。
- 流水线技术
略
ARM处理器的运行
工作模式
- 用户模式(usr):ARM处理器正常的程序执行状态。
- 系统模式(sys):运行具有特权的操作系统任务。
- 快速中断模式(fiq):用于高速数据传输或通道处理。
- 外部中断模式(irq):用于通用的中断处理。
- 管理模式(svc):操作系统使用的保护模式。
- 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
- 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。
寄存器
- 寄存器R8~R12有两个分组的物理寄存器。一个用于除FIQ模式之外的所有寄存器模式,另一个用于FIQ模式。这样在发生FIQ中断后,可以加速FIQ的处理速度
- 寄存器R13、R14分别有6个分组的物理寄存器。一个用于用户和系统模式,其余5个分别用于5种异常模式。
- R13常作为堆栈指针
- R14为链接寄存器: 在每种模式下,模式自身的R14版本用于保存子程序返回地址;当发生异常时,将R14对应的异常模式版本设置为异常返回地址(有些异常有一个小的固定偏移量)
- 寄存器R15为程序计数器(PC),它指向正在取指的地址。
- 寄存器R16(CPSR)用作当前程序状态寄存器, 它包括条件标志位、中断禁止位、当前处理器模式标志位,以及其他一些相关的控制和状态位。
程序状态寄存器
- 条件码标识
- 模式位M
- 控制位
- I、F为中断禁止位,I=1禁止IRQ中断,F=1禁止FIQ中断
- T标志位反映处理器的运行状态, 对于ARM体系结构v5及以上的版本的T系列处理器,当该位为1时,程序运行于Thumb状态,否则运行于ARM状态
异常: 当一个异常出现以后,ARM微处理器会执行以下几步操作
- 将下一条指令的地址存入相应链接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行
- 将CPSR复制到相应的SPSR中
- 根据异常类型,强制设置CPSR的运行模式位
- 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。还可以设置中断禁止位,以禁止中断发生。如果异常发生时,处理器处于Thumb状态,则当异常向量地址加载入PC时,处理器自动切换到ARM状态
- 异常处理完毕之后,ARM微处理器会执行以下几步操作从异常返回: 将连接寄存器LR的值减去相应的偏移量后送到PC中, 将SPSR复制回CPSR中, 若在进入异常处理时设置了中断禁止位,要在此清除
异常优先级
储存系统
- 地址空间: ARM体系结构将存储器看做是从零地址开始的字节的线性组合。作为32位的微处理器,ARM体系结构所支持的最大寻址空间为4GB。当程序正常执行时,每执行一条ARM指令,当前指令计数器加4个字节;每执行一条Thumb指令,当前指令计数器加2个字节。
- 存储器格式: ARM体系结构可以用两种方法存储字数据,称之为大端格式和小端格式。在小端存储格式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节。
指令系统
- 指令格式:
{cond} {S} , {,operand2}
- 其中<>号内的项是必须的,{}号内的项是可选的
- opcode:指令助记符
- cond:执行条件;
- S:是否影响CPSR寄存器的值
- Rd:目标寄存器
- Rn:第1个操作数的寄存器
- operand2:第2个操作数
- 寻址方式
- 立即寻址:
ADD R0,R0,#1
立即数的表示以“#”为前缀;十六进制的立即数在“#”后面加“&”符号;以二进制表示的立即数,要求在“#”后加上“%”。 - 寄存器寻址:
ADD R0,R1,R2
操作数本身存放在寄存器 - 寄存器间接寻址:
LDR R0,[R1]
以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中 - 基址变址寻址:
LDR R0,[R1,#4]
R0←[R1+4]LDR R0,[R1] ,#4
R0←[R1]; R1←R1+4LDR R0,[R1,#4]!
R0←[R1+4] ; R1←R1+4LDR R0,[R1,R2]
R0←[R1+R2]
- 多寄存器寻址: 多寄存器寻址是指一次可以传送多个寄存器的值,允许一条指令可以传送16个寄存器的任何子集, 例如
LDMIA R0,{R1,R2,R3,R4}
- 寄存器移位寻址
- 相对寻址: 相对寻址以程序计数器PC的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址
- 堆栈寻址
- 常用指令
加载和存储:
LDR R0, [R1]
: 将存储器地址为R1 的字数据读入寄存器R0STR R0, [R1], <a href="/tag/8
:” class=“tag” target=“_blank”>#8`: 将R0 中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1
分支指令:
B Label
: 程序无条件跳转到标号Label 处执行BL Label
: 程序无条件跳转到标号Label 处执行,同时将当前的PC 值保存到R14中- BLX: BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。
- BX: BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
数据处理:
MOV R1, R0
: 将寄存器R0的值传送到寄存器R1MVN R0, <a href="/tag/0
:” class=“tag” target=“_blank”>#0`: 将立即数0 取反传送到寄存器R0中CMP R1, R0
: 将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR 的标志位TST R1, R0
: TST指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值TEQ R1, R0
: TEQ指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR 中条件标志位的值。
运算:
ADD R1,R1,R2, LSL <a href="/tag/2
:” class=“tag” target=“_blank”>#2`: R1=R1+R2<
ADC R1, R2, R3
: ADC指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存放到目的寄存器中SUB R0, R1, R2
: R0 = R1 - R2SUB R1, R2, R3
: SBC指令用于把操作数1减去操作数2,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中AND R0, R1, R2
: AND指令用于在两个操作数上进行逻辑与运算,并把结果放置到目的寄存器中ORR R0, R1, R2
: ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中EOR R0, R1, R2
: EOR指令用于在两个操作数上进行逻辑异或运算,并把结果放置到目的寄存器中MUL R0, R1, R2
: R0 = R1×R2MLA R0, R1, R2, R3
: R0 = R1 × R2 + R3BIC R0, R0, #%1011
: 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变
状态寄存器访问:
MSR CPSR, R0
: 传送R0的内容到CPSR
ARM程序设计
伪操作
.section
伪操作: 自定义一个段.text
.data
.bss
: 将汇编系统预定义的段名编译到相应的代码段、数据段和bss段.end
: 表明源文件的结束.include
: 将指定的文件在使用位置处展开.incbin
: 将原封不动的一个二进制文件编译到当前文件中.if
.else
.endif
: 条件表达式
.if 条件表达式
代码段1
.else
代码段2
.endif
.macro
: 定义宏.endm
: 宏结束.string "abcd","hello"
定义字符串.set
: 为程序中标号定义名称.global
.extern
.ltorg
和.pool
用于声明一个数据缓冲池的开始,它可以分配很大的空间
伪指令
LDR r1,=0xff
将0xff读取到r1中
循环
LOOP:
ADD R0,R0,R1 @ R0=R0+R1
CMP R0,#3 @ 比较R0和#3
BLS LOOP @if R0<3 then 跳转到LOOP循环
子程序
BL 子程序名
该指令在执行时完成如下操作:将子程序的返回地址存放在连接寄存器LR中,同时将程序计数器PC指向子程序的入口点,当子程序执行完毕需要返回调用处时,只需要将存放在LR中的返回地址重新复制给程序计数器PC即可
寄存器的使用
- 子程序通过寄存器R0~R3来传递参数,这时寄存器可以记作A0~A3,被调用的子程序在返回前无须恢复寄存器R0~R3的内容。
- 在子程序中,使用R4~R11来保存局部变量,这时寄存器R4~R11可以记作V1~V8。如果在子程序中使用到V1~V8的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则不必执行这些操作。在Thumb程序中,通常只能使用寄存器R4~R7来保存局部变量。
- 寄存器R12用作子程序间临时过渡寄存器,记作IP,在子程序的连接代码段中经常会有这种使用规则。
- 寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用作其他用途。寄存器SP在进入子程序时的值和退出子程序时的值必须相等
- 寄存器R14用作连接寄存器,记作LR。它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其他的用途。
- 寄存器R15是程序计数器,记作PC,它不能用作其他用途。
C语言与汇编相互调用
- C语言, 使用
extern
声明汇编函数 - 汇编, 使用
.extern
声明c函数
裸机开发接口
GPIO
代码示例:
<a href="/tag/define" class="tag" target="_blank">#define</a> GPBCON (*(volatile unsigned int *) 0xE0200040)
<a href="/tag/define" class="tag" target="_blank">#define</a> GPBDAT (*(volatile unsigned int *) 0xE0200044)
void delay()
{
int i = 0x100000;
while (i--);
}
int main(void)
{
int i = 0;
GPBCON &= ~0xfff0000;
GPBCON |= 0x1110000;
while (1)
{
GPC0DAT &= ~(7 << 5);
GPC0DAT |= 1 << (i + 5);
i++;
if (i == 4)
i = 0;
delay();
}
return 0;
}
Linux系统
嵌入式交叉编译环境
略
Bootloader
Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。 对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。
U-Boot: 通用引导程序
设备驱动编程
应用程序调用库函数, 库函数通过系统调用执行软件陷阱指令进入内核, 内核中通过系统调用的异常处理与驱动程序交互, 驱动程序调用硬件设备
代码示例
<a href="/tag/include" class="tag" target="_blank">#include</a> <unistd.h>
<a href="/tag/include" class="tag" target="_blank">#include</a> <stdio.h>
<a href="/tag/include" class="tag" target="_blank">#include</a> <fcntl.h>
static int fd = -1;
int main()
{
int i=0;
fd=open("/dev/gpioled ",O_RDWR);
if(fd<0){
printf("Can't open\n");
return -1;
} else {
printf("open OK 11%x\n", fd);
}
while(1) {
for(i=0;i<8;i++) {
ioctl(fd, LED_ON, 1<<i);
sleep(1);
}
}
close(fd);
return 0;
}