相关说明:
开发板: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配置、主要函数代码及说明:
3.GPIO:
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输出重定向
#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
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]; //用于组合字符串
按键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闪烁)
}
}
}
}
数据更新使用到两个函数:
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;
}
}
函数:Check_Psw()
密码正确返回1,密码错误返回0。
uint8_t Check_Psw()//检测密码是否正确
{
if(B1==MIMA1 && B2==MIMA2 && B3==MIMA3)//密码正确
{
return 1;
}
else//密码错误
{
return 0;
}
}
密码修改用到三个函数:
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;
}
}
}
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;
}
}
在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 */
}
2.密码输入正确
4.串口接收
a.原密码有误
b.格式有误
c.修改密码不合法
d.数据长度有误
e.串口返回
以上就是全部内容,如有错误请批评指正。