stm32学习第二天

210 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、宏定义访问寄存器

因为通过寄存器操作GPIO比较麻烦,不可能每次都去找对应的GPIO地址,所以我们就可以使用宏定义来把一部分GPIO口进行定义,方便操作

在这里插入图片描述

首先要了解的是所有的外设都放在这这三个地方,也就是对应的GPIO外设都能在这里找到,为了方便,STM32也已经对相应的外设地址进行Lee分类: 在这里插入图片描述APB1的第一个地址就是0X4000 0000; APB1的第一个首地址就是0X4000 0000;

APB2的第一个地址就是0X4001 0000;相对于APB1就是偏移了0X10000这么多。

在这里插入图片描述为了方便记忆,我们这里并不从AHB的首地址开始记忆,因为他的前两个地址也没啥大用,所以我们直接DMA1开始,可以看到DMA1的地址是0X4002 0000, 相对于APB1就是偏移了0X20000这么多。

那么我们就可以做一个相对应的宏定义出来

#define PERIPH_BASH ((unsigned int)0X40000000)		//所有外设的基地址
#define APB1PERIPH_BASH  (PERIPH_BASH)				//把所有外设的基地址设置为APB1的基地址
#define APB2PERIPH_BASH  (PERIPH_BASH+0X10000)		//APB2的基地址
#define AHBPERIPH_BASH  (PERIPH_BASH+0X20000)		//AHB的基地址

对应的基地址定义出来后是不是就可以定义详细的端口地址呢 因为上节课我们用的GIOPB,这里我们还是用GPIOB作为的例子

在这里插入图片描述

这里我们可以看到GPIOB是在APB2上的,偏移量是0X0C00;

#define GPIOB_BASH  (APB2PERIPH_BASH + 0X0C00)

寄存器找到了。我们接下来就要去打开时钟了哇。 同样的道理 在这里插入图片描述

#define RCC_BASH  (AHBPERIPH_BASH + 0X1000)

找到控制GPIOB的地址的时钟: 在这里插入图片描述 他相对于时钟基地址偏移量是0X18; 所以代码

#define RCC_ APB2ENR *(unsigned int *)(RCC_BASH + 0X18)

为什么这里要进行强转,你可以认为这是一个具体的GPIO口,所以需要,我是这样。 同样的道理我们是不是可以把GPIOB对应的ODR和CRL表示出来:

#define GPIOB_CRL *(unsigned int *)(GPIOB_BASH + 0X00)
#define GPIOB_CRH *(unsigned int *)(GPIOB_BASH + 0X40)
#define GPIOB_ODR *(unsigned int *)(GPIOB_BASHH + 0X0C)

是不是可以整合一下下:

#define PERIPH_BASH (*(unsigned int)0X40000000)		//所有外设的基地址
#define APB1PERIPH_BASH  (PERIPH_BASH)				//把所有外设的基地址设置为APB1的基地址
#define APB2PERIPH_BASH  (PERIPH_BASH+0X10000)		//APB2的基地址
#define AHBPERIPH_BASH  (PERIPH_BASH+0X20000)		//AHB的基地址

#define GPIOB_BASH  (APB2PERIPH_BASH + 0X0C00)		//GPIOB的基地址

#define RCC_BASH  (AHBPERIPH_BASH + 0X1000)
#define RCC_APB2ENR *(unsigned int *)(RCC_BASH + 0X18)	//配置时钟
#define GPIOB_CRL *(unsigned int *)(GPIOB_BASH + 0X00)	//配置CRL
#define GPIOB_CRH *(unsigned int *)(GPIOB_BASH + 0X40)	//
#define GPIOB_ODR *(unsigned int *)(GPIOB_BASH + 0X0C)	//配置ODRR

我们是不是可以通过宏定义来电亮我们的灯呢! stm32f10x.h头文件

#define PERIPH_BASH ((unsigned int)0X40000000)		//所有外设的基地址
#define APB1PERIPH_BASH  (PERIPH_BASH)				//把所有外设的基地址设置为APB1的基地址
#define APB2PERIPH_BASH  (PERIPH_BASH+0X10000)		//APB2的基地址
#define AHBPERIPH_BASH  (PERIPH_BASH+0X20000)		//AHB的基地址

#define GPIOB_BASH (APB2PERIPH_BASH + 0X0C00)		//GPIOB的基地址

#define RCC_BASH (AHBPERIPH_BASH + 0X1000)
#define RCC_APB2ENR (*(unsigned int*)(RCC_BASH + 0X18)) //配置时钟
#define GPIOB_CRL (*(unsigned int*)(GPIOB_BASH + 0X00))	//配置CRL
#define GPIOB_CRH (*(unsigned int*)(GPIOB_BASH + 0X40))	//
#define GPIOB_ODR (*(unsigned int*)(GPIOB_BASH + 0X0C))	//配置ODRR


#include "stm32f10x.h"

void SystemInit(void)
{
	//函数体为空,目的是为了骗过编译器不报错
}

int main(void)
#if 0 //条件为假不执行
{
		//打开时钟
		*(unsigned int *)0X40021018 |= 0X08;
		//配置寄存器模式
		*(unsigned int *)0X40010C00 |= 0X01;
		//配置输出模式
		*(unsigned int *)0X40010C0C &=~ 0X02;
}
#else  //执行这段
{
		//打开时钟
  		RCC_APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB_CRL |= 0X01;
		//配置输出模式
		GPIOB_ODR &=~ 0X02;
	
#endif
}


同样的板子也会亮唷、

二、结构体访问寄存器

为什么要用结构体来访问地址,因为我们在操作寄存器的时候,操作的是都寄存器的绝对地址,如果每个外设寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 32 个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。 在这里插入图片描述 从上表可以看出,每个GPIO的寄存器都是在GPIO的基地址上进行偏移运算的。所以我们可以定义一个结构体:

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_IypeDef;

#define GPIOB ((GPIO_IypeDef*)GPIOB_BASH)

我们将这个代码放在头文件件里面,并将之前的头文件宏定义注释掉 stm32f10x.h:

#define PERIPH_BASH ((unsigned int)0X40000000)		//所有外设的基地址
#define APB1PERIPH_BASH  (PERIPH_BASH)				//把所有外设的基地址设置为APB1的基地址
#define APB2PERIPH_BASH  (PERIPH_BASH+0X10000)		//APB2的基地址
#define AHBPERIPH_BASH  (PERIPH_BASH+0X20000)		//AHB的基地址

#define GPIOB_BASH (APB2PERIPH_BASH + 0X0C00)		//GPIOB的基地址

#define RCC_BASH (AHBPERIPH_BASH + 0X1000)
#define RCC_APB2ENR (*(unsigned int*)(RCC_BASH + 0X18)) //配置时钟
//#define GPIOB_CRL (*(unsigned int*)(GPIOB_BASH + 0X00))	//配置CRL
//#define GPIOB_CRH (*(unsigned int*)(GPIOB_BASH + 0X40))	//
//#define GPIOB_ODR (*(unsigned int*)(GPIOB_BASH + 0X0C))	//配置ODRR


typedef unsigned int uint32_t;
//定义结构体用来访问GPIO
typedef struct
{
	uint32_t CRL; 
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;     
}GPIO_IypeDef;
//将GPIOB的基地址强转为结构体的内存
#define GPIOB ((GPIO_IypeDef*)GPIOB_BASH)

main.c

#include "stm32f10x.h"

void SystemInit(void)
{
	//函数体为空,目的是为了骗过编译器不报错
}

int main(void)
#if 0
{
		//打开时钟
		*(unsigned int *)0X40021018 |= 0X08;
		//配置寄存器模式
		*(unsigned int *)0X40010C00 |= 0X01;
		//配置输出模式
		*(unsigned int *)0X40010C0C &=~ 0X02;
}
#elif 0
{
		//打开时钟
    RCC_APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB_CRL |= 0X01;
		//配置输出模式
		GPIOB_ODR &=~ 0X02;
}
#elif 1
{
	//打开时钟
    RCC_APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB->CRL |= 0X01;
		//配置输出模式
		GPIOB->ODR &=~ 0X02;
#endif
}



同样下载到板子上,我们的灯也会亮哟。。

三、修改时钟寄存器。

同样的依葫芦画瓢: 在头文件里面添加:

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;   
}RCC_IypeDef;

#define RCC ((RCC_IypeDef*)RCC_BASH)

mian.c

#include "stm32f10x.h"

void SystemInit(void)
{
	//函数体为空,目的是为了骗过编译器不报错
}

int main(void)
#if 0
{
		//打开时钟
		*(unsigned int *)0X40021018 |= 0X08;
		//配置寄存器模式
		*(unsigned int *)0X40010C00 |= 0X01;
		//配置输出模式
		*(unsigned int *)0X40010C0C &=~ 0X02;
}
#elif 0
{
		//打开时钟
    RCC_APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB_CRL |= 0X01;
		//配置输出模式
		GPIOB_ODR &=~ 0X02;
}
#elif 0
{
	//打开时钟
    RCC_APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB->CRL |= 0X01;
		//配置输出模式
		GPIOB->ODR &=~ 0X02;
}
#elif 1
{
		//打开时钟
    RCC->APB2ENR |= 0X08;
		//配置寄存器模式
		GPIOB->CRL |= 0X01;
		//配置输出模式
		GPIOB->ODR &=~ 0X02;
#endif
}



下载到板子上一样会亮呀!