https://zhuanlan.zhihu.com/p/22048373
阿军最近在忙着血氧手环嵌入式系统的技术预研,因为整个嵌入式的任务由阿军一个人负责,而阿军又对这个不太了解,只能从头研究,最近有点忙,过了八月份应该会更新比较频繁吧。
阿军把最近学习ARM芯片和做嵌入式编程的一点体会,如有错误欢迎指正。
---------------------------------------------------
本文着眼于以下内容:
- ARM处理器基本的存储模型;
- C语言示例说明程序在芯片中的存储模型;
- C程序如何在ARM裸板上运行
- C和嵌入式硬件开发
nRF51822[ARM CORTEX-M0]寻址空间,如下图所示
arm 32位芯片地址宽度为32位,通过内部总线挂载设备:
ARM CPU可以直接通过地址总线读写RAM(高速),读取非易失性存储(一般速度),擦除及写入非易失性存储(慢速),读写外设寄存器(一般速度)。
其中,非易失性存储用于存储代码、恒定数据,RAM用于存储指令执行过程中的临时数据,读写外设寄存器用于控制GPIO、UART等外设。
硬件层的设计决定了软件层的设计
存储器为了综合利用两种存储器,设计了这样的软件架构:
- 数据分为固定不变的固定数据(代码也可以理解为一种数据),代码执行过程中一直存在的变量,代码执行过程中临时产生的变量
- 代码和固定的数据保存在非易失性存储中,断电从头开始执行,数据和代码一直保留;
- 全局变量和临时变量存储在易失性存储中,需要由非易失性存储器中的代码和数据初始化;
- CPU从非易失性存储器中加载指令,系统开始时必须初始化全局变量(把全局变量的初始值由非易失性存储移动到易失性存储器),初始化临时变量区。
- 易失性存储器中用静态数据区存储静态变量和全局变量,在堆(通过malloc和free管理)和栈中存储临时变量。
这个原因导致C中指针的引入异常方便与强大,不论是数据、寄存器、函数等都能通过指针直观的操作
对应C中的基本数据类型
...除此之外还有很多硬件和软件的对应,了解系统底层实现原理与熟练掌握C语言相辅相成。
C代码与ARM 存储器结构的对应
const 修饰的数据存储在非易失性存储器,运行时不能修改,比如FLASH、磁盘中
全局数据和static 修饰的变量存储在易失性存储的静态数据区,在程序的整个生命周期内,其一直存在
系统临时变量(比如函数中的变量,函数调用上下文等)保存在易失性存储器的栈中
注意:以上三种数据类型的存储一般由编译器自动控制,我们无法干预
通过malloc和free手动申请及释放,这个用起来比较危险,可能会出现内存溢出,野指针等各种复杂的问题,但是给程序设计人员很大的自由,很强大
C库中的动态内存分配策略采用线性方案,就是寻找合适的没有被使用的堆区域,然后把内存分配出去
随着malloc和free的进行,就会产生内存碎片
采用合适的策略分配和整理内存是操作系统很重要的任务,例如FreeRTOS中就实现了5种动态内存管理函数
C语言之所以是嵌入式系统和操作系统可选择唯一语言的原因就是其强大的指针和对内存的精确管理
示例:编写简单的C语言,从其在MDK上编译连接生成的map文件,了解C语言的存储。(待补充)
nRF51822(ARM CORTEX-M0)的启动代码
现场分析nRF51822的启动代码及汇编执行顺序,了解程序是如何在CPU上加载启动的
启动代码(汇编)
IF :DEF: __STACK_SIZE ;预编译指令 #ifdef _STACK_SIZE
Stack_Size EQU __STACK_SIZE ;#define Stack_Size __STACK_SIZE
ELSE ;#else
Stack_Size EQU 2048 ;#define Stack_Size 2048
ENDIF ;#endif
;AREA 命令指示汇编器汇编一个新的代码段或数据段。
;段是独立的、指定的、不可见的代码或数据块,它们由链接器处理.
;段是独立的、命名的、不可分割的代码或数据序列。一个代码段是生成一个应用程序的最低要求
;默认情况下,ELF 段在四字节边界上对齐。expression 可以拥有 0 到 31 的任何整数。
;段在 2expression 字节边界上对齐
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;代码段名称为STACK,未初始化,允许读写,8字节对齐
Stack_Mem SPACE Stack_Size ;分配Stack_Size的栈空间,首地址赋给Stack_Mem
__initial_sp ; 栈顶指针,全局变量
;基本同栈,初始化分配堆
IF :DEF: __HEAP_SIZE
Heap_Size EQU __HEAP_SIZE
ELSE
Heap_Size EQU 2048
ENDIF
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8 ;8字节对齐
THUMB ;THUMB命令模式
; Vector Table Mapped to Address 0 at Reset,重启时程序从这里运行
AREA RESET, DATA, READONLY ;代码段名称为RESET,DATA类型,只读
EXPORT __Vectors ;中断向量表
EXPORT __Vectors_End ;中断向量表结束指针
EXPORT __Vectors_Size ;中断向量表大小
__Vectors DCD __initial_sp ; Top of Stack 中断向量表首位为栈指针
DCD Reset_Handler ;重启中断
DCD NMI_Handler ;不可屏蔽中断
DCD HardFault_Handler ;硬件错误中断
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ;监控调用模式中断
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler
DCD SysTick_Handler
; External Interrupts
DCD POWER_CLOCK_IRQHandler
DCD RADIO_IRQHandler
DCD UART0_IRQHandler
DCD SPI0_TWI0_IRQHandler
DCD SPI1_TWI1_IRQHandler
DCD 0 ; Reserved
DCD GPIOTE_IRQHandler
DCD ADC_IRQHandler
DCD TIMER0_IRQHandler
DCD TIMER1_IRQHandler
DCD TIMER2_IRQHandler
DCD RTC0_IRQHandler
DCD TEMP_IRQHandler
DCD RNG_IRQHandler
DCD ECB_IRQHandler
DCD CCM_AAR_IRQHandler
DCD WDT_IRQHandler
DCD RTC1_IRQHandler
DCD QDEC_IRQHandler
DCD LPCOMP_IRQHandler
DCD SWI0_IRQHandler
DCD SWI1_IRQHandler
DCD SWI2_IRQHandler
DCD SWI3_IRQHandler
DCD SWI4_IRQHandler
DCD SWI5_IRQHandler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors ;计算中断向量表的大小
AREA |.text|, CODE, READONLY ;代码段,|.text|表示由C语言产生的代码段,CODE类型,只读
; Reset Handler
NRF_POWER_RAMON_ADDRESS EQU 0x40000524 ; NRF_POWER->RAMON address
NRF_POWER_RAMONB_ADDRESS EQU 0x40000554 ; NRF_POWER->RAMONB address
NRF_POWER_RAMONx_RAMxON_ONMODE_Msk EQU 0x3 ; All RAM blocks on in onmode bit mask
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ;[WEAK]修饰代表其他文件有函数定义优先调用
IMPORT SystemInit ;从外部调用SystemInit
IMPORT __main ;从外部调用__main
;以下代码不同芯片不一样,这个是设定RAM块开启关闭的配置,这里配置为全部开启
MOVS R1, #NRF_POWER_RAMONx_RAMxON_ONMODE_Msk
LDR R0, =NRF_POWER_RAMON_ADDRESS
LDR R2, [R0]
ORRS R2, R2, R1
STR R2, [R0]
LDR R0, =NRF_POWER_RAMONB_ADDRESS
LDR R2, [R0]
ORRS R2, R2, R1
STR R2, [R0]
LDR R0, =SystemInit
BLX R0 ;无返回调用SystemInit
LDR R0, =__main
BX R0 ;有返回调用__main
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT POWER_CLOCK_IRQHandler [WEAK]
EXPORT RADIO_IRQHandler [WEAK]
EXPORT UART0_IRQHandler [WEAK]
EXPORT SPI0_TWI0_IRQHandler [WEAK]
EXPORT SPI1_TWI1_IRQHandler [WEAK]
EXPORT GPIOTE_IRQHandler [WEAK]
EXPORT ADC_IRQHandler [WEAK]
EXPORT TIMER0_IRQHandler [WEAK]
EXPORT TIMER1_IRQHandler [WEAK]
EXPORT TIMER2_IRQHandler [WEAK]
EXPORT RTC0_IRQHandler [WEAK]
EXPORT TEMP_IRQHandler [WEAK]
EXPORT RNG_IRQHandler [WEAK]
EXPORT ECB_IRQHandler [WEAK]
EXPORT CCM_AAR_IRQHandler [WEAK]
EXPORT WDT_IRQHandler [WEAK]
EXPORT RTC1_IRQHandler [WEAK]
EXPORT QDEC_IRQHandler [WEAK]
EXPORT LPCOMP_IRQHandler [WEAK]
EXPORT SWI0_IRQHandler [WEAK]
EXPORT SWI1_IRQHandler [WEAK]
EXPORT SWI2_IRQHandler [WEAK]
EXPORT SWI3_IRQHandler [WEAK]
EXPORT SWI4_IRQHandler [WEAK]
EXPORT SWI5_IRQHandler [WEAK]
POWER_CLOCK_IRQHandler
RADIO_IRQHandler
UART0_IRQHandler
SPI0_TWI0_IRQHandler
SPI1_TWI1_IRQHandler
GPIOTE_IRQHandler
ADC_IRQHandler
TIMER0_IRQHandler
TIMER1_IRQHandler
TIMER2_IRQHandler
RTC0_IRQHandler
TEMP_IRQHandler
RNG_IRQHandler
ECB_IRQHandler
CCM_AAR_IRQHandler
WDT_IRQHandler
RTC1_IRQHandler
QDEC_IRQHandler
LPCOMP_IRQHandler
SWI0_IRQHandler
SWI1_IRQHandler
SWI2_IRQHandler
SWI3_IRQHandler
SWI4_IRQHandler
SWI5_IRQHandler
B .
ENDP
ALIGN
; User Initial Stack & Heap,编译器预处理命令
IF :DEF:__MICROLIB ;#ifdef __MICROLIB
EXPORT __initial_sp ;堆栈的设置采用__MICROLIB库中的策略
EXPORT __heap_base
EXPORT __heap_limit
ELSE ;#else
IMPORT __use_two_region_memory ;外部定义的两段存储模式函数
EXPORT __user_initial_stackheap ;用户分配堆栈的地址
;寄存器R0,R2存储管理heap
;寄存器R1,R3管理statck
__user_initial_stackheap PROC
LDR R0, = Heap_Mem
LDR R1, = (Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ENDP
ALIGN
ENDIF
END
其中调用的是__main()函数,而非main()函数,因为C语言在__main()函数里封装了两个函数:
这两个函数完成C语言运行时的构建,在构建好之后进入main函数执行程序
ARM SOC的控制
这里涉及到的技术点就是中断转发了,每一部分都可以按照正常的程序进行编写,MBR负责所有中断的转发,要注意这样转发会有us级的延迟。