课程介绍
ARM开发 --> Linux移植 --> 驱动开发
前后联系:ARM和系统移植为驱动开发学习做准备工作
所需知识:C语言基础及STM32需要的硬件知识
学习方法
学习流程、思想和解决问题的方法即可
知道驱动的基本框架以及基本开发要求
底层课程导学
接口技术
GPIO:通用的输入输出接口(输入:上拉、下拉、浮空、模拟;输出:推挽/开漏)
本质:操作寄存器。使能:配置模式(输出)、输出类型(推挽)、输出速度(默认)、输出数据(相应位写1)。
串口通信协议:UART、IIC、SPI、CAN
电气协议:TTL、RS232、RS485
无线通信:WiFi、蓝牙、NB-IOT、Lora、zigbee
ADC:模数转换(需要得到外界真实数据)
PWM:脉冲宽度调制(用定时器调节平均功率)
MCU与MPU的区别
【三分钟了解什么是CPU_SOC_MPU和MCU】 三分钟了解什么是CPU_SOC_MPU和MCU_哔哩哔哩_bilibili
STM32G030(M0+) S5P6818(A53)
微控制单元(Microcontroller Unit;MCU) ,又称单片微型计算机或者单片机,是把中央处理器的频率与规格做适当缩减,并将内存、计数器(Timer)、USB、A/D转换、UART、、DMA等周边接口整合在单一芯片上,形成芯片级的计算机,为不同的应用场合做不同组合控制。
微处理器单元 (Microprocessor Unit;MPU)。就是把很多 CPU 集成在一起并行处理数据的芯片。通俗来说,MCU 集成了 RAM,ROM 等设备;MPU 则不集成这些设备,是高度集成的通用结构的中央处理器矩阵,也可以认为是去除了集成外设的 MCU。
传统来说,MPU更多采用Arm Cortex-A系列核心,具备丰富的外设接口,MCU则大多采用Arm Cortex-M系列核心,也比MPU更便宜,更容易安装和使用。主要区分是MMU。
更简单粗暴地划分二者的方式是运行Linux就是MPU,基于实时操作系统(RTOS)就是MCU。MCU的目的是永远运行一个相当简单的控制循环,或者直到它中断或停止。
随着技术和应用的发展,某些公司开始逐渐越界,界限开始模糊。
CPU运行原理
根据下载的程序运行,程序是指令和数据的有序集合
指令的解析
一条指令(机器码)的执行通常分为三个阶段:
1)取指:控制器将PC寄存器中的值发送给内存,内存将对应地址中的指令(机器码)传送回CPU的指令寄存器IR中
2)译码:指令译码器对IR中的指令进行识别,将指令(机器码)解析(翻译)成具体的运算操作(+/-/*...)
3)执行:控制器控制运算器中对应的运算单元进行运算,运算结果写入寄存器
注意:PC每取地址一次,自加一次。PC的值自动增加使PC指向内存中的下一条指令
思考:
1.运算器不同,处理指令不同。不同的处理器上如何运行同一个c语言程序?
2.假设指令集有乘法指令,结果并没有乘法运算器,怎么办?
可以转换为加法指令
指令流水线
指令的执行是按照流水线
取指--》取指器 根据PC值取指令
译码--》译码器
执行--》执行器
以上三个器件,都是单周期的器件,三个器件的工作是独立
指令1 指令2 指令3 指令4 指令5
1 取指
2 译码 取指
3 执行 译码 取指
4 执行 译码 取指
5 执行 译码 取指
6 执行 译码
7 执行
PC永远指向当前取指指令的地址,一旦取到指令,pc后移4byte,保存下一条指令地址。
指令流水线机制的引入确实能够大大的提升指令执行的速度,但在实际执行程序的过程中很多情况下流水线时是无法形成的,比如芯片刚上电的前两个周期、执行跳转指令后的两个周期等。
所以指令流水线的引入以及优化只能使平均指令周期不断的接近1而不可能真正的达到1,且流水线级数越多芯片设计的复杂,程度就越高,芯片的功耗就越高。
指令集架构
ARM的命名有指令集架构、 处理器架构、 处理器型号三类命名规则
架构指支持的汇编指令集(架构不同,汇编指令集不同)
ARM体系结构:ARMv8-A ---> Cortex-A53 --> S5P6818 主频:1.4GHZ
内核:Cortex-A9,A53,A73,A75
ARM公司授权芯片的公司,芯片产家在内核的基础上,增加了一些外设,发布一款芯片,这些芯片可以统称为SoC
SoC:System of Chip(片上系统);例如:S5P6818,骁龙(高通),麒麟(海思)
s5p6818(8核)
ARM的工作模式(A系列)
- 用户模式(User ):非特权模式,用户程序的工作模式,运行在操作系统的用户态。它没有权限去操作其他硬件资源,只能执行处理用户自己的数据,并且不能切换到其他模式。要访问硬件资源或切换到其他模式,只能通过软中断或产生异常。
- 快速中断模式(FIQ ):当一个高优先级(fast) 中断产生时将会进入这种模式。它主要用于高速数据处理及通道处理。
- 中断模式(IRQ):当一个低优先级(normal) 中断产生时将会进入这种模式。硬件产生中断信号后,处理器会自动进入这种模式。
- 管理模式/SVC(Supervisor):当复位或软中断指令执行时将会进入这种模式,当用户模式下的用户程序请求使用硬件资源时,通过软件中断可以进入该模式。
- 中止模式(Abort) :用于支持虚拟内存或存储器保护。当用户程序访问非法地址或没有权限读取的内存地址时,会进入该模式。
- 未定义模式(Undef):当执行未定义指令时ARM会进入这种模式
- 系统模式(System):特权模式之一,提供给操作系统内核使用。可以访问更多的寄存器和特权指令,用于执行特权级别的操作和管理系统资源。
- 监控模式(Monitor) : 是为了安全而扩展出的用于执行安全监控代码的模式。也是一种特权模式(Cortex-A特有模式)
工作模式的理解
不同的模式拥有不同的权限
不同的模式执行的代码不同
不同的模式拥有不同的功能
特定的模式拥有特定的权限,执行特定的代码,完成特定的功能
ARM寄存器
工作模式的切换是依据寄存器去实现的。
ARM7,9,11 有37个寄存器
30 个通用寄存器
1 个用作 PC( program counter)
1个用作CPSR(current program status register)
5个用作SPSR(saved program status registers)
Cortex-A多出3个寄存器,即有40个寄存器
Monitor 模式r13_mon , r14_mon, spsr_mon
R0-R15, CPSR,SPSR 这些寄存器是由ARM公司提供,每个寄存器都是32位的。
这些寄存器没有地址,只有一个唯一的编号,通过这些编号,就可以访问对应的地址空间。R0-R15,cpsr,spsr就是对应的编号,每个编号都对应的32位二进制
专用寄存器
R13(SP,Stack Pointer)
栈指针,用于存储当前模式下的栈顶地址
R14(LR,Link Register)
链接寄存器:函数调用时,保存返回地址(下一条地址的指令的地址)
R15(PC,Program Counter)
程序计数器:用于存储当前取址指令的地址(下一条指令的地址)
CPSR寄存器
CPSR(Current Program Status Register),程序状态寄存器
存储当前程序运行状态
NZCV这几位叫条件位,后边八位叫控制位(C表示)
Bit[4:0]
[10000]User [10001]FIQ [10010]IRQ [10011]SVC
[10111]Abort [11011]Undef [11111]System [10110]Monitor
Bit[5]
[0]ARM状态 [1]Thumb状态
Bit[6]
[0]开启FIQ [1]禁止FIQ
Bit[7]
[0]开启IRQ [1]禁止IRQ
Bit[28]
> 当运算器中进行加法运算且产生符号位进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生符号位借位时该位自动置0,否则为1
Bit[29]
> 当运算器中进行加法运算且产生进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生借位时该位自动置0,否则为1
Bit[30]
当运算器中产生了0的结果该位自动置1,否则为0
Bit[31]
当运算器中产生了负数的结果该位自动置1,否则为0
SPSR寄存器
SPSR:保存程序状态的寄存器(saved program status register)
用于保存CPSR(通用寄存器,在切换模式的时候用)
ARM的异常处理
异常
处理器在正常执行过程中可能遇到一些不正常的事件发生,这时处理器就要将当前程序暂停下来转而去执行这个异常事件,异常事件处理完之后返回被异常打断的地方继续执行。
ARM异常源
概念:导致异常产生的事件称为异常源
ARM异常源 | |
FIQ | 快速中断请求引脚有效 |
IRQ | 外部中断请求引脚有效 |
Reset | 复位电平有效 |
Software Interrupt | 执行swi指令 |
Data Abort | 数据终止 |
Prefetch Abort | 指令预取终止 |
Undefined Instruction | 遇到不能处理的指令 |
Reset(复位)
处理器上电或者复位时发生的异常
Data Abort(数据中止)
数据访问错误时发生的异常,一般是与Memory 误操作有关,如对0地址写操作,或对一些Memory 越界操作。或者指令不支持
FIQ (快速中断)、IRQ (普通中断)
FIQ 具有更高的优先级,即当 FIQ 发生时,CPU 会在当前指令执行完成后立即响应 FIQ 中断,而忽略其他 IRQ 中断。这使得 FIQ 更适合处理紧急、实时性要求较高的中断。
Prefetch Abort(指令预取错误时发生的异常)
当CPU在执行指令时,发现指令缓存中没有下一条指令时,就会发生预取指中止异常。这种异常通常是由于程序中的错误或者硬件故障引起的。由于预取指中止异常的信息不够详细,因此很难定位和修复问题。
Software Interrupt(软件中断)
软件中断是一种由应用程序发起的中断,用于请求操作系统提供服务或执行某些操作。当应用程序需要访问受保护的资源或请求操作系统提供服务时,它会发出一个软件中断信号,这个信号会被CPU接收并暂时将控制切换到一个中断处理程序,内核中被中断挂起的进程将在中断被接受后恢复。软件中断可以被看作是同步事件,因为它是由应用程序主动发起的,而不是由外部设备触发的。
Undefined instruction(执行了未定义指令时发生的异常)
Undefined instruction异常是ARM处理器中的一种异常类型,当CPU遇到不认识的指令时,就会触发该异常。在异常处理函数中,可以对该异常进行处理,例如打印debug信息或者进行其他操作。
ARM异常模式
在ARM的基本工作模式中有5个属于异常模式,ARM遇到异常后会切换成对应的异常模式
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制。
ARM异常响应(四大步、三小步)
ARM产生异常后的动作(自动完成)
1、拷贝CPSR中的内容到对应的异常模式下的SPSR
2、修改CPSR的值
2.1修改中断禁止位禁止相应的中断
2.1修改模式位进入相应的异常模式
2.3修改状态位进入ARM状态
3、保存返回地址到对应模式下的LR中
- 设置PC位相应的异常向量
异常向量表
- 异常向量表的本质是内存中的一段代码
- 表中为每个异常源分配了四个字节的存储空间
- 遇到异常后处理器自动将PC修改为对应的地址
- 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址
异常返回
ARM异常返回动作(自己编写)
1、将SPSR的值复制给CPSR-->恢复为跳转之前的模式
2、将LR的值复制给PC-->返回跳转之前程序执行的地方
IRQ异常举例
IRQ异常是指当CPU接收到一个中断请求时,会暂停当前正在执行的程序,转而去执行与中断请求相关的处理程序。
例子:键盘中断
当用户按下键盘上的某个键时,键盘会向CPU发送一个中断请求,CPU会暂停当前正在执行的程序,转而去执行与键盘中断相关的处理程序,以响应用户的操作。在处理完中断请求后,CPU会返回到原来的程序继续执行。
异常优先级
- 环境及工程搭建
汇编编程环境搭建
按照下发的环境安装包安装环境
配置编译工具链
为工程配置链接脚本(map.lds)
将map.lds 复制到工程文件夹(在桌面创建的ARM-ASM文件)
创建汇编文件
接下来我们需要建立一个start.s汇编文件添加到我们的工程中去
- 汇编指令学习
c语言中哪些代码可以生成汇编指令?
1》带#号预处理,辅助编译器怎么编译,编译什么内容
预处理器是C语言编译器的一个组成部分,它在编译代码之前对代码进行处理。预处理器指令以#号开头,告诉编译器在编译代码之前执行一些操作。其中,#include指令用于将头文件包含到源代码中,#define指令用于定义宏。预处理器的主要作用是辅助编译器编译代码。 例如在编译时将头文件中的函数声明插入到源代码中,或者将宏替换为实际的值。预处理器处理完代码后,编译器将生成目标代码,最终生成可执行文件。
2》带;号的语句,可以编译生成指令
在编译器中,分号是语句结束的标志,编译器会将分号之前的语句编译成指令并添加到指令序列中。
汇编整体分类
1》指令:编译完生成一条机器码存储在内存单元当中,CPU执行时能完成对应的操作(类似于C中的语句)
2》伪操作(相当于c中的#的内容,告诉编译器怎么编译):不会生成机器码也不会占用内存,其作用是告诉编译器怎样编译(类似于C中的预处理指令)
3》伪指令:不是指令,编译器在编译时将其替换成等效的指令 (如:cpu中没有乘法器,对应没有乘法指令,3*3 ---》用加法器实现3+3+3,替换实现)
汇编中注释代码用@或;注释一行 ,/* */注释一段代码
指令分类
1.数据处理指令: 对数据进行逻辑、算术运算
2.跳转指令: 实现程序的跳转,实质是修改PC
3.Load/Store指令: 对内存的读写操作
4.状态寄存器传送指令: 对CPSR进行读写操作
5.异常中断产生指令: 触发软中断,常用于内核的系统调用 //SWI:软中断
6.协处理器指令: 操作协处理器的指令
//如3*3 ---》用加法器实现3+3+3,比较慢。我们可以外接一个协处理器(乘法器)(每个协处理器的功能比较单一),协处理器指令就是操作这个协处理器的,用的比较多的cp15协处理器。
汇编指令代码框架
.text @声明一段代码
.global _start @将_start 声明为一个全局的符号,其他.s文件也可以引用
@如调用函数 func : .global func
_start: @汇编的入口
@汇编代码段
.end @汇编的结束
指令的语法格式(拒绝死记硬背)
<opcode{<cond>}{S}> <Rd>, <Rn>, <Operand2>
<操作码> <目标寄存器Rd> <第一操作寄存器Rn> <第二操作数Operand2>
;第一个位置必须是寄存器,第二操作数可以是寄存器,也可以是立即数
<opcode{<cond>}{S}> <Rd>, <Rn>, <Operand2>
opcode:指令的名字
cond:条件码(if else),可以省略不写,默认指令是无条件执行
S:状态标志
加s,指令的执行结果影响CPSR的NZCV位,
不加s,无影响
Rd:目标寄存器
Rn:第一个操作寄存器
oprand2:第二个操作数,可以是普通寄存器,可以是立即数
注:指令的名字,条件码,s连到一起写,指令名和目标寄存器之间使用空格,寄存器和数据之间使用逗号隔开,指令中的字符不区分大小写
ARM汇编:数据处理指令集:MOV、ADD、ADDS、ADC、SUB、SUBS、SBC、RSB、MUL、AND、ORR、EOR、BIC、CMP、TST、TEQ、LSL、LSR、ASR、RORV_汇编 adds-CSDN博客
- 数据处理指令
数据搬移指令 mov
如果是立即数,前边必须加#
什么是立即数?
立即数
立即数通常是指在立即寻址方式指令中给出的数。可以是8位、16位或32位,该数值紧跟在操作码之后。
寻址方式:
寻址方式_百度百科
立即数是保存在指令中的数,取指令的同时将值取过去,和普通变量的区别是,变量保存在内存中的数据,需要单独取值运算。
立即数的本质:立即数是包含在指令当中的数据(即属于指令的一部分)
立即数的优点:读取指令的同时也将立即数读取到了内存中,速度快
立即数的缺点:数量有限
如:MOV ,#0x12345678 @报错,不合法
怎么判断一个数是否立即数?
给定一个数,将这个数中的所有的1,可以组合成一个0-255之间的数,将0-255之间的这个数,循环右移偶数个位数,如果可以得到给定的这个数,说明是立即数。
问:只要挨着的就是立即数?对吗?
如何快速判断立即数-CSDN博客
注:使用mov 给寄存器里面存放值的时候,#号后面需是有效数(1:立即数,2:取反之后是立即数),如果不是立即数需要用ldr指令进行存放。
PC寄存器讲解
指令的执行三步:取址,译码,执行(PC永远指向当前正在取指指令的地址)
算术运算指令
数据运算指令格式
<操作码><目标寄存器><第一操作寄存器><第二操作数>
ADD R3,R1,R2;
操作码 指定当前指令是哪种运算
目标寄存器 存放运算结果
第一操作寄存器 存放参与运算的一个数据(只能是寄存器)
第二操作数 存放参与运算的另一个数据(可以是寄存器/立即数)
算术运算指令
add加法 adc 带进位的加法
sub 减法 sbc 带借位的减法
mul乘法 (乘法运算的R2(第二操作数)不能为立即数)
add 普通加法
adc 带进位加法
假设2个64位的数相加
第一个64位的数,R0存放低32位,R1存放高32位;
第二个64位的数,R2存放低32位,R3存放高32位;
结果R4存放低32位,R5存放高32位
sub 普通减法
subs 减法(刷新CPSR)
减法指令执行时,没有借位时 CPSR 'C' 位置 1
sbc 带借位减法
mul 乘法
注意:mul r2, r0, #0x4 @ 错误------>乘法指令的第二个操作数只能是一个寄存器
mul r2, r1,r0
与 或 异或 左移 右移
.text
.global _start
_start:
MOV R0,#1 @指令
MOV R1,#2 @指令
MUL R2,R1,R0 @指令 R3=R1*R0
AND R3,R1,#1 @指令 与 R3=R1&1
ORR R3,R2,R1 @指令 或 R3=R2|R1
EOR R3,R2,R1 @指令 按位异或 R3=R2^R1
LSL R3,R2,R1 @指令 左移 R3=R2<<R1
LSR R3,R2,R1 @指令 右移 R3=R2>>R1
.end
- 跳转指令
实现程序的跳转,本质是更改PC
修改PC
不建议使用,因为需要查询指令的地址
b bl :指令跳转
格式:b/bl Label
Label: 指令
相当C语言的函数调用
b指令(不带返回的跳转)
不保存返回地址的跳转(返回地址不保存到lr中)
一路向前,不返回
bl指令(带返回的跳转指令)
将LR的值修改成跳转指令下一条指令的地址
再将PC的值修改成跳转标识符下指令的地址
补充了解:
指令条件码表:可跟的判断条件成立跳转(NZCV在用于判断两者之间关系使用比较多)
练习
练习:
实现以下逻辑
unsigned int r1 = 9;
unsigned int r2 = 15;
while(1)
{
if(r1 == r2)
goto stop;
if(r1 > r2)
r1 = r1 - r2;
if(r1 < r2)
r2 = r2 - r1;
}
stop:
while(1);
汇编指令练习答案如下:
mov r1,#9
mov r2,#15
loop:
cmp r1,r2 @cmp 比较指令
beq stop
subhi r1,r1,r2
subcc r2,r2,r1
b loop
stop:
b stop
- load/store指令
对内存的读写操作,将运算结果从cpu写到内存
可用地址查找:(我们不用查找,脚本文件中配置了内存空间的分配)
单寄存器操作指令 ldr / str
- 格式:ldr/str Rm, [Rn]
ldr:读
str:写
Rm: 存储是数据
Rn:存储的数据,地址
mov r1,#0Xff000000
mov r2,#0X40000000
str r1,[r2] @把R1当中的数存到R2的地址中
ldr r3,[r2] @把内存R2地址中的数据读取到CPU R3寄存器中
1> 前索引
mov r1,#0xffffffff
mov r2,#0x40000000
str r1,[r2,#8] @基址加变址寻址
2>后索引
mov r1,#0xffffffff
mov r2,#0x40000000
str r1,[r2],#4 @将R1寄存器的内容存到[R2]地址,然后R2=R2+4
目的:可以做连续存储,压栈时用的比较多 存完一个数,他就把地址自动指向下一个了
3>自动索引(前后索引)
MOV R1,#0XFFFFFFFF
MOV R2,#0X40000000
STR R1,[R2,#4]! ; 将R1寄存器的内容存到【R2+4】地址,然后R2=R2+4
LDR同样支持三种索引方式
批量寄存器操作指令ldm/stm
1、将r1到r4中的值存储到r0指向地址空间中,连续16个字节的地址空间
stm r0, {r1-r4}
2、将r0指向的地址空间中,连续的16个字节的数据,读到r5-r8寄存器中
ldm r0, {r5-r8}
3、如果寄存器列表中的寄存器编号既有连续又有不连续,连续的使用 - 隔开 不连续的使用 ,
stm r0, {r1-r3,r5}
4、不管寄存器列表中的寄存器编号顺序如何变化,都是小地址对应小编号的寄存器高地址对应大编号的寄存器
stm r0,{r4,r2,r1,r3}
ldm r0,{r8,r6,r5,r7}
栈的操作指令 stmfd / ldmfd
栈的种类
空栈(Empty)
栈指针指向的地址是空的,在栈中存储数据时,可以直接存储,存储完成之后需要将栈指针再次指向空的位置。
满栈(Full)
栈指针指向的地址有数据,在栈中存储数据时,需要先将栈指针,指向一个空的位置,然后在存储数据。
增栈(Ascending)
栈指针向高地址方向移动
减栈(Descending)
栈指针向低地址方向移动
操作栈的方式
满增栈,满减栈,空增栈,空减栈
FA:Full Ascending 满增
FD:Full Descending 满减
EA:Empty Ascending 空增
ED:Empty Descending 空减
ARM默认采用的是满减栈
stmfd/ldmfd<code> sp!, {寄存器列表}
stmfd sp!, {r1-r5}(写) (压栈)
更新栈指针指向的地址空间
ldmfd sp!, {r6-r10}(读) (出栈)
特殊:(寄存器不连续)
stmfd sp!, {r1-r5,lr}(写) (压栈)
ldmfd sp!, {r6-r10,pc}(读) (出栈) //r1-r5出栈给r6-r10, 将lr的值出栈给pc
程序中运用
-栈的应用-》叶子函数的调用过程
叶子函数是指一个函数内部没有调用其他函数的函数,也就是说,它是程序调用树的末端节点,不依赖于其他函数。
MOV SP,#0X40000020
MAIN:
MOV R1,#3
MOV R2,#2
BL F
ADD R3,R1,R2
T:
B T
F:
STMFD SP!,{R1,R2}
MOV R1,#5
MOV R2,#4
ADD R3,R1,R2
LDMFD SP!,{R1,R2}
MOV PC,LR
-栈的应用-》非叶子函数的调用过程
非叶子函数是指一个函数内部调用了其他函数的函数,也就是说,它不是程序调用树的末端节点,可以被其他函数调用。
MOV SP,#0X40000020
MAIN:
MOV R1,#3
MOV R2,#2
BL F
ADD R3,R1,R2
T:
B T
F:
STMFD SP!,{R1,R2,LR}
MOV R1,#5
MOV R2,#4
BL D
ADD R3,R1,R2
LDMFD SP!,{R1,R2,LR}
MOV PC,LR
D:
STMFD SP!,{R1,R2,LR}
MOV R1,#6
MOV R2,#7
ADD R3,R1,R2
LDMFD SP!,{R1,R2,LR}
MOV PC,LR
- 状态寄存器操作指令
注:刚上电是在SVC模式下
对CPSR进行读写操作
//其他都不能动CPSR (SWI 指令是linux内核有,所以arm为了匹配才有的指令)(CPSR保存cpu的状态、模式、中断中断开关、运算状态,非常重要,不能任意更改,只有一类指令能操作这个寄存器)
读cpsr指令 mrs
写cpsr指令 msr
一般情况不能修改cpsr,只能用msr命令修改,user模式下不能切换到其他模式。
@状态寄存器操作指令 mrs / msr
ldr r1,=0x12345678
mrs r0,cpsr @读cpsr的值到r0
msr cpsr_c,#0x1f @修改cpsr的值,cpsr_c 指的是后八位控制位
msr cpsr_c,#0x13 @切换到svc
msr cpsr_c,#0x10 @切换到user,非特权模式
ldr r2,=0x87654321
msr cpsr_c,#0x13 @切换到svc
流程分析
注:修改CPSR的控制域(bit[7:0]),修改CPSR时必须指定修改哪个区域;USER模式下不能修改CPSR的值,防止应用程序修改CPU状态,保护操作系统;CPSR_C修改的是CPSR的低八位ctrl(控制)域,一般都只修改C域。
- 异常中断指令
触发软中断,常用于内核的系统调用 //SWI:软中断
@异常中断指令 软中断指令swi
ldr r1,=0x12345678
ldr r2,=0x22345678
ldr r4,=0x42345678
msr cpsr_c,#0x10 @切换到user模式
ldr r3,=0x32345678
swi #1 @软中断
@执行软中断指令:spsr、lr、cpsr、pc 均发生了改变
ldr r6,=0x52345678
过程分析
为什么会出现以上情况?做完的请先自行阅读下方文档
异常源及处理过程
- 协处理器指令
操作协处理器的指令(一般用不到-----协助cpu处理数据)
1.数据运算
2.内存访问
3.与主处理器通信
MRC 将协处理器中寄存器的内容读取到ARM处理器的寄存器中
MCR 将ARM理器中寄存器的内容读取到协处理器的寄存器中
协处理器指令
- 协处理器数据运算指令
CDP
- 协处理器储存器访问指令
STC 将协处理器中的数据储存到存储器
LDC 将存储器中的数据读取到协处理器中
- 协处理器寄存器传送指令
MRC 将协处理器中寄存器的数据传送到ARM处理器中的寄存器
MCR 将ARM处理器寄存器中的数据读取到协处理器寄存器中
伪指令
本质:本身不是指令,但是cpu替换成等效的操作。
举例1:
延时一个指令周期(耗时一条指令的时间) (cpu没有这个指令)
NOP ;执行NOP和MOV R0,R0一个效果,执行NOP,cpu替换成MOV R0,R0
MOV R0,R0
LDR的两种形式
;->指令
LDR R1,[R2]
;->伪指令
LDR R1,=0x12345678 ;R1 = 0x12345678
;可以将任何一个32bit的数据放入寄存器
伪操作
指令是arm公司规定的,而伪操作是编译器规定的,不同的编译器伪操作指令不同。
(我们学的linux,用linux的编译器)
ARM重点:CPU控制硬件原理
开发板介绍
本地开发和交叉开发
- 本地开发:本地编写代码,本地编译代码,本地运行代码
- 交叉开发:本地编写代码,本地编译代码,开发板运行代码
交叉编译工具链
交叉编译工具链的安装
所在位置
cpu控制硬件原理
我们学习的所有指令,六大指令里边,只有内存访问指令(ldr/str)能访问cpu之外的内容。那cpu如何控制硬件?
load/store指令 --》操作外设寄存器
芯片有一个地址映射表。告诉你地址空间是如何映射的,便于我们找到对应的硬件地址。
我们的SoC型号是S5P6818,对应的芯片用户手册为:S5P6818X用户手册V0.00
S5P6818X用户手册V0.00
其中一章是:Memory map或Memory Controller中的一张表中可以看地址隐射关系。
硬件控制原理:
CPU不能直接控制硬件,硬件是由其对应的寄存器(存储器)来控制的
每个寄存器(存储器)都会映射到CPU寻址范围内的一段空间
CPU通过对寄存器(存储器)的读和写实现对硬件间接的控制
LED(红灯)实验
-
- 查看电路原理图
FS6818底板原理图
FS6818核心板原理图
查找时对应硬件板子上有控制灯的名字:RGBLED1,对应查看底板原理图。
-
- 查看用户芯片手册
第二章介绍了每个IO引脚的作用。---》function Table查看功能(重点看:function 0-3)
找到引脚根据自己的情况选择对应功能。
GPIO:通用的输入输出接口。 一共160个,ABCDE每组32个。
CPIOA28:A组的第28个
- 查看对应GPIO硬件映射的地址(寄存器:这里将能控制硬件的空间叫寄存器,在cpu外,有地址)--》找到寄存器描述(Register Description)
引脚是硬件,硬件控制都会隐射到内存的一个地址。所以操作硬件就是操作内存。
每个寄存器怎么控制实现不同功能:
1.GPIOxALTFN0和GPIOxALTFN1两个寄存器:控制一个GPIO引脚功能,A类GPIO有32个引脚,即需要八个字节管理A类GPIO引脚。 ‘x’--》ABCDE
2.GPIOxOUTENB寄存器:
- GPIOxOUT寄存器:
-
- 实验步骤简写
1.分析电路原理图得出LED的控制方法----》高电平亮/低电平灭
2.分析电路原理图得出LED与SOC的连接关系 - GPIOA28
3.分析芯片手册,找到对应的寄存器将GPIOA28设置成GPIO功能->GPIOAALTFN1(0xC001A024)
4.分析芯片手册,找到对应的寄存器将GPIOA28设置成OUTPUT功能->GPIOAOUTENB(0xC001A004)
5.分析芯片手册,找到对应的寄存器将GPIOA28设置输出高/低->GPIOAOUT(0xC001A000)
-
- 汇编代码实现
1》控制led灯亮代码
.text
.global _start
_start:
/*
GPIOAALTFN1
地址:0xC001A024 数据:0x00000000
GPIOAOUTENB
地址:0xC001A004 数据:0x10000000
GPIOAOUT
地址:0xC001A000 数据:0x10000000/0x00000000
*/
@ 设置GPIOA28为GPIO功能
LDR R1,=0x00000000
LDR R2,=0xC001A024
STR R1,[R2]
@ 设置GPIOA28为OUTPUT功能
LDR R1,=0x10000000
LDR R2,=0xC001A004
STR R1,[R2]
@ 设置GPIOA28输出低电平(因为本来就是亮的,这里让他熄灭)
LDR R1,=0x00000000
LDR R2,=0xC001A000
STR R1,[R2]
STOP:
B STOP @死循环
.end
虚拟机中编译生成的二级制文件.bin 如何在板子上运行?
参考步骤如下:
C语言实现绿灯闪烁
- 找绿灯
- 启用功能(往地址写值)
void delay_ms(unsigned int ms)
{
unsigned int i,j;
for(i = 0; i < ms; i++)
for(j = 0; j < 1800; j++);
}
int main()
{
*(unsigned int *)0xC001E000 |= 1<<13;
*(unsigned int *)0xC001E004 |= 1<<13;
*(unsigned int *)0xC001E020 =0;
while (1)
{
*(unsigned int *)0xC001E000 &= ~(1<<13);
delay_ms(200);
*(unsigned int *)0xC001E000 |= 1<<13;
delay_ms(200);
}
}
代码优化:结构体实现
void delay_ms(unsigned int ms)
{
unsigned int i,j;
for(i = 0; i < ms; i++)
for(j = 0; j < 1800; j++);
}
typedef struct
{
unsigned int OUT;
unsigned int OUTENB;
unsigned int A;
unsigned int B;
unsigned int C;
unsigned int A1;
unsigned int B1;
unsigned int C1;
unsigned int ALTFN0;
unsigned int ALTFN1;
}GPIO;
#define GPIOE (*(GPIO *)0XC001E000)
#define GPIOA (*(GPIO *)0XC001A000)
int main()
{
GPIOA.OUT &= ~(1<<28);
GPIOA.OUTENB |= 1<<28;
GPIOA.ALTFN1 =0;
GPIOE.OUT |= 1<<13;
GPIOE.OUTENB |= 1<<13;
GPIOE.ALTFN0 =0;
while (1)
{
GPIOE.OUT &= ~(1<<13);
delay_ms(200);
GPIOE.OUT |= 1<<13;
delay_ms(200);
}
}
代码终极优化:一条函数实现GPIO配置
自己实现
卡里没东西需要自己烧录
将SD卡插到读卡器
将读卡器插到电脑上
- 虚拟机--》可移动设备--》realtek USB3.0-CRW---》连接
- 把资料包里的sdtool压缩包放到虚拟机里,并解压(sudo tar -xvf sdtool.tar.xz)
4、在ubuntu中执行命令
sudo ./s5p6818-sdmmc.sh /dev/sdb ubootpak.bin
如果打印一下信息表示,部署成功:
6、688+1 records in
689+0 records out
352768 bytes (353 kB, 344 KiB) copied, 0.445815 s, 791 kB/s
^_^ The image is fused successfully
总结:报SD卡只读的错误,将sd卡中开关拨到lock的位置,lock靠近sd卡的触点位置
7、测试sd卡是否制作成功
将sd卡插到开发板板上,
设置开发板上的拨码开关,为sd卡启动
8、开发板重新上电,启动成功。