1. 蓝桥杯嵌入式赛项备赛指南
作为一名参加过多次蓝桥杯嵌入式赛项的选手,我深知备赛过程中资料整理和模块化编程的重要性。本文将基于STM32G431RBT6开发板,系统性地分享我在备赛过程中整理的各个功能模块的实现方法和注意事项。
2. 硬件平台与基础配置
2.1 开发板概述
蓝桥杯嵌入式赛项官方指定使用STM32G431RBT6开发板,这是一款基于ARM Cortex-M4内核的微控制器,主频可达170MHz,具有丰富的外设资源。开发板集成了LED、按键、电位器、LCD显示屏等常用外设,非常适合嵌入式学习和竞赛使用。
2.2 引脚分配与注意事项
在开始编程前,必须清楚各个外设的引脚分配:
- LED:PC8-PC15(共8个)
- 按键:PB0-PB2、PA0(共4个)
- 电位器:PB15、PB12、PB4、PA15
- USART1:PA9(TX)、PA10(RX)
- I2C:PB6(SCL)、PB7(SDA)
特别注意:由于LCD显示屏占用了PC0-PC15的大部分引脚,LED控制需要通过锁存器实现,这与常规的GPIO直接控制有所不同。
3. 核心功能模块实现
3.1 LED控制模块
3.1.1 锁存器工作原理
开发板上的LED通过74HC573锁存器控制,这种设计可以节省GPIO资源。控制流程分为三步:
- 拉高PD2(锁存器使能)
- 设置GPIOC输出状态
- 拉低PD2(锁存数据)
3.1.2 代码实现与优化
c复制void led_show(uint8_t led_num, uint8_t led_state)
{
static uint8_t led_sta = 0x00; // 状态压缩,0000 0000
// 更新LED状态
if(led_state == 1) {
led_sta |= (0x01 << (led_num - 1));
} else {
led_sta &= ~(0x01 << (led_num - 1));
}
// 锁存器操作
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_All, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, led_sta << 8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
注意:由于LED连接在GPIOC的高8位,需要将状态值左移8位后再写入。
3.2 按键检测模块
3.2.1 硬件消抖与软件消抖
按键检测需要处理抖动问题。硬件上通常使用RC滤波电路,软件上则采用延时检测法。我推荐使用状态机实现软件消抖,既可靠又节省资源。
3.2.2 状态机实现
c复制struct keys{
uint8_t key_state; // 当前按键状态
uint8_t key_jduge; // 消抖状态
uint8_t key_short_press;// 短按标志
uint8_t key_long_press; // 长按标志
uint32_t key_press_time;// 按下时间计数
}key[4];
void key_alter_state(void)
{
// 读取按键状态
key[0].key_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
// ...其他按键类似
for (uint8_t i = 0; i < 4; i++) {
switch (key[i].key_jduge) {
case 0: // 初始状态
if(key[i].key_state == 0) key[i].key_jduge = 1;
break;
case 1: // 第一次检测到按下
if(key[i].key_state == 0) key[i].key_jduge = 2;
break;
case 2: // 确认按下状态
if(key[i].key_state == 1) { // 按键释放
if(key[i].key_press_time * 10 > 2000) // 长按
key[i].key_long_press = 1;
else // 短按
key[i].key_short_press = 1;
key[i].key_jduge = 0;
key[i].key_press_time = 0;
} else {
key[i].key_press_time++;
}
break;
}
}
}
提示:建议在10ms定时器中断中调用此函数,确保稳定的时间基准。
3.3 ADC采样与电压测量
3.3.1 ADC基本原理
STM32的ADC将0-3.3V的模拟电压转换为12位数字量(0-4095)。转换公式为:
电压值 = (ADC值 / 4095) × 3.3V
3.3.2 单次采样实现
c复制double get_vol(void)
{
if(HAL_ADC_Start(&hadc2) == HAL_OK &&
HAL_ADC_PollForConversion(&hadc2, 10) == HAL_OK) {
uint32_t adc_value = HAL_ADC_GetValue(&hadc2);
return (adc_value / 4095.0) * 3.3;
}
return 0;
}
3.3.3 DMA连续采样优化
对于需要高频采样的场景,建议使用DMA:
c复制uint16_t adc_buffer[1024];
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, 1024);
配置要点:
- 在CubeMX中启用ADC的DMA Continuous Requests
- 选择Circular模式实现自动循环采样
- 设置合适的采样时间和序列长度
4. 定时器高级应用
4.1 PWM输出配置
4.1.1 频率与占空比计算
PWM频率计算公式:
f = 定时器时钟 / [(PSC+1) × (ARR+1)]
占空比计算公式:
占空比 = CCR / (ARR+1) × 100%
4.1.2 代码示例
c复制// 设置TIM3通道2的占空比为30%
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 3000);
4.2 输入捕获测频
4.2.1 测量原理
输入捕获通过记录两次上升沿之间的计数器差值来计算信号周期,进而得到频率。
4.2.2 实现代码
c复制uint32_t input_fre;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim3) {
static uint8_t state = 0;
switch (state) {
case 0:
__HAL_TIM_SET_COUNTER(&htim3, 0);
state = 1;
break;
case 1:
input_fre = 1000000 / HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);
state = 0;
break;
}
}
}
注意:此方法适用于测量低频信号(<1MHz),高频信号建议使用定时器的从模式自动测量。
5. 通信协议实现
5.1 USART通信
5.1.1 printf重定向
c复制#include <stdio.h>
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10);
return ch;
}
5.1.2 不定长数据接收
c复制char rx_buf[32];
char rx_cmd[32];
uint8_t uart_finish_flag = 0;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1) {
sscanf(rx_buf,"%s",rx_cmd); // 去除\r\n
uart_finish_flag = 1;
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)rx_buf, 32);
}
}
5.2 I2C与EEPROM
5.2.1 字节写入时序
c复制void EEPROM_Write_Byte(uint8_t addr, uint8_t byte_data)
{
I2CStart();
I2CSendByte(0xa0); // 设备地址+写
I2CWaitAck();
I2CSendByte(addr); // 内存地址
I2CWaitAck();
I2CSendByte(byte_data);// 数据
I2CWaitAck();
I2CStop();
HAL_Delay(10); // 等待写入完成
}
5.2.2 字节读取时序
c复制uint8_t EEPROM_Read_Byte(uint8_t addr)
{
uint8_t byte_data = 0;
I2CStart();
I2CSendByte(0xa0); // 设备地址+写
I2CWaitAck();
I2CSendByte(addr); // 内存地址
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1); // 设备地址+读
I2CWaitAck();
byte_data = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return byte_data;
}
6. 系统优化技巧
6.1 时钟树配置
合理的时钟配置可以提升系统性能:
- 使用PLL将系统时钟倍频至最高170MHz
- 根据外设需求配置APB1/APB2分频
- 启用需要的时钟源(HSI/HSE)
6.2 DMA应用场景
DMA可以显著降低CPU负载:
- ADC连续采样数据传输
- USART大批量数据收发
- 内存间大数据块搬运
配置示例:
c复制HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, BUFFER_SIZE);
7. 常见问题排查
-
LED不亮:
- 检查锁存器使能信号
- 确认LED状态值左移了8位
- 测量GPIOC输出电平
-
ADC采样值不稳定:
- 增加采样时间
- 添加硬件滤波电路
- 确保参考电压稳定
-
I2C通信失败:
- 检查上拉电阻
- 确认设备地址正确
- 用逻辑分析仪抓取时序
-
定时器不工作:
- 检查时钟使能
- 确认预分频和重载值设置
- 验证中断优先级配置
8. 备赛建议
- 模块化编程:将各个功能封装成独立模块,方便复用和调试
- 提前熟悉开发环境:STM32CubeIDE、Keil等工具的使用
- 准备常用代码模板:如定时器配置、通信协议等
- 多做真题练习:熟悉比赛题型和评分标准
- 注意时间管理:比赛时间有限,合理分配各个模块的开发时间
通过系统化的准备和反复练习,相信每位参赛者都能在蓝桥杯嵌入式赛项中取得好成绩。在实际开发中遇到问题时,要善用STM32参考手册和库函数文档,这些资源包含了解决大多数问题所需的信息。