我们知道寄存器编程是通过将那些具有特殊功能的功能单元以功能为名给这个内存单元取一个别名,这个别名就叫做寄存器,然后通过给寄存器赋值(位与 &,位或 |)来操作寄存器实现特定功能比如点亮一个LED灯。
这里利用寄存器编程会存在几个弊端, 一是如果我不注释你能短时间知道我配置的是那个引脚嘛,以及配置以及端口的输出模式配置的是什么嘛,这里肯定得花点时间去查看STM32的参考手册才能知道,所以可读性不高。二是stm32单片机不像51单片机一样寄存器很少即使用寄存器开发全部记住即可,可stm32每个外设都有很多寄存器,而且到了后期要配置很多寄存器,就光那些 & 和 | 操作都会让人搞懵。
我们都知道单片机的本质是操作寄存器实现特定的功能,本文就通过自己写库函数点亮一个LED灯来看看,如何写一个库函数,如何调用库函数,以及函数内部到底是怎样将特定的参数写入寄存器,也就是说函数如何来操作寄存器
主要介绍GPIO初始化函数GPIO_Init,其他实现寄存器的映射等方法请看
《实现寄存器映射》
这里是模仿ST官方的标准固件库
因为我们点灯操作的是GPIO外设,这里新建三个文件,文章后面会详细讲解ST官方的标准固件库各个文件的功能。
在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起,总线或者外设都以他们的名字作为宏名
//前面转化为指针类型是因为前面只做地址运算而不需要取地址内容
//外设基地址
#define PERIRH_BASE ((unsigned int)0x40000000)
//总线基地址
#define APB1PERIRH_BASE PERIRH_BASE
#define APB2PERIRH_BASE (PERIRH_BASE+0x10000)
#define AHBPERIRH_BASE (PERIRH_BASE+0x20000)
//GPIO 外设基地址
#define RCC_BASE (AHBPERIRH_BASE+0x1000)
#define GPIOA_BASE (APB2PERIRH_BASE+0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE+0x1000)
#define GPIOE_BASE (APB2PERIPH_BASE+0x1800)
#define GPIOD_BASE (APB2PERIRH_BASE+0x1400)
#define GPIOF_BASE (APB2PERIPH_BASE+0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE+0x2000)
typedef unsigned int uint32_t;
typedef short int uint16_t;
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t CLKR;
}GPIO_TypeDef;
//GPIOA~GPIOG外设都有这些寄存器
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;
把外设的基地址
强制类型转换成相应的外设寄存器结构体指针,然后再把该指针替换成外设名,然后外设名就是该外设类型的寄存器结构体指针,指向了该外设的结构体,也就是说该外设结构体的首地址变成的外设的基地址
,通过该指针可以直接操作该外设的全部寄存器
//将GPIOA_BASE GPIOA的基地址强制类型转化成GPIO_TypeDef类型的指针 指向的是整个GPIO_typedef这个结构体的地址
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef*)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef*)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef*)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef*)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef*)GPIOG_BASE)
#define RCC ((RCC_TypeDef*)RCC_BASE)//指向这个结构体的指针
这里就是操作BSRR,BRR寄存器间接操作ODR寄存器来控制引脚的电平,将操作BSRR,BRR寄存器封装成一个函数,如果要控制引脚电平调用函数即可。
这里解释一下为啥BRR寄存器与BSRR寄存器高16位功能一样,还搞个BRR寄存器,这里纯属是为了照顾不同程序员的使用习惯,不用太纠结。
总结:要配置引脚输出高电平就往BSRR相应低16位写1就行了,若要配置引脚输出低电平就往BRR寄存器相应位写1就ok了。
1.定义引脚参数的宏
做这个的目的是为了更加方便,增加可读性,不用将代码写成:
GPIOX->BSRR | = (1<<引脚号);
引脚参数转化为二进制就是对应的引脚号位为1其余位为0,这样直接让寄存器位或这个宏,就将对应的引脚的电平配置完成。
#define GPIO_pin_0 ((uint16_t)0x0001) /* 选择pin0 */—》(1<<0)
#define GPIO_pin_1 ((uint16_t)0x0002 /* 选择pin1 */—》(1<<1)
#define GPIO_pin_2 ((uint16_t)0x0004) /* 选择pin2 */—》(1<<2)
#define GPIO_pin_3 ((uint16_t)0x0008) /* 选择pin3 */
#define GPIO_pin_4 ((uint16_t)0x0010) /* 选择pin4 */
#define GPIO_pin_5 ((uint16_t)0x0020) /* 选择pin5 */
#define GPIO_pin_6 ((uint16_t)0x0040) /* 选择pin6 */
#define GPIO_pin_7 ((uint16_t)0x0080) /* 选择pin7 */
#define GPIO_pin_8 ((uint16_t)0x0100) /* 选择pin8 */
#define GPIO_pin_9 ((uint16_t)0x0200) /* 选择pin9 */
#define GPIO_pin_10 ((uint16_t)0x0400) /* 选择pin10 */
#define GPIO_pin_11 ((uint16_t)0x0800) /* 选择pin11 */
#define GPIO_pin_12 ((uint16_t)0x1000) /* 选择pin12 */
#define GPIO_pin_13 ((uint16_t)0x2000) /* 选择pin13 */
#define GPIO_pin_14 ((uint16_t)0x4000) /* 选择pin14 */
#define GPIO_pin_15 ((uint16_t)0x8000) /* 选择pin15 */ —》(1<<15)
#define GPIO_pin_A11 ((uint16_t)0xFFFF) /* 选择全部引脚*/ //(11111111 11111111)b
2.定义位操作函数
//第一个参数为引脚的端口号GPIOx(A~E)
//第二个参数为引脚号GPIO_Pin_x(0~15)
void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x)
{
GPIOx->BSRR |=GPIO_pin_x;//将相应位置1,相应引脚输出高电平
}
void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x)
{
GPIOx->BRR|=GPIO_pin_x;//将相应位置1,相应引脚输出低电平
}
实现思路
首先就要考虑函数的参数有哪些,我们的目的是为了配置GPIO端口的引脚工作模式,首先第一个参数是端口号,除此之外我们还要知道,配置成什么模式,以及引脚号,还有输出模式下的GPIO 引脚的速率,为了方便不如将这几个参数封装成一个结构体,这几个参数作为结构体的成员变量,我们可以创建一个该结构体的变量
对这些结构体成员变量进行赋值配置我们想要的工作模式
,然后传结构体变量的地址给函数,函数就可以通过地址来访问这些参数最后经过运算,将这些参数写入相应的寄存器,就实现了通过函数配置GPIO的工作模式。
1.定义初始化结构体
typedef struct
{
uint16_t GPIO_Pin; /* 选择要配置的 GPIO 引脚
可输入 GPIO_Pin_ 定义的宏 */
GPIOSpeed_TypeDef GPIO_Speed; /* 选择GPIO 引脚的速率
只能赋值GPIOSpeed_TypeDef 定义的枚举值*/
GPIOMode_TypeDef GPIO_Mode; /* 选择GPIO 引脚的工作模式
只能赋值GPIOMode_TypeDef 定义的枚举值*/
}GPIO_InitTypeDef;
定义一个这样的结构体变量,根据需要配置 GPIO 的工作模式,对这个结构体的各个成员进行赋值,然后把这个变量的地址作为“GPIO 初始化函数”的输入参数,该函数能访问这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。
2.GPIO 枚举类型定义
为了避免我们配置出错,我们用枚举来限定模式和速度的取值
typedef enum
{
GPIO_Speed_10MHz = 1, //10MHZ (01)
GPIO_Speed_2MHz, //2MHZ (10)
GPIO_Speed_50MHz //50MHZ (11)
}GPIOSpeed_TypeDef;
typedef enum
{
GPIO_Mode_AIN = 0x0, //模拟输入 (0000 0000)
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入 (0000 0000)
GPIO_Mode_IPD = 0x28, //下拉输入 (0010 1000)
GPIO_Mode_IPU = 0x48, //上拉输入 (0100 1000)
GPIO_Mode_Out_OD = 0x14, //开漏输出 (0001 0100)
GPIO_Mode_Out_PP = 0x10, //推挽输出 (0001 0000)
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出 (0001 1100)
GPIO_Mode_AF_PP = 0x18 //复用推挽输出 (0001 1000)
}GPIOMode_TypeDef;
bit4用来区分端口是输入还是输出,0 表示输入 1 表示输出,bit2和bit3 对应寄存器的CNFY[1:0]位,是我们真正要写入到 CRL 和 CRH 这两个端口控制寄存器中的值。bit0和 bit1 对应寄存器的MODEY[1:0]位,这里我们暂不初始化,在 GPIO_Init()初始化函数中用来跟据GPIOSpeed的值加上即可实现速率的配置。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
接下就来重点分析这个函数,到底是怎样实现配置GPIO的工作模式(将工作模式的参数写入寄存器)
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
函数参数:
1.GPIOx:GPIO_TypeDef* 类型的指针,可填GPIOA~GPIOG
2.GPIO_InitStruct: GPIO_InitTypeDef* 类型的指针,一般是创建一个GPIO_InitTypeDef的变量再传地址给函数。
3.返回类型:void
先将if语句收缩,看一下函数的整体框架
逐条语句进行分析
判断模式是输入还是输出,若是输出就加上速度的值
判断是低八位还是高八位,我这里是以pin8为例进了高八位的if语句,如果是低八位就会进入低八位的if语句进行配置。
判断是否为上拉还是下拉输入
总结:库函数的本质还是操作寄存器,就是封装一个个函数,我们只要传函数相应的参数,函数会帮我们配置好寄存器,实现指定的功能,这里是以GPIO外设为例,其他外设的库函数本质也是一样。
由于RCC外设没有封装函数,这里配置时钟还是用寄存器操作。
main.c
#include "stm32f103x.h"
#include "stm32f10x_gpio.h"
//定义的延时函数
void delay(uint32_t count)
{
for(;count!=0;count--)
{}
}
//定义一个GPIO_InitTypeDef 类型的结构体变量,用于配置成员变量
GPIO_InitTypeDef GPIO_InitStruct;
//打开GPIOA的时钟
RCC->APB2ENR |=(1<<2);
//配置IO口为推挽输出,配置结构体的成员变量
GPIO_InitStruct.GPIO_Pin=GPIO_pin_8 ;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
//调用外设初始化函数 将配置好的结构体成员写进寄存器中
GPIO_Init(GPIOA,&GPIO_InitStruct);
//让灯闪烁
while(1)
{
//让引脚输出低电平灯亮
GPIO_ResetBits (GPIOA,GPIO_pin_8);
delay(0xfffff);//灯亮一会
//让引脚输出高电平灯灭
GPIO_SetBits (GPIOA,GPIO_pin_8);
delay(0xfffff);//灯灭一会
}
这里解释一下为什么开GPIOA的时钟,时钟相当于人的大脑,寄存器是基于触发器,而触发器的赋值是一定需要时钟的,也就是说有时钟cup才能向寄存器写入值。一般用什么外设就要开该外设的时钟。我们这里用的是GPIOA这个外设,所以要开启GPIOA的时钟。
效果演示
stm32f10x_gpio.c
#include"stm32f10x_gpio.h"
void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x)
{
GPIOx->BSRR |=GPIO_pin_x;
}
void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x)
{
GPIOx->BRR|=GPIO_pin_x;
}
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
stm32f10x_gpio.h
//用来存放函数声明,宏定义等
#ifndef __stm32f10x_gpio_h
#define __stm32f10x_gpio_h
#include "stm32f103x.h"
#define GPIO_pin_0 ((uint16_t)0x0001) /* 选择pin0 */
#define GPIO_pin_1 ((uint16_t)0x0002 /* 选择pin1 */
#define GPIO_pin_2 ((uint16_t)0x0004) /* 选择pin2 */
#define GPIO_pin_3 ((uint16_t)0x0008) /* 选择pin3 */
#define GPIO_pin_4 ((uint16_t)0x0010) /* 选择pin4 */
#define GPIO_pin_5 ((uint16_t)0x0020) /* 选择pin5 */
#define GPIO_pin_6 ((uint16_t)0x0040) /* 选择pin6 */
#define GPIO_pin_7 ((uint16_t)0x0080) /* 选择pin7 */
#define GPIO_pin_8 ((uint16_t)0x0100) /* 选择pin8 */
#define GPIO_pin_9 ((uint16_t)0x0200) /* 选择pin9 */
#define GPIO_pin_10 ((uint16_t)0x0400) /* 选择pin10 */
#define GPIO_pin_11 ((uint16_t)0x0800) /* 选择pin11 */
#define GPIO_pin_12 ((uint16_t)0x1000) /* 选择pin12 */
#define GPIO_pin_13 ((uint16_t)0x2000) /* 选择pin13 */
#define GPIO_pin_14 ((uint16_t)0x4000) /* 选择pin14 */
#define GPIO_pin_15 ((uint16_t)0x8000) /* 选择pin15 */
#define GPIO_pin_A11 ((uint16_t)0xFFFF) /* 选择全部引脚*/ //(11111111 11111111)b
typedef enum
{
GPIO_Speed_10MHz = 1, //10MHZ (01)
GPIO_Speed_2MHz, //2MHZ (10)
GPIO_Speed_50MHz //50MHZ (11)
}GPIOSpeed_TypeDef;
typedef enum
{
GPIO_Mode_AIN = 0x0, //模拟输入 (0000 0000)
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入 (0000 0000)
GPIO_Mode_IPD = 0x28, //下拉输入 (0010 1000)
GPIO_Mode_IPU = 0x48, //上拉输入 (0100 1000)
GPIO_Mode_Out_OD = 0x14, //开漏输出 (0001 0100)
GPIO_Mode_Out_PP = 0x10, //推挽输出 (0001 0000)
GPIO_Mode_AF_OD = 0x1C, //复用开漏输出 (0001 1100)
GPIO_Mode_AF_PP = 0x18 //复用推挽输出 (0001 1000)
}GPIOMode_TypeDef;
typedef struct
{
uint16_t GPIO_Pin; /* 选择要配置的 GPIO 引脚
可输入 GPIO_Pin_ 定义的宏 */
GPIOSpeed_TypeDef GPIO_Speed; /* 选择GPIO 引脚的速率
只能赋值GPIOSpeed_TypeDef 定义的枚举值*/
GPIOMode_TypeDef GPIO_Mode; /* 选择GPIO 引脚的工作模式
只能赋值GPIOMode_TypeDef 定义的枚举值*/
}GPIO_InitTypeDef;
void GPIO_ResetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x);
void GPIO_SetBits(GPIO_TypeDef*GPIOx,uint16_t GPIO_pin_x);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
#endif /*__stm32f10x_gpio.h*/
stm32f103x.h
#ifndef __stm32f103x_h
#define __stm32f103x_h
//用来存放stm32寄存器映射的代码
// 外设 perirhral
//前面转化为指针类型是因为前面只做地址运算而不需要取地址内容
//外设基地址
#define PERIRH_BASE ((unsigned int)0x40000000)
//总线基地址
#define APB1PERIRH_BASE PERIRH_BASE
#define APB2PERIRH_BASE (PERIRH_BASE+0x10000)
#define AHBPERIRH_BASE (PERIRH_BASE+0x20000)
//GPIO 外设基地址
#define RCC_BASE (AHBPERIRH_BASE+0x1000)
#define GPIOA_BASE (APB2PERIRH_BASE+0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE+0x1000)
#define GPIOE_BASE (APB2PERIPH_BASE+0x1800)
#define GPIOD_BASE (APB2PERIRH_BASE+0x1400)
#define GPIOF_BASE (APB2PERIPH_BASE+0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE+0x2000)
#define RCC_APB2END *(unsigned int *)(RCC_BASE+0x18)
#define GPIOA_CRL *(unsigned int *)(GPIOA_BASE+0x00)//这里强制类型转化是告诉系统这是一个地址
#define GPIOA_CRH *(unsigned int *)(GPIOA_BASE+0x04)
#define GPIOA_IDR *(unsigned int *)(GPIOA_BASE+0x08)
#define GPIOA_ODR *(unsigned int *)(GPIOA_BASE+0x0c)
#define GPIOA_BSRR *(unsigned int *)(GPIOA_BASE+0x10)
#define GPIOA_BRR *(unsigned int *)(GPIOA_BASE+0x14)
#define GPIOA_CLKR *(unsigned int *)(GPIOA_BASE+0x18)
typedef unsigned int uint32_t;
typedef short int uint16_t;
typedef struct
{
uint32_t CRL;
uint32_t CRH;
uint32_t IDR;
uint32_t ODR;
uint32_t BSRR;
uint32_t BRR;
uint32_t CLKR;
}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;
//将GPIOA_BASE GPIOA的基地址强制类型转化成GPIO_TypeDef类型的指针 指向的是整个GPIO_typedef这个结构体的地址
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef*)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef*)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef*)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef*)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef*)GPIOG_BASE)
#define RCC ((RCC_TypeDef*)RCC_BASE)//指向这个结构体的指针
#endif /*__stm32f103x.h*/
startup_stm32f10x_hd.s:设置堆栈指针、设置pc指针、初始化中断向量表、配置系统时钟、调用c库函数_main跳转的C语言环境
这个文件非常重要,由于篇幅有限先大概了解一下,后面会出一篇讲解。
system_stm32f10x.c:文件里面有SystemInit函数,把外部时钟HSE=8M,经过PLL(外设)倍频为72M。
system_stm32f10x.h:库函数的声明
stm32f10x.h这个文件是非常牛逼的啦
stm32f10x.h:实现了内核(CPU)之外的所有外设的寄存器映射,以及一些外设库函数的参数
stm32f10x_xx.c: 外设的驱动函数库文件
xx: GPIO、USRAT、I2C、SPI、FSMC…
这里我截取的一了部分
stm32f10x_xx.h: 存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明
库函数编程时,你可以直接看源码编程,也就是说,你用哪个外设就打开哪个外设的C文件,和头文件,库函数编程不就调用函数嘛,你打开外设的头文件翻到最后,里面包括这个外设所有的函数的声明,用哪个函数就复制粘贴,然后传参给函数。如果有不会用的函数怎么办,接下来我来展示一下如何使用一个外设的函数。
这里以USART外设为例:
打开外设头文件
翻到文件最后
smt32库函数的命名规则——> 函数的命名规则
转到函数定义
如果没反应请设置
函数定义这会告诉你这个函数怎么用,非常详细。
core_cm3.h:实现了内核(CPU)里面的外设的寄存器映射
core_cm3.c:内核外设的驱动固件库
misc.h:NVIC_InitTypeDef结构体,以及库函数的参数和声明
misc.c:NVIC(嵌套向量中断控制器)、SysTick(系统滴答定时器)相关函数
stm32f10x_conf.h:头文件的头文件
只需要包含stm32f10x_conf.h 用那个外设就将取消注释
一般默认包含所有头文件
但是我们一般不去包含这个文件,因为stm32f10x.h文件里面已经包含了stm32f10x_conf.h 这个文件,我们只要包含stm32f10x.h这个文件就OK了。
这里还对条件编译有问题的请参考《预处理指令》
stm32f10x_it.c
stm32f10x_it.h
中断服务函数你可以随意放在其他的地方,并不是一定放在stm32f10x_it.c只是放在一个文件里面更方便管理。
所谓库函数编程,就是调用ST官方已经封装好的函数来帮助我们操作寄存器实现特定功能,我们只需要调用和传参会使用函数即可,极大的节省了我们查阅参考手册的时间,到这里文章就圆满结束,若文章对你有帮助就赶快点赞收藏叭!!!