STM32单片机学习笔记(三)-自己写一个库,点亮LED灯(尝试构建库函数雏形)

写在前面:本系列内容均为自学笔记,参考资料为野火指南者开发板资料及芯片参考手册等,使用野火指南者开发板进行学习,该系列内容仅用于记录笔记,不做其他用途,笔记的内容可能会存在不准确或者错误等,如有大佬看到错误内容还望能够评论指正,感谢各位。
本节内容是在前两节的基础上进行学习的,旨在将前两节的程序完善,并尝试写一个自己的库文件。
本节包括前两节的程序,请参考野火开发板资料,里面由更加清晰的教学,野火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)
{
	//函数体为空,目的是骗过编译器
}

以上仅作笔记参考,不做教学,感谢观看。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值