在进行一款单片机学习时,最基本也是最简单的外设就是控制I/O口的高低电平。LED、蜂鸣器以及数码管这些都是可以作为外围电路连接在单片机的I/O口上,进而可以实现通过单片机对其进行控制。在本章节中,会以这三种外围电路的控制来学习stm32单片机中的外设资源—GPIO(General-purpose input/output)。
所使用的基于stm32f103zet6芯片的开发板中,关于LED外围电路的设计如下图中所示。从图中可以看出,只有当二极管(LED)的阴极电压为0V(低电平)时其会导通。因此通过单片机将相应接口的GPIO设置为低电平后,便能够控制LED灯亮,设置为高电平时,LED灯灭。
通过前一章节对寄存器开发方式的简单介绍后,在本小节中使用寄存器开发方式实现对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);
}
本小节应用库函数开发的方式来点亮一颗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口电平
}
}
打开在上一章节中已经建立好的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 */
{
/* 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 */
}
经过了对LED的简单操作,可以发现在这三种方式中,寄存器开发方式最为复杂。其在编写程序前需要查找好所使用寄存器的地址等相关信息,出现了bug时很难精准的定位、开发周期长的缺点,因此寄存器开发常常不会被采用。标准库方式在其基础上简化了很多步骤,只需要通过调用相关外设的接口函数便可实现对应的功能,因此这一种方式也被广泛地应用到stm32单片机的开发中。HAL库开发方式由于CubeMX软件的存在,可以以图形化的方式对各种外设进行配置,大大方便了配置的步骤流程相比较于标准库来说还要更加便捷。
蜂鸣器是一种可以发声的器件,无源蜂鸣器需要对其提供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);
}
}
数码管是一种发光器件,基本单元是发光二极管,本小节中用于数码管显示的单片机外围电路如下图中所示。数码管是一个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);
}
}
}