STM32F1基于HAL库的学习记录实用使用教程分享(一、GPIO_Output)


前言

本文记录了通过使用STM32F103ZET6单片机进行GPIO_Output的学习

随着嵌入式行业的兴起,于此记录个人的一些学习过程并进行分享,LED的控制即为大部分同学的第一课——点灯(GPIO_Output)

本系列对于各个软件的下载及部分配置过程将带过,有需要的同学可以参考其他的博客和经验分享。

本系列使用的芯片有STM32F103C8T6、STM32F103ZET6、STM32F743VIT6。大部分为基于STM32F103ZET6单片机。

常使用的软件包括但不限于:STM32CubeMX6.11.1、STM32CubeIDE 1.15.1、MDK-ARM 5.35、嘉立创EDA(专业版)、SOLIDWORKS 2021、OpenMV IDE、PyCharm、Anaconda。


声明:本文章在编写过程中,我始终致力于尊重并保护所有原创内容及其知识产权。然而,由于信息来源的多样性和复杂性,可能存在个别内容未明确标注出处、存在事实性错误或无意中侵犯了他人的知识产权的情况。对于任何可能存在的上述问题,我深感歉意,并在此提前向受影响的作者表示最诚挚的歉意。我始终秉持着尊重原创、维护知识产权的原则,绝无意侵犯任何人的合法权益。一旦收到您的反馈,我将立即核实并在第一时间内对文章进行修改。这包括但不限于补充相关引用信息、更正错误内容或删除涉嫌侵权的内容。再次感谢您的关注与支持,期待与您共同营造一个更加美好的知识共享空间。新手文章诸多不足,还望海涵

一、GPIO_Output

GPIO支持4种输出模式,分别为:推挽输出、开漏输出、推挽复用输出、开漏复用输出。其后两种复用输出模式一般都是用于外设使用,本文不做过多介绍。

1.推挽输出(Output Push Pull)

推挽输出同时在标准库里也叫做:GPIO_Mode_Out_PP
推挽复用输出同时在标准库里也叫做:GPIO_Mode_Out_PP

IO口输出0或1。一般情况下都是选取这种模式。该模式可以输出高低电平,连接外部数字器件进行控制。

在推挽输出状态下,如果高电平和低电平直接连接在一起,会导致短路电流流入(倒灌),这极有可能会损坏设备。(由此造成过惨痛的教训)

2.开漏输出(Output Open Drain)

开漏输出同时在标准库里也叫做:GPIO_Mode_Out_OD
开漏复用输出同时在标准库里也叫做:GPIO_Mode_AF_OD

I/O口输出0或1。在没有连接上拉电阻的情况下,该接口只能输出低电平,而无法驱动高电平。要实现高电平输出,必须借助外部的上拉电阻。需要注意的是,I/O口的实际电平与输出电平可能不一致,这取决于连接的上拉电阻。

开漏输出的一个重要优势是能够灵活地调节输出电平,因为输出电平由上拉电阻所连接的电源电压来决定。因此,开漏输出一般用于需要电平转换的场合。

开漏输出的另一个好处在于可以实现**“线与”**功能,所谓的"线与"指的是多个信号线直接连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平;只要有任意一个或者多个信号为低电平,则总线为低电平。

二、配置

在选择好芯片并进入配置界面后的操作如下进行

1.RCC

外部的高速和低速时钟均有三个可选项目,具体情况需根据个人的硬件情况进行个性化配置,一般选择高速时钟下的:Crystal/Ceramic Resonator(外部晶体/陶瓷谐振器)如图2-1所示:
图2-1 RCC

2.SYS

(1).Debug

在STM32CubeMX中配置系统(SYS)时,关于调试接口的设置是一个重要环节。默认情况下,调试功能可能被启用,但如果不需要实时调试或希望优化资源使用,可以选择关闭调试(即选择“No Debug”选项)。然而为了开发便利,我们一般都保留调试接口。

在调试协议的选择上,STM32CubeMX提供了SW(Serial Wire)和JTAG两种选项。鉴于SWD(Serial Wire Debug)模式仅需要两个引脚,且兼容性好、调试效率高,因此它是许多开发者的首选。我选择使用SW协议

(2).System Wake-Up

System Wake-Up功能允许STM32微控制器在低功耗模式下被特定的外部事件唤醒。这些外部事件可以包括但不限于外部中断、RTC闹钟、USART接收数据等。

通过配置System Wake-Up功能,微控制器可以在不需要时进入低功耗模式以节省电能,同时在需要时能够快速响应外部事件并恢复正常工作。

(3).Timebase Source

通过对 Timebase Source进行合理配置以确保程序的正确运行。通过选择合适的Timebase Source(如SysTick或TIMx),并合理配置其参数,可以实现高效、可靠的时间管理功能。
图2-2 SYS

3.时钟树

依此点击:PLL、Enable CSS、HCLK(MHz)改为72之后回车即可。
图2-3 时钟树

4.LED引脚配置

(1).选择引脚和模式

根据硬件点击所需要使用的引脚,之后在弹窗里选择GPIO_Output,如图2-4 :
图2-4 选择GPIO_Output

(2).进行细节配置

根据硬件进行细节配置,如图2-5 :

(A).GPIO output level

Low :IO初始化默认输出低电平
High:IO初始化默认输出高电平

(B).GPIO mode

Output Open Drain,开漏输出
Output Push Pull ,推挽输出

©.GPIO Pull-up/Pull-down

No pull-up and no pull-down,浮空输入,配置为不上拉和下拉
Pull-up,上拉输入
Pull-down,下拉输入

(D).Maximum output speed

Low,GPIO速度为低速,通常为2MHZ
Medium,GPIO速度为中速,通常为10MHZ
High,GPIO速度为高速,通常为50MHZ

(E).User Laber

用户标号,给引脚定义一个易识别的名称,以此以提高代码的可读性和可维护性。我直接定义为:LED
图2-5 细节配置

5.基本定时器(TIM6、TIM7)配置

(1).勾选Activated

(2).Prescaler (PSC -16 bits value)

PSC即为预分频,其参数配置支持 1~65536
我在这里使用7199

(3).Counter Period (AutoReload Register - 16 bits value )

ARR是一个16位的寄存器,这里装着计数器能计数的最大数值。当计数到这个值的时候,如果使能了中断的话,定时器就会产生溢出中断
我在这里使用99

(4).auto-reload preload

自动重装载预加载,在此选择Enable

图2-6 Parameter Settings

(5).NVIC Settings

开启TIM6中断
图2-7 NVIC Settings

6.定时时间计算

(1).Tout=((ARR+1)*(PSC+1))/Tclk

Tclk为定时器挂载的时钟线的频率。对于STM32F103而言最高为72Mhz(根据自己的设置有关),具体看配置的时钟树。

在这里我们的溢出时间为:((7199+1)*(99+1))/72us=10000us=10ms

7.工程项目生成

(1).项目及编译器

(A).Project Name

给工程起个名字

(B).Project Location

给工程选个存放位置,避免中文路径

©.Toolchain/lDE

IDE选择,我在这里选择的是MDK-ARM(即Keil 5)
图2-8 项目命名及选择编译器

(2).代码生成

以下配置勾选后点击界面右上角的:GENERATE CODE

(A).Copy all used libraries into the project folder

将所有使用过的库复制到项目文件夹中

(B).Generate peripheral initialization as a pair of’c/.h’ files per peripheral

每个外设生成独立的’.c/.h’文件
图2-9 代码生成

三、程序

1.HAL_GPIO_WritePin(GPIO_TypeDef* GPIOX,uint16_t GPIO_Pin,GPIO_PinState pinstate);

电平输出HAL函数
GPIOX代表目标引脚的端口号,例如GPIOC。
GPIO_Pin代表目标引脚的引脚号,例如GPIO_Pin_13。
pinstate代表当前引脚的高低电平,一般来讲:
高电平(GPIO_PIN_SET)、低电平(GPIO_PIN_RESET)。
由于我在User Laber中已将PC13定义标签“LED”故在main.h中可找到宏定义:

/* Private defines -----------------------------------------------------------*/
#define LED_Pin GPIO_PIN_13
#define LED_GPIO_Port GPIOC

故令小灯闪烁的程序为

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */		
	HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//点亮小灯
	HAL_Delay(1000);//延时1秒(1000毫秒)
	HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//熄灭小灯
	HAL_Delay(1000);//延时1秒(1000毫秒)
  }
  /* USER CODE END 3 */

以此实现小灯1秒内的循环闪烁效果

2.HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOX,uint16_t GPIO_Pin);

电平翻转HAL函数

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */			
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//反转小灯
		HAL_Delay(1000);//延时1秒(1000毫秒)
  }
    /* USER CODE END 3 */

以此写法也可以实现每秒翻转一次小灯状态。

3.LED=!LED;

该写法来源是参考标准库的写法,在后续其他的代码参考与移植中可以变得便利一些。
毕竟标准库的例程和开源程序还是有很多的。

同时作为我的学习过程记录备份和代码整体结构的学习记录分享。

(1).sys.h

新建一个sys.h文件并写入以下内容并在外部调用:

#ifndef __SYS_H
#define __SYS_H	  

#include "stm32f1xx_hal.h"
//0,不支持OS
//1,支持OS
#define SYSTEM_SUPPORT_OS		0		//定义系统文件夹是否支持OS
	 
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

//Ex_NVIC_Config专用定义
#define GPIO_A 0
#define GPIO_B 1
#define GPIO_C 2
#define GPIO_D 3
#define GPIO_E 4
#define GPIO_F 5
#define GPIO_G 6 
#define FTIR   1  //下降沿触发
#define RTIR   2  //上升沿触发
								   

//JTAG模式设置定义
#define JTAG_SWD_DISABLE   0X02
#define SWD_ENABLE         0X01
#define JTAG_SWD_ENABLE    0X00	

typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;

typedef const int32_t sc32;
typedef const int16_t sc16;
typedef const int8_t sc8;

typedef __IO int32_t  vs32;
typedef __IO int16_t  vs16;
typedef __IO int8_t   vs8;

typedef __I int32_t vsc32;
typedef __I int16_t vsc16;
typedef __I int8_t vsc8;

typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;

typedef const uint32_t uc32;
typedef const uint16_t uc16;
typedef const uint8_t uc8;

typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;

typedef __I uint32_t vuc32;
typedef __I uint16_t vuc16;
typedef __I uint8_t vuc8;


#endif

(2).led.h

新建一个led.h文件并写入以下内容并在外部调用:

#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

#define LED PCout(13)	

#endif

将PC13引脚进行宏定义 命名为LED

(3).stm32f1xx_it.c和stm32f10x_it.h

stm32f10x_it.c和stm32f10x_it.h一般是存放STM32工程的中断函数的文件。
在此我省去了自动生成的大部分程序,仅留下需要我们人为添加的部分
stm32f10x_it.c

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "led.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PM */
uint16_t TimerCount10ms = 0;//10ms中断计数变量
uint16_t TimerCount500ms = 0;//500ms中断计数变量
/* USER CODE END PM */

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器溢出中断回调函数
{//定时器中断时,每进行完一个中断,并不会立刻退出,而是会进入到中断回调函数中
	if(htim == &htim6)//10ms进入一次定时器6的中断
	{
		TimerCount10ms=1;//10ms中断计数变量标志位
		TimerCount500ms++;//每进入一次中断计数加1
		if(TimerCount500ms%50 == 0)//当整除50时进入判断
		{
			LED=!LED;//反转小灯
		}
	}
}
/* USER CODE END 1 */

stm32f10x_it.h
extern 表明变量或者函数是定义在其他其他文件中的。
简单理解为:外部调用其他文件中定义的变量即可

/* USER CODE BEGIN EFP */
extern uint16_t TimerCount10ms;//10ms中断计数变量
extern uint16_t TimerCount500ms;//500ms中断计数变量
/* USER CODE END EFP */

(4).main.c

在此我省去了自动生成的大部分程序,仅留下需要我们人为添加的部分

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "led.h"
#include "sys.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_TIM6_Init();
  MX_USART1_UART_Init();
  MX_TIM4_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim6); //开启定时器6中断
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  if(TimerCount10ms==1)//10ms执行一次
	  {
          //在此处可以放入一些执行函数
		  TimerCount10ms=0;//将标志位置0
	  }
  }
  /* USER CODE END 3 */

四、演示效果

GPIO_LED

总结

经过一个充实而忙碌的夜晚,我满怀热情地撰写了一篇关于单片机学习的文章,虽然已尽力详尽,但深知文章中仍不免有诸多省略和未及细讲之处。对于那些在阅读过程中仍感意犹未尽、存有疑问的同学,建议大家不妨拓宽视野,多阅读几篇来自不同作者、风格各异的优秀文章。相信通过多角度、多层次的学习,你们定能集百家之长从而对单片机有更加深入和全面的理解。

在此,我的初衷不仅是为初学者提供一份学习路上的指引,也是对自己学习历程的一次回顾与总结。通过分享,我希望能激发更多人对单片机技术的兴趣,共同探索这片充满挑战与机遇的领域。

为了保持内容的连贯性和避免不必要的重复,我计划在未来的博客中,对于本文中已提及但未深入展开的话题,将通过链接的方式引导大家回到本文进行查阅。同时,我也将不断优化和完善文章内容,力求为大家提供更加准确、全面、易于理解的学习资源。

下期预告——关于GPIO_Input的学习分享。

感谢大家的关注与支持


参考文献引用

嵌入式-STM32-GPIO输出和输入的HAL库函数
GPIO推挽输出和开漏输出模式区别详解
【野火】TIM-基本定时器

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: STM32F1_HAL库使用手册文件是STM32F1系列单片机的编程接口及其应用程序的软件开发包。该手册具有非常详细的介绍和说明,包括HAL库的功能、使用方法、配置及编译软件等多个方面。 首先,该手册详细讲解了STM32F1系列单片机的基本知识,如器件特性、体系结构和系统时钟等。其次,该手册介绍了HAL库的结构、API(应用程序接口)函数,文件和数据类型,并通过代码示例来演示如何使用HAL库进行应用程序的开发。此外,该手册还列出了各类功能实现的驱动库函数。 在使用HAL库进行开发时,手册中提供了充足的文献资料,可以帮助开发人员解决各种问题,例如在编写驱动程序时如何使用HAL中的定时计数器、串口转换器和DMA等。手册中还提供了各种应用示例,如PWM控制、定时器中断、SPI通讯和ADC采集等等,展示了HAL库在各种应用场景下的使用方法。 最后,该手册中还包含了硬件抽象层标准库的配置工具,如CubeMX和STM32工具箱,用于帮助开发人员更为快速地进行底层开发工作,降低了开发成本和时间成本。 综上所述,STM32F1系列单片机HAL库使用手册文件是非常重要的开发工具。它深入浅出地解释了HAL库如何应用于STM32F1系列单片机开发。对于开发人员来说,熟练掌握该手册的内容,可以促进开发过程,提高开发效率。 ### 回答2: stm32f1_hal库是ST公司提供的一种硬件抽象层的库文件,支持对于STM32F1系列的微控制器进行控制并输出操作。这个编程库文件非常适合初学者或者是想要快速地进行STM32F1系列微控制器开发的程序员,因为它提供一种抽象的、高级的、更易于理解的方式来写代码。同时,stm32f1_hal库的使用手册文件非常重要,因为它是学习和使用stm32f1_hal库的关键,提供了详细的操作指南和样例代码。 stm32f1_hal库使用手册文件包含五个章节:库的概述、库的安装、库的使用、库的例子和库中的附录。第一个章节介绍了stm32f1_hal库的主要特性,这些特性包括高级的外设驱动、支持中斷实时和低功耗模式、易于使用和理解的API等等。 第二个章节讲解了如何在STM32F1系列微控制器中安装该库,此处需要注意的是不同的开发环境安装该库的方法可能不同。 第三个章节是重点所在,讲解了常见的库API及其使用方法,例如初始化外设、读取和写入数据。此处需要注意的是,代码中所调用的API需要根据不同的外设进行调整。 第四个章节列出了一些常见的例子,展示了如何使用stm32f1_hal库来实现不同的功能,例如LED、串口通信、时钟控制等等。 最后一个章节中提供了附录中的内容,为在实际开发中对库API的使用提供支持。总之,stm32f1_hal库使用手册文件是使用STM32F1微控制器开发的重要参考书,值得认真参考。 ### 回答3: stm32f1_hal库STM32F1系列的外设驱动库。使用手册文件提供了完整的、详细的说明,包含了使用方法、函数及其参数的解释、编程范例等,是程序员使用stm32f1_hal库的重要参考资料。 手册文件主要介绍了STM32F1系列芯片的存储器、时钟、GPIO、中断、USART、SPI、I2C、DMA、ADC、DAC等各种外设的使用方法。通过手册,用户可以了解到如何对寄存器进行初始化,使外设工作正常。手册还提供了各种编程范例,程序员可以根据自己的需求进行调整和优化,大大提高了开发效率。 需要特别提醒的一点是,由于HAL库是由ST官方提供的驱动库,所以不同芯片的HAL库会有一些差别,用户在选择芯片型号后,务必下载对应的版本的手册。另外,由于HAL库是基于底层库的封装,对于一些特殊的需求,或者需要更高的性能的场合,程序员也可以直接使用底层库进行编程。 总之,stm32f1_hal库使用手册文件是STM32F1系列的外设驱动库的重要参考资料。对于初学者来说,掌握使用手册,能够快速地编写STM32F1的应用程序;对于有经验的开发者,可以通过更深入的阅读和理解手册,更好地优化应用程序,提高应用程序的稳定性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值