此蓝桥杯嵌入式系列将加班加点的更新下去并且完全免费,这几天代码调试花了点时间,更新慢了点。
希望大家给一个免费的赞和收藏。
❤️❤️❤️❤️❤️❤️祝大家在比赛中取得好成绩❤️❤️❤️❤️❤️❤️
另外本次代码,加入了更多宏定义,以及命名方式 。
使代码更加方便阅读 和 后期参数修改
大家如果有什么 好的建议 或者 不懂的 都可以一起在评论区讨论,看到了会尽快回答的;
本套源文件在文末有链接,想直接的看的可以直接到文末下载

1.cubemx配置








2.文件配置

3.题目分析
一、核心考点(按分值权重排序,重点突破)
结合真题85分程序设计要求,考点分模块,优先掌握高权重模块(ADC、PWM、LCD、按键),再突破次要模块,避免丢基础分。
1. 高权重考点(60%+,必拿分)
-
ADC两路模拟采样:对应R37(模拟A)、R38(模拟B),要求0~3.3V采集,配合中断/定时器触发,需加滤波(满足LCD无噪点要求)。
-
PWM输出(PA7):频率、占空比按模拟信号阶梯式调节,需精准计算PSC、CCR2,满足频率偏差±3%、占空比偏差±2%。
-
LCD显示:3个界面(监控、统计、参数),严格按格式显示(背景黑、前景白,参数=值,单位规范),刷新频率0.1秒。
-
按键处理:4个按键(B1~B4),实现界面切换、锁定/解锁、参数加减、长按重置,需防抖(响应≤0.2秒)。
2. 中权重考点(20%,易丢分)
-
定时器应用:TIM6触发ADC采样(固定100ms)、TIM3输出PWM、PA15脉冲捕获(计算DF)。
-
参数校验与锁定:参数(DS/DR/FS/FR)合法性校验,锁定功能(锁定后PWM参数不变)。
-
LED指示:LD1~LD3对应界面、锁定、异常状态,LD4~LD7熄灭。
3. 低权重考点(5%,不丢基础分)
-
系统运行时长:计时格式xxHyyMzzS,支持长按B2重置。
-
频率异常判定:PA15捕获频率(DF)与PA7输出频率(CF)偏差>1000Hz,触发异常(LD3点亮)。
-
文件提交规范:仅提交自行编写的.c/.h和.hex(准考证号命名),压缩包≤30MB。
二、做题思路(按开发流程,分步落地,避免混乱)
核心原则:先搭框架,再填细节;先实现基础功能,再优化性能;先调试单个模块,再整合联调。全程贴合真题初始状态和功能要求。
步骤1:CubeMX基础配置(10分钟,奠定基础)
系统时钟:配置为80MHz(真题强制要求),外部晶振。
ADC配置:ADC1(R38,模拟B)、ADC2(R37,模拟A),开启常规转换,选择TIM6 TRGO触发(100ms采样),开启EOC中断(转换完成中断)。
定时器配置:
TIM3:输出PWM(PA7,CH2),ARR初始设999(配合PSC计算频率)。
TIM6:定时100ms(PSC=7999,ARR=999),开启TRGO触发ADC,不开启定时器中断(仅用于触发ADC)。
TIMx(PA15):输入捕获模式,捕获PA15脉冲信号(计算DF)。
GPIO配置:B1~B4(按键,上拉输入)、LD1~LD7(推挽输出,初始熄灭)。
LCD配置:复用官方驱动,配置对应GPIO(按资源包驱动要求)。
步骤2:单个模块开发(30分钟,逐个调试)
ADC采样模块:
编写ADC中断回调函数(HAL_ADC_ConvCpltCallback),读取采样值,转换为电压(3.3*采样值/4096)。
添加滑动平均滤波(10次采样,满足LCD无噪点),存储到全局变量(R37_vol、R38_vol)。
调试:旋转R37/R38,查看采样值是否在0~4095、电压0~3.3V平滑变化。
PWM输出模块:
根据真题公式,计算VA(占空比档位电压跨度)、VB(频率档位电压跨度)。
编写PWM参数更新函数,根据R37_vol确定占空比(CCR2),R38_vol确定频率(PSC)。
调试:旋转电位器,查看PA7输出频率、占空比是否符合阶梯调节规则,偏差在要求范围内。
按键处理模块:
编写按键扫描函数(防抖处理,延时10ms),区分短按/长按(B2长按2秒重置时长)。
实现界面切换(B1)、锁定/解锁(B2短按)、参数加减(B3/B4)、参数切换(B2在参数界面)。
调试:按下按键,响应时间≤0.2秒,无误触发、重复触发。
LCD显示模块:
编写3个界面的显示函数,严格按真题格式(位置、大小写、单位)。
设置LCD背景色(黑)、前景色(白),0.1秒刷新一次(配合定时器或主循环延时)。
调试:切换界面,显示内容清晰、无噪点,参数更新及时。
步骤3:模块整合与功能完善(20分钟)
整合模块:将ADC采样→PWM更新→LCD显示→按键处理串联,实现“采样→调节→显示”闭环。
实现锁定功能:添加g_lock_flag全局变量,锁定时不更新PWM参数,LD2点亮。
实现频率异常判定:计算CF(PA7频率)与DF(PA15捕获频率)的差值,偏差>1000Hz时,LD3点亮,记录异常参数到统计界面。
实现系统时长计时:用定时器(如TIM7)定时1秒,累加时、分、秒,支持长按B2重置。
步骤4:参数校验与优化(10分钟,避免扣分)
参数校验:退出参数界面时,校验DS<DR、FS<FR,且至少能调节一个步长,非法则还原参数并跳转监控界面。
性能优化:ADC滤波、按键防抖、LCD刷新频率,确保所有性能要求达标(响应时间、偏差、无噪点)。
调试异常:排查ADC中断不触发、PWM参数错误、LCD显示错乱、按键误触发等问题。
步骤5:提交准备(5分钟,避免零分)
编译生成.hex文件,以准考证号命名。
筛选需提交的文件:仅保留自行编写/修改的.c/.h(如main.c、fun.c、headfile.h),不包含资源包原有未修改文件。
压缩文件,确保大小≤30MB,按考试系统要求提交。
三、核心知识点(对应考点,直击真题,避免冗余)
1. ADC采样知识点(真题核心)
-
STM32G431 ADC:12位分辨率,参考电压3.3V,采样范围0~4095(对应0~3.3V)。
-
触发方式:定时器TRGO触发(真题推荐,固定100ms采样),替代轮询,解放CPU。
-
中断配置:开启ADC全局中断(NVIC)、EOC中断(转换完成触发),回调函数中读取采样值并重启中断。
-
滤波:滑动平均滤波(10次采样),解决采样波动,满足LCD无噪点要求(真题性能要求)。
-
电压转换公式:电压值 = 3.3f × 采样值(uint32_t) / 4096.0f。
2. PWM输出知识点(真题核心)
-
PWM核心寄存器:
-
PSC(预分频器):分频系统时钟(80MHz),计数频率 = 80000000 / (PSC+1)。
-
ARR(自动重载值):定时器计数上限,决定PWM周期,周期 = (PSC+1)×(ARR+1) / 80000000。
-
CCR2(比较寄存器):控制PWM占空比,占空比 = (CCR2 / (ARR+1)) × 100%(真题要求精准计算)。
-
-
真题调节规则:
-
占空比:10%(固定最小值)~DR,步长DS,VA = 3.3 / [(DR-10)/DS + 1](+1是计算档位总数)。
-
频率:1000Hz(固定最小值)~FR,步长FS,VB = 3.3 / [(FR-1000)/FS + 1]。
-
-
频率/占空比偏差控制:精准计算PSC和CCR2,避免整数运算误差(强制转换为float)。
3. 定时器知识点
-
TIM6(触发ADC):仅用于产生TRGO信号,定时100ms,无需开启中断,配置PSC=7999、ARR=999。
-
TIM3(PWM输出):通道2(PA7),配置为PWM模式1,ARR初始999,根据频率需求修改PSC。
-
输入捕获(PA15):配置为上升沿捕获,计算两次捕获的时间差,换算为频率(DF = 80000000 / 时间差)。
-
计时功能:用TIM7定时1秒,累加时、分、秒,注意溢出处理(如秒满60进1,分满60进1)。
4. LCD显示知识点
-
显示规范:背景色黑色,前景色白色,参数与值用“=”间隔,单位规范(Hz小写,H大写)。
-
界面要求:3个界面(监控、统计、参数),严格按真题图示的位置、内容、大小写显示。
-
刷新频率:0.1秒一次,可通过定时器中断或主循环延时实现,避免显示卡顿、噪点。
-
复用官方驱动:调用资源包中的LCD_Init()、LCD_ShowString()等函数,无需重新编写驱动。
5. 按键处理知识点
-
防抖处理:按键扫描时,延时10ms后再次检测,确认按键状态稳定(避免机械抖动导致误触发)。
-
按键功能区分:
-
B1:界面切换(监控→统计→参数→监控)。
-
B2:监控界面(短按锁定/解锁,长按2秒重置时长);参数界面(切换参数)。
-
B3/B4:仅在参数界面有效,对应参数加减(按真题调节规则)。
-
-
长按判定:记录按键按下时间,超过2秒视为长按,松开后执行对应功能(不触发短按)。
6. 其他关键知识点
-
全局变量:用于存储采样值、PWM参数、系统状态(g_lock_flag、运行时长、异常参数等),方便跨函数调用。
-
参数校验:DS<DR、FS<FR,且(DR-10)能被DS整除、(FR-1000)能被FS整除(确保阶梯调节有效)。
-
LED控制:LD1(监控界面亮)、LD2(锁定亮)、LD3(异常亮),低电平点亮(按竞赛板硬件特性)。
4.headfile.h
#ifndef __HEADFILE_H__
#define __HEADFILE_H__
#include "main.h"
#include "gpio.h"
#include "tim.h"
#include "adc.h"
#include "stdio.h"
#include "stdint.h"
#include "string.h"
#include "fun.h"
#include "key.h"
#include "lcd.h"
#include "headfile.h"
#define DEFAULT_DS_1 1
#define DEFAULT_DR_8080
#define DEFAULT_FS_100100
#define DEFAULT_FR_20002000
#define DEFAULT_BASE_DUTY_10 10
#define DEFAULT_BASE_FREQ_1000 1000
#define DEFAULT_SYSTEM_CLOCK_80MHZ 80000000
#define DEFAULT_TIM2_CLOCK_1MHZ 1000000UL
#define PWM_TIM3_ARR_100 100
#define LOCK 1
#define UNLOCK 0
#define PWM 0
#define RECD 1
#define PARA 2
// DS:占空比小步进(+1%)
#define DEFAULT_DS_STEP_1 1 // 单位:%
// DR:占空比大步进(+10%)
#define DEFAULT_DR_STEP_10 10 // 单位:%
// ———————- 频率相关步进 ———————-
// FS:频率小步进(+100Hz)
#define DEFAULT_FS_STEP_100 100 // 单位:Hz
// FR:频率大步进(+1000Hz)
#define DEFAULT_FR_STEP_1000 1000 // 单位:Hz
// ———————- 参数类型标识(用于判断当前选择) ———————-
// 用宏定义参数类型,方便代码中判断当前要调节的参数
#define PARAM_TYPE_DS 0 // 当前调节:占空比小步进(DS)
#define PARAM_TYPE_DR 1 // 当前调节:占空比大步进(DR)
#define PARAM_TYPE_FS 2 // 当前调节:频率小步进(FS)
#define PARAM_TYPE_FR 3 // 当前调节:频率大步进(FR)
extern uint8_t g_lock_flag;
extern uint8_t lcd_flag;
extern uint8_t g_param_DS;
extern uint8_t g_param_DR;
extern uint32_t g_param_FS;
extern uint32_t g_param_FR;
extern uint8_t g_param_temp_DS;
extern uint8_t g_param_temp_DR;
extern uint32_t g_param_temp_FS;
extern uint32_t g_param_temp_FR;
extern uint32_t g_pa15_freq;
extern uint8_t g_initial_flag;
#endif
5.fun.c
#include "headfile.h"
char text[21];
uint32_t pa7_freq;
uint32_t pa7_duty;
uint8_t lcd_flag;
uint8_t g_lock_flag;
uint8_t abnormal_flag;
uint16_t run_hour;
uint16_t run_minute;
uint16_t run_second;
uint16_t run_abnormal_hour;
uint16_t run_abnormal_minute;
uint16_t run_abnormal_second;
uint8_t g_param_DS = DEFAULT_DS_1 ;
uint8_t g_param_DR = DEFAULT_DR_80 ;
uint32_t g_param_FS = DEFAULT_FS_100 ;
uint32_t g_param_FR = DEFAULT_FR_2000 ;
uint8_t g_param_temp_DS;
uint8_t g_param_temp_DR;
uint32_t g_param_temp_FS;
uint32_t g_param_temp_FR;
uint8_t g_param_n_duty;
uint8_t g_param_n_freq;
uint32_t param_abnormal_CF;
uint8_t param_abnormal_CD;
uint32_t param_abnormal_DF;
uint32_t param_abnormal_XF;
int param_XF;
float VA;
float VB;
double R37_vol;
double R38_vol;
/**
* @brief 获取ADC采样值并转换为电压
* @param hadc ADC句柄指针
* @return 转换后的电压值(单位:伏特)
*/
double get_vol(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Start(hadc); // 启动ADC转换
uint32_t adc_value = HAL_ADC_GetValue(hadc); // 读取ADC转换结果
return 3.3 * adc_value / 4096; // 将ADC值转换为电压(参考电压3.3V,12位ADC)
}
/**
* @brief 系统初始化函数
* @note 检查系统启动时间,达到200ms后设置初始化完成标志
*/
void Initial_System()
{
if(HAL_GetTick() >= 100) // 检查系统运行时间是否达到200ms
{
g_initial_flag = 1; // 设置初始化完成标志位
}
}
/**
* @brief 系统状态更新和控制函数
* @note 负责读取ADC值、计算运行时间、调整PWM参数、检测异常和更新LED状态
*/
void change(void)
{
/* 读取ADC电压值 */
R37_vol = get_vol(&hadc2); // 读取ADC2通道电压(用于占空比控制)
R38_vol = get_vol(&hadc1); // 读取ADC1通道电压(用于频率控制)
/* 计算系统运行时间 */
run_hour = HAL_GetTick() / 3600000; // 计算运行小时数
run_minute = HAL_GetTick() / 60000 – run_hour * 60; // 计算运行分钟数
run_second = HAL_GetTick() / 1000 – run_hour * 3600 – run_minute * 60; // 计算运行秒数
/* PWM占空比控制 */
if(g_lock_flag == UNLOCK) // 检查是否锁定(0=未锁定,允许调整)
{
/* 计算占空比调整步长 */
VA = 3.3f / (float)((g_param_DR – DEFAULT_BASE_DUTY_10)/g_param_DS + 1);
/* 根据R37电压值查找对应的占空比档位 */
for(g_param_n_duty = 0;\\
g_param_n_duty < ((g_param_DR – DEFAULT_BASE_DUTY_10)/g_param_DS + 1); \\
g_param_n_duty++ \\
)
{
if(R37_vol >= (g_param_n_duty * VA) \\
&& R37_vol < ((g_param_n_duty+1) * VA) \\
)
{
TIM3->CCR2 = DEFAULT_BASE_DUTY_10 + (g_param_n_duty * g_param_DS); // 设置TIM3通道2的占空比
}
}
/* PWM频率控制 */
VB = 3.3f / (float)((g_param_FR – DEFAULT_BASE_FREQ_1000)/g_param_FS + 1);
/* 根据R38电压值查找对应的频率档位 */
for(g_param_n_freq = 0; \\
g_param_n_freq < ((g_param_FR – DEFAULT_BASE_FREQ_1000)/g_param_FS + 1); \\
g_param_n_freq++ \\
)
{
if(R38_vol >= (g_param_n_freq * VB) \\
&& R38_vol < ((g_param_n_freq+1) * VB))
{
/* 计算并设置TIM3的预分频器,实现频率调整 */
TIM3->PSC = DEFAULT_SYSTEM_CLOCK_80MHZ / ((DEFAULT_BASE_FREQ_1000 + g_param_n_freq * g_param_FS)*PWM_TIM3_ARR_100 ) – 1;
}
}
/* 更新当前PWM参数 */
pa7_freq = DEFAULT_SYSTEM_CLOCK_80MHZ/ PWM_TIM3_ARR_100 / (TIM3->PSC + 1); // 当前频率值
pa7_duty = PWM_TIM3_ARR_100 * TIM3->CCR2 / (TIM3->ARR+1); // 当前占空比值
}
/* 频率异常检测 */
if(g_initial_flag) // 系统初始化完成后才进行异常检测
{
/* 计算PA7输出频率与PA15参考频率的差值 */
param_XF = g_pa15_freq – pa7_freq;
if(param_XF < 0)
{
param_XF = pa7_freq – g_pa15_freq; // 取绝对值
}
/* 判断频率差是否超过阈值(1000Hz) */
if(param_XF > 1000)
{
if(abnormal_flag == 0) // 首次检测到异常
{
abnormal_flag = 1; // 设置异常标志
/* 记录异常发生时的各项参数 */
param_abnormal_CF = pa7_freq; // 异常时的当前频率
param_abnormal_CD = pa7_duty; // 异常时的当前占空比
param_abnormal_DF = g_pa15_freq; // 异常时的参考频率
param_abnormal_XF = param_XF; // 异常时的频率差值
run_abnormal_hour = run_hour; // 异常发生的小时
run_abnormal_minute = run_minute; // 异常发生的分钟
run_abnormal_second = run_second; // 异常发生的秒数
}
}
else
{
abnormal_flag = 0; // 频率正常,清除异常标志
}
}
/* LED状态显示 */
led_show(1, lcd_flag == 0); // LED1显示LCD状态
led_show(2, g_lock_flag); // LED2显示锁定状态
led_show(3, abnormal_flag); // LED3显示异常状态
}
/**
* @brief 控制LED显示状态
* @param led LED编号(1-3),对应PC8、PC9、PC10引脚
* @param mode LED模式(1=点亮,0=熄灭)
* @note 使用74HC595锁存器控制,通过PD2引脚触发锁存
*/
void led_show(uint8_t led, uint8_t mode)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); // PD2置高,准备锁存数据
if(mode) // 模式为1,点亮LED
{
/* 根据led编号选择对应的GPIO引脚并置低(点亮) */
/* led=1: PC8, led=2: PC9, led=3: PC10 */
HAL_GPIO_WritePin(GPIOC, (GPIO_PIN_8 << (led – 1)), GPIO_PIN_RESET);
}
else // 模式为0,熄灭LED
{
/* 根据led编号选择对应的GPIO引脚并置高(熄灭) */
/* led=1: PC8, led=2: PC9, led=3: PC10 */
HAL_GPIO_WritePin(GPIOC, (GPIO_PIN_8 << (led – 1)), GPIO_PIN_SET);
}
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); // PD2置低,触发锁存,更新LED状态
}
/**
* @brief 在LCD屏幕上显示PWM参数和系统状态
* @note 显示内容包括:标题、输出频率、占空比、参考频率、锁定状态和运行时间
*/
void pwm_show(void)
{
/* 显示标题 */
sprintf(text, " PWM "); // 格式化标题字符串
LCD_DisplayStringLine(Line1, (uint8_t *)text); // 在LCD第1行显示标题
/* 显示PA7输出频率 */
sprintf(text, " CF=%dHz ", pa7_freq); // 格式化当前频率字符串(CF=Current Frequency)
LCD_DisplayStringLine(Line3, (uint8_t *)text); // 在LCD第3行显示当前频率
/* 显示PA7占空比 */
sprintf(text, " CD=%d%% ", pa7_duty); // 格式化当前占空比字符串(CD=Current Duty)
LCD_DisplayStringLine(Line4, (uint8_t *)text); // 在LCD第4行显示当前占空比
/* 显示PA15参考频率 */
sprintf(text, " DF=%dHz ", g_pa15_freq); // 格式化参考频率字符串(DF=Desired Frequency)
LCD_DisplayStringLine(Line5, (uint8_t *)text); // 在LCD第5行显示参考频率
/* 显示锁定状态 */
if(g_lock_flag == UNLOCK) // 判断是否处于解锁状态
{
sprintf(text, " ST=UNLOCK "); // 格式化解锁状态字符串(ST=Status)
}
else if(g_lock_flag == LOCK) // 判断是否处于锁定状态
{
sprintf(text, " ST=LOCK "); // 格式化锁定状态字符串
}
LCD_DisplayStringLine(Line6, (uint8_t *)text); // 在LCD第6行显示锁定状态
/* 显示系统运行时间 */
sprintf(text, " %02dH%02dM%02dS ", run_hour, run_minute, run_second); // 格式化运行时间字符串(时:分:秒)
LCD_DisplayStringLine(Line7, (uint8_t *)text); // 在LCD第7行显示运行时间
}
/**
* @brief 在LCD屏幕上显示异常记录信息
* @note 显示异常发生时的各项参数:频率、占空比、参考频率、频率差值和发生时间
*/
void recd_show(void)
{
/* 显示标题 */
sprintf(text, " RECD "); // 格式化标题字符串(RECD=Record,记录)
LCD_DisplayStringLine(Line1, (uint8_t *)text); // 在LCD第1行显示标题
/* 显示异常时的当前频率 */
sprintf(text, " CF=%dHz ", param_abnormal_CF); // 格式化异常时的频率字符串(CF=Current Frequency)
LCD_DisplayStringLine(Line3, (uint8_t *)text); // 在LCD第3行显示异常时的频率
/* 显示异常时的当前占空比 */
sprintf(text, " CD=%d%% ", param_abnormal_CD); // 格式化异常时的占空比字符串(CD=Current Duty)
LCD_DisplayStringLine(Line4, (uint8_t *)text); // 在LCD第4行显示异常时的占空比
/* 显示异常时的参考频率 */
sprintf(text, " DF=%dHz ", param_abnormal_DF); // 格式化异常时的参考频率字符串(DF=Desired Frequency)
LCD_DisplayStringLine(Line5, (uint8_t *)text); // 在LCD第5行显示异常时的参考频率
/* 显示异常时的频率差值 */
sprintf(text, " XF=%dHz ", param_abnormal_XF); // 格式化频率差值字符串(XF=X Frequency,差值频率)
LCD_DisplayStringLine(Line6, (uint8_t *)text); // 在LCD第6行显示频率差值
/* 显示异常发生的时间 */
sprintf(text, " %02dH%02dM%02dS ", run_abnormal_hour, run_abnormal_minute, run_abnormal_second); // 格式化异常发生时间
LCD_DisplayStringLine(Line7, (uint8_t *)text); // 在LCD第7行显示异常发生时间
}
/**
* @brief 在LCD屏幕上显示系统配置参数
* @note 显示内容包括:占空比步长、占空比范围、频率步长和频率范围
*/
void para_show(void)
{
/* 显示标题 */
sprintf(text, " PARA "); // 格式化标题字符串(PARA=Parameter,参数)
LCD_DisplayStringLine(Line1, (uint8_t *)text); // 在LCD第1行显示标题
/* 显示占空比步长 */
sprintf(text, " DS=%d%% ", g_param_temp_DS); // 格式化占空比步长字符串(DS=Duty Step)
LCD_DisplayStringLine(Line3, (uint8_t *)text); // 在LCD第3行显示占空比步长
/* 显示占空比范围 */
sprintf(text, " DR=%d%% ", g_param_temp_DR); // 格式化占空比范围字符串(DR=Duty Range)
LCD_DisplayStringLine(Line4, (uint8_t *)text); // 在LCD第4行显示占空比范围
/* 显示频率步长 */
sprintf(text, " FS=%dHz ", g_param_temp_FS); // 格式化频率步长字符串(FS=Frequency Step)
LCD_DisplayStringLine(Line5, (uint8_t *)text); // 在LCD第5行显示频率步长
/* 显示频率范围 */
sprintf(text, " FR=%dHz ", g_param_temp_FR); // 格式化频率范围字符串(FR=Frequency Range)
LCD_DisplayStringLine(Line6, (uint8_t *)text); // 在LCD第6行显示频率范围
/* 清空第7行 */
sprintf(text, " "); // 格式化空字符串
LCD_DisplayStringLine(Line7, (uint8_t *)text); // 在LCD第7行显示空行
}
/**
* @brief LCD显示控制函数
* @note 根据lcd_flag标志位选择不同的显示界面
* lcd_flag=0: 显示PWM参数界面
* lcd_flag=1: 显示异常记录界面
* lcd_flag=2: 显示系统参数界面
*/
void lcd_show(void)
{
switch(lcd_flag) // 根据LCD显示标志位选择显示界面
{
case 0: // 标志位为0,显示PWM参数界面
pwm_show(); // 调用PWM参数显示函数
break;
case 1: // 标志位为1,显示异常记录界面
recd_show(); // 调用异常记录显示函数
break;
case 2: // 标志位为2,显示系统参数界面
para_show(); // 调用系统参数显示函数
break;
}
}
6.key.c
#include "headfile.h"
uint8_t B1_state;
uint8_t B2_state;
uint8_t B3_state;
uint8_t B4_state;
uint8_t B1_last_state = 1;
uint8_t B2_last_state = 1;
uint8_t B3_last_state = 1;
uint8_t B4_last_state = 1;
uint8_t para_select;
/**
* @brief 按键扫描函数
* @note 扫描4个按键状态,处理按键事件,实现界面切换、参数调整、锁定/解锁、复位等功能
* 按键去抖动时间:20ms
*/
void key_scan(void)
{
static uint32_t B2_last_2s; // 记录B2按键按下的时间戳,用于判断长按
static uint32_t last_tick; // 上次扫描的时间戳
uint32_t current_tick = HAL_GetTick(); // 获取当前时间戳
if(current_tick – last_tick < 20) return; // 去抖动处理,20ms内不重复扫描
last_tick = current_tick; // 更新上次扫描时间
/* 读取4个按键的当前状态 */
B1_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0); // 读取B1按键状态(PB0)
B2_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1); // 读取B2按键状态(PB1)
B3_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2); // 读取B3按键状态(PB2)
B4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); // 读取B4按键状态(PA0)
/* B1按键处理:切换LCD显示模式 */
if(B1_last_state == 1 && B1_state == 0) // 检测B1按键下降沿(按下)
{
lcd_flag++; // 切换到下一个显示模式
lcd_flag %= 3; // 循环切换(0-2)
if(lcd_flag == PARA) // 进入参数设置界面
{
/* 保存当前参数到临时变量 */
para_select = 0; // 重置参数选择索引
g_param_temp_DS = g_param_DS; // 保存占空比步长
g_param_temp_DR = g_param_DR; // 保存占空比范围
g_param_temp_FS = g_param_FS; // 保存频率步长
g_param_temp_FR = g_param_FR; // 保存频率范围
}
if(lcd_flag == PWM) // 离开参数设置界面,返回PWM显示界面
{
/* 验证参数有效性,确认后保存参数 */
if(DEFAULT_BASE_DUTY_10 + g_param_temp_DS <= g_param_temp_DR &&
DEFAULT_BASE_FREQ_1000 + g_param_temp_FS <= g_param_temp_FR)
{
g_param_DS = g_param_temp_DS; // 应用占空比步长
g_param_DR = g_param_temp_DR; // 应用占空比范围
g_param_FS = g_param_temp_FS; // 应用频率步长
g_param_FR = g_param_temp_FR; // 应用频率范围
}
}
}
/* B2按键处理:参数选择/锁定解锁/复位 */
if(B2_last_state == 1 && B2_state == 0) // 检测B2按键下降沿(按下)
{
if(lcd_flag == PARA) // 在参数设置界面
{
para_select++; // 切换到下一个参数
para_select %= 4; // 循环切换(0-3,对应4个参数)
}
if(lcd_flag == PWM) // 在PWM显示界面
{
B2_last_2s = HAL_GetTick(); // 记录按键按下时间
}
}
else if(B2_last_state == 0 && B2_state == 1) // 检测B2按键上升沿(释放)
{
if(lcd_flag == PWM) // 在PWM显示界面
{
if(HAL_GetTick() – B2_last_2s > 2000) // 按键按下超过2秒(长按)
{
uwTick = 0; // 复位系统运行时间计数器
}
else // 短按
{
g_lock_flag = !g_lock_flag; // 切换锁定/解锁状态
}
}
}
/* B3按键处理:增加参数值 */
if(B3_last_state == 1 && B3_state == 0) // 检测B3按键下降沿(按下)
{
if(lcd_flag == PARA) // 在参数设置界面
{
switch(para_select) // 根据当前选择的参数类型进行处理
{
case PARAM_TYPE_DS: // 占空比步长
g_param_temp_DS = (g_param_temp_DS < 100) ? (g_param_temp_DS + DEFAULT_DS_STEP_1) : 90;
break;
case PARAM_TYPE_DR: // 占空比范围
g_param_temp_DR = (g_param_temp_DR < 100) ? (g_param_temp_DR + DEFAULT_DR_STEP_10) : 100;
break;
case PARAM_TYPE_FS: // 频率步长
g_param_temp_FS += DEFAULT_FS_STEP_100;
break;
case PARAM_TYPE_FR: // 频率范围
g_param_temp_FR += DEFAULT_FR_STEP_1000;
break;
}
}
}
/* B4按键处理:减少参数值 */
if(B4_last_state == 1 && B4_state == 0) // 检测B4按键下降沿(按下)
{
if(lcd_flag == PARA) // 在参数设置界面
{
switch(para_select) // 根据当前选择的参数类型进行处理
{
case PARAM_TYPE_DS: // 占空比步长
g_param_temp_DS = (g_param_temp_DS > 0) ? (g_param_temp_DS – DEFAULT_DS_STEP_1) : 1;
break;
case PARAM_TYPE_DR: // 占空比范围
g_param_temp_DR = (g_param_temp_DR > 0) ? (g_param_temp_DR – DEFAULT_DR_STEP_10) : 20;
break;
case PARAM_TYPE_FS: // 频率步长
g_param_temp_FS = (g_param_temp_FS > 0) ? (g_param_temp_FS – DEFAULT_FS_STEP_100) : 100;
break;
case PARAM_TYPE_FR: // 频率范围
g_param_temp_FR = (g_param_temp_FR > 0) ? (g_param_temp_FR – DEFAULT_FR_STEP_1000) : 1000;
break;
}
}
}
/* 更新按键状态,用于下次检测边沿 */
B1_last_state = B1_state; // 保存B1按键状态
B2_last_state = B2_state; // 保存B2按键状态
B3_last_state = B3_state; // 保存B3按键状态
B4_last_state = B4_state; // 保存B4按键状态
}
7.handler.c
#include "headfile.h"
uint32_t g_pa15_freq;
uint32_t capture;
uint32_t initial_timer;
uint8_t g_initial_flag;
/**
* @brief 定时器输入捕获回调函数
* @param htim 定时器句柄指针
* @note 用于测量PA15引脚输入的PWM信号频率
* 使用TIM2的输入捕获模式,捕获信号周期并计算频率
*/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2) // 判断是否为TIM2触发中断
{
/* 读取输入捕获值 */
capture = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1) + 1; // 读取TIM2通道1的捕获值(周期计数值),+1修正
/* 计算输入信号频率 */
g_pa15_freq = DEFAULT_TIM2_CLOCK_1MHZ / capture; // 频率 = 定时器时钟 / 捕获值(周期)
}
}
8.lcd.c(修改部分)
void LCD_Init(void)
{
uint16_t temp = GPIOC->ODR & 0xff00; //此处修改
LCD_CtrlLinesConfig();
dummy = LCD_ReadReg(0);
if(dummy == 0x8230)
{
REG_8230_Init();
}
else
{
REG_932X_Init();
}
dummy = LCD_ReadReg(0);
GPIOC->ODR = (GPIOC->ODR & 0x00ff) | temp; //此处修改
}
void LCD_Clear(u16 Color)
{
uint16_t temp = GPIOC->ODR & 0xff00; //此处修改
u32 index = 0;
LCD_SetCursor(0x00, 0x0000);
LCD_WriteRAM_Prepare(); /* Prepare to write GRAM */
for(index = 0; index < 76800; index++)
{
LCD_WriteRAM(Color);
}
GPIOC->ODR = (GPIOC->ODR & 0x00ff) | temp; //此处修改
}
void LCD_DisplayStringLine(u8 Line, u8 *ptr)
{
uint16_t temp = GPIOC->ODR & 0xff00; //此处修改
u32 i = 0;
u16 refcolumn = 319;//319;
while ((*ptr != 0) && (i < 20)) //20
{
LCD_DisplayChar(Line, refcolumn, *ptr);
refcolumn -= 16;
ptr++;
i++;
}
GPIOC->ODR = (GPIOC->ODR & 0x00ff) | temp; //此处修改
}
9.main.c
#include "main.h"
#include "adc.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ———————————————————-*/
/* USER CODE BEGIN Includes */
#include "headfile.h"
/* USER CODE END Includes */
/* Private typedef ———————————————————–*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ————————————————————*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro ————————————————————-*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ———————————————————*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes ———————————————–*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ———————————————————*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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();//初始化GPIO
MX_TIM2_Init();//初始化TIM2
MX_ADC1_Init();//初始化ADC1
MX_ADC2_Init();//初始化ADC2
MX_TIM3_Init();//初始化TIM3
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);//打开锁存器
LCD_Init(); //初始化LCD
LCD_SetBackColor(Black); //设置背景颜色
LCD_SetTextColor(White); //设置字体颜色
LCD_Clear(Black); //清屏
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); //开启TIM2通道1的中断
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); //开启TIM3通道2的PWM输出
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); //校准ADC1
HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED); //校准ADC2
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
Initial_System();
key_scan();//扫描按键
lcd_show();//显示LCD
change();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
10.源文件(含题目)
通过网盘分享的文件:16届蓝桥杯嵌入式省赛真题
链接: https://pan.baidu.com/s/1qzvEbgAE6wTdRKC5uCU2ug?pwd=thsi 提取码: thsi


