stm32学习笔记——基本的地址操作

一、stm32的存储器
在这里插入图片描述
一共4G大小,地址范围0x0000 0000到0xFFFF FFFF,分成8个块(Block0~Block7),每块大小为512M。
其中片上外设地址(0x4000 0000)-(0x5FFFFFFF)
在这里插入图片描述
在这里插入图片描述
一共有3条总线:APB1、APB2、AHB。

总线名称总线基地址
APB10x4000 0000
APB20x4001 0000
AHB0x4001 8000

APB1、APB2和AHB都是在外设地址(0x4000 0000)偏移,分别偏移0x0,0x0001 0000
和0x0002 0000。
在这里插入图片描述
在这里插入图片描述
相应的寄存器,又是在挂载总线的地址上进行偏移的。

二、stm32最最底层的地址操作。
以PC13引脚输出高低电平

int main(void{
	*unsigned int*0x4002 1018 |=(1<<4);//打开时钟
	*unsigned int*0x40011004 &= ~(0x0F<<(4*5));//对应位清零
    *unsigned int*0x40011004 |= (1<<20);//配置输出模式
    *unsigned int*0x4001 100C&=~(1<<13)//配置输出低电平
    *unsigned int*0x4001 100C|=(1<<13)//配置输出高电平
}

根据单片机的地址映射,查找到相应总线地址,再去查找相应的GPIO的地址(可直接查表,也可根据相对总线的地址去算偏移地址),在相应的GPIO下面,又有对应的寄存器的地址,最后就可以算出要操作的寄存器的地址。

找到相应的地址后,这个还只是一个数字,跟程序没有关系,通过定义一个(unsigned int*)类型的指针,把这个数字转换成单片机能够识别的相应的地址,再在这个地址前面加上*,如*(unsigned int*)0x4002 1018,就可以对它进行赋值了,*表示取地址的内容

下面的程序和上面的是一个意思,只是把相应的地址操作用#define替换成了相应的寄存器名字。

#define PERIPH_BASE   0x4000 0000
//片上外设基地址
#define  APB1PERIPH_BASE   PERIPH_BASE     // APB1总线地址
#define  APB2PERIPH_BASE   (PERIPH_BASE+0x00010000)  //APB2总线地址
#define  AHBPERIPH_BASE   (PERIPH_BASE+0x00020000)  //AHB总线地址

#define  RCC_BASE  (AHBPERIPH_BASE+0x1000)   //时钟地址
#define  RCC_APB2ENR  *(unsigned int*)(RCC_BASE+0x18)  
//APB2外设时钟使能寄存器地址

#define  GPIOC_BASE    (APB2PERIPH_BASE+0x00010000)   //GPIOC地址
//GPIOC相关寄存器的地址
#define GPIOC_CRL          *(unsigned int *)( GPIOC_BASE+0x00)
#define GPIOC_CRH          *(unsigned int *)( GPIOC_BASE+0x04)
#define GPIOC_IDR          *(unsigned int *)( GPIOC_BASE+0x08)
#define GPIOC_ODR          *(unsigned int *)( GPIOC_BASE+0x0C)
#define GPIOC_BSRR         *(unsigned int *)( GPIOC_BASE+0x10)
#define GPIOC_BRR          *(unsigned int *)( GPIOC_BASE+0x14)
#define GPIOC_LCKR         *(unsigned int *)( GPIOC_BASE+0x18)

这些宏定义可直接定义在头文件中。
以上实现过程都是先找到基地址,再经过偏移取找到相应的地址。
修改后就变成了下满的可读性强一点的代码了。

int main(void{
	RCC_APB2 |=(1<<4);//打开时钟
	GPIOC_CRH &= ~(0x0F<<(4*5));//配置输出模式
    GPIOC_CRH |= (1<<20);
    GPIOC_ODR&=~(1<<13);//配置输出低电平
    GPIOC_ODR|=(1<<13);//配置输出高电平
}

三、结构体的方式把同一外设的寄存器归类

stm32固件库中的外设基本上都是通过结构体的方式,把相应的外设的寄存器定义在一起的。

由于GPIOA—GPIOG有众多组,每组有7个寄存器,如果每次都去找地址再用宏替换,就会写很多,实际上每个GPIO组,里面的7个寄存器都是一样的,所以可以直接把这七个寄存器定义在一起(采用结构体方式)。

typedef unsigned int uint32_t; //(4字节)
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

结构体中定义的变量都是连续排列的,恰好每个寄存器都是32位的,占用4个字节,这个就和每个寄存器在实际的存储器映像中偏移量是一样的了,看上面的结构体,我们只需要,定义一个结构体指针,让他指向需要使用到的GPIO口的地址就可以了。就相当于给了一个入口地址,再用结构体指针,就很容易的引用其中的成员变量了。

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)

这条语句中,GPIOC_BASE是之前用宏定义过的GPIOC的地址(只是一个数值),前面加上一个(GPIO_TypeDef*)把他转换为单片机能够识别的地址。这里的GPIOC_BASE就相当于给这个结构体传递了一个GPIO口的地址,然后就可以很方便的引用GPIO口中的寄存器了。

上面这条语句的意思就是用GPIOC替换替换了 (GPIO_TypeDef*)类型的GPIOC的地址

如果要引用GPIOC中的寄存器,用结构体成员指针就ok。

GPIOC->CRH    
GPIOC->ODR

时钟相关的寄存器定义也和GPIO口差不多

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

然后是在相应的头文件也应该进行更改

Stm32f10x.h

#define PERIPH_BASE           ((unsigned int)0x40000000) 
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
/* AHB总线基地址 */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
/*GPIOC外设基地址*/
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)	
 /*RCC外设基地址*/
#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)
typedef unsigned int uint32_t;
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

四、编写头文件
在这里插入图片描述
说明:BSRR寄存器,低16位是用来置1的,高16位是用来置0的。
都是1有效,0不对相应的位产生影响。
在这里插入图片描述
说明:BRR寄存器是用来置0,高16位不用,低16位置0,高电平有效

头文件中,一般写入 宏替换 函数的声明 结构体类型等
比如要定义一个控制GPIO的头文件 stm32f10x_gpio.h

#ifndef  __STM32F10X_GPIO_H
#define  __STM32F10X_GPIO_H    //头文件中声明头文件的格式

#include "stm32f10x.h"   //引用的另一个头文件

#define GPIO_Pin_0  ((uint16_t)0x0001)//二进制:0b0000 0001
#define GPIO_Pin_1  ((uint16_t)0x0002)//二进制:0b0000 0010
#define GPIO_Pin_2  ((uint16_t)0x0004)//二进制:0b0000 0100
#define GPIO_Pin_3  ((uint16_t)0x0008)
#define GPIO_Pin_4  ((uint16_t)0x0010)
#define GPIO_Pin_5  ((uint16_t)0x0020)
#define GPIO_Pin_6  ((uint16_t)0x0040)
#define GPIO_Pin_7  ((uint16_t)0x0080)
#define GPIO_Pin_8  ((uint16_t)0x0100)
#define GPIO_Pin_9  ((uint16_t)0x0200)
#define GPIO_Pin_10  ((uint16_t)0x0400)
#define GPIO_Pin_11  ((uint16_t)0x0800)
#define GPIO_Pin_12  ((uint16_t)0x1000)
#define GPIO_Pin_13  ((uint16_t)0x2000)
#define GPIO_Pin_14  ((uint16_t)0x4000)
#define GPIO_Pin_15  ((uint16_t)0x8000)
#define GPIO_Pin_all  ((uint16_t)0xFFFF)

void GPIO_SetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef * GPIOx,uint16_t GPIO_Pin);

#endif  //第一行,第二行,最后一行都是固定格式

其中声明了两个函数在stm32f10x_gpio.c中

#include "stm32f10x_gpio.h"   
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
    GPIOx->BSRR |= GPIO_Pin;  //用BSRR寄存器置1
}
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
     GPIOx->BRR |= GPIO_Pin;  //用BRR寄存器置0
}

定义这两个函数之后,就可以直接调用了。

GPIO_ResetBits(GPIOC,GPIO_Pin_0);

这个函数中GPIOC就是之前讲过的用宏替换成了一个结构体指针了。

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)

GPIO_Pin_0是用二进制码在头文件中定义过了的。

stm32f10x_gpio.h中调用的一个头文件
Stm32f10x.h

#ifndef  __STM32F10X_H
#define __STM32F10X_H

#define PERIPH_BASE           ((unsigned int)0x40000000)

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
/* AHB总线基地址 */
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)

/*GPIOC外设基地址*/
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)	
	
/*RCC外设基地址*/
#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)

typedef unsigned int uint32_t; 	
typedef unsigned short uint16_t;
typedef struct 
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

typedef struct 
{
	uint32_t CR;
	uint32_t CFGR;
	uint32_t CIR;
	uint32_t APB2RSTR;
	uint32_t APB1RSTR;
	uint32_t AHBENR;
	uint32_t APB2ENR;
	uint32_t APB1ENR;
	uint32_t BDCR;
	uint32_t CSR;
}RCC_TypeDef;

#define  GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)
#define  RCC  ((RCC_TypeDef*)RCC_BASE)

#endif

整个主程序

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main(void)
{
	RCC->APB2ENR |= 1<<4;
	GPIOC->CRH &=~(0x0F<<(4*5));
	GPIOC->CRH |=(1<<(4*5));
	GPIO_ResetBits(GPIOC,GPIO_Pin_0);	
	while(1);
}
void SystemInit()
{
}

这篇文章主要是帮助理解stm32固件库的写法,了解一下就差不多了,如果是寄存器开发的话,还是用固件库的模板,直接用结构体成员指针找到对应的寄存器直接操作就好了,里面的结构体都是固件库中定义好的。如果用库函数的形式开发,根本用不上这些。

  • 8
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值