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


前言

相关说明:

开发板: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.串口返回
在这里插入图片描述

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

在这里插入图片描述


总结

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

猜你喜欢

转载自blog.csdn.net/Octopus1633/article/details/124110280