版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pengfei240/article/details/54693102
背景
使用Cortex-M3已经有一段时间了,大大小小也做过几个项目,可以说对这个系列的片子是有一定的了解。相对于以前的8位单片机来说,其存储空间,外设种类都有较大的提升,这对于产品的应用显得非常便利。最近,因为项目上的原因做了一些汇编工作,个人觉得如果想把Cortex-M3系列的芯片用好,汇编方面的理论知识还是需要学习和补充的,因此就有了写这一系列文档的动力,权当总结一下汇编的基础知识和实用样例。
本文重点不在于学习每一条汇编指令,而是通过使用汇编语言编写一个个简单实例使我们能对GCC汇编有一个基本的了解。
编译环境
主机环境: Ubuntu 16.04.1
目标板名称: TI DB-LM3S9B96
主芯片型号: TI LM3S9B96
交叉编译链: gcc-arm-none-eabi-5_4-2016q3
在另一篇文章 http://blog.csdn.net/pengfei240/article/details/52912833 中,已经详细描述了如何下载和制作交叉编译链,大家如果感兴趣可以参考下这篇文章。
工程目录
工程的结构按下面的目录形式进行组织:
- 根目录以 asm 开始
- source 为源码目录,该目录下每个例程为一个文件夹,分别以example1,example2…的形式进行组织
- toolchain 为各个版本的交叉编译链存放的位置,其中软链接CrossCompile指向当前使用的交叉编译链
$ tree -L 2 asm
asm
├── source
│ └── example1
└── toolchain
├── CrossCompile -> gcc-arm-none-eabi-5_4-2016q3
└── gcc-arm-none-eabi-5_4-2016q3
第一个点灯程序
硬件原理图
由原理图可知,点灯方式为PF3端口设置为高电平。
源码
/*
* example1
*
* Description: It controls the port to turn on the LED.
* Author: Peter Peng
* Ver: 0.1
*/
.equ STACK_BASE, 0x20000000
.equ STACK_TOP, 0x400
.equ SYSCTL_RCGC1_R, 0x400FE104
.equ SYSCTL_RCGC2_R, 0x400FE108
.equ GPIO_PORTF_AFSEL_R, 0x40025420
.equ GPIO_PORTF_DIR_R, 0x40025400
.equ GPIO_PORTF_DEN_R, 0x4002551C
.equ GPIO_PORTF_DATA_R, 0x400253FC
.equ SYSCTL_RCGC2_GPIOF, 0x00000020
.text
.thumb
.syntax unified
.global _start
_start:
.word STACK_BASE + STACK_TOP /* Top of Stack */
.word start /* Reset Handler */
.word NmiSR /* NMI Handler */
.word FaultISR /* Hard Fault Handler */
.word IntDefaultHandler /* MPU Fault Handler */
.word IntDefaultHandler /* Bus Fault Handler */
.word IntDefaultHandler /* Usage Fault Handler */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word 0 /* Reserved */
.word IntDefaultHandler /* SVCall Handler */
.word IntDefaultHandler /* Debug Monitor Handler */
.word 0 /* Reserved */
.word IntDefaultHandler /* PendSV Handler */
.word IntDefaultHandler /* SysTick Handler */
.word IntDefaultHandler /* GPIO Port A */
.word IntDefaultHandler /* GPIO Port B */
.word IntDefaultHandler /* GPIO Port C */
.word IntDefaultHandler /* GPIO Port D */
.word IntDefaultHandler /* GPIO Port E */
.word IntDefaultHandler /* UART0 */
.word IntDefaultHandler /* UART1 */
.word IntDefaultHandler /* SSI */
.word IntDefaultHandler /* I2C */
.word IntDefaultHandler /* PWM Fault */
.word IntDefaultHandler /* PWM Generator 0 */
.word IntDefaultHandler /* PWM Generator 1 */
.word IntDefaultHandler /* PWM Generator 2 */
.word IntDefaultHandler /* Quadrature Encoder */
.word IntDefaultHandler /* ADC Sequence 0 */
.word IntDefaultHandler /* ADC Sequence 1 */
.word IntDefaultHandler /* ADC Sequence 2 */
.word IntDefaultHandler /* ADC Sequence 3 */
.word IntDefaultHandler /* Watchdog */
.word IntDefaultHandler /* Timer 0A */
.word IntDefaultHandler /* Timer 0B */
.word IntDefaultHandler /* Timer 1A */
.word IntDefaultHandler /* Timer 1B */
.word IntDefaultHandler /* Timer 2A */
.word IntDefaultHandler /* Timer 2B */
.word IntDefaultHandler /* Comp 0 */
.word IntDefaultHandler /* Comp 1 */
.word IntDefaultHandler /* Comp 2 */
.word IntDefaultHandler /* System Control */
.word IntDefaultHandler /* Flash Control */
.word IntDefaultHandler /* GPIO Port F */
.word IntDefaultHandler /* GPIO Port G */
.word IntDefaultHandler /* GPIO Port H */
.word IntDefaultHandler /* UART2 Rx and Tx */
.word IntDefaultHandler /* SSI1 Rx and Tx */
.word IntDefaultHandler /* Timer 3 subtimer A */
.word IntDefaultHandler /* Timer 3 subtimer B */
.word IntDefaultHandler /* I2C1 Master and Slave */
.word IntDefaultHandler /* Quadrature Encoder 1 */
.word IntDefaultHandler /* CAN0 */
.word IntDefaultHandler /* CAN1 */
.word IntDefaultHandler /* CAN2 */
.word IntDefaultHandler /* Ethernet */
.word IntDefaultHandler /* Hibernate */
.word IntDefaultHandler /* USB0 */
.word IntDefaultHandler /* PWM Generator 3 */
.word IntDefaultHandler /* uDMA Software Transfer */
.word IntDefaultHandler /* uDMA Error */
.word IntDefaultHandler /* ADC1 Sequence 0 */
.word IntDefaultHandler /* ADC1 Sequence 1 */
.word IntDefaultHandler /* ADC1 Sequence 2 */
.word IntDefaultHandler /* ADC1 Sequence 3 */
.word IntDefaultHandler /* I2S0 */
.word IntDefaultHandler /* External Bus Interface 0 */
.word IntDefaultHandler /* GPIO Port J */
.thumb_func
start:
/* SYSCTL_RCGC2_R |= SYSCTL_RCGC2_GPIOF */
LDR R0, =SYSCTL_RCGC2_R
LDR R1, [R0]
ORR R1, #SYSCTL_RCGC2_GPIOF
STR R1, [R0]
NOP
NOP
/* GPIO_PORTF_AFSEL_R = 0x00000000 */
MOV R0, #0x00
LDR R1, =GPIO_PORTF_AFSEL_R
STR R0, [R1]
/* GPIO_PORTF_DIR_R = 0x00000008 */
MOV R0, #0x08
LDR R1, =GPIO_PORTF_DIR_R
STR R0, [R1]
/* GPIO_PORTF_DEN_R = 0x00000008 */
LDR R1, =GPIO_PORTF_DEN_R
STR R0, [R1]
/* GPIO_PORTF_DATA_R = 0x00000008 */
MOV R0, #0x08
LDR R1, =GPIO_PORTF_DATA_R
STR R0, [R1]
b .
.thumb_func
NmiSR:
b .
.thumb_func
FaultISR:
b .
.thumb_func
IntDefaultHandler:
b .
.end
源码解析
- 注释: 以“//”、“/**/”和“#”标注的部分为注释语句
- .equ: 将符号的值设置为表达式的值(相当于C语言的#define)
- .text: 指定后续编译出来的内容放在代码段
- .thumb: 同.code 16,告诉编译器后面的指令16bit对齐,即生成为Thumb指令
- .syntax unified: 格式:syntax [unified | divided]。
默认情况下[syntax divided],ARM和Thumb指令都有自己独立的语法;当使用统一语法时[syntax unified],ARM和Thumb使用相同的格式编写,由编译器将指令翻译为具体的ARM或Thumb指令 - .global: 将符号导出给链接器
- .word: 在当前位置放一个word型的值,在源码中,其实放置的就是各个中断处理程序的函数指针
- 0x00复位向量: M3的0x00的位置放置的是MSP(堆栈指针)的初始值,在系统上电时,由硬件自动将该值加载到SP寄存器中。这一点是与其它芯片不太一致的地方
- .thumb_func: 指示后面的符号是一个Thumb的函数。
ARM 和 Thumb 指令集的动态切换,是通过 BX 指令使用一个寄存器名作为参数来完成,如果 LSB=1, 则进入 Thumb 指令处理模式;如果 LSB=0, 则进入 ARM 指令处理模式。但是,当使用.word形式放置函数指针时,链接器并不知道此处的LSB是多少(根本不知道放置的这个WORD是什么内容)。因此,需要使用.thumb_func来告知链接器,这儿的LSB=1(thumb指令集的函数指针) - .end: 表示汇编文件结束,将不再处理该指示符后的内容
start函数中实际就是几个寄存器(SysCtl和PortF)的赋值操作,代码中都有详细的注释,在这就不解释每一句汇编语法的含义了。
编译和下载
可以使用下面的四条命令来编译和调试程序,Makefile和Linker文件以后再实现:
# 编译
../../toolchain/CrossCompile/bin/arm-none-eabi-gcc -mcpu=cortex-m3 -c -mthumb -mthumb-interwork start.s
# 链接
../../toolchain/CrossCompile/bin/arm-none-eabi-ld -Ttext 0x0 -o start.out start.o
# objdump,如果需要调试可以使用该命令来反编译二进制文件
../../toolchain/CrossCompile/bin/arm-none-eabi-objdump -S start.out > start.dump
# ELF转换为bin文件,该bin文件就是我们下载需要的文件
../../toolchain/CrossCompile/bin/arm-none-eabi-objcopy -O binary start.out start.bin
最后在Windows上通过LM Flash Programmer软件将bin文件下载到目标板(Linux下的烧写工具以后再尝试),可以看见开发板上的LED灯被点亮。