【昨日作业】
.text .global start _start: mov r0,#0 @存放sum mov r1,#1 @存放相加的数值 loop: cmp r1,#100 bhi wh add r0,r0,r1 add r1,r1,#1 b loop wh: b wh .end
【内存读写指令】
通过内存读写指令可以实现向内存中写入指定数据或者读取指定内存地址的数据
c语言内存访问方式是通过指针实现 int a=10; int *p=&a; //从内存中获取a的数值 int b= *p; //向内存中写 *p=100;
关于调试时如何查看内存数据:
关于可用内存查看:
1.单寄存器内存读写指令
将一个寄存器中的数值写入到内存,或者从内存中读取数据放在某一个指定寄存器中
1.1 指令码和功能
1.向内存中写: str{条件码} 目标寄存器,[目标地址]:将目标寄存器的4字节数值写入到目标地址为首地址的空间中 strh{条件码} 目标寄存器,[目标地址]:将目标寄存器的2字节数值写入到目标地址为首地址的空间中 strb{条件码} 目标寄存器,[目标地址]:将目标寄存器的1字节数值写入到目标地址为首地址的空间中 2.从内存中读: ldr{条件码} 目标寄存器,[目标地址]:从目标地址为首地址的空间中读取4字节数据存放在目标寄存器中 ldrh{条件码} 目标寄存器 ,[目标地址]:从目标地址为首地址的空间中读取2字节数据存放在目标寄存器中 ldrb{条件码} 目标寄存器 ,[目标地址]:从目标地址为首地址的空间中读取1字节数据存放在目标寄存器中
1.2 示例
.text .global start _start: mov r0,#0XFFFFFFFE mov r1,#0X40000000 @R1保存内存地址 @将r0的值写入到r1对应的地址空间 str r0,[r1] @从r1对应的地址空间中读取数据保存在r2中 ldr r2,[r1] wh: b wh .end
1.3 单寄存器内存索引方式
前索引方式
str{条件码} 目标寄存器,[目标地址,#立即数] //将目标寄存器的数据保存在目标地址+8为起始地址的内存中 ldr{条件码} 目标寄存器,[目标地址,#立即数] //从目标地址+8为起始地址的内存中读取数据保存在目标寄存器
后索引方式
str{条件码} 目标寄存器,[目标地址],#立即数 //将目标寄存器的数据保存在目标地址为起始地址的内存中,接着目标地址自加立即数大小 ldr{条件码} 目标寄存器,[目标地址],#立即数 //从目标地址为起始地址的内存中读取数据保存在目标寄存器,接着目标地址自加立即数大小
自动索引方式
str{条件码} 目标寄存器,[目标地址,#立即数]! //将目标寄存器的数据保存在目标地址+立即数为起始地址的内存中,接着目标地址自加立即数大小 ldr{条件码} 目标寄存器,[目标地址,#立即数]! //从目标地址+立即数大小为起始地址的内存中读取数据保存在目标寄存器,接着目标地址自加立即数大小
2.批量寄存器的内存读写方式
将多个寄存器的数据存放在内存中以及从内存中取出数据保存在多个寄存器中
2.1 指令码以及格式
向内存写: stm 目标地址,{目标寄存器列表} 将列表中各个寄存器的数值保存在目标地址对应的地址空间中 从内存中读取 ldm 目标地址,{目标寄存器列表} 从目标地址对应的地址空间中拿数据保存到寄存器列表中各个寄存器中 注意: 1.寄存器列表中每一个寄存器之间用','分隔,如果寄存器列表中寄存器的编号连续,那么可以用-表示一定范围内的 寄存器,比如 {r1-r5} 2.无论寄存器列表中的寄存器表现形式如何,在存取数据时始终是小编号寄存器对应低地址
2.2 示例
.text .global start _start: mov r0,#0X40000000 mov r1,#1 mov r2,#2 mov r3,#3 mov r4,#4 mov r5,#5 @向内存中写 @stm r0,{r2,r1,r4,r3,r5} stm r0,{r1-r5} @从内存中读取数据 ldm r0,{r6-r10} wh: b wh .end
2.3 批量寄存器的地址增长方式
每次向寄存器保存的地址对应的地址空间中写入一个数据,这个寄存器保存的地址会发生相应的增长变化,这就是批量寄存器的地址增长方式
.text
.global start
_start:
mov r0,#0X40000000
mov r1,#1
mov r2,#2
mov r3,#3
mov r4,#4
mov r5,#5
@向内存中写
stm r0!,{r1-r5}
wh:
b wh
.end
内存读写命令后加ia后缀
先向r0数值为起始地址的内存空间中保存一个数据,然后r0数值往高地址方向增长
内存读写命令后加ib后缀
先r0数值往高地址方向增长,然后向r0数值为起始地址的内存空间中保存一个数据
内存读写命令后加da后缀
先向r0数值为起始地址的内存空间中保存一个数据,然后r0数值往低地址方向增长
内存读写命令后加db后缀
先r0数值往低地址方向增长,然后向r0数值为起始地址的内存空间中保存一个数据
3.栈内存的读写
栈指针寄存器:SP/R13 保存栈顶的地址
栈:本质上就是一段内存。在内存中选取一段内存作为栈内存,可以用于保存临时数据。
3.1 栈的类型
增栈:每次压栈结束,SP保存的栈顶地址往高地址方向增栈 减栈:每次压栈结束,SP保存的栈顶地址往低地址方向增栈 空栈:压栈结束后,SP保存的栈顶空间中没有有效数据 满栈:压栈结束后,SP保存的栈顶空间中有有效数据 空增栈(EA)/空减栈(ED)/满增栈(FA)/满减栈(FD) 当前ARM处理器使用的是哪种栈?满减栈
3.2 满减栈压栈出栈操作
1. push {寄存器列表}@压栈 pop {寄存器列表}@出栈 2. .text .global start _start: @初始化栈 ldr
1.
push {寄存器列表}@压栈
pop {寄存器列表}@出栈
2.
.text
.global start
_start:
@初始化栈
ldr SP,=0X40000020
mov r1,#1
mov r2,#2
mov r3,#3
mov r4,#4
mov r5,#5
@压栈
stmdb sp!,{r1-r5}
@出栈
ldmia sp!,{r6-r10}
wh:
b wh
.end
3.
.text
.global start
_start:
@初始化栈
ldr SP,=0X40000020
mov r1,#1
mov r2,#2
mov r3,#3
mov r4,#4
mov r5,#5
@压栈
stmfd sp!,{r1-r5}
@出栈
ldmfd sp!,{r6-r10}
wh:
b wh
.end
3.3 栈的应用实例---叶子函数调用过程
当我们在主函数中调用一个函数,被调用的这个函数中没有别的函数调用,那么 这个函数就叫做叶子函数
1.
push {寄存器列表}@压栈
pop {寄存器列表}@出栈
2.
.text
.global start
_start:
@初始化栈
ldr SP,=0X40000020
mov r1,#1
mov r2,#2
mov r3,#3
mov r4,#4
mov r5,#5
@压栈
stmdb sp!,{r1-r5}
@出栈
ldmia sp!,{r6-r10}
wh:
b wh
.end
3.
.text
.global start
_start:
@初始化栈
ldr SP,=0X40000020
mov r1,#1
mov r2,#2
mov r3,#3
mov r4,#4
mov r5,#5
@压栈
stmfd sp!,{r1-r5}
@出栈
ldmfd sp!,{r6-r10}
wh:
b wh
.end
3.4 栈的应用实例---非叶子函数调用过程
当我们在主函数中调用一个函数,被调用的这个函数中存在别的函数调用,那么 这个函数就叫做非叶子函数
_start:
@初始化栈
ldr SP,=0X40000020
b main
main:
mov r1,#3
mov r2,#4
bl fun1
add r3,r1,r2
b main
fun1:
@压栈保护现场
stmfd sp!,{r1,r2,lr}
mov r1,#7
mov r2,#9
bl fun2
sub r4,r2,r1
@出栈恢复现场
ldmfd sp!,{r1,r2,lr}
mov pc,lr @程序返回
fun2:
stmfd sp!,{r1,r2}
mov r1,#4
mov r2,#8
mul r4,r2,r1
@出栈恢复现场
ldmfd sp!,{r1,r2}
mov pc,lr @程序返回
.end
【程序状态寄存器传输指令】
指令的作用实现CPSR寄存器数值的读取以及数值的修改
1.指令码以及格式
格式: msr CPSR,第一操作数 将第一操作数的数值写入到CPSR寄存器中 mrs 目标寄存器,CPSR 读取CPSR数值保存到目标寄存器中
2.实例
.text
.global _start
_start:
mrs r1,CPSR @读取CPSR数值
@切换到USR模式,取消FIQ和IRQ禁止
msr CPSR,#0x10
.end
注意:user模式是ARM处理器工作模式中唯一的非特权模式,这种模式下无法通过手动修改CPSR数值切换到异常模式,只要发生对应的异常后才可以切换到异常模式
【软中断指令】
swi 中断号
注意:中断号是一个由24位二进制数组成的一个整数,用于区分不同的中断
1.概念
软中断是从软件层次上模拟的硬件中断,原理和硬件中断一样。软中断触发之后CPU进行异常模式的切换(SVC),紧接着执行软中断对应的异常处理程序。
2.软中断指令码以及使用
swi 中断号 注意:中断号是一个由24位二进制数组成的一个整数,用于区分不同的中断
3.异常处理过程分析
3.1 异常模式和异常源的对应关系
5种异常模式对应7种异常源
异常模式 | 异常源 | 解释 |
FIQ | FIQ类型异常源 | 一些硬件发生了FIQ异常事件进入FIQ模型 |
IRQ | IRQ类型异常源 | 一些硬件发生了IRQ异常事件进入IRQ模型 |
SVC | 复位信号 | 按键复位/上电复位时产生 |
swi软中断指令 | 执行swi指令 | |
undef | 未定义异常源 | 译码器在翻译指令时,遇到无法翻译的指令,指令未定义 |
abort | data abort | 取数据发生异常时 |
prefetch abort | 取指令发生异常时 |
3.2 异常的处理过程分析(面试重点)
***********异常的处理过程********
当一个异常源产生之后CPU会进行一些工作用于程序的跳转以及异常模式的切换,这个过程分为四大步三小步
1.保存发生异常之前的CPSR的值到对应异常模式下的SPSR寄存器中
2.修改CPSR的数值
2.1 根据实际情况设置FIQ和IRQ中断禁止 CPSR[7:6]
2.2 修改处理器工作状态为ARM状态 CPSR[5]
2.3 修改处理器的工作模式为对应的异常模式 CPSR[4:0]
3.保存主程序的返回地址到对应模式下的LR寄存器中
4.修改PC的值到对应异常模式下的异常向量表中
*********处理完异常之后现场的恢复过程*********
1.恢复CPSR寄存器的值为未发生异常之前的状态
2.修改PC的值为未发生异常之前的下一条指令地址 PC=LR
3.3 异常向量
1.异常向量表是内存空间中的一段内存。这段内存占据了32字节。这个内存被平分为8等份,一份是4字节。每一份内存对应一种异常源,有一份保留,在异常向量表内存里存放的是当前异常源对应的异常处理程序的跳转指令。当发生异常之后,CPU会修改PC的值为对应异常源在异常向量中的位置,执行这个位置中的跳转指令,去处理异常处理程序。
2.每一种异常源在异常向量表中的位置是固定,不能随便修改
3.只要设置了异常向量表的基地址,就可以根据不同异常在一场向量表中的位置找到对应异常的跳转指令
4.软中断异常处理实例
.text
.global _start
_start:
@初始化异常向量表
b main
b .
b do_swi
b .
b .
b .
b .
b .
main:
@初始化栈
mov sp,#0X40000020
@切换到USER模式
MSR CPSR,#0X10
MOV R1,#1
MOV R2,#2
@触发软中断
SWI 1
add r3,r1,r2
b main
do_swi:@异常处理
@保护现场
stmfd sp!,{r1,r2,lr}
mov r1,#3
mov r2,#4
mul r4,r1,r2
@恢复现场
ldmfd sp!,{r1,r2,pc}^ @ ^的作用是修改PC的值的同时将SPSR的值赋值给CPSR
.end
【混合编程】
1.混合编程的意义
所谓的混合编程就是c语言资源和汇编资源的相互调用
- 一般工程会有汇编启动程序,启动程序完成堆栈的相关初始化,完毕之后才跳转到c语言的main函数
- c语言中几乎不可以直接操作寄存器,但是有些特定场景下需要c中操作寄存器,这时候就需要c语言中嵌套汇编的语法
2.概述
所谓的混合编程就是c语言资源和汇编资源的相互调用
- 一般工程会有汇编启动程序,启动程序完成堆栈的相关初始化,完毕之后才跳转到c语言的main函数
- c语言中几乎不可以直接操作寄存器,但是有些特定场景下需要c中操作寄存器,这时候就需要c语言中嵌套汇编的语法
3.汇编调用C语言的函数
将C语言的函数当作汇编的标签使用
*****汇编文件**********
.text
.global _start
_start:
@ 1. 初始化栈指针,C代码运行必须有栈
ldr sp, =0x40000820
@ 2. 汇编调用c函数
@ 2.1 给C的函数传递实参值
mov r0, #3 @ a = 3
mov r1, #4 @ b = 4
mov r2, #5 @ c = 5
mov r3, #6 @ d = 6
@ 2.2 汇编调用c的函数
bl add_func
@ 2.3 函数的返回通过r0返回,查看r0寄存器中的值
loop:
b loop
.end
**********c文件********************
// c代码的函数是一个全局的函数
int add_func(int a, int b, int c, int d)
{
return (a+b+c+d);
}
4.c语言调用汇编标签
将汇编的标签当作c语言的函数
********起始汇编文件**********
.text
.globl _start
_start:
@ 1. 初始化栈指针,C代码运行必须有栈
ldr sp, =0x40000820
@ 2. 汇编调用c,跳转到main函数
b main
.end
********c文件************
// 使用extern对函数进行声明
extern int add_func(int a, int b, int c, int d);
int sum = 0;
int main()
{
// 在c代码中调用汇编代码
sum = add_func(1,2,3,4);
while(1);
return 0;
}
********汇编文件**********
.text
.global add_func @ 将add_func函数声明为全局
add_func:
add r0, r0, r1
add r0, r0, r2
add r0, r0, r3
mov pc, lr
.end
5.c语言内联汇编
在某一些特定的场景下需要在c语言中直接使用汇编的语法,此时需要内联汇编。内联汇编的实现需要通过asm关键字进行修饰
5.1 格式
asm volatile(
"汇编指令模板\n\t" //"\n\t"表示一条指令的结束
.....
:输出列表 //指令结果的输出值
:输入列表 //指令的数据输入
:破坏列表 //破坏列表指定我们当前可用的寄存器
);
5.2 实例
********汇编启动文件*******
.text
.globl _start
_start:
@ 1. 初始化栈指针,C代码运行必须有栈
ldr sp, =0x40000820
@ 2. 汇编调用c,跳转到main函数
b main
.end
**********c语言文件***********
// 内联汇编
int add_func2(int a, int b, int c, int d)
{
int sum = 0;
// 使用汇编实现求和
asm volatile( "add r0, r0, r1\n\t" "add r0, r0, r2\n\t" "add r0, r0, r3\n\t" :"=r"(sum) :"r"(a),"r"(b),"r"(c),"r"(d) :"memory" );
return sum;
}
//"=r"(sum)表示输出从寄存器中放到变量sum中
// "r"(a) 指定输入从变量a中获取放到通用寄存器
//"memory"声明使用内存
// 使用extern对函数进行声明
extern int add_func(int a, int b, int c, int d);
int sum = 0;
int main()
{
// 调用内联汇编的函数
sum = add_func2(5,6,7,8);
// 在c代码中调用汇编代码
sum = add_func(1,2,3,4);
while(1);
return 0;
}
*********汇编文件*************
.text
.global add_func @ 将add_func函数声明为全局
add_func:
add r0, r0, r1
add r0, r0, r2
add r0, r0, r3
mov pc, lr
.end
【环境安装】
1.在ubuntu中安装交叉编译工具链
交叉开发:PC端编写代码,开发板上运行代码,这种开发方式叫做交叉开发。 本地开发:PC端编写代码,PC端运行代码,这种开发方式叫做本地开发。
安装交叉编译工具链,需要安装兼容32位库,ubuntu一定需要联网
sudo apt-get install lib32z1
1. 在ubuntu的家目录下创建toolchain目录
cd ~
mkdir toolchain
2. 拷贝交叉编译工具链的压缩包到ubuntu的~/toolchain目录下
交叉编译工具链的压缩包的路径:
STM32MP157-学生资料\02_开发工具\02.5_ubuntu版本交叉编译工具链\
gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf.tar.xz
3. 使用tar命令进行解压缩
tar -vxf gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf.tar.xz
4. 解压缩成功之后得到一个gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf,修改文件加额名字
mv gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf gcc-7.5.0
5. 交叉编译工具链的路径为:
~/toolchain/gcc-7.5.0/bin
6. 修改PATH环境变量,将交叉编译工具链的路径添加到PATH环境变量中
/etc/bash.bashrc --》 对所有的用户都有效
/etc/profile --》 对所有的用户都有效
/etc/environment --》 对所有的用户都有效
~/.bashrc --》 对当前的用户都有效
修改/etc/bash.bashrc文件,在这个文件的最后一行添加以下内容:
export PATH=$PATH:/home/ubuntu/toolchain/gcc-7.5.0/bin/
修改为自己的交叉编译工具链的路径
7. 使修改后的环境变量立即生效
source /etc/bash.bashrc
8. 测试是否安装成功
arm-linux-gnueabihf-gcc -v ---> 查看交叉编译工具链的版本号
打印一些结果表示安装成功:
gcc version 7.5.0 (Linaro GCC 7.5-2019.12)
9. 安装一个32位软件的兼容库
9.1 ubuntu必须可以联网
9.2 配置源
打开源的配置文件/etc/apt/sources.list,将https修改为http
9.3. 更新源
sudo apt-get update
9.4 安装lib32z1库
sudo apt-get install lib32z1
2.安装securetCRT串口工具
1. securetCRT软件包的路径: STM32MP157-学生资料\02_开发工具\02.1_串口工具\securetCRT\scrt-sfx-9.0.0.2430 2. 安装步骤参考《SCRT804安装说明文档.docx》安装文档。
3.M4开发环境搭建
3.1 安装PACK包
【1】安装pack包
参考路径:D:\STM32MP157-学生资料\STM32MP157-学生资料\03_M4资料\03-软件工具\MDK Keil
参考文档:keil安装教程.docx
【2】成功现象
3.2 安装STM32CubMX软件
参考路径:D:\STM32MP157-学生资料\03_M4资料\03-软件工具\STM32CubeMx
参考文档:安装stm32cubeMX软件教程.docx
【1】安装jre-8u271-windows-x64.exe,以管理员身份运行,不能出现中文路径
【2】安装SetupSTM32CubeMX-6.3.0-Win.exe,以管理员身份运行,不能出现中文路径
【3】导入STM32MP1包(可以先不做)
1、打开cubmx软件
2.点击”Help--->manager Embedded software packages”,弹出如下图工作框
【4】导入en.STM32Cube_FW_MP1_V1.4.0_v1.4.0.zip,这个包不需要解压
【5】安装成功的现象
3.3 安装ST-LINK驱动
参考路径:D:\STM32MP157-学生资料\03_M4资料\03-软件工具\ST LINK驱动及教程
参考文档:STLink驱动的安装和固件升级教程.docx