ARM内核控制外设寄存器
1、ARM内核寄存器组(M3为例)
Cortex‐M3 处理器拥有 R0‐R15 的寄存器组。其中 R13 作为堆栈指针 SP。SP 有两个,但在同一 时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。
R0-R12:通用寄存器
通用寄存器,用于数据操作。比如我们常用的加减乘除。MOV R0,#1(给R0寄存器赋值1)
R13(SP): 两个堆栈指针:
主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包 括中断服务例程)
进程堆栈指针(PSP):由用户的应用程序代码使用。
R14(LR):连接寄存器
当我们的程序调用子程序时,PC寄存器的值被改变,执行完子程序,想要返回主程序执行,就需要将PC修改回,调用子程序之前的地址。这个地址就存储在LR当中。
R15 (PC):程序计数寄存器
PC寄存器指向我们当前运行程序的地址,修改PC寄存器的值能够改变程序的执行位置。
程序的执行就是内核根据PC中的地址,找到程序执行位置,对内存中的程序进行读取,期间通过通用寄存器进行数据的存储运算。
2、内存映射(M3为例)
内核可以直接读写R0、R2、PC这些寄存器,但是我们在单片机的程序设计中经常需要控制片上外设,像GPIO、USART、IIC等这些外设有自己独立的寄存器,该怎么去访问?
前面的内容介绍过了,内核使用通用寄存器进行数据的运算,PC的读写控制程序执行的地址,返回地址用LR进行存储,外面的寄存器肯定是不能这样了。但是内核可以通过对内存的读写操作来读写内核外部的寄存器。下面我们先来看下M3内核的内存映射图。

在内存映射中有程序的存储地址、 外围设备地址等。硬件结构上,片上外设的寄存器连在了外围设备这段地址上(0X40000000--0x5FFFFFFF)
这里我们再看个片上外设的寄存器,以GPIO的为例。

这个寄存器是控制GPIO端口模式的,我们可以看到偏移地址为0x00,这里还需要个基地址,基地址加上偏移地址就是我们寄存器的准确地址了。
基地址我们可以理解为在上面的存储器映射表中,0X40000000开始接片上外设的寄存器,这个地址就可以理解为基地址,偏移地址就是在这个地址的基础上向上偏移了多少,接的我们需要使用的寄存器。当然实际中,这段 0X40000000开始的地址还会被细分,比如我们的GPIOA、GPIOB。但是追根到底是我们需要用到的每一个寄存器,都会在这上面找到一个具体的地址,这个地址就相当于内核对外的接口,一共排列了4G大小的长度,寄存器在某一个位置通过导线连接这样就能够和内核通信了。
我们控制时,以上面的寄存器为例,内核对该寄存器的地址0和1位写11就可以控制PIN0引脚为模拟模式了。这里的可以看到32位一个分成了16组,每2位控制一个PIN引脚。
再举个例子如果我们想将GPIOA的第0个引脚,即PA0配置为通用输出模式,那么我们就可以找到GPIOA的端口模式寄存器地址,然后直接给这个地址里的0和1位分别赋值1和0。
一个完整镜像的程序入口:
对于在flash中存储的一个完整的程序代码,其起始部分应该为向量表,向量表的内容格式固定如下表(参考《cortex-M3权威指南》7.3节)
上电后的向量表:

由表可知,对于起始存储地址为0的一段完整程序,其首地址处存放的是MSP的初始值,偏移4字节的地址处存放的是PC指针的初始值,我们要运行这段完整的程序,只需将这段完整程序的SP、PC初始值赋给SP和PC寄存器即可,具体实现的函数如下:
__asm void boot_jump(uint32_t address)
{
LDR SP, [R0] ;
LDR PC, [R0, #4] ;
}
对于此函数的解释:__asm是MDK的编译器提供的嵌入汇编的指令
函数体中两行汇编代码的功能分别为:
LDR SP, [R0]:把R0中的值作为地址,将此地址中的值赋给SP
LDR PC ,[R0, #4]:把R0中的值加4作为地址,将此地址中的值赋给PC
这里涉及到一个问题,r0中的值是什么?我们根据ATPCS(ARM-THUMBprocedure call standard)可知,对于参数少于等于4的函数,参数是通过R0~R3传递的,第一个参数放在R0中,依次类推。所以这里的R0存放的正式UserApp的起始地址,回过头再看前面的两行汇编代码,它们做的事正是将UserApp的SP、PC初始值赋给相应寄存器,达到开始运行UsrApp的目的。