认识STM32 (内部)
一、STM32内部
芯片里面有什么:内核(ARM的Cortex-M3)+外设。内核相当于电脑主板上的CPU;外设相当于显卡、内存。内核和外设之间通过总线连接。
1、ICode总线
程序存在Flash中,通过ICode(Instruction Code)总线与Cortex连接。
2、驱动单元
DCode总线
数据被存放在外设内部Flash(SRAM)中,通过DCode(Data Code)访问。
System总线
访问外设的寄存器,通常说的寄存器编程就是用这条总线的。
DMA总线
数据变量拷贝时可以不占用CPU,通过DMA(Direct Memory Access)总线和DMA1、DMA2完成。
3、被动单元
闪存存储器
即FLASH存放程序指令,内核通过ICode来读取指令。
SRAM
存放变量,内核通过DCode来访问。
FSMC
可以用来扩展内存。
AHB到APB的桥
挂着很多stm32特色外设,如:GPIO、串口、IIC、SPI等。学STM32的重点就是学编程这些外设去驱动外部的各种设备。
二、存储器映射
1、寄存器操作
操作单片机的本质都是操作存储中的寄存器,实际的操作过程就是改变内存中一定的地址对应的值。最原始的办法是直接访问地址取值进行修改。
例如*(unsigned int *)(0x40010c0c) = 0xffff
2、存储器地址划分
存储器本身不具有地址信息,地址由芯片厂家分配,给存储器分配地址。STM32具有32位寻址能力,寻址内存可达32Gbit,这分为了8个Block。其中Block0——2非常重要。
序号 | 用途 |
---|---|
Block0 | Code |
Block1 | SRAM |
Block2 | 片上外设 |
3、存储器映射
而如果每一次的操作都通过绝对地址来访问的话过于麻烦,因此可以给不同的地址块重新命名。给对应的地址取别名重新定义的过程称为存储器映射。
例如51单片机的reg52.h就是进行存储器映射的一个头文件。(使用sfr关键字)
4、Block2片上外设
Block2连接着3条总线APB1、APB2、AHB。这些总线上挂着许多不同的外设。
三、C语言对寄存器的封装
以GPIO口的寄存器为例
1、封装总线和外设基地址
用宏定义对外设和总线的基地址进行取别名,如下
1 /* 外设基地址 */
2 #define PERIPH_BASE ((unsigned int)0x40000000)
3 /* 总线基地址 */
4 #define APB1PERIPH_BASE PERIPH_BASE
5 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
6 #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
7 /* GPIO 外设基地址 */
8 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
9 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
2、封装寄存器列表
如果用上述办法定义每一个GPIO的寄存器的话略显繁琐,因为每一个GPIO口都是有着一组相同功能的寄存器的。为了更方便的访问不同GPIO的寄存器,可以用C语言中结构体的语法对寄存器进行封装。如下:
1 typedef unsigned int uint32_t; /*无符号 32 位变量*/
2 typedef unsigned short int uint16_t; /*无符号 16 位变量*/
3
4 /* GPIO 寄存器列表 */
5 typedef struct {
6 uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
7 uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
8 uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
9 uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
10 uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
11 uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
12 uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
13 } GPIO_TypeDef;
之后便可以通过结构体访问寄存器了,如下:
GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOB_BASE; //把指针地址设置为宏 GPIOH_BASE 地址
GPIOx->IDR = 0xFFFF;
GPIOx->ODR = 0xFFFF;
uint32_t temp;
temp = GPIOx->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中
3、修改寄存器的位操作方法
如果只希望修改某长串地址中的某一位的话可以通过如下的方式:
置1
unsigned char a = 0x02;
//a = 0000 0010
//现在希望将a的最高位置1
a |= (1<<7);//将1左移7位,然后进行或赋值
置0
unsigned char a = 0x02;
//a = 0000 0010
//现在希望将a的第2位置0
a &= ~(1<<1);//将1左移1位,取反,然后进行与赋值
取反
unsigned char a = 0x02;
//a = 0000 0010
//现在希望将a的第2位取反
a ^= (1<<1);//将1左移1位,然后进行取反赋值
如果想操作很多位的话可以如下:
unsigned char a = 0x02;
//a = 0000 0010
//现在希望将a改为0x32
a |= 0x30;
来源:oschina
链接:https://my.oschina.net/u/4295884/blog/4357697