STM32学习笔记——1、存储器映射

知识引导

怎么学习STM32?

学习stm32的本质就是去操作它的各个外设,虽然各种型号的单片机寄存器定义库函数不一样,但是对外设的基本操作是一样的。如串口,spi、i2c这些外设的通信协议是不变的,操作这些外设的过程就是通过单片机去实现软件层次的通信协议。


什么是存储器的映射?

对单片机的操作是通过向各个寄存器里写数据实现的,这些寄存器就是一个个的存储单元,这些存储单元,就像教学楼的一个个教室一样,我们要想在无数的教室中找到我们要上课的教室,最好的办法就是给所有的教室起一个名字,比如“初三一班,A410等等”。同理我们要从无数的寄存器中找到要操作的寄存器,就需要给寄存器起个名字,这就是存储器的映射。


知识总览

Cortex-M4存储器映射

图1,是M4内核的推荐存储器映射,将4GB的存储空间分为多个区域。

图1

32位单片机的寻址范围是4GB,这个4GB是按字节编址算出来的。一个字节是8位,实际上在stm32中是以四字节(32位)对齐的,这个从地址的偏移量上来看就知道了(如下图2所示),stm32的地址偏移每次是四。

图2

STM32F407的存储器映射

图3

如图3所示,stm32f407的存储器被分为八个块,每个块0.5GB。

如何访问寄存器

以GPIOA的寄存器组为例:

已知GPIOA的起始地址为0x40020000

各寄存器的偏移地址如下:

MODER;             /*Address offset: 0x00 */

OTYPER;            /*Address offset: 0x04 */

OSPEEDR;          /*Address offset: 0x08 */

PUPDR;              /*Address offset: 0x0C */

IDR;                     /*Address offset: 0x10 */

ODR;                   /*Address offset: 0x14 */第二

BSRR;                 /*Address offset: 0x18 */

LCKR;                 /*Address offset: 0x1C */

第一种方式:直接操作寄存器

优点:编译后代码所占空间小,相比于库函数的方式代码运行速度快。

这种方式的缺点很明显。stm32的寄存器太多了,而且后期代码不利于维护。所以我们一般不使用直接操作寄存器的方式。

#define     GPIOA_BASE        ( (unsigned int ) 0x40020000 )
#define     GPIOA_ODR         ( GPIOA_BASE  +  0x14 )

//读操作
val  = *(unsigned int *) GPIOA_ODR  ;

//写操作
*(unsigned int *)  GPIOA_ODR = val  ;

//改进

#define     GPIOA_ODR    ( *(unsigned int *) ( GPIOA_BASE  +  0x14 ))

val  = GPIOA_ODR  ;    //读
GPIOA_ODR = val  ;     //写

unsingned int *是强制类型转换;*(unsingned int *)是操作指针所对应的地址(也就是指针所指向的存储区域)。

第二种方式:使用库函数

用上面的方法去定义地址,还是稍显繁琐、根据我们每一类外设对应的寄存器组地址都是连续增长的特点,我们引入 C 语言中的结构体语法对寄存器进行封装

typedef struct {
uint32_t		MODER;        	 /*Address offset: 0x00 */
uint32_t		OTYPER; 	/*Address offset: 0x04 */
uint32_t		OSPEEDR; 	/*Address offset: 0x08 */
uint32_t		PUPDR;	 	/*Address offset: 0x0C */
uint32_t		IDR; 		/*Address offset: 0x10 */
uint32_t		ODR; 		/*Address offset: 0x14 */
uint32_t		BSRR; 		/*Address offset: 0x18 */
uint32_t		LCKR; 		/*Address offset: 0x1C */
} GPIO_TypeDef;

#define     GPIOA_BASE        ( (unsigned int ) 0x40020000 )
#define     GPIOA	 ((GPIO_TypeDef *) GPIOA_BASE)

//对结构体成员的访问举例
GPIOA->MODER     =  0x20 ; 
GPIOA->OSPEEDR  =  0x16 ;

每一组寄存器都是在基地址的基础上以四个字节为增量连续增长的。正好可以定义为unsigned int类型的。

结构体的存储空间是连续的就像数组一样,只需要知道头数据的地址,就可以算出其余数据的地址,这也就是为什么数组的名字可以当做指针使用。

#define     GPIOA_BASE        ( (unsigned int ) 0x40020000 )
#define     GPIOA	 ((GPIO_TypeDef *) GPIOA_BASE)

这里指明了GPIOA的基地址以上两句就等价于下面的语句

  #define     GPIOA	 ((GPIO_TypeDef *)  ( (unsigned int ) 0x40020000 ))

这样就指明了结构体中的第一个数据的地址。

补充知识点

结构体指针的使用;

通过结构体指针可以获取结构体成员,一般有两种方式:

第一种方式:

(*pointer).memberName

“.”的优先级高于*,(*pointer)两边的括号不能少。如果去掉括号写作*pointer.memberName,那么就等效于*(pointer.memberName),这样意义就完全不对了。

第二种方式:

pointer->memberName

->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接取得结构体成员;
这也是->在C 语言中的唯一用途。

我们一般使用第二种箭头的方式来访问结构体成员。

C

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值