《单片机小白转嵌入式Linux学习记录,基于S3C2440----目录》
英文参考:ARM Architecture Reference Manual
中文参考:<ARM体系结构与编程> 杜春雷
CPU模式(Mode) 状态(State)与寄存器
7种Mode: P72
usr/sys 用户模式/系统模式
undefined(und) 无定义指令模式,CPU遇到不认识的指令时会进入该模式
Supervisor(svc) 管理模式
Abort(abt) 终止模式 1. 指令预取终止 流水线预取出错 2. 数据访问终止 数据读取过程中产生的异常。
IRQ(irq) 中断模式
FIQ(fiq) 快速中断模式 有多个专用寄存器,所以中断速度比较快
2种状态:
ARM state 4字节指令
Thumb state 2字节指令 用于压缩程序代码 对小容量芯片非常有用,S3C2440芯片一般不用
寄存器:
通用寄存器
分组寄存器(banked register) 特权模式的专用寄存器 P73
当前程序状态寄存器 CPSR : (Current Program State Register) 属于通用寄存器
CPSR的备份寄存器 SPSR: (Saved Program State Register) 特权模式专用寄存器,用于保存被中断模式的CPSR 程序状态
除了 usr 其它模式均为特权模式(privileged mode)。特权模式之间可以通过修改CPSR 低5位来切换。普通模式无法修改CPSR进入特权模式。 CPSR 详见 P76
-------------------------------------------------------------------
中断属于异常的一种。异常对应不同的CPU工作模式,不同的工作模式,有不同的专有寄存器 lr、sp、spsr。
lr 保存被中断模式中的下一条即将执行的指令的地址 进入异常之前硬件自动保存
sp 当前异常模式专用的栈指针, 进入异常后先设置栈指针
spsr 备份被中断状态的程序状态 进入异常之前硬件自动保存
CPU每执行完一条指令都会去检查是否有异常产生,如果有异常,就开始去处理异常。
对于不同的异常,跳去不同的地址去执行。这整个过程是有硬件决定的,这个跳转的
地址也是固定的。不同的异常跳转地址组成了异常向量表,如下:
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
如果是中断异常,则硬件会自动跳到 0x18(ldr pc, _irq),跳转到0x18位置是由硬件决定的,0x18这个地址放什么指令是由软件决定。
我们一般在这个位置放置一条跳转命令,跳转到更复杂的程序(中断处理程序)
我们要在中断处理程序中做什么呢?
1. 保存现场 (保存寄存器数据)
2. 调用处理函数
3. 恢复现场 (恢复寄存器数据)
2 中调用的处理函数,判断中断类型,根据不同的中断类型调用不同中断理函数。
进入异常硬件动作: P79
1. 保存被中断模式的 下一条指令地址到 对应异常模式的LR专用控制器
2. 保存被中断模式的CPSR程序状态寄存器到 对应异常模式的 SPSR (状态备份 )
3. 修改CPSR中的低5位 切换到对应的异常模式。
4. 修改PC指针指向异常向量表对应的异常入口
退出异常软件操作:
1. 如果是中断产生的异常,清除中断源
2. 恢复程序状态寄存器 CPSR = SPSR(对应异常模式的程序状态备份寄存器)
3. 修改PC指针 = _LR - offset(4 or 8) 参考 P80 table 2-2
und 未定义指令异常
当CPU发现未定义指令时,会强制跳转到 0x04 地址,同时会将异常前的下一条指令地址保存在 lr_und CPSR 保存在 SPSR_und保存有被中断模式的CPSR
所以我们保存现场时要保存 lr_und 到栈中,保存现场之前要设置异常模式的栈地址。
恢复现场的时候将lr恢复到pc 将 SPSR_und恢复到CPSR
我们对start.S文件进行修改
----------------------------
_start:
b reset /* vector 0 : reset */
b do_und /* vector 4 : und */
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
.align 4 /* 这里要加上4k 对齐 否则上面und_string数据段的长度会影响程序的正确执行 */
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
略……
----------------------------
上面代码并不完善,我们要进行一些细节修改:
1. b do_und 函数中的 bl printException 函数有可能超出4K 范围,所以我们需要进行强制跳转到runtime addr 地址(SDRAM执行)
2. 经过反汇编代码分析知道,ldr pc,=do_und 这条指令中是将 do_und的入口地址放置在.data数据段中,数据段默认是放置在start.S .text代码段结尾,如果代码长度超过4K 并且代码是在NAND上时,ldr pc,=do_und将无法获取到do_und的入口,所以我们需要人为将入口地址放置在前面,保证程序能正确获取到入口地址。
3. 数据段 后面的代码要保持 4字节对齐,如:und_string: 中的数据长度如果有变化,会导致代码运行出错。
修改后的 start.S
----------------------------
.text
.global _start
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
und_addr:
.word do_und
do_und:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x4的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 保存现场 */
/* 处理und异常 */
mrs r0, cpsr
ldr r1, =und_string
bl printException
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
und_string:
.string "undefined instruction exception"
.align 4
reset:
/* 关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 设置CPU工作于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8=100
* p = PDIV+2 = 1+2 = 3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12)|(1<<4)|(1<<0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 设置内存: sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址, 再读出来
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
* 否则就是nor启动
*/
mov r1, #0
ldr r0, [r1] /* 读出原来的值备份 */
str r1, [r1] /* 0->[0] */
ldr r2, [r1] /* r2=[0] */
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
moveq sp, #4096 /* nand启动 */
streq r0, [r1] /* 恢复原来的值 */
bl sdram_init
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
/* 重定位text, rodata, data段整个程序 */
bl copy2sdram
/* 清除BSS段 */
bl clean_bss
ldr pc, =sdram
sdram:
bl uart0_init
/* 故意加入一条未定义指令 */
und_code:
.word 0xeeadc0de
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
halt:
b halt
----------------------------
SVC异常 (SWI软中断)
一般情况下APP应该运行于usr mode 。usr受限不可访问硬件。APP想要访问硬件,必须切换mode ,通过异常(未定义异常、中断异常可遇不可求),所以我们APP需要调用swi #val 进入svc异常。然后系统根据不同的异常标号,执行相应的操作。
怎么获取异常标号。
进入异常时 lr_svc 保存了被中断模式的下一条指令地址 ,通过lr_svc - 4 即可获取上一条指令的地址:SWI #val ,然后读取指令并对指令进行解析,即可得到VAL值 参考 P86
注释:C函数调用时会用到 r4 - r11 所以在函数调用时会被保存,函数返回时恢复,所以我们可以用r4来获取lr的值 参考第8章内容 ATPCS 寄存器使用规则
--------------------------
start.S代码
.text
.global _start
_start:
/* 中断向量表 对应工作模式 参考 P82 */
b reset /* vector 0x00 : reset svc 复位 管理模式*/
ldr pc, _undefined_instruction /* vector 0x04 : und 无定义指令模式 */
ldr pc, _software_interrupt /* vector 0x08 : swi svc 软中断 管理模式 */
b reset /* vector 0x0C : abt(prefetch) 终止模式 预取终止 */
b reset /* vector 0x10 : abt(data) 终止模式 数据终止 */
b reset /* vector 0x14 : Reserved 保留 */
b reset /* vector 0x18 : IRQ 中断模式 */
b reset /* vector 0x1C : FIQ 快速中断模式 */
/* 将函数的入口放置在前面,如果不定义默认放置在.text 段的
* 末尾 ,start.S 汇编后超过4k 在NAND启动时CPU有可能无法
* 读取到入口
*/
_undefined_instruction:
.word undefined_instruction /* 保存入口地址。运行时地址,会跳转到SDRAM执行 */
_software_interrupt:
.word software_interrupt
undefined_instruction:
/* 执行到这里之前:
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_und保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
* 4. 跳到0x04的地方执行程序
*/
/* sp_und未设置, 先设置它 */
ldr sp, =0x34000000
/* 保存现场 */
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 处理und异常 */
mrs r0, cpsr // 获取程序状态寄存器 传递给 printState
bl printState
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
software_interrupt:
/* 执行到这里之前:
* 1. lr_swi保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_swi保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10011, 进入到swi模式
* 4. 跳到0x08的地方执行程序
*/
/* sp_swi未设置, 先设置它 */
ldr sp, =0x33f00000
/* 保存现场 */
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr是异常处理完后的返回地址, 也要保存 */
stmdb sp!, {r0-r12, lr}
/* 处理swi异常 */
/* C函数调用时会用到 r4 - r11 所以在函数调用时会被保存,函
* 数返回时恢复,所以我们可以用r4来获取lr的值 参考第8章内
* 容 ATPCS 寄存器使用规则
*/
mov r4, lr // 将lr 返回地址 保存到 r4 sub r0, r4, #4 获取触发SWI异常的指令 ,根据指令获取传递进来的软中断号
mrs r0, cpsr // 获取程序状态寄存器 传递给 printState
bl printState
/* 获取软中断号 */
sub r0, r4, #4
bl getSWIVal
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
/* 上面打印字符串长度有可能不是4字节对齐,所以这里要强
* 制4字节对齐,否则程序可能会出错
*/
.align 4
reset:
/* 关闭看门狗 */
ldr r0,=0x53000000
ldr r1,=0
str r1,[r0]
/* PLL配置---------------------------- */
/* 设置MPLL,FCLK:HCLK:PCLK=400M:100M:50M */
/* 设置lock time 详见手册P255
* LCOKTIME(0x4c000000)=0xFFFFFFFF
*/
ldr r0,=0x4c000000
ldr r1,=0xFFFFFFFF
str r1,[r0]
/* 设置HCLK、PCLK 相对于FCLK的分频系数 详见手册P260
* CLKDIVN(0x4c000014)=0x05 tFCLK:tHCLK:tPCLK=1:4:8
*/
ldr r0,=0x4c000014
ldr r1,=0x05
str r1,[r0]
/* 设置CPU工作于异步模式
* 详见手册P244
*/
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
/* 配置MPLLCON 来设置FCLK频率 详见手册P255~256
* 设置MPLLCON(0x4c000004)=(92<<12)|(1<<4)|(1<<0)
* m = MDIV+8 = 92+8 =100
* p = PDIV+2 =1+2 =3
* s = SDIV = 1
* FCLK = 2*m*Fin/(p*2^s)=2*100*12/(3*2^1)=400MHz
*/
ldr r0,=0x4c000004
ldr r1,=(92<<12)|(1<<4)|(1<<0)
str r1,[r0]
/* 一旦设置PLL,就会锁定 lock time 直到PLL输出稳定 */
/* PLL配置--END-------------------------- */
/* 设置内存:sp 栈 */
/* 分辨是nor/nand启动
* 写0到0地址,再读出来;如果读出来是0,表示
* 操作是内部RAM,是nand启动;否则就是nor启动
* 因为nor flash 不向内存那样能直接读写
*/
mov r1,#0
ldr r0,[r1] /* 备份0地址数据 */
str r1,[r1] /* 将0写入0地址 */
ldr r2,[r1] /* 读取0地址数据到r2 */
cmp r1,r2 /* 判断r1 r2是否相等 */
ldr sp,=0x40000000+4096 /* 假设nor启动 */
moveq sp,#4096 /* 如果r1==r2 nand启动 */
streq r0,[r1] /* 恢复原来的值 */
/* 整个程序重定位 */
bl sdram_init /* 初始化SDRAM 否则下面无法搬运数据 */
bl copy2sdram /* .text .rodata .data 段数据拷贝 */
bl clean_bss /* .bss 段清除 */
/* 复位之后, cpu处于svc模式
* 现在, 切换到usr模式
*/
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
msr cpsr, r0
/* 设置 sp_usr */
ldr sp, =0x33e00000
bl uart0_init /* 故障代码中调用了串口打印,所以先初始化串口 */
und_code:
.word 0xeeadc0de /* 未定义指令 P86 指令集格式 原代码 0xdeadc0de 有误 参考:http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=20299&highlight=und%D2%EC%B3%A3 */
/* 获取当前状态的CPSR */
mrs r0, cpsr // 获取程序状态寄存器 传递给 printState
bl printState
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
.align 4
/* 调用main函数 */
ldr pc, =main /* 绝对跳转, 跳到SDRAM 运行 */
/* 死循环 */
halt:
b halt
相关 C 函数代码
/* 当前状态打印 P78 */
void printState(unsigned int cpsr)
/* 获取SWI 异常参数 */
void getSWIVal(unsigned int *pSWI)
串口打印相关的函数请参考我前面的文章
/* 当前状态打印 P78 */
void printState(unsigned int cpsr)
{
puts("cpsr = ");
printHex(cpsr);
puts(" ");
switch(cpsr&0x1F) // M[4:0]
{
case 0x10: puts(" User State !\n\r"); break;
case 0x11: puts(" FIQ State !\n\r"); break;
case 0x12: puts(" IRQ State !\n\r"); break;
case 0x13: puts(" Supervisor State !\n\r"); break;
case 0x17: puts(" Abort State !\n\r"); break;
case 0x1B: puts(" Undefined State !\n\r"); break;
case 0x1F: puts(" System State !\n\r"); break;
default: puts(" error State !\n\r");break;
}
}
/* 获取SWI 异常参数 */
void getSWIVal(unsigned int *pSWI)
{
puts("SWI val = ");
printHex(*pSWI & ~0xff000000);
puts("\n\r");
}
-----------------------------------------------------------------
# gcc Thumb 指令集 编译 不重要可以跳过
由于Thumb 是压缩的指令集,所以代码体积比较小
.c 代码编译
修改makeflie 添加编译选项 -mthumb
------------
%.o : %.c
arm-linux-gcc -mthumb -c -o $@ $<
-----------
.s 代码编译
.cdoe 32 表示使用arm 指令集编译
-----------
.text
.global _start
.code 32 //使用ARM
_start:
……
-----------
.code 16 表示使用thumb 指令集编译
-----------
……
.code 16
thumb_func:
bl sdram_init
……
-----------
怎么从 arm state 切换到 thumb state 状态运行
用bx 跳转 如果跳转地址的bit0 =1 则切换到 thumb state 运行
-----------
……
adr r0,thumb_fun
add r0,r0,#1
bx r0
……
-----------
thumb 编译事项
1. 不能用 ldr pc,=main 跳转到main函数,需要:
ldr r0,=main
mov pc,r0
2. cpy2mem 数组 前添加 const static 修饰 防止编译出错。
-----------------------------------------
最近编辑记录 xinxiaoci (2018-05-16 14:37:35)
离线