BACK_TO_BASE
蓝桥杯嵌入式第十五届省赛笔记
Engineering Notebook // Build Log
/
22:18:07
/
NOTEBOOK_ENTRY

蓝桥杯嵌入式第十五届省赛笔记

一、赛题功能概述 本题要求实现一个 双通道频率检测与分析系统 ,核心功能包括: | 功能模块 | 描述 | | | | | 频率采集 | 通过输入捕获测量两路信号(通道A、通道B)的频率 | | LCD 显示 | 三个页面切换显示:数据页 DATA、参数页 PARA、记录页 RECD | | 按键控制 | 4 个按键实现页面切换、参数调节、模式切换、长按清零 | | LED 指示 | 根据系统状态点亮/熄灭对应 LED | | 频率波动…

Notebook Time
4 min
Image Frames
1
View Tracks
79
嵌入式
FIELD_GUIDE

FIELD GUIDE

Use the guide rail to jump between sections.

一、赛题功能概述

本题要求实现一个 双通道频率检测与分析系统,核心功能包括:

功能模块描述
频率采集通过输入捕获测量两路信号(通道A、通道B)的频率
LCD 显示三个页面切换显示:数据页 DATA、参数页 PARA、记录页 RECD
按键控制4 个按键实现页面切换、参数调节、模式切换、长按清零
LED 指示根据系统状态点亮/熄灭对应 LED
频率波动检测统计频率波动超标次数
频率越限检测统计频率超过阈值的次数

二、硬件资源分配

2.1 定时器资源

定时器功能引脚配置
TIM2输入捕获(通道A)PA15 (TIM2_CH1)PSC=79, ARR=65535, 上升沿捕获
TIM16输入捕获(通道B)PB4 (TIM16_CH1)PSC=79, ARR=65535, 上升沿捕获
TIM7基本定时器(1ms 周期中断)PSC=79, ARR=999

为什么 PSC 都配置为 79?

系统时钟为 80MHz,分频系数 = PSC + 1 = 80,所以定时器计数频率为:

fcnt=80MHz80=1MHzf_{cnt} = \frac{80\text{MHz}}{80} = 1\text{MHz}

即每个计数值代表 1μs,这样计算频率和周期时非常方便。

2.2 GPIO 资源

功能引脚
按键 B1PB0(输入,无上拉)
按键 B2PB1(输入,无上拉)
按键 B3PB2(输入,无上拉)
按键 B4PA0(输入,无上拉)
LED 数据线PC8 ~ PC15(输出)
LED 锁存使能PD2(输出)

三、软件架构设计

3.1 整体架构 —— 非阻塞轮询

// main.c 主循环
while (1)
{
    LCD_proc();   // LCD 显示处理
    KEY_proc();   // 按键扫描处理
    LED_proc();   // LED 状态更新
}
// 典型时间片写法
u32 lcd_tick = 0;
void LCD_proc()
{
    if(uwTick - lcd_tick < 100) return;  // 100ms 执行一次
    lcd_tick = uwTick;
    // ... 业务逻辑
}

3.2 文件组织

文件职责
main.cCubeMX 生成的主函数,初始化 + 主循环调用
user.c / user.h所有用户逻辑(LCD、KEY、LED、回调函数)
tim.cCubeMX 生成的定时器初始化
gpio.cCubeMX 生成的 GPIO 初始化

四、核心功能详解

4.1 输入捕获测频率

这是本题最核心的技术点。采用 两次上升沿捕获法 测量信号周期,再求倒数得到频率。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim == &htim2)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            if(uhCaptureIndex == 0)
            {
                // 第一次捕获:记录第一个上升沿的计数值
                uwIC2Value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
                uhCaptureIndex = 1;
            }
            else if(uhCaptureIndex == 1)
            {
                // 第二次捕获:记录第二个上升沿的计数值
                uwIC2Value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);

                // 计算两次捕获的差值(即信号周期,单位 μs)
                if (uwIC2Value2 > uwIC2Value1)
                    uwDiffCapture = uwIC2Value2 - uwIC2Value1;
                else  // 处理计数器溢出
                    uwDiffCapture = (0xFFFF - uwIC2Value1) + uwIC2Value2 + 1;

                // 频率 = 1,000,000 / 周期(μs)
                uwFrequency = 1e6 / uwDiffCapture;
                uhCaptureIndex = 0;
            }
        }
    }
    // TIM16 通道B 逻辑完全相同
}

原理图解:

信号:  ___/‾‾‾\___/‾‾‾\___/‾‾‾\___
            ↑           ↑
        第1次捕获    第2次捕获
        uwIC2Value1  uwIC2Value2

周期 T = uwIC2Value2 - uwIC2Value1 (单位: μs)
频率 f = 1,000,000 / T (单位: Hz)

为什么需要处理溢出?

定时器是 16 位(ARR=65535),如果信号周期较长,第二次捕获时计数器可能已经溢出归零。此时差值需要加上 0xFFFF - Value1 + Value2 + 1

4.2 基本定时器周期回调(TIM7)

TIM7 每 1ms 产生一次中断,在 HAL_TIM_PeriodElapsedCallback 中完成以下逻辑:

TIM7 中断 (每 1ms)
├── time7_tick++
└── 每 100ms (time7_tick % 100 == 0)
├── 存入缓冲区 a_frq_buff[frq_cnt]
├── frq_cnt++
└── 当 frq_cnt == 30 (即 3 秒)
├── 计算 30 个采样的最大值和最小值
└── frq_cnt 清零,重新采集
├── 频率越限检测
│ ├── a_frq > PH → LED2 亮,若从低到高跳变则 NHA++
│ └── b_frq > PH → LED3 亮,若从低到高跳变则 NHB++

为什么用 100ms 采样、30 个点(3秒)做统计?

  • 100ms 采样一次频率值,既不会过于频繁占用资源,也能保证足够的时间分辨率
  • 30 个采样点 = 3 秒窗口,通过窗口内的最大值-最小值来判断频率波动幅度
  • 这是一种简单高效的 滑动窗口统计 方法

4.3 频率越限检测(NHA / NHB)

// 以通道A为例
if(a_frq <= ph)
{
    pha_lock = 0;         // 频率低于阈值,解锁
    led_num &= ~0x02;    // LED2 灭
}
else
{
    led_num |= 0x02;     // LED2 亮
    if(pha_lock == 0)     // 上次是低于阈值的状态
    {
        nha++;            // 计一次越限
        pha_lock = 1;     // 加锁,防止重复计数
    }
}

为什么需要 pha_lock 锁?

频率可能长时间高于阈值 PH,如果每次检测到高于阈值就计数,会导致 重复计数。使用锁标志实现:

  • 只在频率从低于 PH 跳变到高于 PH 的那一刻计数一次
  • 必须等频率重新降到 PH 以下(pha_lock = 0),下次再升上去才再计一次

这是一种经典的 边沿检测 思路。

4.4 按键处理

按键扫描与消抖

void KEY_read()
{
    if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == 0) key_value = 1;
    else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == 0) key_value = 2;
    else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == 0) key_value = 3;
    else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0) key_value = 4;
    else key_value = 0;

    key_down = key_value & (key_value ^ key_old);   // 按下瞬间
    key_up   = ~key_value & (key_value ^ key_old);  // 松开瞬间
    key_old  = key_value;
}

为什么用异或运算做边沿检测?

表达式含义
key_value ^ key_old找出状态发生变化的键
& key_value变化的且当前为按下 → 按下沿
& ~key_value变化的且当前为松开 → 松开沿

这是蓝桥杯嵌入式中最常用的按键处理模板,配合 20ms 的扫描周期实现软件消抖。

长按检测

// B3 按下时记录时间
else if(key_down == 3 && ui == recd)
{
    long_tick = uwTick;
}
// B3 松开时,如果持续时间 > 1000ms,执行清零
else if(key_up == 3 && ui == recd && (uwTick - long_tick) > 1000)
{
    nda = 0; ndb = 0; nha = 0; nhb = 0;
}

为什么在松开时判断?

  • 按下记录 long_tick,松开时用 uwTick - long_tick 计算按压时长
  • 只有松开后才执行操作,避免长按过程中重复触发
  • 阈值 1000ms(1秒)区分短按和长按

4.5 LCD 显示

三个页面通过 ui 变量切换:

ui 值页面显示内容
0 (data)数据页频率/周期模式切换:A、B 通道数据
1 (para)参数页PD(波动阈值)、PH(高频阈值)、PX(频率偏移)
2 (recd)记录页NDA、NDB(波动次数)、NHA、NHB(越限次数)

数据页的频率/周期切换:

if(data_mode == 0)  // 频率模式
{
    if(a_frq <= 1000)
        sprintf(lcd_buf, "     A=%dHz     ", a_frq);
    else
        sprintf(lcd_buf, "     A=%.2fKHz  ", a_frq / 1000.0f);
    if(a_frq < 0)
        sprintf(lcd_buf, "     A=NULL     ");
}
else  // 周期模式
{
    a_t = 1e6 / a_frq;  // 频率转周期 (μs)
    if(a_t <= 1000)
        sprintf(lcd_buf, "     A=%duS       ", a_t);
    else
        sprintf(lcd_buf, "     A=%.2fmS     ", a_t / 1000.0f);
}

为什么要做单位自适应? 当数值大于 1000 时自动切换更大的单位(Hz→KHz,μs→ms),提升显示可读性。

4.6 LED 控制

void LED_disp(u8 led)
{
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);    // 锁存器使能
    HAL_GPIO_WritePin(GPIOC, 0xFF00, GPIO_PIN_SET);        // 先全灭
    HAL_GPIO_WritePin(GPIOC, led << 8, GPIO_PIN_RESET);    // 点亮目标 LED
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);  // 锁存器关闭
}

为什么要用锁存器?

蓝桥杯嵌入式开发板的 LED 通过 74HC573 锁存器 连接到 PC8~PC15,PD2 控制锁存使能。操作流程:

  1. 拉高 PD2 打开锁存器
  2. 先将所有 LED 置高(灭)
  3. 将目标 LED 对应位置低(亮,低电平点亮)
  4. 拉低 PD2 锁定状态

LED 位分配:

LED条件
bit0 (0x01)LED1当前处于 DATA 页面
bit1 (0x02)LED2通道A频率 > PH
bit2 (0x04)LED3通道B频率 > PH
bit7 (0x80)LED8NDA ≥ 3 或 NDB ≥ 3(报警)

五、参数说明

参数默认值范围步长含义
PD1000 Hz100 ~ 1000100频率波动判定阈值
PH5000 Hz1000 ~ 10000100频率越限判定阈值
PX0 Hz-1000 ~ 1000100频率修正偏移量

六、做题技巧总结

6.1 CubeMX 配置要点

  1. 系统时钟:HSE → PLL → 80MHz(蓝桥杯 G4 板标准配置)
  2. 输入捕获定时器:PSC 设为 79 使计数频率为 1MHz,ARR 设为 65535 获取最大量程
  3. 基本定时器:PSC=79, ARR=999 → 1ms 中断周期,用于周期性任务调度
  4. GPIO:按键配置为 Input No Pull,LED 引脚 PC8~PC15 + PD2 配置为 Output

6.2 代码编写模板

① 时间片轮询框架(必背)

u32 xxx_tick = 0;
void XXX_proc()
{
    if(uwTick - xxx_tick < 周期ms) return;
    xxx_tick = uwTick;
    // 业务逻辑
}

② 按键扫描三件套(必背)

key_down = key_value & (key_value ^ key_old);
key_up   = ~key_value & (key_value ^ key_old);
key_old  = key_value;

③ 输入捕获测频率(必背)

// 两次上升沿捕获,差值即周期
// 频率 = 计数频率 / 差值
uwFrequency = 1e6 / uwDiffCapture;

④ LED 锁存器操作(必背)

HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, 0xFF00, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, led << 8, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);

6.3 常见陷阱与注意事项

问题解决方案
LCD 刷新闪烁控制刷新周期 100ms,避免频繁全屏清除
按键误触发20ms 扫描 + 异或边沿检测
频率为 0 时除法出错if(a_frq < 0) 判断,显示 NULL
计数器溢出捕获差值计算时处理翻转情况
LED 与 LCD 共用 GPIO每次操作 LED 后及时关闭锁存器(拉低 PD2)
长按与短按冲突按下记时间,松开时判断时长
页面切换时残留内容切换页面时调用 LCD_Clear(Black)

6.4 做题顺序建议

1. CubeMX 配置(时钟 + GPIO + 定时器) ────── 约 15 分钟
2. LCD 显示框架(三个页面的静态文字)   ────── 约 15 分钟
3. 按键扫描 + 页面切换                ────── 约 10 分钟
4. LED 基本控制                       ────── 约 5 分钟
5. 输入捕获测频率                     ────── 约 15 分钟
6. 参数调节(PD/PH/PX)              ────── 约 10 分钟
7. 频率波动检测 + 越限检测            ────── 约 20 分钟
8. 数据模式切换(频率/周期)          ────── 约 5 分钟
9. 长按清零 + LED 报警逻辑           ────── 约 10 分钟
10. 测试调试                          ────── 约 15 分钟

原则:先搭框架,再填功能,最后处理细节。 优先拿稳定的基础分(LCD 显示 + 按键 + LED),再攻高分项(频率检测 + 统计逻辑)。


七、完整变量一览

变量类型含义
uiu8当前页面(0=DATA, 1=PARA, 2=RECD)
data_mode_Bool数据显示模式(0=频率, 1=周期)
a_frq / b_frqs32通道 A/B 的当前频率
uwFrequency / uwFrequency2u32输入捕获原始频率值
pdu32波动检测阈值(默认 1000)
phu32越限检测阈值(默认 5000)
pxs32频率偏移校准值(默认 0)
nda / ndbu8通道 A/B 波动超标次数
nha / nhbu8通道 A/B 越限次数
pha_lock / phb_lock_Bool越限计数锁(防重复计数)
a_frq_buff / b_frq_buffs32[30]频率采样缓冲区
led_numu8LED 状态位图
para_lineu8参数页当前选中行

八、总结

本题考察的知识点覆盖了蓝桥杯嵌入式竞赛的核心内容:

  • 定时器输入捕获:测量外部信号频率
  • 基本定时器中断:周期性任务调度
  • LCD 多页面显示:UI 状态机
  • 按键处理:消抖、短按、长按
  • LED 锁存控制:74HC573 操作
  • 数据统计与分析:滑动窗口、边沿检测