一、stm32的存储器
一共4G大小,地址范围0x0000 0000到0xFFFF FFFF,分成8个块(Block0~Block7),每块大小为512M。
其中片上外设地址(0x4000 0000)-(0x5FFFFFFF)
一共有3条总线:APB1、APB2、AHB。
总线名称 | 总线基地址 |
---|---|
APB1 | 0x4000 0000 |
APB2 | 0x4001 0000 |
AHB | 0x4001 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固件库的写法,了解一下就差不多了,如果是寄存器开发的话,还是用固件库的模板,直接用结构体成员指针找到对应的寄存器直接操作就好了,里面的结构体都是固件库中定义好的。如果用库函数的形式开发,根本用不上这些。