文章目录
一、前言
1.1 开发环境
①Keil MDK V5.38a
②STM32CubemxV6.10.0
③正点原子战舰板(STM32F103ZET6)
④高速DAP仿真器
⑤MCU Package V1.8.5
1.2 GPIO电路原理
GPIO(General-purpose input/output,通用输入输出),通过与外围电路的直接连接,达到获取外围电路电平状态(输入),或者控制外围电路电平状态(输出)。GPIO的电路原理框图如下所示:
由上图可知,GPIO存在3种输入状态:模拟/复用/普通 + 上/下/浮空输入,将输入上/下拉的目的,在于指定未连接外围电路时的输入电平状态。上拉表示在未连接外围电路时读取到IO输入电平应为高;下拉表示在未连接外围电路时读取到IO输入电平应为低,浮空则表示不关心此电平状态。
GPIO存在2种输出状态:普通/复用 + 推挽/开漏输出,将输出推挽/开漏的目的,在于指定输出高电平的强度。推挽输出时的高电平强度大,相当于把I/O直接连到了电源,此时若将I/O连接到GND将会发生短路,大概率会烧毁I/O,通常在控制外围电路时使用此功能(例:点亮/熄灭LED);开漏输出时需要通过外围电路的上拉才能输出高电平,此时若将I/O连接到GND不会发生短路现象,通常在仅需接收外围电路传输信息时使用此功能。
1.3 板卡电路原理
1.3.1 按键电路原理
由下图可知,Key0、Key1和Key2会在按键按下时,将对应的Gpio管脚连接到Gnd,即将Gpio管脚拉低,当按键没有按下时Gpio管脚应该默认为高电平,用来区分按键按下和未按下两种状态;所以需要使能Gpio管脚的输入模式,并配置Gpio管脚上拉。同理Wkup连接的Gpio管脚应该配置为输入模式,并配置Gpio管脚下拉。
1.3.2 Led电路原理
由下图可知,当Gpio管脚输出低电平时,会在Led的两端产生电压差,从而驱动Led点亮,当Gpio管脚输出高电平时,会在Led的两端没有电压差,Led熄灭;所以需要使能Gpio管脚为输出模式,并配置Gpio管脚上拉。
1.3.3 Beep电路原理
由下图可知,当Gpio管脚输出高电平时,会在导通三极管,同时Beep的两端会产生电压差,从而驱动Beep鸣叫,当Gpio管脚输出低电平时,会在关断三极管,同时Beep的两端没有电压差,Beep停止鸣叫;所以需要使能Gpio管脚为输出模式,并配置Gpio管脚下拉。
二、功能实现
2.1 配置STM32Cubemx工程
1、配置MCU调试功能(PS:若不配置该参数可能出现程序下载一次后,再次下载失败的情况)
根据实际调试器与MCU引脚的连接数量选择调试参数,通常调试器除了VCC和GND外,只有2个管脚,需要选择Serial Wire。
2、配置连接LED1的GPIO管脚(鼠标左键选择GPIO_Output即可,鼠标右键选择Enter User Label即可设置GPIO管脚名称)
3、配置连接BEEP的GPIO管脚
4、配置连接KEY2的GPIO管脚
5、配置连接KEY1的GPIO管脚
6、配置连接KEY0的GPIO管脚
7、配置MCU时钟树,默认使用外部时钟源,并配置系统时钟为72MHz
8、生成Keil MDK工程
若不勾选"Generate ......",则生成的所有的驱动代码都会在main.c中;勾选后生成的驱动代码将存在于各自的驱动文件。
2.2 Keil MDK工程编码
2.2.1 按键驱动代码
Key.c:
#include "Key.h"
static GPIO_TypeDef *Key_Gpio_Port[4] = { GPIOE, GPIOE, GPIOE, GPIOA };
static uint32_t Key_Gpio_Pinx[4] = { KEY0_Pin, KEY1_Pin, KEY2_Pin, WKUP_Pin };
/*
* @brief 初始化独立按键
* @param Key_Index:独立按键索引号
*/
void Key_Init(KEY_INDEX_E Key_Index)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能Key管脚端口时钟 */
if((KEY_INDEX_0 == Key_Index) || \
(KEY_INDEX_1 == Key_Index) || \
(KEY_INDEX_2 == Key_Index))
{
__HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitStruct.Pull = GPIO_PULLUP;
}
else
{
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
}
/* 设置Key管脚初始化参数并完成初始化 */
GPIO_InitStruct.Pin = Key_Gpio_Pinx[Key_Index];
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(Key_Gpio_Port[Key_Index], &GPIO_InitStruct);
}
/*
* @brief 初始化独立按键
* @param Key_Index:独立按键索引号
* @retval 独立按键当前状态
*/
KEY_STATUS_E Key_Get_Status(KEY_INDEX_E Key_Index)
{
GPIO_PinState pin_status = GPIO_PIN_RESET;
KEY_STATUS_E retval = KEY_STATUS_NO_PRESS;
pin_status = HAL_GPIO_ReadPin(Key_Gpio_Port[Key_Index], \
Key_Gpio_Pinx[Key_Index]);
if(KEY_INDEX_WKUP == Key_Index)
{
if(GPIO_PIN_SET == pin_status)
{
retval = KEY_STATUS_PRESS;
}
}
else
{
if(GPIO_PIN_RESET == pin_status)
{
retval = KEY_STATUS_PRESS;
}
}
return retval;
}
/*
* @brief 反初始化独立按键
* @param Key_Index:独立按键索引号
*/
void Key_DeInit(KEY_INDEX_E Key_Index)
{
HAL_GPIO_DeInit(Key_Gpio_Port[Key_Index], Key_Gpio_Pinx[Key_Index]);
}
Key.h
#ifndef __KEY_H
#define __KEY_H
#include "main.h"
/* 独立按键索引号枚举 */
typedef enum
{
KEY_INDEX_0 = 0,
KEY_INDEX_1,
KEY_INDEX_2,
KEY_INDEX_WKUP,
} KEY_INDEX_E;
/* 独立按键状态枚举 */
typedef enum
{
KEY_STATUS_NO_PRESS = 0,
KEY_STATUS_PRESS,
} KEY_STATUS_E;
/*
* @brief 初始化独立按键
* @param Key_Index:独立按键索引号
*/
extern void Key_Init(KEY_INDEX_E Key_Index);
/*
* @brief 初始化独立按键
* @param Key_Index:独立按键索引号
* @retval 独立按键当前状态
*/
extern KEY_STATUS_E Key_Get_Status(KEY_INDEX_E Key_Index);
/*
* @brief 反初始化独立按键
* @param Key_Index:独立按键索引号
*/
extern void Key_DeInit(KEY_INDEX_E Key_Index);
#endif /* __KEY_H */
2.2.2 LED灯驱动代码
#include "Led.h"
static GPIO_TypeDef *Led_Gpio_Port[2] = { GPIOB, GPIOE };
static uint32_t Led_Gpio_Pinx[2] = { LED0_Pin, LED1_Pin };
/*
* @brief 初始化Led
* @param Led_Index:Led索引号
*/
void Led_Init(LED_INDEX_E Led_Index)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能Led管脚端口时钟 */
if(LED_INDEX_0 == Led_Index)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
}
else
{
__HAL_RCC_GPIOE_CLK_ENABLE();
}
/* 设置Led管脚初始化电平,确保初始化后Led处于关闭状态 */
HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
Led_Gpio_Pinx[Led_Index], GPIO_PIN_SET);
/* 设置Led管脚初始化参数并完成初始化 */
GPIO_InitStruct.Pin = Led_Gpio_Pinx[Led_Index];
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(Led_Gpio_Port[Led_Index], &GPIO_InitStruct);
}
/*
* @brief 开启Led
* @param Led_Index:Led索引号
*/
void Led_Open(LED_INDEX_E Led_Index)
{
HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
Led_Gpio_Pinx[Led_Index], GPIO_PIN_RESET);
}
/*
* @brief 关闭Led
* @param Led_Index:Led索引号
*/
void Led_Close(LED_INDEX_E Led_Index)
{
HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
Led_Gpio_Pinx[Led_Index], GPIO_PIN_SET);
}
/*
* @brief 反初始化Led
* @param Led_Index:Led索引号
*/
void Led_Deinit(LED_INDEX_E Led_Index)
{
HAL_GPIO_DeInit(Led_Gpio_Port[Led_Index], Led_Gpio_Pinx[Led_Index]);
}
Led.h
#ifndef __LED_H
#define __LED_H
#include "main.h"
/* Led索引号枚举类型 */
typedef enum
{
LED_INDEX_0 = 0,
LED_INDEX_1,
} LED_INDEX_E;
/*
* @brief 初始化Led
* @param Led_Index:Led索引号
*/
extern void Led_Init(LED_INDEX_E Led_Index);
/*
* @brief 开启Led
* @param Led_Index:Led索引号
*/
extern void Led_Open(LED_INDEX_E Led_Index);
/*
* @brief 关闭Led
* @param Led_Index:Led索引号
*/
extern void Led_Open(LED_INDEX_E Led_Index);
/*
* @brief 反初始化Led
* @param Led_Index:Led索引号
*/
extern void Led_Deinit(LED_INDEX_E Led_Index);
#endif /* __LED_H */
2.2.3 Beep驱动代码
Beep.c
#include "Beep.h"
/*
* @brief 初始化蜂鸣器
*/
void Beep_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能Beep管脚端口时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 设置Beep管脚初始化电平,确保初始化后蜂鸣器处于关闭状态 */
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
/* 设置Beep管脚初始化参数并完成初始化 */
GPIO_InitStruct.Pin = BEEP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(BEEP_GPIO_Port, &GPIO_InitStruct);
}
/*
* @brief 开启蜂鸣器
*/
void Beep_Open(void)
{
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);
}
/*
* @brief 关闭蜂鸣器
*/
void Beep_Close(void)
{
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
}
/*
* @brief 反初始化蜂鸣器
*/
void Beep_Deinit(void)
{
HAL_GPIO_DeInit(BEEP_GPIO_Port, BEEP_Pin);
}
Beep.h
#ifndef __BEEP_H
#define __BEEP_H
#include "main.h"
/*
* @brief 初始化蜂鸣器
*/
extern void Beep_Init(void);
/*
* @brief 开启蜂鸣器
*/
extern void Beep_Open(void);
/*
* @brief 关闭蜂鸣器
*/
extern void Beep_Close(void);
/*
* @brief 反初始化蜂鸣器
*/
extern void Beep_Deinit(void);
#endif /* __BEEP_H */
2.2.4 功能测试代码
void Drive_Func_Test(void)
{
Led_Key_Beep_Test();
}
static void Led_Key_Beep_Test(void)
{
static uint8_t Led0_Status = 0;
static uint8_t Led1_Status = 0;
static uint8_t Beep_Status = 0;
static uint32_t Key_Shake_Off_Cnt = 0;
/* 初始化Led */
Led_Init(LED_INDEX_0);
Led_Init(LED_INDEX_1);
/* 初始化Key */
Key_Init(KEY_INDEX_0);
Key_Init(KEY_INDEX_1);
Key_Init(KEY_INDEX_2);
Key_Init(KEY_INDEX_WKUP);
/* 初始化Beep */
Beep_Init();
while(1)
{
if(Key_Shake_Off_Cnt++ >= 5)
{
Key_Shake_Off_Cnt = 0;
/* Key0按下后Led0状态翻转 */
if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_0))
{
if(0 == Led0_Status)
{
Led_Open(LED_INDEX_0);
}
else
{
Led_Close(LED_INDEX_0);
}
Led0_Status = !Led0_Status;
}
/* Key1按下后Led1状态翻转 */
if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_1))
{
if(0 == Led1_Status)
{
Led_Open(LED_INDEX_1);
}
else
{
Led_Close(LED_INDEX_1);
}
Led1_Status = !Led1_Status;
}
/* Key2按下后Beep状态翻转 */
if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_2))
{
if(0 == Beep_Status)
{
Beep_Open();
}
else
{
Beep_Close();
}
Beep_Status = !Beep_Status;
}
/* Wkup按下后Beep鸣叫100ms */
if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_WKUP))
{
Beep_Open();
HAL_Delay(100);
Beep_Close();
}
}
HAL_Delay(10);
}
}
2.2.5 Main函数代码
extern void Drive_Func_Test(void);
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();
/* USER CODE BEGIN 2 */
Drive_Func_Test();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
2.2.5 Keil MDK调试参数配置
若不勾选"Reset and Run",则通过调试器下载代码后需要在手动复位,程序才能运行起来;勾选后可不用再手动复位,程序就可以运行起来。
2.2.6 下载验证
①按下Key0后,Led0状态翻转
②按下Key1后,Led1状态翻转
③按下Key2后,Beep状态翻转
④按下Wkup后,Beep鸣叫100ms后关闭
三、参考资料
[1]正点原子战舰板原理图-WarShip STM32F1_V3.4_SCH
[2]STM32F1开发指南-HAL库版本_V1.1
[3]STM32中文参考手册_V10
[4]Cortex-M3权威指南(中文)