一、简介
以STM32F407为例,介绍了内存分布、寄存器访问、位带操作、总线架构,可以在入门时对STM32的大题结构有个基本认识,水平有限,如有错误欢迎指出。
本博客参考硬石科技STM32F407开发手册资料。
二、内存分布
首先ARM公司提供内核的技术支持,在这基础上ST公司添加外设、总线、存储器等来控制内核。
2.1内存空间分布
由于处理器(CPU)是32位的,2^32=4G,所以CPU可以很轻松的找到4G以内的地址,于是ARM公司就把RAM,ROM,寄存器,输入输出端口都组织在同一个4G的线性地址空间内,空间分为8个部分,每个部分0.5G,分布如下图。
block0是代码段:存储程序代码和常量数据。flash是1M,所以最多写1M的代码(相当多了)
block1是SRAM:192KB,就是运行内存,存储程序运行过程中产生的数据,保存临时数据
block2:AHB和APB挂载的各种外设
block3、4:FSMC用于与外部存储器设备进行通信,用于连接静态存储器设备,如SRAM
block5:外部RAM
2.2寄存器的访问
通过把片上外设的寄存器映射到外设区block2,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。
比如把0x40020000~0x400203FF 总共 0x3FF 长度的地址空间分配给GPIOA,而端口A又有PA0~15共十六个引脚,每个引脚都有各种各样的功能,要实现这么多功能,就要分配了0x3FF这么长的空间才行(实际用不了这么多),比如直接向0x40020018地址写入0x00010000就可以让PA0输出高电平,写入0X00030000就可以使得PA0,PA1都输出高电平。
为什么会这样子呢?
因为在STM32F407 芯片内部硬件上已经把 0x40020018 地址与端口 A 输出高电平这个功能挂钩了,这个挂钩介质我们命名为“寄存器”,寄存器通常是32位的,这意味着一个寄存器占用4个地址,每个外设都有对应的一组寄存器,且寄存器的存储数据空间和寄存器的地址空间都在外设区,寄存器是硬件电路的一部分,不占用SRAM或Flash存储空间。
疑问:
这4G空间在哪里?
答:这4G空间是虚拟地址空间,这是一个逻辑概念,表示处理器可以寻址的范围,实际存在的硬件存储器,比如SRAM、Flash存储器,以及外设寄存器。它们被映射到虚拟地址 空间中的特定位置。(这是一个映射关系)
虽然地址空间是4GB,但实际物理存储器和外设的大小远小于4GB。例如:
-
内部Flash:STM32F407通常有1MB的内部Flash,映射到代码区的一个子区域。
-
内部SRAM:STM32F407有192KB的内部SRAM,映射到SRAM区的一个子区域。
-
外设:各个外设的寄存器映射到外设区的特定地址范围。
2.3位带操作
位带区域分为两部分:SRAM位带区和外设位带区。
这两个区域都有1M的位带区,而f407的SRAM和外设地址占用都小于1M,也就是外设寄存器以及SRAM里的数据都可以位操作。
如何操作?
我们可以通过一个叫别名地址的地址来访问每一位。
别名地址 = 位带别名区基地址 + (字节偏移量 × 32) + (位偏移量 × 4)
其中:
-
字节偏移量是相对于位带区基地址的字节偏移。
-
位偏移量是要访问的位在字节中的位置(0-7)。
-
基地址SRAM区是0x22000000 外设区是0x42000000
假设我们要访问外设位带区中的一个位,具体示例如下:
-
原始地址:0x40020000(假设这是GPIOA的ODR寄存器的地址)
-
要访问的位:ODR寄存器的第0位(PA0)
计算位带别名地址:
-
字节偏移量 = 0x20000
-
位偏移量 = 0
-
别名地址 = 0x42000000 + (0x20000 × 32) + (0 × 4) = 0x42400000
通过访问0x42400000地址,可以直接读写GPIOA_ODR寄存器的第0位。
#define BITBAND_ALIAS_BASE 0x42000000
#define PERIPH_BASE 0x40000000
#define BITBAND_PERI(addr, bitnum) ((BITBAND_ALIAS_BASE + ((addr - PERIPH_BASE) * 32) + (bitnum * 4)))
#define GPIOA_ODR 0x40020014
#define PA0_BIT 0
#define PA0_BITBAND_ADDR BITBAND_PERI(GPIOA_ODR, PA0_BIT)
#define PA0 (*(volatile uint32_t *)PA0_BITBAND_ADDR)
void set_PA0_high(void) {
PA0 = 1; // 设置PA0为高电平
}
void set_PA0_low(void) {
PA0 = 0; // 设置PA0为低电平
}
三、总线架构
STM32F407的总线架构包括多个总线主设备和总线从设备,通过一个总线矩阵连接起来。
总线架构图:
怎么读这张图呢?
首先看上面的黄色,这8条主控总线(Cortex-M4 内核 I 总线,D 总线和 S 总线;DMA1 存储器总线,DMA2 存储器总线;DMA2 外设总线;以太网 DMA 纵向的;USB OTG HS DMA 总线;)
(主控可以发起总线事务的设备)
(被控响应总线事务的设备)
可以去访问蓝色区域的7条被控总线(内部 FLASH ICode 总线;内部 FLASH DCode 总线;主要内部 SRAM1(112KB);辅助内部 SRAM2(16KB);AHB1 外设和 AHB2 外设,FSMC 控制器)
怎么访问?访问同时吗?怎么区分什么时候谁访问谁?
内核可以从Flash存储器读取访问指令
总线矩阵通过仲裁机制来决定哪个主设备在特定时间可以访问哪个从设备。
总线矩阵允许多个主设备同时访问不同的从设备。
对于CCM:就是一个直接接入到内核上的一个RAM存储器,速度超级快,因为内核可以直接访问它,而不需要通过总线矩阵。
// 定义在CCM数据RAM中的变量
uint32_t __attribute__((section(".ccmram"))) fastData[1024];
int main(void) {
// 使用CCM数据RAM中的变量
fastData[0] = 0xDEADBEEF;
// 其他代码
}
四、运行过程
综上,stm32的大致运行过程如下
-
启动(Booting):
- 上电或复位后,芯片开始执行启动代码,通常从Flash的特定地址开始。
- 启动代码初始化堆栈指针和系统时钟配置,然后跳转到主程序入口。
-
初始化:
- 系统初始化,包括配置时钟系统(RCC)、中断向量表(NVIC)、外设初始化等。
- 初始化内存,包括SRAM、堆栈和全局变量。
-
执行主程序:
- Cortex-M4内核通过ICode总线读取Flash中的程序指令并执行。
- 在执行过程中,根据程序需求,内核可能需要通过总线从某个外设(如GPIO、UART、ADC等)读取数据。
- 数据读取后,内核进行相应的计算和处理。
-
外设交互:
- 内核通过总线与外设进行数据交互。计算过程中需要的外设数据通过总线读取。
- 计算结果通过总线传送到特定的外设,外设接收到数据后执行相应的动作。
-
SRAM操作:
- 计算过程中会产生许多中间变量,内核需要频繁地与SRAM进行数据传输。
- 内核通过总线从SRAM读取和写入数据,以存储和读取中间计算结果。
-
中断处理:
- 当外设产生中断(如定时器中断、UART接收中断等)时,NVIC(中断控制器)将中断请求发送给内核。
- 内核暂停当前执行的任务,跳转到对应的中断服务程序(ISR)处理中断。
- 中断处理完成后,内核返回继续执行主程序。
-
低功耗管理:
- 根据应用需求,内核可以进入低功耗模式(如睡眠模式、停止模式等),以节省能源。
- 当有外部事件或定时器触发时,系统可以从低功耗模式唤醒并继续执行任务。
-
数据通信:
- 内核通过DMA(直接内存访问)控制器实现高效的数据传输,而不占用处理器资源。
- 内核配置DMA控制器,从外部存储器或外设读取数据并写入SRAM,或从SRAM读取数据并写入外设。