STM32G431RB--基于HAL库(第十三届蓝桥杯嵌入式省赛真题解析)

本文详细阐述了如何使用CubeMX配置STM32G431RB开发板,并介绍了在蓝桥杯省赛中实现密码锁功能的代码设计,包括按键扫描、数据更新、密码验证和PWM控制。通过实际操作展示了如何处理LCD与LED交互、串口数据处理及定时器应用。


前言

相关说明:

开发板:CT117E-M4(STM32G431RB 蓝桥杯嵌入式比赛板)
开发环境: CubeMX+Keil5
涉及题目:第十三届蓝桥杯嵌入式省赛真题
题目难点:可能会遇到的LED与LCD冲突,切换PWM输出频率,LCD显示输出信号频率以及占空比,串口数据判别,定时器的运用,密码锁设计逻辑。
总体思路:LCD初始显示密码为@,每次KEY1、2、3按下数字从0~9循环显示,KEY4按下则检测密码是否正确。
密码正确则点亮LED1,输出频率为2KHZ、占空比为10%的信号,LCD显示输出信号频率以及占空比,5秒后LCD切换回密码输入界面。
密码错误则重现显示密码输入界面,密码显示为@,并且三次密码错误后LED2以0.1s频率闪烁5s。


CubeMX配置、主要函数代码及说明:

一、CubeMX配置(第十三届省赛完整版)

1.使能外部高速时钟:
在这里插入图片描述

2.配置时钟树:在这里插入图片描述

3.GPIO:

在这里插入图片描述

4.PWM(TIM2):在这里插入图片描述在这里插入图片描述
在这里插入图片描述

5.TIM3(检测串口数据传输结束):在这里插入图片描述

6.TIM6(LED以0.1s间隔闪烁):在这里插入图片描述

7.TIM7(PWM切换,LED1熄灭):在这里插入图片描述

8.USART1:在这里插入图片描述
9.NVIC:
在这里插入图片描述

二、代码相关定义、声明

1.函数声明

main.c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//定时器中断函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);	//串口接收中断
void LCD_Init_Show(void);	//LCD初始化显示
void LCD_Refresh(void);		//LCD更新显示 
void Rec_Check(void);		//接收检测

gpio.h
void KEY_Scan(void);//按键扫描
void LED_AllClose(uint8_t *LED_Close);//LED更新显示 值为1则表示下标对应LED关闭

tim.h
void PWM_Out(uint16_t Period,uint16_t Pulse);//PWM输出配置

usart,h
int fputc(int ch,FILE *f) ;//printf输出重定向

2.宏定义

#define LED_GPIO_PORT GPIOC
#define LED1_GPIO_PIN GPIO_PIN_8
#define LED2_GPIO_PIN GPIO_PIN_9
#define LED3_GPIO_PIN GPIO_PIN_10
#define LED4_GPIO_PIN GPIO_PIN_11
#define LED5_GPIO_PIN GPIO_PIN_12
#define LED6_GPIO_PIN GPIO_PIN_13
#define LED7_GPIO_PIN GPIO_PIN_14
#define LED8_GPIO_PIN GPIO_PIN_15

#define ON 	GPIO_PIN_RESET
#define OFF	GPIO_PIN_SET

#define LED1(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED1_GPIO_PIN,a)
#define LED2(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED2_GPIO_PIN,a)
#define LED3(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED3_GPIO_PIN,a)
#define LED4(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED4_GPIO_PIN,a)
#define LED5(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED5_GPIO_PIN,a)
#define LED6(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED6_GPIO_PIN,a)
#define LED7(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED7_GPIO_PIN,a)
#define LED8(a) HAL_GPIO_WritePin(LED_GPIO_PORT,LED8_GPIO_PIN,a)

#define KEY1_GPIO_PORT 	GPIOB
#define KEY1_GPIO_PIN	GPIO_PIN_0
#define KEY2_GPIO_PORT 	GPIOB
#define KEY2_GPIO_PIN	GPIO_PIN_1
#define KEY3_GPIO_PORT 	GPIOB
#define KEY3_GPIO_PIN	GPIO_PIN_2
#define KEY4_GPIO_PORT 	GPIOA
#define KEY4_GPIO_PIN	GPIO_PIN_0

3.变量定义

main.c
int B1=-1;//初始值为-1是为了自增后为0
int B2=-1;
int B3=-1;

int MIMA1=1;//密码值
int MIMA2=2;
int MIMA3=3;

uint16_t F;	//输出信号频率
uint8_t D;	//输出信号占空比

uint8_t LED_Close[3]={1,1,1};//LED状态控制数组 值为1则下标对应LED关闭

char recStr[100];		//串口接收数据数组
uint8_t recDat;			//串口每次接收到的一字节数据
uint32_t recDex=0;		//数组下标
uint8_t rec_chek=0;		//检查数据标志位

uint8_t Page=1;				//LCD显示页

uint8_t data_change=0;		//数据改变标志位 该位为0时不断更新LCD密码显示为@

uint32_t TIM_Clock=1000000;	//定时器时钟频率

char str[30]; 				//用于组合字符串

三、主要函数

1.按键扫描

按键KEY1 ~KEY3功能为更改B1 ~B3的值,按下后实现两个操作
1.数据更改。
2.数据更新在LCD上。

按键KEY4功能为确认密码,确认密码后分两种情况:
1.密码正确

a.LCD更新显示(频率、占空比)
b.LED1亮
c.PWM输出信号切换
d.开定时器(5s)

2.密码错误

a.LCD更新显示(密码输入界面)
b.错误标记++
c.错误达三次LED2闪烁(频率0.1s,持续5s)
d.开定时器(0.1s)
void KEY_Scan()//按键扫描
{
	static uint32_t error_num=0;
	if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET && Page==1)//B1++
	{
		if(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET)
		{
			while(HAL_GPIO_ReadPin(KEY1_GPIO_PORT,KEY1_GPIO_PIN)==GPIO_PIN_RESET);
			Data_Change(1);//数据更改 
			LCD_Change(1);//LCD更新
		}
	}
	
	else if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET && Page==1)//B2++
	{
		if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET)
		{
			while(HAL_GPIO_ReadPin(KEY2_GPIO_PORT,KEY2_GPIO_PIN)==GPIO_PIN_RESET);
			Data_Change(2);
			LCD_Change(2);
		}
	}
	
	else if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET && Page==1)//B3++
	{
		if(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET)
		{
			while(HAL_GPIO_ReadPin(KEY3_GPIO_PORT,KEY3_GPIO_PIN)==GPIO_PIN_RESET);
			Data_Change(3);
			LCD_Change(3);
		}
	}
	
	else if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)//确认密码
	{
		if(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET)
		{
			while(HAL_GPIO_ReadPin(KEY4_GPIO_PORT,KEY4_GPIO_PIN)==GPIO_PIN_RESET);
			data_change=0;
			if(Check_Psw()==1)//密码正确
			{
				Page=2;//LCD显示第二页
				LED_Close[1]=0;//LED1亮
				PWM_Out(2000,10);//PWM输出更新
				TIM7->CNT=0;//定时器计数值清零
				HAL_TIM_Base_Start_IT(&htim7); //开启定时器
				error_num=0;//密码错误次数重置
			}
			else//密码错误
			{
				Page=1;//LCD显示第一页
				LCD_Refresh();//LCD更新显示@
				B1=-1;//重置输入密码
				B2=-1;
				B3=-1;
				error_num++;//错误次数增加
			}
			
			if(error_num>=3)//如果错误次数大于或等于三次
			{
				TIM6->CNT=0;
				HAL_TIM_Base_Start_IT(&htim6); //开启定时器(LED2闪烁)
			}
		}
	}
}

2.数据更新

数据更新使用到两个函数
1.Data_Change(uint8_t BX),参数是要修改的数值(B1~B3)。
2.LCD_Change(uint8_t BX),参数是要更新显示的数值(B1~B3)。

void Data_Change(uint8_t BX)//数据改变
{
	data_change=1;
	switch(BX)
	{
		case 1:
			B1++;
			B1%=10;
			break;
		
		case 2:
			B2++;
			B2%=10;
			break;
		
		case 3:
			B3++;
			B3%=10;
			break;
	}
}

void LCD_Change(uint8_t BX)//LCD更新显示
{
	char str[30];
	switch(BX)
	{
		case 1:
			sprintf(str,"    B1:%d               ",B1);
			LCD_DisplayStringLine(Line3,(unsigned char*)str);
			break;
		
		case 2:
			sprintf(str,"    B2:%d               ",B2);
			LCD_DisplayStringLine(Line4,(unsigned char*)str);
			break;
		
		case 3:
			sprintf(str,"    B3:%d               ",B3);
			LCD_DisplayStringLine(Line5,(unsigned char*)str);
			break;
	}
}

3.判断密码

函数:Check_Psw()
密码正确返回1,密码错误返回0。

uint8_t Check_Psw()//检测密码是否正确
{	
	if(B1==MIMA1 && B2==MIMA2 && B3==MIMA3)//密码正确
	{
		return 1;
	}
	else//密码错误
	{
		return 0;
	}
}

4.密码修改

密码修改用到三个函数:
1.HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
串口接收,每次接收到一字节数据则重新开启定时器,数据一直在接收的话就一直进不了定时器中断函数,直到接收到最后一字节数据5us后进入定时器中断函数
2.HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
判断接收数据长度,在接收到最后一字节数据5us后进入定时器中断函数,在中断函数中判断接收的数据长度(recDex)是否符合题意(7)。
3.Rec_Check()
数据合法性检测,检测原密码是否正确,数据格式是否正确,修改的密码是否均为0到9的字符

/*LONG CHECK*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断函数
{
	if(htim->Instance==TIM3)//判断接收数据长度
	{
		HAL_TIM_Base_Stop_IT(&htim3);//关闭定时器
		HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//重新以中断方式打开串口接收
		if(recDex==7)//接收数据长度为7
		{
			rec_chek=1;//检测标志位置1
		}	
		recDex=0;//清零
	}
}

/*DATA REC*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//串口接收中断
{
	HAL_TIM_Base_Stop_IT(&htim3);//停止定时器
	
	recStr[recDex++]=recDat;//保存至接收数组
	
	TIM3->CNT=0;
	HAL_TIM_Base_Start_IT(&htim3);//重新打开定时器
	
	HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//重新以中断方式打开串口接收
}

/*DATA CHECK*/
void Rec_Check()//接收检测
{
	if(recStr[0]==(MIMA1+48) && recStr[1]==(MIMA2+48) && recStr[2]==(MIMA3+48) && recStr[3]=='-')//检测原密码以及格式
	{
		if(recStr[4]>='0' && recStr[4]<='9' && recStr[5]>='0' && recStr[5]<='9' && recStr[6]>='0' && recStr[6]<='9')//检测修改密码合法性
		{
			MIMA1=recStr[4]-48;//修改密码
			MIMA2=recStr[5]-48;
			MIMA3=recStr[6]-48;
		}
	} 
}

5.切换PWM

KEY4按下并且判断密码无误后,重新配置PWM输出参数,从1KHZ的方波切换为2KHZ,占空比为10%的信号,并在5s后重新切换为1KHZ的方波
实现这一功能共用到两个函数
1.PWM_Out(uint16_t HZ,uint16_t AIR),第一个参数为输出信号频率,第二个参数为输出信号占空值(50则表示占空比为50%),配置定时器的重装载值和Pulse用到两个公式

重装载值=定时器时钟频率/输出信号频率
Pulse=占空值/100*重装载值

2.HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim),5s定时器,控制PWM切换

tim.c
void PWM_Out(uint16_t HZ,uint16_t AIR)//PWM输出配置
{
	htim2.Init.Period = TIM_Clock/HZ;
	sConfigOC.Pulse = htim2.Init.Period*1.0*AIR/100;
	HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2);
	HAL_TIM_PWM_Init(&htim2);
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
}

main.c
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断函数
{
	if(htim->Instance==TIM7)//LED1熄灭 PWM输出频率为1KHz的方波 屏幕显示切换回密码输入界面
	{
		HAL_TIM_Base_Stop_IT(&htim7); //关闭定时器
		LED_Close[1]=1;//LED1灭
		PWM_Out(1000,50);//输出频率为1KHz的方波
		Page=1;
		B1=-1;//重置B1~B3的值
		B2=-1;
		B3=-1;
	}
}

6.Main函数

在Main函数中注意清除定时器中断标志位,避免程序刚开始时就进入定时器中断函数并且开启串口接收中断和PWM输出

TIM3->SR=0;//中断标志位清零
TIM6->SR=0;
TIM7->SR=0;
HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//开启接收中断
HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); //PWM输出   

Main函数主要负责按键扫描、LCD和LED的更新、接收数据合法性检测


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_TIM2_Init();
  MX_TIM6_Init();
  MX_TIM7_Init();
  MX_USART1_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
	LCD_Init();//LCD初始化
	LCD_Init_Show();//LCD初始化显示
	
	TIM3->SR=0;//中断标志位清零
	TIM6->SR=0;
	TIM7->SR=0;
	
	HAL_UART_Receive_IT(&huart1,&recDat,sizeof(recDat));//开启接收中断
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); //PWM输出   
  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
		KEY_Scan();//按键扫描
		if(Page==2)//如果LCD显示页为2 
		{
			F=TIM_Clock/TIM2->ARR;//输出信号频率
			D=TIM2->CCR2*1.0/TIM2->ARR*100;//输出信号占空比
			LCD_Refresh();//LCD更新显示
		}
		if(!data_change)//如果数据没改变
		{
			LCD_Refresh();//LCD更新显示@
		}
		if(rec_chek==1)//如果接收检测标志位为1
		{
			rec_chek=0;//标志位清零
			Rec_Check();//检测数据合法性
		}
		LED_AllClose(LED_Close);//LED更新显示
  }
  /* USER CODE END 3 */
}

四、实验结果

1.密码输入状态
在这里插入图片描述请添加图片描述

2.密码输入正确
在这里插入图片描述

请添加图片描述3.密码输入正确5s后又回到密码输入状态

4.串口接收
a.原密码有误
在这里插入图片描述
b.格式有误
在这里插入图片描述
c.修改密码不合法
在这里插入图片描述
d.数据长度有误
在这里插入图片描述
在这里插入图片描述
e.串口返回
在这里插入图片描述

五、源码(转载请注明出处)

在这里插入图片描述


总结

以上就是全部内容,如有错误请批评指正。

评论 17
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AゞOctopus๊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值