在进行一款单片机学习时,最基本也是最简单的外设就是控制I/O口的高低电平。LED、蜂鸣器以及数码管这些都是可以作为外围电路连接在单片机的I/O口上,进而可以实现通过单片机对其进行控制。在本章节中,会以这三种外围电路的控制来学习stm32单片机中的外设资源—GPIO(General-purpose input/output)。

1、点亮LED灯
所使用的基于stm32f103zet6芯片的开发板中,关于LED外围电路的设计如下图中所示。从图中可以看出,只有当二极管(LED)的阴极电压为0V(低电平)时其会导通。因此通过单片机将相应接口的GPIO设置为低电平后,便能够控制LED灯亮,设置为高电平时,LED灯灭。
在这里插入图片描述

1.1 寄存器方式
通过前一章节对寄存器开发方式的简单介绍后,在本小节中使用寄存器开发方式实现对LED灯的亮灭控制。下图是一个关于GPIO的结构图,通过对其了解可以知道GPIO可以被配置成输入、输出、复用功能以及模拟输入输出四种模式。其中输入模式又被细分为模拟(ADC采集)、上拉、下拉以及浮空(输入电平不确定,完全由外部的输入决定,按键电路)模式;输出模式被分为推挽(输出高低电平)和开漏模式(输出高阻态或低电平);复用功能也被分为推挽和开漏两种模式,但是输出信号源于其它外设,输入正常可用,但一般直接用外设的寄存器来获取该数据信号;模拟输入输出模式,上下拉无影响。
在这里插入图片描述清楚了GPIO的模式配置后,下面通过寄存器来操作端口PC0输出一个低电平,进而使连接在其上的一颗LED灯亮起。最开始的模板准备工作在前一章节中已经详细描述,在此就不做过多赘述。
打开之前已经建立好了的寄存器开发模板,在main.c文件中编写的代码如下。

#include "stm32f10x.h"			//头文件中定义各种寄存器
void SystemInit()				//初始化
{
	
}

int main()								//主函数
{
	RCC_APB2ENR |= 1<<4;			//开启GPIOC挂载的总线APB2的时钟
	GPIOC_CRL &= ~(0x0F<<(4*0));	//
	GPIOC_CRL |= (3<<4*0);			//配置GPIOC为通用推挽输出模式
	GPIOC_BSRR = (1<<(16+0));		//配置PC0端口为低电平,LED亮
	while(1);	
}

1.2 库函数方式
本小节应用库函数开发的方式来点亮一颗LED。重复上一章节中库函数工程模板的建立,对stm32的GPIO进行操作就需要将标准库中的stm32f10x_gpio.c和stm32f10x_cc.c文件添加到项目中,前者是关于GPIO操作的函数声明及模式选项配置的宏定义,后者是关于时钟的一些函数和宏定义。在这里采用模块化编程,也就是将led看做一个功能模块,在项目中新建led.c及led.h文件,最后在main.c文件中包含使用。具体的代码如下所示:

led.c

#include "led.h"

void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;					//定义GPIO操作结构体变量
	
	RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);	//开启时钟
	
	GPIO_InitStructure.GPIO_Pin = LED_PIN;					//设置led所在IO口
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		//设置为推挽输出模式
	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//设置传输速率
	
	GPIO_Init(LED_PORT, &GPIO_InitStructure);				//初始化GPIO口
	
	GPIO_SetBits(LED_PORT, LED_PIN);						//将IO口设置为高电平,LED灭
	
}

led.h

#ifndef _led_H
#define _led_H

#include "stm32f10x.h"

#define LED_PORT_RCC RCC_APB2Periph_GPIOC
#define LED_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT  GPIOC

void LED_Init(void);

#endif

main.c

#include "stm32f10x.h"
#include "led.h"
int main()
{
	LED_Init();
	while(1)
	{
		GPIO_ResetBits(LED_PORT,GPIO_Pin_0);			//翻转IO口电平
	}
}

1.3 HAL库方式

  1. 打开在上一章节中已经建立好的STM32CubeMX项目,在芯片的图形化界面中找到PC0端口单击后将其设置为GPIO_Output模式;

在这里插入图片描述

在左侧的System Core菜单中选择RCC(时钟配置),将HSE和LSE设置为Crystal/ceramic Resonator模式,即外部晶振;
在这里插入图片描述

将GPIO选项中的GPIO Mode and Configuration中的GPIO mode设置为Output Push Pull(推挽输出),User Label设置为LED(端口标签),其它的选项默认即可;
在这里插入图片描述

随后切换主菜单到Clock Configuration时钟配置,在HCLK下方的框中输入72,然后按下回车则系统的时钟就被配置到了72MHz,最后点击GENRATE CODE,即可生成代码;
在这里插入图片描述

紧接着在keil5中打开自动生成的项目,如下图中所示。可以看出整个项目的构成与标准库开发的方式类似,只不过这里的代码都是自动生成的。其中gpio.c文件中的代码为GPIO初始化与上一节中的led.c中的函数功能是相同的,只不过这里是使用了HAL库函数。在需要注意的是如果需要在这些CubeMX软件自动生成的文件中编写自定义的代码,是需要将其写在下述这段注释之间的,XXX表示的是某一个模块,比如Init(初始化)、SystInit(系统时钟初始化)等等;

  /* USER CODE BEGIN XXX*/
    code
  /* USER CODE END XXX */

在这里插入图片描述

  1. 最后点亮一颗LED,只需要在main()函数中写下如下代码即可。
{
  /* 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();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
  }
  /* USER CODE END 3 */
}

1.4 三种方式的总结与比较
经过了对LED的简单操作,可以发现在这三种方式中,寄存器开发方式最为复杂。其在编写程序前需要查找好所使用寄存器的地址等相关信息,出现了bug时很难精准的定位、开发周期长的缺点,因此寄存器开发常常不会被采用。标准库方式在其基础上简化了很多步骤,只需要通过调用相关外设的接口函数便可实现对应的功能,因此这一种方式也被广泛地应用到stm32单片机的开发中。HAL库开发方式由于CubeMX软件的存在,可以以图形化的方式对各种外设进行配置,大大方便了配置的步骤流程相比较于标准库来说还要更加便捷。

2、无源蜂鸣器的使用
蜂鸣器是一种可以发声的器件,无源蜂鸣器需要对其提供1.5~5KHz频率的脉冲信号,其才可以发出声音。蜂鸣器电路的设计如下图中所示,通过一个三极管将单片机IO口的电流放大后再驱动蜂鸣器,这一点是为了避免出现一个IO输出电流过大导致其它IO口的电流过小的情况。从电路图中可以看出,只需要控制PB5口输出一定频率的脉冲波蜂鸣器即可发声,详细的代码如下所示。
在这里插入图片描述beep.c

#include "beep.h"

void Beep_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(BEEP_PORT_RCC,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=BEEP_PIN;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(BEEP_PORT,&GPIO_InitStructure);
}

beep.h

#ifndef _beep_H
#define _beep_H

#include "system.h"

#define BEEP_PORT_RCC RCC_APB2Periph_GPIOB

#define BEEP_PIN GPIO_Pin_5

#define BEEP_PORT GPIOB
void Beep_Init(void);

#define BEEP PBout(5)

#endif

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "beep.h"

int main()
{
	u16 i=0;
	SysTick_Init(72);
	LED_Init();
	Beep_Init();
	
	while(1)
	{
		i++;
		if(i%10==0)
		{
			BEEP=!BEEP;
		}
		delay_us(10);
	}
}

3、无源蜂鸣器的使用
数码管是一种发光器件,基本单元是发光二极管,本小节中用于数码管显示的单片机外围电路如下图中所示。数码管是一个8个LED组成的器件,因此要点亮数码管和LED方式相同,给相应的IO口一个低电平即可。此外,如果需要让数码管显示指定的数字,那么就需要同时点亮对应段的LED。比如需要显示数字“0”,则需要控制a,b,c,d,e,f段对应的PC0~5端口输出低电平即可。
在这里插入图片描述

smg.c

#include "smg.h"

void SMG_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(SMG_PORT_RCC,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin=SMG_PIN;
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(SMG_PORT,&GPIO_InitStructure);
	
}

smg.h

#ifndef _smg_H
#define _smg_H

#include "system.h"

#define SMG_PORT_RCC 		RCC_APB2Periph_GPIOC

#define SMG_PIN	 			(GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
	
#define SMG_PORT  GPIOC
void SMG_Init(void);

#endif

main.c

#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "smg.h"

u8 smgduan[16]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,
             0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71};	//数码管显示0~F的编码

int main()
{
	u8 i;
	SysTick_Init(72);
	LED_Init();
	SMG_Init();
	
	while(1)
	{
		for(i=0;i<16;i++)
		{
			GPIO_Write(SMG_PORT,~smgduan[i]);
			delay_ms(1000);
		}
	}
}