(本文仅为记录本人的学习过程以供个人复习、查询等。如有错误欢迎交流指正,本人不胜感激!)
此篇为更好的理解并使用库函数而进行的过渡。
目录
寄存器地址封装优化
代码部分
stm32f103x.h
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#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 RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
main.c
#include "stm32f10x.h"
int main()
{
RCC_APB2ENR |= (1<<4);//打开pc13对应时钟
GPIOC_CRH &= ~(0x0f<<(4*5));//配置输出模式
GPIOC_CRH |= (1<<(4*5));
GPIOC_ODR |= (1<<13);//输出高电平
GPIOC_ODR &= ~(1<<13);//输出低电平
while(1);
}
void SystemInit(void)
{
}
讲解部分
stm32f103x.h
.h文件一般是用于存放.c文件中需要用到的变量、函数、定义等声明,方便主函数的编写,同时使得主函数条理清晰、易于阅读。
结合之前的寄存器操作的部分,我们可以将原来的地址等变量给放到.h文件中并给他一个利于我们理解的名称,这样整个代码就会更容易读懂也更容易进行编写。
因此我们编写头文件的目的就是按照储存器映像的逻辑来将地址放入变量里
从参考手册中我们可以看出, 寄存器地址分成了三条总线AHB、APB2、APB1,这三条总线又分出了许多寄存器,因此我们可以从这样的逻辑出发进行我们头文件的编写。
#define PERIPH_BASE ((unsigned int)0x40000000)//片上外设起始地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)//APB2起始地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)//AHB起始地址
接下来的编写就是以他们为基础进行的编写
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)//GPIO端口C的起始地址
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)//C的端口配置低寄存器
#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)//C的端口配置高寄存器
#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)//C的输入数据寄存器
#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)//C的输出数据寄存器
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)//复位和时钟控制
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)//外设时钟使能寄存器
将地址封装到.h头文件里后,我们就可以使用对应寄存器的名字来进行编写,主函数就比原先单纯使用地址更加地十分简洁明了。
main.c
与之前的点灯原理一样,只不过将原先冗长复杂的地址用定义好的名称来表示
#include "stm32f10x.h"//添加头文件
int main()
{
RCC_APB2ENR |= (1<<4);//打开pc13对应时钟
GPIOC_CRH &= ~(0x0f<<(4*5));//配置输出模式
GPIOC_CRH |= (1<<(4*5));
GPIOC_ODR |= (1<<13);//输出高电平
GPIOC_ODR &= ~(1<<13);//输出低电平
while(1);
}
void SystemInit(void)
{
}
寄存器地址结构体封装优化
代码部分
stm32f103x.h
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#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)
main.c
#include "stm32f10x.h"//添加头文件
int main(void)
{
RCC->APB2ENR |= (1<<4);//打开pc13对应时钟
GPIOC->CRH &= ~(0x0f<<(4*5));//配置输出模式
GPIOC->CRH |= (1<<(4*5));
GPIOC->ODR |= (1<<13);//输出高电平
GPIOC->ODR &= ~(1<<13);//输出低电平
while(1);
}
void SystemInit(void)
{
}
讲解部分
stm32f103x.h
定义总体的寄存器地址
#define PERIPH_BASE ((unsigned int)0x40000000)//片上外设初始地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)//总线APB2初始地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)//总线AHB初始地址
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)//GPIOC初始地址
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)//RCC时钟控制器的初始地址
结构体定义和类型定义
typedef unsigned int uint32_t;//定义一个新类型名称为uint32_t,其作用相当于unsigned int
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;
//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;
//RCC寄存器使用的类型定义
将结构体和初始地址结合起来
#define GPIOC ((GPIO_TypeDef*)GPIOC_BASE)//将地址类型改为结构体类型
#define RCC ((RCC_TypeDef*)RCC_BASE)
main.c
与之前的点灯原理一样,只不过将原先冗长复杂的地址用结构体的形式来表示
#include "stm32f10x.h"//添加头文件
int main(void)
{
RCC->APB2ENR |= (1<<4);//打开pc13对应时钟
GPIOC->CRH &= ~(0x0f<<(4*5));//配置输出模式
GPIOC->CRH |= (1<<(4*5));
GPIOC->ODR |= (1<<13);//输出高电平
GPIOC->ODR &= ~(1<<13);//输出低电平
while(1);
}
void SystemInit(void)
{
}
附:添加头文件
#include " xxxxx"与#include <xxxxxx>
在添加头文件时,<>会访问系统集成库而不会查找用户目录,“”会访问当前目录以及指定的路径找不到后再去找系统集成库。简单来说系统自带的头文件使用<>,自定义的头文件使用“”,平常编程时也请分开使用以便区分与后续维护。在此还是建议将使用到的头文件放在一个文件夹并设定好目录方便后续编程。
头文件路径的添加在“魔术棒”如下界面操作 :