一、实验原理(以STM32F103RCT6开发板例)
1、寄存器映射
程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB(32位,2^32=4GB)的线性地址空间内。数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。可访问的存储器空间被分成8个主要块,每个块为512MB,分别为block0-7。其他所有没有分配给片上存储器和外设的存储器空间都是保留的地址空间,请参考相应器件的数据手册中的存储器映像图。
如果我们要访问或者写某一个寄存器,就要知道他的地址,我们可以通过一步步找到,外设基地址+挂载在那个总线的基地址+GPIO地址+具体某个寄存器偏移地址。
2、对应功能寄存器
· APB2 外设时钟使能寄存器(RCC_APB2ENR)
使能对应管脚上面的外设,我们使用到PA8和PD2属于APB2时钟总线上面,所以我们需要通过该寄存器来使能对应GPIO口,具体地址和IO口的位置参考如下,详细可参考中文手册
· 端口配置低寄存器(GPIOx_CRL/CRH) (x=A..E)
输入模式:
浮空输入模式:浮空输入状态下,IO 的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。可做KEY按键识别。
上下拉输入模式:内部设有上拉和下拉电阻,当外部电路为低电平,IO口设为下拉模式,当外部电路为低电平,IO口设为上拉模式。
模拟输入:用作内部ADC输入或DAC输出,预防干扰。
输出模式:推挽输出模式:(最常用)
开漏输出模式:(不常用)
推挽、开漏复用模式
配置对应GPIO口的工作模式(输入/输出),详细配置参数可查询中文手册
· 端口输出数据寄存器(GPIOx_ODR) (x=A..E)
配置对应GPIO口的高低电平作为输出,具体地址及操作位参考中文手册
3、寄存器地址
功能寄存器地址=寄存器组起始地址+地址偏移
寄存器组起始地址
地址偏移
二、程序实现
· 宏定义对应功能寄存器映射地址
//-------------------APB2使能时钟寄存器-----------------
#define RCC_AP2ENR *((unsigned volatile int*)0X40021018)
//-------------------GPIOA_CRH配置寄存器----------------
#define GPIOA_CRH *((unsigned volatile int*)0X40010804)
//----------------GPIOA_ODR端口输出寄存器---------------
#define GPIOA_ODR *((unsigned volatile int*)0X4001080C)
//-------------------GPIOD_CRL配置寄存器----------------
#define GPIOD_CRL *((unsigned volatile int*)0X40011400)
//-------------------GPIOD_ODR输出寄存器----------------
#define GPIOD_ODR *((unsigned volatile int*)0X4001140C)
· 时钟使能及初始化对应GPIO
RCC_AP2ENR|=1<<2|1<<5; //使能GPIOA和GPIOD端口时钟
GPIOA_CRH&=0XFFFFFFF0;
GPIOA_CRH|=0X00000002; //设置PA8通用推挽输出频率10MHZ
GPIOA_ODR|=1<<8; //GPIOA置1,灯灭
GPIOD_CRL&=0XFFFFF0FF;
GPIOD_CRL|=0X00000200; //设置PD2通用推挽输出频率10MHZ
GPIOD_ODR|=1<<2; //GPIOD置1,灯灭
delay_init(72); //延时函数初始化
总体程序代码设计如下:
#include "delay.h"
//-------------------APB2使能时钟寄存器-----------------
#define RCC_AP2ENR *((unsigned volatile int*)0X40021018)
//-------------------GPIOA_CRH配置寄存器----------------
#define GPIOA_CRH *((unsigned volatile int*)0X40010804)
//----------------GPIOA_ODR端口输出寄存器---------------
#define GPIOA_ODR *((unsigned volatile int*)0X4001080C)
//-------------------GPIOD_CRL配置寄存器----------------
#define GPIOD_CRL *((unsigned volatile int*)0X40011400)
//-------------------GPIOD_ODR输出寄存器----------------
#define GPIOD_ODR *((unsigned volatile int*)0X4001140C)
int main(void)
{
RCC_AP2ENR|=1<<2|1<<5; //使能GPIOA和GPIOD端口时钟
GPIOA_CRH&=0XFFFFFFF0;
GPIOA_CRH|=0X00000002; //设置PA8通用推挽输出频率10MHZ
GPIOA_ODR|=1<<8; //GPIOA置1,灯灭
GPIOD_CRL&=0XFFFFF0FF;
GPIOD_CRL|=0X00000200; //设置PD2通用推挽输出频率10MHZ
GPIOD_ODR|=1<<2; //GPIOD置1,灯灭
delay_init(72); //延时函数初始化
while(1)
{
GPIOA_ODR&=~(1<<8); //PA8亮PD2灭
GPIOD_ODR|=1<<2;
delay_ms(100); //延时100ms
GPIOA_ODR|=1<<8; //PA8灭PD2亮
GPIOD_ODR&=~(1<<2);
delay_ms(100); //延时100ms
}
}
三、实验结果
流水灯
四、总结
通过本实验体验了通过寄存器开发的整体流程,最大的收获就是学会了如何查寻对应芯片数据手册和参考手册,通过去了解每一个芯片的对应地址和如何去配置实现相应功能,对于arm芯片底层的原理有了更深的理解,果然要想更深入的了解学习一块嵌入式芯片必须的学会使用寄存器操作,然后配合库函数开发就是锦上添花、如虎添翼。