1. 蓝桥杯嵌入式竞赛STM32G4开发模板深度解析
作为一名参加过多次蓝桥杯嵌入式竞赛的选手,我深知一个完善的开发模板对比赛的重要性。今天我将分享一个基于STM32G4系列芯片的竞赛模板,这个模板已经帮助我获得了不错的成绩,现在经过优化和详细注释后分享给大家。
2. 模板整体架构设计
2.1 硬件平台概述
这个模板针对蓝桥杯嵌入式竞赛官方开发板设计,主控芯片采用STM32G431RBT6。该芯片基于Arm Cortex-M4内核,主频可达170MHz,内置128KB Flash和32KB SRAM,外设资源丰富,非常适合嵌入式竞赛使用。
2.2 软件架构设计
模板采用模块化设计,主要分为以下几个部分:
- 主程序框架(main.c):负责系统初始化和主循环调度
- 总头文件(hfile.h):集中管理所有头文件包含
- 功能模块(fun.h/fun.c):实现各类竞赛常用功能
- 外设驱动:包括LCD、按键、ADC等外设的驱动
这种架构设计使得代码结构清晰,各功能模块高内聚低耦合,便于在比赛中快速开发和调试。
3. 主程序框架详解
3.1 系统初始化流程
主函数中的初始化代码是模板的核心部分,下面逐行解析关键初始化操作:
c复制// 失能PD2锁存器,关闭LED锁存
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
// LCD初始化
LCD_Init();
// 清屏,背景色黑色
LCD_Clear(Black);
// 设置LCD背景色为黑色
LCD_SetBackColor(Black);
// 设置LCD文字颜色为白色
LCD_SetTextColor(White);
这段代码完成了LCD屏幕的初始化设置。使用黑色背景和白色文字是竞赛中常见的显示配置,既能保证显示清晰度,又能降低功耗。
3.2 定时器与PWM配置
c复制// 开启定时器16中断(用于按键长按计时)
HAL_TIM_Base_Start_IT(&htim16);
// 开启TIM3通道1 PWM输出
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
// 开启TIM3通道2 PWM输出
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
定时器16用于按键的长按计时,TIM3则配置为PWM输出模式,可用于控制LED亮度或电机转速等应用。在竞赛中,PWM输出是常见的考点之一。
3.3 输入捕获与串口配置
c复制// 开启TIM2通道1输入捕获中断(测频率)
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
// 开启TIM2通道2输入捕获中断(测占空比)
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_2);
// 定义串口发送缓冲区
char tx_date[50];
// 格式化发送字符串
sprintf(tx_date, "start\r\n");
// 串口1发送启动提示信息
HAL_UART_Transmit(&huart1, (uint8_t *)tx_date, strlen(tx_date),20);
// 开启串口1接收中断(每次接收1字节)
HAL_UART_Receive_IT(&huart1, &rxdate, 1);
输入捕获功能可用于测量外部信号的频率和占空比,这在信号处理类题目中非常有用。串口通信则是与上位机交互的重要手段。
4. 关键功能模块实现
4.1 按键扫描算法
模板中实现了完善的按键检测功能,支持单击、双击、长按和连按等多种操作方式:
c复制// 按键状态:无操作
#define KEY_STATE_NONE 0
// 按键状态:按下
#define KEY_STATE_DOWN 1
// 按键状态:短按抬起
#define KEY_STATE_PRESSED 2
// 按键状态:双击
#define KEY_STATE_DOUBLE 3
// 按键状态:长按
#define KEY_STATE_LONG 4
// 按键状态:连按按住
#define KEY_STATE_PRESSING 5
按键扫描函数通过检测边沿和计时实现了这些复杂的状态判断,这在需要丰富人机交互的题目中非常实用。
4.2 多页面显示管理
模板实现了基于LCD的多页面显示系统,方便在比赛中组织不同的功能界面:
c复制// 当前显示页数,默认6页
uint8_t page = 6;
void LCD_Proc(void)
{
// 100ms刷新一次
if(uwTick - lcd_tick < 100) return;
lcd_tick = uwTick;
switch(page)
{
case 1: // LED页面
case 2: // KEY页面
case 3: // LCD页面
case 4: // ADC电压页面
case 5: // PWM与输入捕获页面
case 6: // RTC时间日期页面
}
}
这种分页设计使得代码结构清晰,不同功能的数据显示互不干扰,调试时也能快速定位问题。
5. 外设驱动与应用
5.1 ADC电压采集实现
电压采集是竞赛中的常见需求,模板中实现了双通道ADC采集:
c复制void ADC_Proc(void)
{
// 100ms采集一次
if(uwTick - adc_tick < 100) return;
adc_tick = uwTick;
// 启动ADC2并读取R37电压
HAL_ADC_Start(&hadc2);
R37_V = 3300 * HAL_ADC_GetValue(&hadc2) / 4095;
// 启动ADC1并读取R38电压
HAL_ADC_Start(&hadc1);
R38_V = 3300 * HAL_ADC_GetValue(&hadc1) / 4095;
}
这里采用了整数运算避免浮点运算的开销,同时将电压值放大1000倍保存,既保证了精度又提高了运算效率。
5.2 PWM输出控制
PWM输出控制通过以下代码实现:
c复制// 设置PWM输出频率
uint16_t PWM_F = 1000;
// TIM3_CH1占空比
uint16_t PWM_D6 = 50;
// TIM3_CH2占空比
uint16_t PWM_D7 = 50;
// 在按键处理中调整PWM参数
if(page == 5 && state == KEY_STATE_PRESSED)
{
PWM_D6 += 10;
TIM3->CCR1 = PWM_D6;
}
通过直接操作定时器的CCR寄存器可以快速调整PWM输出,这种方式效率高且响应迅速。
6. 输入捕获频率测量
输入捕获是测量外部信号频率和占空比的有效方法,模板中实现了完整的测量功能:
c复制void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
// CH1测周期,CH2测高电平时间
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// 读取周期值
F = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
// 计算占空比
D = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_2) * 100 / F;
// 换算频率
F = 1000000 / F;
// 计数器清零
TIM2->CNT = 0;
}
}
}
这段代码通过两个通道分别捕获信号的周期和高电平时间,从而计算出频率和占空比。在实际比赛中,这种测量方法精度高且稳定可靠。
7. 竞赛应用技巧与优化建议
7.1 内存与性能优化
- 使用
uint16_t等固定宽度类型替代int可以节省内存 - 避免在中断服务程序中进行复杂运算
- 合理设置各任务的执行频率,避免不必要的CPU负载
7.2 调试技巧
- 利用串口打印关键变量值
- 使用LED指示程序运行状态
- 为各功能模块添加调试宏定义
7.3 常见问题排查
- LCD无显示:检查初始化顺序和背光控制
- 按键无响应:确认GPIO模式和上下拉配置
- PWM无输出:验证定时器时钟和通道配置
- ADC读数异常:检查参考电压和采样时间设置
8. 模板使用指南
8.1 快速上手步骤
- 克隆或下载模板代码
- 使用STM32CubeIDE导入工程
- 根据具体硬件调整引脚定义
- 编译下载到开发板测试
8.2 功能扩展建议
- 添加更多传感器驱动
- 实现更复杂的人机交互
- 增加数据存储功能
- 优化电源管理
在实际比赛中,我通常会根据题目要求选择性地启用模板中的相应功能模块,这样可以大幅提高开发效率。例如,如果题目涉及信号测量,就直接使用输入捕获模块;如果需要显示复杂界面,就基于现有的LCD驱动进行扩展。