一、使用技术手册找取寄存器地址
本文以打开 PB8 LED灯为例
1、定位APB总线寄存器位置
1. 根据下图可知,GPIOB桥接在APB2上
所以需要打开RCC_APB2的时钟,
而打开时钟就需要先找到RCC_APB2的地址,在技术手册中先打开2.3存储器映射章节
在此章到RCC端口的基地址为 0x4002 1000 而APB2的地址只需加上偏移量就可确定
偏移量可在6.3.7节找到
于此可知偏移地址为 0x18 所以RCC_APB2的地址就为
RCC基地址0x4002 1000 + APB2偏移量 0x18
在keil中就可定义
#define RCC_APB2ENR *((volatile unsigned int *) (0x40021000 + 0x18)) //RCC起始地址加偏移
volatile是C的关键词,作用是不让编译器优化代码,防止程序出错。
0x40021000 + 0x18,如果不加任何操作,他便只是一串数字,所以需要先将其转换成指针类型((volatile unsigned int *) (0x40021000 + 0x18))然后再将其中的值取出所以需要再加上*号。
这样就可对这个地址存的值进行操作。
2、定位GPIO端口位置
在2.3存储器映射章节可找到GPIOB的基地址为0X4001 0C00
然后找到偏移地址便可确定GPIOB的地址,
所以想pin8口就需要配置高寄存器,高寄存器的偏移地址为 0x04
于此可知偏移地址为 0x04所以GPIOB高寄存器的地址就为
GPIOB基地址0X4001 0C00 + APB2偏移量 0x04
在keil中就可定义
3、找到端口输出数据(ODR)
此寄存器为配置pin口 1 或 0
#define GPIOB_ODR *((volatile unsigned int *) (0x40010C00 + 0x0C)) //GPIOB端口地址加偏移
以上就找到了所有需要配置的寄存器
4、RCC_APB2时钟使能
由此可知要想打开GPIOB的时钟就要控制此寄存器的第三位。
所以只需要将第三位 置一 就可打开GPIOB的时钟
RCC_APB2ENR |= (0x01 << 3); //APB2时钟启用
5、IO端口配置
此图中的CNF8,MODE8就是代表的就是pin8口
如图进行配置便可初始化GPIOB.
二进制0011转换为16进制就为0x03
GPIOB_CRH |= 0x03; // 推完输出 50Hz PB8
6、引脚置零
GPIOB_ODR &=~ (0x01 << 8); //PB8置0
7、完整代码
#define RCC_APB2ENR *((volatile unsigned int *) (0x40021000 + 0x18)) //RCC起始地址加偏移
#define GPIOB_CRH *((volatile unsigned int *) (0x40010C00 + 0x04)) //GPIOB起始地址加偏移
#define GPIOB_ODR *((volatile unsigned int *) (0x40010C00 + 0x0C)) //GPIOB端口地址加偏移
int main(void)
{
RCC_APB2ENR |= (0x01 << 3); //APB2时钟启用
GPIOB_CRH |= 0x03; // 推完输出 50Hz PB8 PB9
GPIOB_ODR &=~ (0x01 << 8); //PB8置0
while(1)
{
}
}
二、使用 stm32f10x.h 头文件
观看一后再看二,不然必定看不懂 ! ! !
在stm32f10x.h中已将RCC的基地址强制转换成了结构体类型并且define为了 “RCC”
所以只需要在mian中引用头文件即可
#include "stm32f10x.h" // Device header
引用之后便可直接使用,GPIOB也是如此
代码为
#include "stm32f10x.h" // Device header
int main(void)
{
RCC->APB2ENR |=(0x01 << 3); //APB2时钟启用
GPIOB->CRH |= 0x03;
GPIOB->ODR &=~ (0x01 << 8);
}
三、扩展
#define __IO volatile /*!< defines 'read / write' permissions */
- 可看到__IO 即为volatile 的define,
- 在这个结构体中,可看到其中的成员为CR、CFGR、CIR...
- 而在技术手册中6.3.11 RCC寄存器地址映像中可找到
这些成员正是按照寄存器的顺序而定义,并且每个寄存器的大小都为32字节,而uint32_t正好占据32字节,所以一旦将GPIO的地址作为此结构体的首地址,那么所有成员的首地址都将与所对应寄存器一一对应。所以在我们在用结构体调用成员赋值的时候才能给对应寄存器进行赋值,所有这种类型的结构体都是如此。