详细代码放在github里自行下载github
本次蓝桥杯省赛有led、串口、定时器等模块。题目简单不难。
led模块:
查看原理图可知,该led模块由PD2所控制,当PD2为高电平时,led模块才能够启用,在这个时候输入是有效的;当为低电平时,led模块会锁住当前PC口的值。所以在你要控制led灯时,就需要操控PD2。
点击pc8
选择输出模式
同样的操作配置好其他PC口和PD2口,如图:
这个user label 是自己定义的名字,STM32CubeMX会根据你定义的名字来宏定义这些端口。
void led_turndown(void)
{
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED5_GPIO_Port,LED5_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED6_GPIO_Port,LED6_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED7_GPIO_Port,LED7_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(LED8_GPIO_Port,LED8_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
需要注意的是在操作led时要先写好端口的值,然后操控开关PD2,这一步骤必须连续,否则回合lcd显示屏模块冲突,因为他们都是用的pc口。
串口模块:
串口按照图中配置即可,波特率题目要求为9600,然后记得去NVIC中把中断打开。
//打开串口的中断接收功能 这句代码放在主函数while循环前
HAL_UART_Receive_IT(&huart1,(uint8_t *)&Rxbuff,sizeof(Rxbuff));
//中断回调函数接收传输来的密码,触发串口中断后系统会自动调用这个函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1){//串口传输密码,密码替换
// 重新使能中断
HAL_UART_Receive_IT(huart,(uint8_t *)&Rxbuff,sizeof(Rxbuff));
if(Rxbuff[3] == '-' && Rxbuff[0] == password[0] && Rxbuff[1] == password[1] && Rxbuff[2] == password[2]
&& Rxbuff[4] >= '0' && Rxbuff[4] <= '9' && Rxbuff[5] >= '0' && Rxbuff[5] <= '9'
&& Rxbuff[6] >= '0' && Rxbuff[6] <= '9'){
password[0] = Rxbuff[4];
password[1] = Rxbuff[5];
password[2] = Rxbuff[6];
}
}
}
按键:
按键端口选择输入模式,按键按下是读取会是低电平,上拉输入最好。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32g4xx_hal.h"
#include "stm32g4xx_it.h"
#include "main.h"
// 按键结果结构体 需要注意的是任意俩者不能够同时为1
struct keys{
//按键单次按下 0-无效 1-按键按下
int flag;
//双击按键的标志 0-无效 1-双次按下
int doubleflag;
//长按按键的标志 0-无效 1-长按
int longflag;
};
//按键状态判断的结构体
struct keyState{
//记录按键状态机的状态
unsigned char judgeSate;
//记录按键的状态
unsigned char keyState;
//记录按键是否双击 双击就会计时
unsigned char doubleClickTimerFlag;
//记录按键按下的时间
int keyTime;
//记录按键双击的时间
unsigned char doubleClickTime;
};
void key_init(void);
//定义一个按键变量 key[0]=B1 key[1]=B2 key[2]=B3 key[3]=B4
extern struct keys key[];
/***************************************************************************************************
* 函数功能:按键扫描函数 注意此函数放在定时器中断(10ms)中的使用效果最佳 否则双击与长按会出现问题
* 函数参数:无
* 函数返回值:无
****************************************************************************************************/
void key_scanf(void);
#endif
key.c
#include "key.h"
struct keys key[4] = {0,0,0,0};
/****************************************************************************************************
* 函数功能:按键扫描函数 注意此函数放在定时器中断(10ms)中的使用效果最佳 否则双击与长按会出现问题
* 函数参数:无
* 函数返回值:无
*****************************************************************************************************/
void key_scanf(void)
{
static struct keyState _key[4];
//获取按键的最新状态
_key[0].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
_key[1].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
_key[2].keyState = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
_key[3].keyState = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
//处理按键的最新状态
for(int i=0;i<4;i++){
switch (_key[i].judgeSate){
//按键第一次按下
case 0:
if(_key[i].keyState == 0){
//跳转按键的状态
_key[i].judgeSate=1;
//清空按键时间
_key[i].keyTime=0;
}
break;
//按键第二次按下 两次相隔10ms可以起到消抖作用
case 1:
//按键再次按下 跳转按键状态
if(_key[i].keyState == 0)
_key[i].judgeSate=2;
//上一次按键按下是抖动按下 属于无效状态 应该退回最开始的状态
else
_key[i].judgeSate=0;
break;
//确定按键按下后的处理过程
case 2:
//等待松开过程,且非长按键
if((_key[i].keyState==1) && _key[i].keyTime<30){
//可能双击按键的第一次,进入计时
if(_key[i].doubleClickTimerFlag == 0) {
_key[i].doubleClickTimerFlag = 1;
_key[i].doubleClickTime = 0;
}
//在计时范围内又按了一次
else{
key[i].doubleflag=1;//双击情况
_key[i].doubleClickTimerFlag = 0;
}
_key[i].judgeSate = 0;
}
//松开且是长按键
else if(_key[i].keyState==1 && _key[i].keyTime>=30)
{
_key[i].judgeSate = 0;
key[i].longflag = 1;
}
//按下 且为长按键
else
_key[i].keyTime++;
break;
}
//按键单次按下
if(_key[i].doubleClickTimerFlag == 1 && _key[i].doubleClickTime >= 25) {
key[i].flag = 1;
_key[i].doubleClickTimerFlag = 0;
}
//按键双击 双击计时
else if(_key[i].doubleClickTimerFlag == 1){
_key[i].doubleClickTime++;
}
}
}
按键函数包含了单击、双击和长按,自行按需修改。
定时器输出pwm
此处有误,我并未使用PA1作为输出引脚,请大家自己看一PA1哪个定时器更改即可。
我的系统是170MHZ 所以170分频,这里填170 - 1;自动重装载值填1000 - 1。
如何计算:
系统频率是170M = 170 000 000,我要1000HZ频率的方波就除以1000.结果为170 000 然后分频系数填170,170 000 / 170 = 1 000这两个数就是这么来的。
后面还要在程序里修改为2000HZ再算。 170 000 000 / 2000 = 850 000 然后 850 000 / 170(分频) = 500(重装载)。
最后设置占空比以及打开中断。方波就是高一半第一半,2000HZ占空比10%设置200即可。
HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_1);//打开定时器
void Pwm_chage(void)//切换输出波形
{
TIM_OC_InitTypeDef sConfigOC = {0};
if(pwm_time != 0 && pwm_flag == 0){//pwm切换到2KHZ 5s
led1_turnOn();
htim2.Init.Period = 500 - 1;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 200;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
pwm_flag = 1;
}
else if(pwm_flag == 1 && pwm_time >= 50)//pwm切换到1KHZ
{
led1_turnDown();
htim2.Init.Period = 1000 - 1;
sConfigOC.Pulse = 500;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
pwm_flag = 2;
}
}
lcd模块是系统提供的就不多说了
if(password_HZ == 0){//启动界面显示
sprintf((char*)lcd_str0," PSD ");
sprintf((char*)lcd_str1," B1:%c ",'@');
sprintf((char*)lcd_str2," B2:%c ",'@');
sprintf((char*)lcd_str3," B3:%c ",'@');
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
LCD_DisplayStringLine(Line2,lcd_str0);
LCD_DisplayStringLine(Line4,lcd_str1);
LCD_DisplayStringLine(Line5,lcd_str2);
LCD_DisplayStringLine(Line6,lcd_str3);
}
else{//密码锁显示
sprintf((char*)lcd_str0," PSD ");
sprintf((char*)lcd_str1," B1:%c ",(uint8_t)(password_text[0] + 48));
sprintf((char*)lcd_str2," B2:%c ",(uint8_t)(password_text[1] + 48));
sprintf((char*)lcd_str3," B3:%c ",(uint8_t)(password_text[2] + 48));
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
LCD_DisplayStringLine(Line2,lcd_str0);
LCD_DisplayStringLine(Line4,lcd_str1);
LCD_DisplayStringLine(Line5,lcd_str2);
LCD_DisplayStringLine(Line6,lcd_str3);
}
}