1. 赛题核心功能解析

第十五届蓝桥杯嵌入式省赛题目的硬件配置围绕按键、LED、LCD和输入捕获四大核心功能展开。输入捕获功能需要测量外部信号的频率,而按键处理涉及状态机设计和长短按识别。这些功能在嵌入式系统中非常常见,但实现起来容易踩坑,尤其是定时器配置和中断处理部分。

先说说输入捕获,题目指定使用PA15和PB4引脚(对应开发板上的R39和R40)。这两个引脚需要配置为定时器的输入捕获模式,具体来说,PA15对应TIM2的通道1(TIM2_CH1),PB4对应TIM3的通道1(TIM3_CH1)。在CubeMX中配置时,除了选择正确的定时器和通道,还得注意预分频系数和自动重装载值(ARR)的设置。预分频系数设为80-1,ARR设为65535,这样能确保定时器有足够的计数范围来捕获高频信号。别忘了开启中断,输入捕获功能依赖中断来触发捕获事件,这一点和普通的定时器计时不同。

按键处理部分用到状态机,这是嵌入式开发中处理输入事件的经典方法。状态机将按键动作分解为多个状态(如初始、按下确认、长按判断等),通过定时器中断周期性地检测按键状态,实现短按和长按的识别。状态机的实现要注意状态转换的条件和标志位的管理,一旦逻辑出错,按键响应就会混乱。

2. CubeMX配置详解

CubeMX的配置是基础,但细节决定成败。首先打开TIM2和TIM3,选择通道1为输入捕获直接模式。时钟树配置为HCLK 80MHz,PCLK1 24MHz,这是STM32F103的常见配置。预分频系数设置为80-1,意味着定时器时钟为1MHz(80MHz/80)。ARR设为65535,让定时器工作在16位模式下,最大计数范围0-65535。

输入捕获模式下,需要开启捕获/比较中断和更新中断。在NVIC设置中,使能TIM2和TIM3的全局中断。按键处理用的定时器(如TIM4)配置为普通定时器,中断频率通常设为1kHz(即1ms中断一次),这样能平衡响应速度和系统负载。

GPIO配置方面,PA15和PB4设置为复用推挽输出,按键引脚(如PB0、PB1、PB2、PA0)设置为输入模式,上拉或下拉根据电路设计选择。LED和LCD的引脚按常规配置即可。这些配置看起来简单,但一旦引脚或模式选错,整个功能就无法工作,尤其是复用功能引脚,容易遗漏。

3. 输入捕获的实现细节

输入捕获的核心是在中断回调函数中处理捕获事件。首先在main函数中启动输入捕获中断:

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);

中断回调函数HAL_TIM_IC_CaptureCallback中,需要判断是哪个定时器触发了中断,然后读取捕获值并计算频率。以TIM2为例:

if (htim->Instance == TIM2) {
    TA = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    __HAL_TIM_SetCounter(htim, 0);
    FA = (80000000 / 80) / TA;
}

这里TA是捕获的计数值,FA是计算出的频率。公式中的80000000是系统时钟频率,80是预分频系数,所以分频后定时器时钟为1MHz,周期1us。频率FA等于1MHz除以TA,结果单位是Hz。需要注意的是,读取捕获值后要立即清零计数器,避免下次捕获时计数值累积。

实测中容易遇到两个问题:一是频率计算错误,往往是预分频系数或时钟配置不对;二是捕获值不稳定,可能是信号噪声或中断处理延迟。建议在计算频率时加入滤波处理,比如多次测量取平均值。

4. 按键状态机的设计与实现

按键处理使用状态机,在定时器中断(如TIM4)中周期执行。状态机通常包含3个状态:0(初始)、1(按下确认)、2(长按判断)。每个按键用一个结构体管理状态、时间和标志位:

typedef struct {
    uint8_t key_sta;
    uint8_t jungle_sta;
    uint16_t key_time;
    uint8_t short_flag;
    uint8_t long_flag;
} Key_TypeDef;

Key_TypeDef key[4];

在HAL_TIM_PeriodElapsedCallback中断回调中,先读取所有按键状态,然后遍历每个按键处理状态转换:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM4) {
        key[0].key_sta = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
        // 读取其他按键状态...
        for (int i = 0; i < 4; i++) {
            switch (key[i].jungle_sta) {
                case 0: // 初始状态
                    if (key[i].key_sta == 0) { // 按键按下
                        key[i].jungle_sta = 1;
                        key[i].key_time = 0;
                    }
                    break;
                case 1: // 按下确认
                    if (key[i].key_sta == 0) {
                        key[i].jungle_sta = 2; // 确认按下,进入长按判断
                    } else {
                        key[i].jungle_sta = 0; // 松开,回到初始
                    }
                    break;
                case 2: // 长按判断
                    if (key[i].key_sta == 1) { // 按键松开
                        key[i].jungle_sta = 0;
                        if (key[i].key_time < 100) { // 短按
                            key[i].short_flag = 1;
                        }
                    } else { // 仍按住
                        key[i].key_time++;
                        if (key[i].key_time > 100) { // 长按
                            key[i].long_flag = 1;
                        }
                    }
                    break;
            }
        }
    }
}

状态机中,时间阈值100对应100ms(假设中断周期1ms),短按小于100ms,长按大于100ms。标志位short_flag和long_flag在主循环中检测并处理,处理完后要手动清零。这种设计避免了按键抖动问题,还能区分短按和长按。

5. 频率计算与浮点数处理

输入捕获得到的频率值可能需要转换为浮点数进行显示或处理。例如,将频率FA转换为千赫兹(kHz)并保留两位小数:

float A = (float)FA / 1000.0f;
printf("频率: %.2f kHz\n", A);

这里需要注意浮点数的处理效率。STM32F103没有硬件浮点单元(FPU),浮点运算靠软件模拟,比较耗时。如果系统实时性要求高,可以考虑用整型运算代替,比如将频率值乘以100转为整型,显示时再处理小数点。

频率超限处理也是常见问题。题目可能要求频率超过一定范围时显示错误或忽略,可以在计算FA后加入判断:

if (FA > 100000) { // 频率超过100kHz视为超限
    // 处理超限情况
}

超限可能由信号噪声或捕获错误引起,加入软件滤波可以提高稳定性。比如连续多次测量超限才判定为超限,单次超限可能是干扰。

6. 界面切换与参数调整逻辑

比赛题目通常要求多个界面切换和参数调整。例如,按键B3用于切换频率和周期显示,其他按键调整参数。逻辑实现主要在主循环中处理按键标志位:

while (1) {
    if (key[0].short_flag) { // 短按按键0
        key[0].short_flag = 0;
        // 处理短按逻辑
    }
    if (key[1].long_flag) { // 长按按键1
        key[1].long_flag = 0;
        // 处理长按逻辑
    }
    // 其他按键处理...
}

界面切换时,通常需要清屏再刷新新内容:

if (interface_flag == 1) {
    LCD_Clear();
    // 显示界面1内容
} else {
    // 显示界面2内容
}

参数调整常用para变量记录当前选择的参数,按键按下时修改对应参数:

if (key[2].short_flag) {
    para++;
    if (para > 2) para = 0;
}
if (key[3].short_flag) {
    if (para == 0) {
        PH += 100;
    } else if (para == 1) {
        PL += 100;
    }
}

这种逻辑结构清晰,但要注意参数范围和界面切换的同步问题。比如参数调整后要及时更新显示,避免显示滞后。

7. 常见问题与调试技巧

输入捕获容易遇到频率测量不准的问题。可能的原因有定时器配置错误、中断处理延迟或信号质量差。调试时可以用示波器对比实际信号和测量值,或者通过串口打印捕获的原始计数值TA,检查是否稳定。

按键响应不灵敏或误触发,往往是状态机逻辑或时间阈值设置不合理。可以用逻辑分析仪抓取按键波形,确认按键抖动时间和按住时间,调整状态机阈值。另外,按键引脚的上拉/下拉电阻配置错误也会导致状态读取异常。

LCD显示乱码或刷新慢,可能是清屏和刷新逻辑问题。清屏函数LCD_Clear()耗时较长,频繁清屏会导致显示闪烁,建议局部刷新代替全屏刷新。浮点数显示时,格式转换可能占用较多资源,影响系统实时性。

中断冲突也是常见问题。多个定时器中断同时触发时,可能因为中断优先级设置不当导致某些中断被延迟处理。在CubeMX中配置NVIC优先级,输入捕获中断的优先级应高于按键定时器中断,确保频率测量及时性。

8. 备赛建议与实战经验

蓝桥杯嵌入式省赛题目通常侧重基础功能的组合和逻辑实现。备赛时建议多往届真题练习,尤其是第十一届到第十四届省赛题,熟悉常见功能模块和出题思路。省赛难点往往不在单一功能,而在多功能的协调和逻辑整合。

开发环境搭建是第一步,确保Keil、CubeMX和调试驱动正常。工程模板准备好常用模块的初始化代码,比如LCD、按键、定时器等,比赛时可以快速搭建框架。代码结构要清晰,模块化设计,便于调试和修改。

比赛时间有限,合理分配时间很重要。先完成基础功能(输入捕获、按键识别),再实现逻辑和界面。功能测试时,分段验证,比如先测试输入捕获是否正常,再处理按键逻辑。最后留时间整体测试和优化显示效果。

调试技巧方面,串口打印是简单有效的调试手段,可以输出变量值或状态信息。LED灯也可以作为状态指示,比如不同闪烁模式表示不同状态。如果现场允许,自带示波器或逻辑分析仪,可以快速定位硬件问题。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐