写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节内容是在前两节的基础上进行学习的,旨在将前两节的程序完善,并尝试写一个自己的库文件。
本节包括前两节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火B站账号:野火官方B站账号链接。
学习目标
1、尝试写一个库,并用其点亮LED灯。
电路图
本节能内容依然用到前两节的点灯电路,电路图如下图所示:
图3-1:LED灯点灯电路
一、将GPIO和RCC用结构体的方式表明
1、了解一下结构体的定义方式
本次将用到下面这种方式:
typedef struct
{
int a;
char b;
int c;
......
}example;
其中:typedef 表示为一种数据类型(基本数据类型或自定义数据类型)重新命名,如
typedef unsigned int uint32_t;//表示用uint32_t表示unsigned int类型
struct为结构体的关键字,example为结构体变量的变量名,于是可以进行以下操作:
example s1;
s1.a = 1;
//或
example* s1;
s1->a = 1;
通过上述方式即可对结构体中的成员进行赋值等其他操作。
2、用结构体表述GPIO和RCC
stm32f10x.h文件中的程序如下:
//stm32f10x.h文件中的内容,存放用于寄存器映射的程序
//外设英文:peripheral,本程序简写为PERIPH
//十六进制的加减法,不要用十进制加减法算,两种方式结果不一样
//编译器可能会对未执行程序的变量进行优化,使用volatile可以防止这类情况
//定义结构体时可以加上这个前缀,如“volatile uint32_t CRL;”
//volatile:表示易变的变量,防止编译器优化
#ifndef _STM32F10X_H
#define _STM32F10X_H //为防止重复只用而导致的报错,加上这个前缀
#define PERIPH_BASE ((unsigned int)0x40000000) //宏定义外设基地址,不做强制转换,方便后面进行宏定义
#define APB1PERIPH_BASE (PERIPH_BASE + 0x00000) //APB1基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x18000) //AHB基地址
#define RCC_BASE (AHBPERIPH_BASE + 0x9000) //RCC寄存器基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) //GPIO寄存器基地址
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; //定义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 GPIOB ((GPIO_TypeDef*)GPIOB_BASE) //强制类型转换为GPIO_TypeDef结构体类型
#define RCC ((RCC_TypeDef*)RCC_BASE) //强制类型转换为RCC_TypeDef结构体类型
#endif /*_STM32F10X_H*/
上述程序首先定义GPIO和RCC的结构体变量GPIO_TypeDef和RCC_TypeDef,接着将GPIO和RCC的基地址强制转换为结构体类型指针并重新命名为GPIOB和RCC,至此我们可以用GPIOB和RCC来操作相应结构体中的成员,程序如下:
main.c中的程序如下:
#include "stm32f10x.h"
int main(void)
{
//使能GPIOB时钟
RCC->APB2ENR |= (1<<3);
//设置端口为输出模式,速率为10MHz
GPIOB->CRL &= ~(1<<(4*0)); //防止其他程序影响配置,先将第0位的4位数值清零
GPIOB->CRL |= (1<<(4*0));
//设置PB0_0为低电平
GPIOB->ODR &= ~(1<<0); //点亮LED灯
// GPIOB->ODR |= (1<<0); //熄灭LED灯
}
void SystemInit(void)
{
//函数体为空,目的是骗过编译器
}
上述程序,通过GPIOB和RCC指向所需寄存器即可对相关寄存器进行赋值,原因是在结构体中一旦确认该结构体的地址,本程序中分别对两个结构体的地址进行了声明,即GPIOB_BASE和RCC_BASE,结构体中的成员会从该地址开始依次向后排序并对成员赋予地址,本程序中所用到的成员均为unsigned int类型的,所以地址相差0x04,正好与寄存器偏移地址对应,所以可以直接对其进行操作。
标题二、优化程序,对main.c中ODR部分优化
优化程序使用GPIOx_BSRR和GPIOx_BRR两个寄存器,具体程序如下:
stm32f10x_gpio.c文件中的程序如下:
#include "stm32f10x_gpio.h"
void GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRR |= GPIO_Pin; //往相应位写1
}
void GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BRR |= GPIO_Pin; //往相应位写0
}
GPIO_SetBits函数中,使用GPIOx_BSRR寄存器,通过对该寄存器低16位置1,可实现对GPIOx_ODR寄存器的置1操作,反之低16位置0,则GPIOx_ODR寄存器不发生变化;GPIOx_BRR寄存器中,对低16位置1则GPIOx_ODR寄存器将被清0,反之对低16位置0,则GPIOx_ODR寄存器不发生变化;
stm32f10x_gpio.h文件中的程序如下:
#ifndef _STM32F10X_GPIO_H
#define _STM32F10X_GPIO_H
#include "stm32f10x.h"
#define GPIO_Pin_0 ((uint16_t)0x0001) //0000 0000 0000 0001 //选择pin0
#define GPIO_Pin_1 ((uint16_t)0x0002) //0000 0000 0000 0010 //选择pin1
#define GPIO_Pin_2 ((uint16_t)0x0004) //0000 0000 0000 0100 //选择pin2
#define GPIO_Pin_3 ((uint16_t)0x0008) //0000 0000 0000 1000 //选择pin3
#define GPIO_Pin_4 ((uint16_t)0x0010) //0000 0000 0001 0000 //选择pin4
#define GPIO_Pin_5 ((uint16_t)0x0020) //0000 0000 0010 0000 //选择pin5
#define GPIO_Pin_6 ((uint16_t)0x0040) //0000 0000 0100 0000 //选择pin6
#define GPIO_Pin_7 ((uint16_t)0x0080) //0000 0000 1000 0000 //选择pin7
#define GPIO_Pin_8 ((uint16_t)0x0100) //0000 0001 0000 0000 //选择pin8
#define GPIO_Pin_9 ((uint16_t)0x0200) //0000 0010 0000 0000 //选择pin9
#define GPIO_Pin_10 ((uint16_t)0x0400) //0000 0100 0000 0000 //选择pin10
#define GPIO_Pin_11 ((uint16_t)0x0800) //0000 1000 0000 0000 //选择pin11
#define GPIO_Pin_12 ((uint16_t)0x1000) //0001 0000 0000 0000 //选择pin12
#define GPIO_Pin_13 ((uint16_t)0x2000) //0010 0000 0000 0000 //选择pin13
#define GPIO_Pin_14 ((uint16_t)0x4000) //0100 0000 0000 0000 //选择pin14
#define GPIO_Pin_15 ((uint16_t)0x8000) //1000 0000 0000 0000 //选择pin15
#define GPIO_Pin_ALL ((uint16_t)0xffff) //1111 1111 1111 1111 //选择全部引脚
void GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
#endif
在stm32f10x_gpio.h中对低16位进行重新声明,将其逐个命名,以便在程序中使用,可理解为((uint16_t)0x4000)相当于stm32f10x_gpio.c中的uint16_t GPIO_Pin,至此可使用以上程序控制LED灯,程序如下:
main.c中的程序程序如下:
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
int main(void)
{
//使能GPIOB时钟
RCC->APB2ENR |= (1<<3);
//设置端口为输出模式,速率为10MHz
GPIOB->CRL &= ~(1<<(4*0)); //防止其他程序影响配置,先将第0位的4位数值清零
GPIOB->CRL |= (1<<(4*0));
//设置PB0_0为低电平
GPIO_SetBits(GPIOB,GPIO_Pin_0); //熄灭LED灯
GPIO_ResetBits(GPIOB,GPIO_Pin_0); //点亮LED灯
}
void SystemInit(void)
{
//函数体为空,目的是骗过编译器
}
三、最终优化
在接下来的程序中将会用到枚举,在此大致写一下枚举的结构:
typedef enum
{
a, //注意用逗号,不用分号
b,
c,
......
}example1;
在枚举类型中,变量的值将从0开始依次递增,当然也可以自己赋值,枚举中的变量可以作为结构体变量的子选项,可起到对结构体中的变量说明的作用;
直接看最终程序,该部分程序详情请参考野火的资料,这里只做笔记记录,程序讲解暂时没有,直接去看野火的视频就可以:
stm32f10x_gpio.c中的程序如下:
#include "stm32f10x_gpio.h"
void GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BSRR |= GPIO_Pin; //往相应位写1
}
void GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{
GPIOx->BRR |= GPIO_Pin; //往相应位写0
}
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 "stm32f10x.h"
#define GPIO_Pin_0 ((uint16_t)0x0001) //0000 0000 0000 0001 //选择pin0
#define GPIO_Pin_1 ((uint16_t)0x0002) //0000 0000 0000 0010 //选择pin1
#define GPIO_Pin_2 ((uint16_t)0x0004) //0000 0000 0000 0100 //选择pin2
#define GPIO_Pin_3 ((uint16_t)0x0008) //0000 0000 0000 1000 //选择pin3
#define GPIO_Pin_4 ((uint16_t)0x0010) //0000 0000 0001 0000 //选择pin4
#define GPIO_Pin_5 ((uint16_t)0x0020) //0000 0000 0010 0000 //选择pin5
#define GPIO_Pin_6 ((uint16_t)0x0040) //0000 0000 0100 0000 //选择pin6
#define GPIO_Pin_7 ((uint16_t)0x0080) //0000 0000 1000 0000 //选择pin7
#define GPIO_Pin_8 ((uint16_t)0x0100) //0000 0001 0000 0000 //选择pin8
#define GPIO_Pin_9 ((uint16_t)0x0200) //0000 0010 0000 0000 //选择pin9
#define GPIO_Pin_10 ((uint16_t)0x0400) //0000 0100 0000 0000 //选择pin10
#define GPIO_Pin_11 ((uint16_t)0x0800) //0000 1000 0000 0000 //选择pin11
#define GPIO_Pin_12 ((uint16_t)0x1000) //0001 0000 0000 0000 //选择pin12
#define GPIO_Pin_13 ((uint16_t)0x2000) //0010 0000 0000 0000 //选择pin13
#define GPIO_Pin_14 ((uint16_t)0x4000) //0100 0000 0000 0000 //选择pin14
#define GPIO_Pin_15 ((uint16_t)0x8000) //1000 0000 0000 0000 //选择pin15
#define GPIO_Pin_ALL ((uint16_t)0xffff) //1111 1111 1111 1111 //选择全部引脚
typedef enum //枚举类型,将CRL寄存器中关于速率的部分单独列举赋值
{
GPIO_Speed_10MHz = 1, // 10MHZ (01)b
GPIO_Speed_2MHz, // 2MHZ (10)b
GPIO_Speed_50MHz // 50MHZ (11)b
}GPIOSpeed_TypeDef;
typedef enum //枚举类型,将CRL寄存器中关于模式的部分单独列举赋值
{ GPIO_Mode_AIN = 0x0, // 模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;
typedef struct
{
uint16_t GPIO_Pin;
uint16_t GPIO_Speed;
uint16_t GPIO_Mode;
}GPIO_InitTypeDef; //外设初始化结构体
void GPIO_SetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
#endif
stm32f10x.h中的程序如下:
#ifndef _STM32F10X_H
#define _STM32F10X_H
//stm32f10x.h文件中的内容,存放用于寄存器映射的程序
//外设英文:peripheral,本程序简写为PERIPH
//十六进制的加减法,不要用十进制加减法算,两种方式结果不一样
//编译器可能会对未执行程序的变量进行优化,使用volatile可以防止这类情况
//定义结构体时可以加上这个前缀,如“volatile uint32_t CRL;”
//volatile:表示易变的变量,防止编译器优化
#define PERIPH_BASE ((unsigned int)0x40000000) //宏定义外设基地址,不做强制转换,方便后面进行宏定义
#define APB1PERIPH_BASE (PERIPH_BASE + 0x00000) //APB1基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2基地址
#define AHBPERIPH_BASE (PERIPH_BASE + 0x18000) //AHB基地址
#define RCC_BASE (AHBPERIPH_BASE + 0x9000) //RCC寄存器基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) //GPIO寄存器基地址
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; //定义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 GPIOB ((GPIO_TypeDef*)GPIOB_BASE) //强制类型转换为GPIO_TypeDef结构体类型
#define RCC ((RCC_TypeDef*)RCC_BASE) //强制类型转换为RCC_TypeDef结构体类型
#endif
main.c中的程序如下:
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#define LED_G_GPIO_PORT GPIOB
#define LED_G_GPIO_CLK_ENABLE (RCC->APB2ENR |= ( (1) << 3 ))
#define LED_G_GPIO_PIN GPIO_Pin_0
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//使能GPIOB时钟
LED_G_GPIO_CLK_ENABLE;
//设置端口为输出模式,速率为10MHz
GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStruct);
//设置PB0_0为低电平
GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); //熄灭LED灯
GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN); //点亮LED灯
}
void SystemInit(void)
{
//函数体为空,目的是骗过编译器
}
以上仅作笔记参考,不做教学,感谢观看。