ARM基础
ARM认识
FLASH相当于电脑的硬盘,内存相当于电脑的运行内存,控制器搬移这些运算指令,(ALU)运算机进行解析,寄存器用于临时存放用于运算的数据
ARM开发环境
- 安装交叉编译工具链 我们用的交叉编译工具链是arm-2011.09-70-arm-none-linux-gnueabi.exe
在该可执行文件上右键->属性 -> 兼容性 选择兼容window 7
确定后以管理员身份运行即可,可能需要很长时间。
注意:win8运行会报错。 - 安装keil(MDK) 直接运行ARM环境\ARM-tools\mdk454_mcu123\MDK454
安装路径选择默认,一路next,会花费很长时间。 - 创建一个project
3.1 新建一个文件夹(keil_proc/test),用于存放工程。
3.2 新建一个工程,Project -> New…, 然后选择型号,arm9(小端)
3.3 在工程里面添加或者new一个文件(start.s),在工程管理区的Source Group右键, 选择add group … 选择自己 新建的文件,或者是其他文件添加到工程里面.
3.4. 可以通过Target -> Manage components去修改工程和groups的名字,或者批量添加文件 - 关联arm-none-linux-gnueabi工具链 Project -> Manage-> Components … -> Folders/Extensions 做如下操作
- 导入链接脚本 Project -> Options for Target … -> Linker
设置Linker Scipt file为工程目录下的map.lds(需要先将ARM环境\ARM-tools\里面的map.lds放到新建的工程里面即 keil_proc/test里面) - 写代码
.text .
global _start
_start:
movr1, #0xff
stop:
b stop
.end - 编译和调试
分别为编译单个文件和多个文件
调试ARM的工作模式及寄存器
ARM工作模式
- ARM主要有7个基本工作模式
User : 非特权模式,大部分任务执行在这种模式
FIQ : 当一个高优先级(fast) 中断产生时将会进入这种模式
IRQ : 当一个低优先级(normal) 中断产生时将会进入这种模式
Supervisor :当复位或软中断指令执行时将会进入这种模式
Abort : 当存取异常时将会进入这种模式
Undef : 当执行未定义指令时会进入这种模式
System : 使用和User模式相同寄存器集的特权模式 - ARM有37个寄存器(存放数据与指令)
1个用作PC( Program Counter)
1个用作CPSR(Current Program Status Register)
5个用作SPSR(Saved Program Status Registers)
30 个通用寄存
搬移指令 - mov r1,#3
- mov r2,r1
CPRS寄存器
条件位:
N = Negative result from ALU
Z = Zero result from ALU
C = ALU operation Carried out or borrow
V = ALU operation oVerflowed
T 位 J 位
T = 0;J=0 处理器处于 ARM 状态
T = 1;J=0 处理器处于 Thumb 状态
中断禁止位:
I = 1: 禁止 IRQ.
F = 1: 禁止 FIQ
Mode位:处理器模式位
10000 User mode 10011 SVC mode;
10010 IRQ 10001 FIQ mode;
10111 Abort mode 11011 Undfined mode 11111 System mode;
CPSR / SPSR操作指令
mrs r0,CPSR
msr CPSR,r0
ARM指令
搬移指令
- mov r13,#3 //将立即数3赋值给寄存器r13
- mov r0,r1//将r1中的数赋值给r0
- mov r0,r1,LSL#2 //将r1中的数逻辑左移2两位到r0
- mov r0,r1,LSR#2
- mrs r0,cpsr //将cpsr读到寄存器r0中
- msr cpsr,r0//将寄存器r0的值读出到cpsr
条件执行
@if (a==0) x=0;//@表示汇编语言的注释
@if (a>0) x=x+3;
mov r0,#0//赋值
cmp r0,#0//比较指令
moveq r1,#0//对前面一次结果进行判断,如果相等,将立即数赋值给r1
addgt r1,r1,#3//对前面一次结果进行判断,如果大于0,将r1加3赋值给r1
逻辑指令 - and r0,r1,#0xFF // r0 = r1&0xFF
- orr r3,r0,#0x0F // r3 = r0|0x0F
- bic r0,r0,#0x03 // 清除r0中的0号位和1号位
- tst r0,#0x20 //测试第6位是否为0 ,为0则Z标志置1
- cmp r1,r0 //将R1与R0相减做比较,并根据 结果设置CPSR的标志位
算术指令 - add r0,r1,r2 //r0=r1+r2
- sub r0,r1,#3 //r0= r1 - 3
- sub r0,r1,r2,LSL#1 //r2左移一位ie,r1-r2,然后赋值给r0
- mul r1,r2,r3 //r1=r2*r3
跳转指令 - b main //跳转到标号为main地代码处
- bl func //保存下一条要执行的指令的位置到 LR寄存器,跳转函数func
//当跳转代码结束后,用MOV PC,LR指令跳回来 - beq addr //当CPSR寄存器中的Z条件码置位时,跳转到该地址处(相等)
- bne addr //当不等时,跳转到地址addr(不相等)
@ 实现 延时1秒函数
@delay fos 1 second
.text
main:
bl delay1s
main end:
b main_end
delay1s:
ldr r4,=0x3FFFF
loop_delay1s:
sub r4,r4,#1
cmp r4,#0
bne loop_delay1s
delay1s_end:
mov pc,lr
.end
@用汇编实现求最大公约数?(如9 15 值是3)
int GCD(int a,int b)
{
while(1)
{
if(a==b)
break;
if(a>b){
a=a-b;
}else{
b=b-a;
}
}
return a;
}
Load/Store 指令
注:load/store架构规定,存储器之间不能直接拷贝(FLASH不能直接到内存单元中),需通过寄存器做中转
ldr r0,[r1] (load) //r0=*r1 r1里存放的是地址,把该地址里存放的内容读入到r0中
//LDRB(byte) LDRH(half word)
ldr r0,[r1,#8] //r0=*(r1+8) 存储器地址为r1+8的字数据读入寄存器0。
ldr pc,_irq // pc = *(_irq) 将标号中的内容放入pc中
str r0,[r1] (store) // *r1 = r0 将寄存器r0中值写入到存储器地址为r1的空间中
str r0,[r1],#4 // r0=*r1, r1=r1+4 将r0 中的字数据写入以r1为地址的内存中,并将新地址r1+4 写入r1
str r0,[r1,#4] //*(r1+4)=r0 将r0 中的字数据写入以r1+4 为地址的内存中
Pre or Post Indexed(前或后索引) 寻址
@拷贝srcBuf里内容 到destBuf中
.text(代码段,数据只能读)
ldr r0,=srcBuf //将FLASH数据读入到寄存器中
ldrb r1,[r0]
ldr r0,=destBuf //将寄存器中数据存入到内存单元中
strb r1,[r0]
srcBuf:
.byte 0x01,02,0x03,0x04
.data(数据段,数据可读可写)
destBuf:
.space 8
.end
/*用汇编实现下面功能
main()
{
int i=0;
const char buf[]={1,2,3};
char destBuf[8];
for(i=0,i<3,i++)
{
destBuf[i] = buf[i];
}
}
*/
.text(代码段,数据只能读)
main:
mov r5,#0
ldr r7=Buf //将FLASH数据读入到寄存器中
ldr r8,=dest_Buf //将寄存器中数据存入到内存单元中
loop:
cmp r5,#3
beq main_end
ldrb r0,[r7],#1
strb r0,[r8],#1
b loop
main_end:
b mian_end
Buf:
.byte 1,2,3
.data(数据段,数据可读可写)
dest_Buf:
.space 8
.end
GUN汇编伪指令
.text 将定义符开始的代码编译到代码段
.data 将定义符开始的代码编译到数据段
.end 文件结束
.equ GPG3CON, 0XE03001C0 定义宏(即用GPG3CON代替 0XE03001C0)
.byte 定义变量 1字节
.byte 0x11,‘a’,0 定义字节数组
.word 定义word变量 (4字节 32位机)
.word 0x12344578,0x33445566
.string 定义字符串 .string “abcd\0”
ldr r0,=0xE0028008 载入大常数0xE0028008 到r0中
.global _start 声明_start 为全局符号
@拷贝ROM中字符串到RAM中
.text
start:
ldr r5,=srcBuf
ldr r6,=destBuf
loop:
ldrb r4,[r5]
cmp r4,#0
beq main_end
ldrb r0,[r5],#1
strb r0,[r6],#1
b loop
main_end:
b main_end
srcBuf:
.string "abcdefg\0"
.data
destBuf:
.space 8
.end
批量操作
批量操作指令 (ia-Increment After ib-Increment Before da-Dec After db-Dec Before)
ldmia r0!, {r3 - r10} //r0里地址指向的内容批量,load 到r3~r10寄存器中, r0里地址会自动加4
stmia r0!, {r3 - r10} //把r3~r10寄存器中内容,store 到r0里地址执行空间中,r0里地址会自动加4
@例:实现块数据批量拷贝
@r12指向源数据起始地址
@r14指向源数据尾地址
@r13指向目的数据起始地址
.text
ldr r12,=srcBuf
ldr r13,=dstBuf
ldmia r12!,{r0 - r11}
stmia r13!,{r0 - r11}
.data
srcBuf:
.string "abdfasdf13535dfksjdlfkjlksldkjflkl\0"
srcBuf_end:
dstBuf:
.space 12*4
.end
堆栈操作指令
- stmfd sp!,{r0-r12,lr}
将寄存器r0~r12 lr中的值存入栈中
常用于中断保护现场,! 表示会自动偏移(栈空间地址会进行减法,如果存入一个寄存器,则栈地址减去4个字节) - ldmfd sp!,{r0-r12,pc}^
将栈中值逐个弹出到寄存器r0~r12 pc中
常用于中恢复断现场,^表示会恢复spsr到cpsr
ARM指令高级 - 软中断指令
swi 0x02 产生软中断, 软中断号为2
ARM–异常处理
认识异常
异常处理过程
-
异常与工作模式关系:
复位,软中断 -->SVC
Prefetch(预取),Data Abort -->Abort
其他中断对应对应名字的模式user system 模式不是异常触发切换的,是程序员修改CPSR,实现切换的
异常是随机的
软中断处理程序
@ 在用户模式下触发6号软中断,在6号软中断中修改寄存器的值,最后返回到用户模式
.text
.global _start
_start:
b reset
ldr pc, _undefined_instruction // pc = *(_undefined_instruction) 将标号中的内容放入pc中,相对于 b指令,突破了32M空间的限制。
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undefined_instruction:
.long _undefined_instruction //定义long变量
_software_interrupt:
.long software_interrupt
_prefetch_abort:
.long _prefetch_abort
_data_abort:
.long _data_abort
_not_used:
.long _not_used
_irq:
.long _irq
_fiq:
.long _fiq
reset:
/*初始化栈指针*/
ldr sp, = stack_base
/*切换为用户模式*/
mrs r0, cpsr
bic r0, #0x1f
orr r0, #0x10
msr cpsr, r0
swi 0x06 @触发软中断异常
@保存返回地址到lr(返回地址为下一条指令,该指令地址等于lr-4)
@切换到SVC工作模式
nop
nop
stop:
b stop
do_swi:
mov r3, #3
mov r4, #4
mov pc, lr
software_interrupt:
@如果是IRQ或者FIQ: sub lr ,#4
stmfd sp!, {r0-r12,lr} @保护现场
sub lr, #4 @取出软中断号,并且比较软中断号
ldr r0, [lr]
bic r0, #0xff000000
cmp r0 ,#0x6
bleq do_swi @处理软中断
ldmfd sp!, {r0-r12,pc}^ @恢复现场(^表示会恢复spsr到cpsr)
stack_end:
.space 200
stack_base:
.end
C和汇编混合编程
裸机开发
裸机驱动的开发步骤
//最简例子 LED灯控制
- 看电路图
a. 找到我要控制的设备
b. 找到设备在CPU侧的控制管脚(如GPX2_7) - 看芯片手册 (先看相关的中文文档,熟悉设备,再看手册)
a. 搜索电路图里对应控制管脚的名称(如GPX2)
b. 看目录找到对应的控制模块(如:6 General Purpose Input/Output (GPIO) Control)
c. 看该模块的overview 了解该模块的大概功能
d. 看控制寄存器(REGISTER DESCRIPTION) 重点,难点
注: 如果寄存器比较多什么办?
看技术支持提供的例子程序, 找到需要修改的寄存器(常常只有几个)。
部分厂商会提供配置软件,通过界面去配置功能, 我们只需使用配置好的寄存器值就可以了 - 编程
a. 定义要控制的寄存器的宏 (与手册里的寄存器地址对应起来)
b. 设备初始化 (如设置GPIO为输出状态)
c. 把功能分成最基本的小块,逐个实现 如点亮灯-> 在灭灯-> 加延时->闪烁-> 跑马灯
@LED灯延迟一秒闪烁控制程序
.equ GPX2CON,0x11000C40
.equ GPX2DAT,0x11000C44
.section .text
.globl _start
_start:
@set Pin OutPut(设置GPX2_7管脚为输出状态)
ldr r0,=GPX2CON //将GPX2_7管脚地址赋值给通用寄存器r0
ldr r1,[r0] //将r0地址里面存放的内容放入r1中
bic r1,#0xF0000000 //将GPX2_7管脚清零
orr r1,#0x10000000 //将GPX2_7置为1
str r1,[r0] //将r1的值赋给地址为r0的空间
loop:
@set Pin High Level for led on(设置GPX2_7为高电平)
ldr r2,=GPX2DAT
ldr r3,[r2]
orr r3,#0x80
str r3,[r2]
bl delay1s //保存下一条要执行指令的地址,跳到函数delay1s中
@set Pin low Level for led on
ldr r2,=GPX2DAT
ldr r3,[r2]
bic r3,#0x80
str r3,[r2]
bl delay1s
b loop //直接跳到函数loop
delay1s:
ldr r4,=0x1ffffff //用伪指令将较大数值数值付给r4
delay1s_loop:
sub r4,r4,#1 //将r4的值-1后赋值给r4
cmp r4,#0 //比较r4与0
bne delay1s_loop //比较结果不相等,则跳到函数delay1s_loop
mov pc,lr //将lr地址赋给pc
.end
@Makefile编译程序
CROSS = arm-none-linux-gnueabi- 注:指定交叉编译工具
CC=$(CROSS)gcc
LD=$(CROSS)ld
OBJCOPY=$(CROSS)objcopy
all: led.s
$(CC) -g -c -o led.o led.s
$(LD) led.o -Ttext 0x43e00000 -o led.elf
注: -Ttext 指定链接地址为 0x43e00000
$(OBJCOPY) -O binary -S led.elf led.bin
注:转换为 binary 格式的,这样在u-boot中才能直接运行
$(CROSS)objdump -D led.elf > led.dis
注:objdump -D 反汇编生成文件 led.dis
clean:
rm -f *.o *.elf *.bin *.dis
Linux开发环境搭建
- 虚拟机与window建立文件共享
VM -> setting -> Options -> ShareFolder->add E盘 -> Always enable
$cd /mnt/hgfs/E 该目录就是共享目录 - 交叉编译工具安装
拷贝gcc-4.6.4.tar.xz到linux系统中
$ tar xvf gcc-4.6.4.tar.xz
$ export PATH=xxx/gcc-4.6.4/bin:$PATH
xxx是 gcc-4.6.4解压后所在的目录
$ arm-n 按Tab键能补全为
arm-none-linux-gnueabi- 表示安装成功 - 看到串口信息
连接串口线,安装串口驱动
打开超级终端 选择对应串口
串口设置为 115200 flow control = none
重上电能看到串口信息 - 用串口传输文件
串口终端里输入
loadb 40001000
再选传送-> 发送文件->选择待发送的文件
如led.bin
选协议为kermit ,点传送 - 运行裸机程序
go 40001000
通讯接口
设备间通讯实现
串行通信与并行通信的区别:串行通信只是用一根信号线传输数据,每次传输一位,串行传输的特点是线路简单成本低。而并行通信是使用多根信号先进行信号传输,相当而言并行传输更开快。
同步与异步的区别:接受与发送使用的是否是同一时钟
UART编程
- 初始化
管脚设置为UART模式(com2口对应TXD2、RXD2)
串口协议设置(奇偶校验位,数据位等)
串口波特率设置 - 发送字符
发送状态判断
发送 - 接收字符后环回
接收状态判断
接收
/*UART程序*/
typedef struct {
unsigned int GPA1CON; //配置管脚模式 -- uart
unsigned int GPA1DAT;
}gpa1;
#define GPA1 (*(volatile gpa1*)0x11400020)
typedef struct {
unsigned int ULCON2; //设置uart 帧格式
unsigned int UCON2; //设置uart工作模式 -- poll
unsigned int UFCON2;
unsigned int UMCON2;
unsigned int UTRSTAT2; //查询发送和接收状态
unsigned int UERSTAT2;
unsigned int UFSTAT2;
unsigned int UMSTAT2;
unsigned int UTXH2; //发送(写)
unsigned int URXH2; //接收(读)
unsigned int UBRDIV2; //设置波特率整数部分
unsigned int UFRACVAL2; //设置波特率小数部分
unsigned int UINTP2;
unsigned int UINTSP2;
unsigned int UINTM2;
}uart2;
#define UART2 ( * (volatile uart2 *)0x13820000 )
void uart_init()
{
//-----管脚配置为uart 模式
GPA1.GPA1CON = (GPA1.GPA1CON & ~0xFF ) | (0x22); //GPA1_0:RX;GPA1_1:TX
//-----Uart 模块的设置
//设置uart 帧格式 为8 data bit 1 stop ,none pairty
UART2.ULCON2 =0x03;
//设置uart 发送和接收为普通的polling 模式
UART2.UCON2 = 0x5;
/*设置uart 波特率为115200
* Baud-rate 115200: src_clock:100Mhz
* DIV_VAL = (100*10^6 / (115200*16) -1) = (54.3 - 1) = 53.3
* UBRDIV2 = (Integer part of 53.3) = 53 = 0x35
* UFRACVAL2 = 0.3*16 = 0x5
* */
UART2.UBRDIV2 = 0x35;
UART2.UFRACVAL2 = 0x5;
}
void putc(const char c)
{
//检测发送缓存为空,则写入数据。否则死循环
while(!(UART2.UTRSTAT2 & 0X2)) ;
UART2.UTXH2 = c;
}
/*链接脚本map.lds*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0;
. = ALIGN(4);
.text :
{
start.o(.text)
*(.text)
}
. = ALIGN(4);
.data :
{ *(.data) }
. = ALIGN(4);
.bss :
{ *(.bss) }
}
中断机制
中断过程
- 中断初始化
a. 管脚初始化
b. 中断控制器初始化 - 中断向量表
a.中断发生后,硬件自动跳转
b. 现场保护
c. 调用中断处理 - 中断处理
a. 根据中断号做相应处理
b. 清中断
c. 现场恢复
协处理器改变内核访问的基地址
- 协处理器指令
MRC Pn,op1,Rd,CRn,CRm,op2 //mrc p15,0,r0,c1,c0,0 读入cp15的c1寄存器的内容到r0中
MCR Pn,op1,Rd,CRn,CRm,op2 //mcr p15,0,r0,c1,c0,0 写r0内容到cp15的c1寄存器中
@ set Vector Base Address 为0x40008000
ldr r0,=0x40008000
波形控制
I2C设备
双线 i2c (半双工 同步)
支持一主机对多从机
可主从切换
PWM蜂鸣器
- PWM(Pulse Width Modulation) :
脉冲宽度调制 。常见应用有:电机控制,DAC输出等 - 占空比:
就是输出的PWM中,高电平保持的时间 与该PWM的时钟周期的时间之比WDT看门狗
工作原理:在系统运行以后也就启动了看门狗的计数器,看门狗就开始自动计数,如果到了一定的时间还不去清看门狗,那么看门狗计数器就会溢出从而引起看门狗中断,造成系统复位PWM控制框图
/*PWM控制蜂鸣器*/
void PWM_init(void)
{
GPD0.CON = (GPD0.CON & ~(0xf)) | 0x2; // GPD0_0 : TOUT_0
PWM.TCFG0 = (PWM.TCFG0 & ~(0xFF)) |0x63; //Prescaler 0 value for timer 0; 99 + 1 = 100
PWM.TCFG1 = (PWM.TCFG1 & ~(0xF)) | 0x3; // 1/8 input for PWM timer 0
PWM.TCNTB0 = 200; //设置脉冲周期数 200 TOUT PWM输出频率 = TLK/周期数
PWM.TCMPB0 = 100; //设置占空比 TCMPB0/TCNTB0 =100/200=1/2
/* auto-reload, Inverter Off, manual update */
PWM.TCON = (PWM.TCON & ~(0XF)) | 0XA;
/* auto-reload, Inverter Off, manual update off, start Timer0*/
PWM.TCON = (PWM.TCON & ~(0xF)) | 0X9;
}