简介:这份资源是2024年蓝桥杯第十五届物联网省赛的完整工程代码,面向备赛物联网赛项的选手与嵌入式学习者,聚焦矩阵按键扫描、LoRa无线通信与电位器模拟量采集三类典型赛题任务。压缩包共371个文件、约11.27MB,以h头文件、c源文件与o目标文件为主体,配合crf交叉引用、d依赖文件及uvprojx、ioc工程配置,另有hex烧录文件与map、lst编译映射记录,完整保留了STM32L0系列HAL库工程的构建痕迹。资源内含LORA_A与LORA_B两套工程,可对照理解双节点LoRa收发流程、按键信号处理与ADC取值转换的实现思路。目前已有189人学习,适合希望参照省赛真题梳理工程结构、复现通信链路与排查编译配置问题的读者研读。
1. 省赛物联网这道坎:矩阵按键、LoRa 通信、电位器取值,三块板子决定你能不能进国赛
蓝桥杯物联网省赛的题面看起来年年换壳,但拆开看,硬件层永远绕不开三件事:矩阵按键怎么扫、LoRa 怎么通、电位器怎么采。很多人栽跟头不是因为不会写代码,而是把这三块当成独立模块各写各的,最后联调时发现按键一按通信就丢包、电位器一拧数据就跳变。这场考试考的是“嵌入式系统集成能力”,不是单点知识背诵。拿分逻辑很直接:底层驱动稳、通信协议对、数据采集准,三个环节各占一块分值,任何一块翻车都会拖垮整题。适合已经写过 STM32 基础外设、但没做过完整物联网小系统联调的备考生——如果你还在纠结 GPIO 怎么配,先补基础再来看这篇。下面按考点分布、高频题拆解、易错点、答题节奏四层往下推,每一层都能直接拿去练。
2. 考点分布与分值拆解:三模块各占多少、先练哪个性价比最高
2.1 省赛物联网的评分骨架
省赛物联网赛题通常给一个场景描述加若干功能要求,评分按功能点逐项给分,不是按代码行数给分。从历年题面结构看,分值大致落在三个区间:矩阵按键识别与状态机处理约占 30%,LoRa 收发与协议解析约占 35%,电位器 ADC 采集与数据处理约占 25%,剩下 10% 是系统整合与稳定性。这个比例意味着什么?LoRa 权重最高,但它依赖按键触发发送、依赖电位器提供数据源,所以三者是串联关系,不是并联关系。你单独把 LoRa 调通但按键没做消抖,发送触发就会抖出一串重复帧,协议解析端直接判错。
备考顺序建议反过来:先练电位器 ADC 采集,因为它是数据源头,调通后你手里有稳定变化的数值可以拿来当测试 payload;再练矩阵按键,把触发逻辑做扎实;最后攻 LoRa,因为前两者就绪后,LoRa 调试才有真实数据可发。这个顺序能让你在练 LoRa 时不用一边怀疑数据源一边怀疑通信,减少“玄学丢包”的误判。
2.2 三个模块的依赖关系与联调顺序
矩阵按键和 LoRa 的耦合点在“发送触发条件”。常见题面要求“按下指定键后发送当前电位器值”,如果你按键扫描放在主循环里做延时消抖,主循环周期被拉长,LoRa 发送缓冲区的填充时机就会漂移。正确做法是按键扫描用定时器中断做状态机,主循环只读键值标志位,这样按键响应和通信时序互不阻塞。电位器和 LoRa 的耦合点在“数据格式”:ADC 原始值通常是 12 位(0~4095),直接塞进 LoRa 帧里发出去,接收端如果按百分比解析就会全错。所以采集端要先做量纲转换,把原始值映射成题面要求的物理量或百分比,再打包发送。
提示:联调顺序固定为“ADC 单模块验证 → 按键单模块验证 → 按键触发本地打印 → 按键触发 LoRa 发送 → 双机收发对拍”,每步验证通过再进下一步,不要跳步。
2.3 真题演练:给定功能描述,先拆评分点再写代码
假设题面要求:“系统上电后循环采集电位器电压并转换为 0~100 的百分比,矩阵键盘 4×4 中按下 S1 键发送当前百分比到对端,对端收到后在串口打印,按下 S2 键停止发送。”拿到这种题,先别急着写代码,先在纸上拆评分点:ADC 初始化与采样(约 8 分)、百分比映射与边界处理(约 7 分)、矩阵按键扫描与消抖(约 10 分)、按键状态机与发送触发(约 8 分)、LoRa 初始化与发送(约 12 分)、接收端解析与打印(约 10 分)、S2 停止逻辑(约 5 分)。拆完你就知道哪块不能丢分——LoRa 初始化和接收解析合计 22 分,是最大单项,但按键扫描加状态机 18 分更容易拿满,先把容易的分锁死。
答题思路按这个骨架走:先写 ADC 采样函数并串口打印验证,再写按键扫描返回键值并打印验证,然后把两者拼起来做“按键触发打印百分比”,最后把打印换成 LoRa 发送。每一步都有可观测输出,出问题能立刻定位是哪个环节。这类题面试官最想听的是你有没有“分步验证”的意识,而不是一上来就贴一大段整合代码。
3. 高频考点一:矩阵按键扫描与状态机,别再用延时消抖
3.1 矩阵按键的两种扫描方式与选型
4×4 矩阵按键有行扫描和列扫描两种驱动方式,本质都是逐行(或逐列)输出低电平、读列(或行)电平判断哪个键按下。省赛题面通常不限定方式,但评分时会看你的扫描是否稳定、是否支持多键同时按下的合理处理。行扫描的写法是:行线依次输出低电平,其余行输出高电平,读四根列线,哪根为低就说明该行该列的键按下。列扫描反过来。选哪种取决于你的硬件接线习惯,但代码里要注释清楚行列对应关系,否则联调时自己都对不上键号。
关键参数是扫描周期。放在定时器中断里做,周期取 10ms 比较稳:太短会频繁占用 CPU,太长会漏掉快速按键。消抖不要用delay_ms(20)这种阻塞写法,而是用状态机:连续两次扫描到同一键值才确认有效,松开同样连续两次确认。这样主循环完全不被阻塞,LoRa 发送时序也不会被按键拖慢。
3.2 真题演练:按键触发发送的完整状态机
题面常见要求:“S1 按下发送一次,长按 2 秒进入连续发送模式,S2 按下退出连续模式。”这种题考的是状态机设计,不是单次按键检测。答题思路是先画状态:IDLE(空闲)、SINGLE(单次触发)、LONG_PRESS(长按计时)、CONTINUOUS(连续发送)。每个状态记录进入条件、退出条件和动作。代码骨架如下:
/* 按键状态机:10ms 周期调用,key_val 为当前扫描键值,0 表示无键 */ typedef enum { KEY_IDLE, KEY_PRESSED, KEY_LONG, KEY_CONTINUOUS } key_state_t; static key_state_t state = KEY_IDLE; static uint16_t press_cnt = 0; void key_fsm(uint8_t key_val) { switch (state) { case KEY_IDLE: if (key_val == KEY_S1) { press_cnt = 0; state = KEY_PRESSED; } break; case KEY_PRESSED: if (key_val == KEY_S1) { press_cnt++; if (press_cnt >= 200) { state = KEY_LONG; } /* 200×10ms = 2s */ } else { send_once(); /* 短按:发一次 */ state = KEY_IDLE; } break; case KEY_LONG: if (key_val != KEY_S1) { state = KEY_CONTINUOUS; } /* 长按后松开进入连续 */ break; case KEY_CONTINUOUS: if (key_val == KEY_S2) { state = KEY_IDLE; } /* S2 退出 */ else { send_once(); } /* 连续模式每周期发 */ break; } }这段代码的逻辑说明:press_cnt以 10ms 为单位累加,200 次即 2 秒,对应长按判定;短按在松开瞬间触发一次发送,长按在松开后进入连续模式,连续模式下每个调用周期发一次,S2 按下退出。参数怎么改:如果题面要求长按 1.5 秒,把 200 改成 150;如果连续发送要求间隔 500ms,在KEY_CONTINUOUS分支里加一个计数器分频,不要在主循环里加延时。失败时看什么:如果短按偶尔触发两次,说明扫描周期和状态机调用周期不一致,检查定时器中断里是否只置标志、主循环是否重复调用;如果长按进不去,打印press_cnt看是否被其他分支清零。
3.3 面试官视角:按键题最容易问的三个追问
这类题在答辩或面试环节常被追问:“你怎么保证按键不抖?”“多键同时按怎么处理?”“按键和通信抢资源怎么办?”第一个答状态机加周期扫描,不要说“加了延时”;第二个答按优先级取一个有效键或忽略组合键,题面没要求组合功能就不要自作聪明做组合;第三个答按键扫描放中断、通信放主循环、用标志位解耦。面试官最想听的是你有没有“中断做轻活、主循环做重活”的分层意识,这比背出消抖代码更能说明你做过真实项目。
4. 高频考点二:LoRa 收发与协议解析,帧格式定生死
4.1 LoRa 初始化的关键参数与常见配置
LoRa 模块在省赛平台上通常走 SPI 接口,初始化要配频点、扩频因子、带宽、编码率、前导码长度、CRC 开关。这些参数两端必须完全一致,任何一项不同都表现为“发得出去收不到”,而且没有任何报错,这是最典型的翻车场景。频点按题面给定值配,扩频因子和带宽影响距离与速率,省赛场景距离近,取中等配置即可,但两端要抄同一组值。CRC 建议打开,能帮你过滤掉噪声帧,减少接收端解析时的无效数据。
初始化之后先做一次“回环自测”:发送端发固定字符串,接收端原样打印,确认链路通再上业务数据。这一步很多人跳过,直接拿业务帧调,结果分不清是链路问题还是协议问题。血泪经验是:链路自测用最短的固定帧,业务调试用带序号的可变帧,两者分开,出问题看序号就知道丢在第几帧。
4.2 真题演练:自定义帧格式与解析函数
题面常要求:“发送帧包含帧头、电位器百分比、按键状态、校验和。”答题思路是先定帧格式再写收发。推荐格式:帧头 2 字节固定值(如 0xAA 0x55)、数据长度 1 字节、载荷 N 字节、校验和 1 字节(前面所有字节累加取低 8 位)。接收端按状态机逐字节解析:找帧头、读长度、收载荷、验校验、交付业务。代码骨架:
/* 发送端打包:payload[0]=百分比, payload[1]=按键状态 */ void lora_send_frame(uint8_t percent, uint8_t key) { uint8_t buf[6]; buf[0] = 0xAA; buf[1] = 0x55; buf[2] = 2; /* 载荷长度 */ buf[3] = percent; buf[4] = key; buf[5] = 0; for (int i = 0; i < 5; i++) buf[5] += buf[i]; /* 累加校验 */ lora_transmit(buf, 6); } /* 接收端解析:逐字节喂入,返回 1 表示收到完整有效帧 */ uint8_t lora_parse_byte(uint8_t byte, uint8_t *percent, uint8_t *key) { static uint8_t step = 0, len = 0, sum = 0, idx = 0; static uint8_t payload[8]; switch (step) { case 0: if (byte == 0xAA) step = 1; break; case 1: step = (byte == 0x55) ? 2 : 0; break; case 2: len = byte; sum = 0xAA + 0x55 + byte; idx = 0; step = 3; break; case 3: payload[idx++] = byte; sum += byte; if (idx >= len) step = 4; break; case 4: step = 0; if (byte == sum) { *percent = payload[0]; *key = payload[1]; return 1; } break; } return 0; }逻辑说明:发送端把帧头、长度、载荷、校验依次填入缓冲区,校验是前 5 字节累加低 8 位;接收端用step状态机逐字节推进,只有帧头匹配才继续,长度字段决定收几个载荷字节,最后比对校验和。参数怎么改:载荷字段增减时同步改buf[2]的长度值和接收端payload数组大小;校验算法换成异或就把累加改成^=,两端同步改。失败时看什么:收不到帧先打印原始字节流,看帧头是否出现;帧头出现但校验不过,打印sum和收到的校验字节对比,多半是发送端长度字段和实际载荷数不一致。
4.3 面试官视角:通信题怎么答出“工程感”
被问“LoRa 丢包怎么办”,不要答“重发就行”。面试官想听的是分层排查:先确认物理层参数两端一致,再确认帧格式和校验逻辑,最后才谈重发机制。可以补一句“重发要带序号去重,否则接收端会重复执行动作”,这一句就能把工程感拉满。被问“为什么不用现成协议”,答“题面数据量小、字段固定,自定义短帧开销低、解析可控,现成协议栈引入的复杂度在省赛时限内不划算”。这类题通常这么答:先给结论,再给排查顺序,最后给一个你踩过的具体坑。
5. 高频考点三:电位器 ADC 采集与数据映射,精度和稳定性怎么保
5.1 ADC 采样配置与滤波策略
电位器接 ADC 通道,采样配置关注三件事:采样时间、分辨率、参考电压。采样时间太短,高阻电位器分压点充电不足,读数偏低且跳;分辨率按题面要求选 12 位即可;参考电压决定满量程对应值,映射公式里要用实际参考电压而不是想当然的 3.3V。滤波不要只做一次采样就交付,推荐“多次采样取中值再平均”:连续采 9 次,排序取中间 3 个求平均,既能滤掉脉冲噪声又不至于像纯平均那样把真实变化也抹平。
映射到百分比时注意边界:原始值可能因为噪声略超 4095 或低于 0,映射前先做钳位,否则百分比会算出负数或超过 100,接收端显示直接翻车。公式写成percent = (raw_clamped * 100) / 4095,整数运算避免浮点开销,除法前先乘后除防止精度丢失。
5.2 真题演练:从原始值到百分比再到发送的完整链路
题面要求:“采集电位器电压,转换为 0~100 百分比,保留整数,按下 S1 发送。”答题思路是把链路拆成三段函数:adc_read_filtered()返回滤波后原始值,raw_to_percent()做钳位和映射,lora_send_frame()打包发送。三段各自可单独测试:第一段串口打印原始值看是否随旋钮线性变化,第二段打印百分比看边界是否卡在 0 和 100,第三段用固定百分比测试收发。代码片段:
/* 中值平均滤波:采 9 次排序取中间 3 个平均 */ uint16_t adc_read_filtered(void) { uint16_t s[9]; for (int i = 0; i < 9; i++) s[i] = adc_read_once(); /* 简单插入排序,9 个元素开销可忽略 */ for (int i = 1; i < 9; i++) { uint16_t t = s[i]; int j = i - 1; while (j >= 0 && s[j] > t) { s[j+1] = s[j]; j--; } s[j+1] = t; } return (s[3] + s[4] + s[5]) / 3; } uint8_t raw_to_percent(uint16_t raw) { if (raw > 4095) raw = 4095; /* 钳位防溢出 */ return (uint8_t)((raw * 100) / 4095); }逻辑说明:排序后取第 4、5、6 个(下标 3、4、5)求平均,避开最小和最大的极端值;映射前钳位保证百分比落在 0~100。参数怎么改:采样次数改成 5 次就取中间 1 个或中间 3 个,排序循环边界同步改;参考电压不是 3.3V 时,百分比映射公式不变,但若题面要求输出电压值,则用raw * vref / 4095并注意整数运算的放大倍数。失败时看什么:旋钮拧到底百分比不到 100,检查钳位上限和实际满量程原始值(打印看看是不是只到 4000 左右);数值乱跳,先看原始值是否跳,原始值稳而百分比跳就是映射里的整数除法截断问题,加大放大倍数再除。
5.3 面试官视角:采集题怎么体现“数据可信”
被问“你怎么保证采集数据可信”,答三层:硬件层采样时间够、软件层滤波去极值、业务层钳位防越界。再补一句“我会把原始值和转换值同时打印一段时间,确认线性度和边界再接入通信”,这句话说明你有验证习惯。这类题通常这么答:先讲滤波方法,再讲边界处理,最后讲你怎么验证的。不要只报一个“用了中值滤波”就结束,面试官要的是你知道为什么选它、它防住了什么。
6. 易错点与失分点:这五类错误年年有人踩
6.1 按键消抖用阻塞延时,拖垮通信时序
错在哪:在主循环里写if(key) { delay_ms(20); ... }。为什么错:20ms 阻塞期间 LoRa 发送缓冲可能被填满或接收中断被延误,表现为“按键一按通信就卡”。怎么答对:消抖放定时器中断做状态机,主循环只读标志位,任何需要等待的逻辑都用计数器分频而不是死等。
6.2 LoRa 两端参数不一致,发得出去收不到
错在哪:发送端扩频因子配 7、接收端配 9,或频点差一点。为什么错:LoRa 物理层参数不匹配时链路层完全静默,没有任何错误码,新手会误判为模块坏了。怎么答对:初始化函数抽成公共配置,两端调同一个函数;上业务前先做固定字符串回环自测,链路不通不往下走。
6.3 帧长度字段与实际载荷不符,校验永远不过
错在哪:发送端buf[2]写死 2,后来加了字段没改长度。为什么错:接收端按长度字段收载荷,收多了把校验字节当载荷、收少了校验对不上,表现为“偶尔能收但数据错”。怎么答对:长度字段用宏或sizeof推导,收发两端共用同一份帧格式定义,改字段时只改一处。
6.4 ADC 映射不做钳位,百分比越界
错在哪:直接raw * 100 / 4095不判边界。为什么错:噪声让原始值瞬时超过 4095,百分比算出 101 甚至更多,接收端显示或判断逻辑直接异常。怎么答对:映射前钳位到量程内,映射后再钳一次 0~100,双保险成本极低。
6.5 连续发送模式没有退出路径,状态机卡死
错在哪:进了连续发送就只会发,S2 退出条件写在错误的 case 分支里。为什么错:状态机分支覆盖不全,某个状态下按键值被忽略,表现为“按 S2 没反应只能复位”。怎么答对:画状态转移表,每个状态列出所有输入和对应动作,空白格就是漏洞;联调时逐个状态打印当前状态名,按一遍所有键确认每条边都走得通。
7. 考场答题节奏:先锁分再攻难点,附三模块联调检查表
省赛时间通常紧张,答题节奏比代码技巧更影响最终得分。我的建议是开考后前 10 分钟只做一件事:把题面功能逐条拆成评分点,按“容易拿满且依赖少”排序。ADC 采集和按键扫描通常 30 分钟内能调通并串口验证,先把这两块的分数锁死;LoRa 初始化加回环自测再花 20 分钟;剩下时间做整合和边界处理。不要一上来就啃 LoRa,链路不通时你连数据源都没验证过,排查会陷入两头怀疑。
联调阶段按这张检查表逐项过,每项都有可观测输出才算通过:
| 检查项 | 验证方法 | 通过标准 |
|---|---|---|
| ADC 单模块 | 串口打印原始值,旋钮全程拧一遍 | 数值单调变化,边界接近 0 和 4095 |
| 百分比映射 | 打印转换值,拧到两端 | 稳定卡在 0 和 100,不越界 |
| 按键扫描 | 逐个按键打印键号 | 每个键对应正确,无串键 |
| 按键状态机 | 短按、长按、退出各走一遍 | 状态打印与预期一致,无卡死 |
| LoRa 回环 | 发固定字符串,对端打印 | 原样收到,连续发 20 帧不丢 |
| 业务帧收发 | 按键触发发送百分比 | 对端解析值与本地打印一致 |
| 连续模式 | 长按进入、S2 退出 | 进入后持续发,退出后立即停 |
这张表的价值在于把“感觉调通了”变成“逐项有证据”。考场上一旦某题卡住超过 15 分钟,先跳过做下一题,回头再查——省赛评分是按功能点给的,卡在一个点上丢掉后面容易的分是最不划算的失分方式。
最后说一个我自己的习惯:每次练完一套题,不管成没成,都把当次踩的坑记一行,写清“现象、定位过程、根因、改法”。练到第三套时你会发现翻来覆去就是那几类问题,考前翻一遍这页纸比翻代码管用。从那以后我每次进考场前都强制走一遍“拆评分点、锁易得分、逐项验证”的流程,心态稳很多。希望帮到你。
本文还有配套的精品资源,点击获取