1. 项目概述
本次蓝桥杯嵌入式省赛真题基于STM32G431RBT6/STM32F103RBT6平台,要求实现一个综合性的嵌入式系统,包含密码锁、脉冲输出、串口通信、LCD显示和LED指示等功能。作为参加过多次嵌入式竞赛的选手,我认为这道题目很好地考察了选手对STM32外设的综合运用能力和模块化编程思维。
系统核心功能包括:
- 3位密码锁功能(初始密码123)
- 脉冲输出(1KHz方波和2KHz 10%占空比脉冲)
- 串口密码修改功能
- LCD双界面显示(密码输入界面和状态显示界面)
- LED状态指示
提示:在实际比赛中,建议先完成核心功能确保基础分,再逐步完善附加功能。根据我的参赛经验,LCD显示和定时器配置往往是耗时最多的部分,需要特别注意。
2. 硬件外设配置解析
2.1 GPIO配置要点
GPIO配置是系统的基础,需要特别注意以下几点:
-
按键输入配置:
- B1-B4按键配置为上拉输入模式
- 需要硬件消抖,响应时间≤0.1秒
- 对应引脚:B1(PB0)、B2(PB1)、B3(PB2)、B4(PA0)
-
LED输出配置:
- LD1-LD8配置为推挽输出
- 特别注意LD3-LD8必须保持熄灭状态
- 对应引脚:PC8-PC15
-
脉冲输出引脚:
- PA1配置为推挽输出,用于产生PWM波形
- 初始输出1KHz方波
c复制// 典型GPIO初始化代码片段
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 按键配置
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// LED配置
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
2.2 定时器配置详解
定时器是系统的核心外设,需要完成两个主要功能:
-
PWM波形生成:
- 使用TIM2产生PWM波形
- 系统时钟80MHz,预分频值计算:
- 1KHz方波:PSC=800-1, ARR=100-1
- 2KHz脉冲:PSC=400-1, ARR=100-1
- 占空比控制:
- 50%占空比:CCR2=50-1
- 10%占空比:CCR2=10-1
-
定时中断功能:
- 使用TIM6产生100ms定时中断
- 用于按键消抖、LED闪烁计时等
- 配置代码示例:
c复制// TIM6初始化代码
htim6.Instance = TIM6;
htim6.Init.Prescaler = 8000-1; // 80MHz/8000=10kHz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 1000-1; // 10kHz/1000=10Hz(100ms)
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
3. 软件逻辑实现
3.1 状态机设计
系统采用双状态设计,这是整个程序的核心框架:
-
密码输入状态(PSD界面):
- B1-B3按键调整3位密码(0-9循环)
- B4按键确认密码
- 密码正确切换到STA界面
- 密码错误3次触发LED2报警
-
输出状态(STA界面):
- 显示当前输出频率和占空比
- 5秒后自动返回PSD界面
- 密码重置为初始状态
状态切换流程图:
code复制[PSD界面] → (密码正确) → [STA界面] → (5秒超时) → [PSD界面]
3.2 密码处理逻辑
密码处理需要注意以下几个关键点:
-
初始密码设置:
c复制char g_password[3] = {'1','2','3'}; // 初始密码123 -
密码调整逻辑:
- B1-B3分别控制第1-3位密码
- 每次按下数值加1,0-9循环
- 未设置状态显示为'@'
-
密码验证逻辑:
c复制if(g_password[0]==g_password_1+'0' && g_password[1]==g_password_2+'0' && g_password[2]==g_password_3+'0') { // 密码正确处理 g_password_right = 1; } else { // 密码错误处理 g_password_error = 1; }
3.3 脉冲输出控制
脉冲输出根据密码验证状态变化:
-
默认状态:
- 输出1KHz方波(50%占空比)
- TIM2配置:PSC=800-1, ARR=100-1, CCR2=50-1
-
密码正确状态:
- 输出2KHz脉冲(10%占空比,持续5秒)
- TIM2重新配置:PSC=400-1, ARR=100-1, CCR2=10-1
- 5秒后恢复默认状态
注意:频率和占空比精度是评分重点,必须确保误差在要求范围内(频率≤5%,占空比≤2%)
4. 模块化编程实现
4.1 按键处理模块(key.c)
按键模块需要实现消抖和状态检测:
c复制void Key_Scan()
{
// 消抖处理(20ms间隔)
tick = HAL_GetTick();
if(tick - last_tick < 20) return;
last_tick = tick;
// 读取按键状态
B1_state = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
// ...其他按键读取
// 按键动作处理
if(B1_state==0 && B1_last_state==1) {
g_password_1 = (++g_password_1 > 9) ? 0 : g_password_1;
}
// ...其他按键处理
// 保存当前状态用于下次检测
B1_last_state = B1_state;
// ...其他按键状态保存
}
4.2 LCD显示模块(fun.c)
LCD显示分为两个界面:
-
密码输入界面(PSD):
c复制void Psd_Show() { sprintf(text," PSD "); LCD_DisplayStringLine(Line1,(uint8_t *)text); // 显示各位密码 sprintf(text," B1:%c ", g_password_1==10?'@':g_password_1+'0'); LCD_DisplayStringLine(Line3,(uint8_t *)text); // ...其他位显示 } -
状态显示界面(STA):
c复制void Sta_Show() { sprintf(text," STA "); LCD_DisplayStringLine(Line1,(uint8_t *)text); // 根据状态显示不同参数 sprintf(text," F:%sHz ", g_password_right?"2000":"1000"); LCD_DisplayStringLine(Line3,(uint8_t *)text); sprintf(text," D:%s%% ", g_password_right?"10":"50"); LCD_DisplayStringLine(Line4,(uint8_t *)text); }
4.3 中断处理模块(handler.c)
中断处理包括串口接收和定时器中断:
-
串口接收中断:
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { // 验证密码修改格式"旧密码-新密码" if(memcmp(g_password,g_rx_data,3)==0 && g_rx_data[3]=='-') { // 更新密码 memcpy(g_password,&g_rx_data[4],3); } // 重新启动接收 HAL_UART_Receive_IT(&huart1,(uint8_t *)g_rx_data,7); } } -
定时器中断:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance==TIM6) { g_timer_5s++; // 100ms计数 // 密码错误处理 if(g_password_error && g_continuous_error_cnt>=3) { g_password_error_led = !g_password_error_led; // LED闪烁 if(++g_timer_led_5s >= 50) { // 5秒后停止 g_password_error = 0; g_password_error_led = 0; } } // 5秒超时处理 if(g_timer_5s >= 50) { g_lcd_mode = 0; // 返回PSD界面 // 恢复PWM默认设置 TIM2->PSC = 800-1; TIM2->CCR2 = 50-1; g_password_right = 0; } } }
5. 常见问题与调试技巧
5.1 典型问题排查
-
PWM输出不正常:
- 检查定时器时钟是否使能
- 验证预分频和自动重装值计算
- 使用示波器测量实际输出波形
-
LCD显示异常:
- 确认初始化顺序正确
- 检查背景色和前景色设置
- 验证文本缓冲区是否足够大
-
串口通信失败:
- 检查波特率设置(必须9600)
- 验证接线是否正确(TX/RX交叉)
- 确保接收中断正确配置
5.2 调试经验分享
-
分模块调试:
- 先单独测试每个外设功能
- 使用LED或串口打印调试信息
- 逐步集成各模块
-
状态监控技巧:
c复制// 在main循环中添加调试输出 printf("Mode:%d P1:%d P2:%d P3:%d\n", g_lcd_mode, g_password_1, g_password_2, g_password_3); -
时间精度验证:
- 使用定时器捕获功能测量脉冲参数
- 通过系统滴答定时器验证时间间隔
5.3 竞赛注意事项
-
评分要点:
- 功能完整性(必须实现所有要求功能)
- 参数精度(频率、占空比、响应时间)
- 代码规范性(模块化、注释清晰)
-
时间管理建议:
- 先完成核心功能确保基础分
- 留出足够时间进行整体测试
- 最后检查文件提交格式
-
易错点提醒:
- LD3-LD8必须保持熄灭
- 界面切换逻辑要严格符合要求
- 串口密码修改格式必须正确
6. 完整工程实现
6.1 主程序框架(main.c)
c复制int main(void)
{
// HAL库初始化
HAL_Init();
SystemClock_Config();
// 外设初始化
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM6_Init();
MX_USART1_UART_Init();
// 模块初始化
LCD_Init();
LCD_SetBackColor(Black);
LCD_SetTextColor(White);
LCD_Clear(Black);
// 启动外设
HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx_data, 7);
// 初始PWM设置
TIM2->CCR2 = 50-1;
TIM2->ARR = 100-1;
TIM2->PSC = 800-1;
// 主循环
while (1)
{
Key_Scan();
Change();
}
}
6.2 头文件配置(headfile.h)
c复制#ifndef _HEADFILE_H__
#define _HEADFILE_H__
#include "main.h"
#include "stdio.h"
#include "string.h"
// 全局变量声明
extern uint8_t g_password_1;
extern uint8_t g_password_2;
extern uint8_t g_password_3;
extern uint8_t g_lcd_mode;
extern char g_rx_data[7];
extern char g_password[3];
extern uint8_t g_password_right;
extern uint8_t g_password_error_led;
extern uint8_t g_continuous_error_cnt;
extern uint8_t g_timer_5s;
// 函数声明
void Led_Show(uint8_t led, uint8_t mode);
void Psd_Show();
void Sta_Show();
void Lcd_Show();
void Change();
void Key_Scan();
#endif
6.3 工程结构建议
code复制项目目录/
├── Core/
│ ├── Src/
│ │ ├── main.c
│ │ ├── gpio.c
│ │ ├── tim.c
│ │ └── usart.c
│ └── Inc/
├── Drivers/
├── User/
│ ├── key.c
│ ├── fun.c
│ ├── handler.c
│ └── lcd.c
└── MDK-ARM/
在实际开发中,我建议使用STM32CubeMX生成基础工程框架,然后添加用户代码模块。这种模块化结构便于维护和调试,也符合竞赛的评分要求。