当前位置: 首页 > news >正文

【模块化设计-12】ADC 数据采集与队列模块的模块化编程 “踩坑” 与优化

文章关键字

模块化编程、ADC 数据采集、队列模块、嵌入式开发、代码重构、C 语言编程、数据滤波

文章摘要

本文以嵌入式项目中 ADC 数据采集结合队列处理的实际代码为例,深度吐槽现有实现中模块化设计的典型问题:队列模块接口语义混乱、ADC 采集逻辑与业务强耦合、初始化流程缺失、数据处理鲁棒性不足等;同时结合模块化编程的核心思想,给出针对性的重构思路与实现方案,帮助嵌入式开发者规避 “能用但难维护” 的代码陷阱,提升代码的可复用性、可读性与可维护性。


作为嵌入式开发者,我们常说 “模块化编程是嵌入式开发的灵魂”,但实际项目中,总有一些代码 “看似模块化,实则一团糟”。最近接手一个 ADC 数据采集 + 队列滤波的项目代码,看完直接蚌埠住了 —— 明明是基础的 ADC 采集 + 队列处理场景,却把模块化编程的 “坑” 踩了个遍。今天就借着这个案例,一边吐槽问题,一边聊真正的模块化编程该怎么做。

一、先看 “槽点拉满” 的原始实现

先交代背景:这段代码的核心需求是采集 ADC 光照数据,通过队列做简单滤波,同时读取 GPIO 状态并记录状态变化时间。但从代码实现来看,处处都是模块化设计的 “反面教材”。

槽点 1:队列模块 —— 语义混乱 + 接口 “名不副实”,复用性为 0

队列(queue.c/queue.h)是嵌入式最基础的通用模块,结果这段代码把 “通用模块” 写成了 “一次性模块”:

  • 接口语义完全错位IsQueue16函数注释写着 “出队”,实际功能是判断队列是否满(返回 0 表示满、1 表示非满),命名和注释的错位,直接让后续开发者 “猜谜”;
  • 边界处理逻辑矛盾EnQueue16中出现if (q->rear == q->maxsize) { q->rear = q->maxsize; },这行代码完全是无效逻辑(环形队列的 rear 本身是取模的,不可能等于 maxsize),既暴露了开发者对环形队列的理解漏洞,也让队列模块的可靠性大打折扣;
  • 通用模块 “硬编码”+ 无初始化关联:队列模块头文件没有定义通用的队列大小宏,却在 queue.c 中硬编码#define MAXQSIZE 5;更关键的是,poll.c 中定义了RtFilter1Sql[RT_MAX_CHX]队列结构体和CapBuf缓存数组,但从未调用InitQueue16初始化队列—— 队列的 base、maxsize 等核心参数都是随机值,滤波逻辑从根上就不可靠。
槽点 2:ADC 采集模块 —— 采集与业务强耦合,无复用性

ADC 采集逻辑(poll.c 中的ADC_Get_Value_Average)本应是通用的硬件驱动层功能,结果:

  • 采集参数 “写死”:采集次数(20 次)、无效数据跳过数(前 2 次)、极值剔除后的计算方式(右移 4 位,等价于除以 16)全部硬编码在函数内,若要修改采集次数、调整滤波规则,必须改函数源码,完全违背 “开闭原则”;
  • 硬件依赖直接暴露:函数内直接调用drv_adc_read(chx),但没有对 chx 做合法性校验(比如超过 ADC 最大通道数),一旦传错参数直接导致程序异常;
  • 采集与业务逻辑混在一起poll_scan中把 ADC 采集结果直接换算为光照值(rtdata*2000/4095),采集层和业务层的耦合,让 ADC 采集函数无法复用到其他通道(比如温度采集)。
槽点 3:poll 模块 —— 初始化 “摆烂”+ 逻辑残缺,稳定性堪忧

poll.c 作为业务层模块,把 “模块化编程” 的 “内聚性” 抛之脑后:

  • 初始化流程缺失关键步骤poll_init只做了 ADC 使能和定时器设置,却完全漏掉了队列的初始化(InitQueue16),导致RtFilter1Filter中的队列操作都是基于未初始化的野指针 / 随机值,滤波功能等于 “薛定谔的滤波”;
  • 逻辑残缺 + 无效代码g_ctrl_l2_time > 24*60*60判断后无任何处理逻辑,既没有注释说明设计意图,也没有实际动作,属于典型的 “写了一半忘了改”;
  • 全局变量泛滥g_LightValg_Ctrl_l2g_ctrl_l2_time等全局变量直接暴露,无封装、无访问控制,后续若要扩展多通道、多实例,全局变量会直接导致数据冲突。

二、模块化编程的正确打开方式:重构核心思路

吐槽归吐槽,核心是解决问题。针对上述槽点,我们以 “高内聚、低耦合、可复用” 为核心,重构这两个模块:

1. 队列模块:回归 “通用属性”,标准化接口
  • 重构接口语义
    • 重命名IsQueue16IsQueue16Full,明确功能是判断队列是否满;新增IsQueue16Empty判断队列是否空,接口语义一目了然;
    • 移除EnQueue16中的无效逻辑,统一环形队列的边界处理规则;
  • 通用化设计
    • 在 queue.h 中暴露MAXQSIZE配置宏,支持外部自定义;
    • 队列初始化函数增加返回值,校验入参合法性(比如 buff 是否为空、maxsize 是否合理),避免野指针操作;
// 重构后的EnQueue16示例 u8_t EnQueue16(sqqueue16* q, u16_t e) { if (q == NULL || q->base == NULL) return 0; // 入参校验 if (IsQueue16Full(q)) return 0; // 队列满则入队失败 q->base[q->rear] = e; q->rear = (q->rear + 1) % q->maxsize; return 1; } // 新增IsQueue16Empty,语义清晰 u8_t IsQueue16Empty(sqqueue16* q) { if (q == NULL) return 1; return (q->front == q->rear) ? 1 : 0; }
2. ADC 采集模块:解耦硬件与业务,参数可配置
  • 封装采集参数:定义 ADC 采集配置结构体,支持外部传入采集次数、无效样本数、极值剔除开关等参数;
  • 分层设计:驱动层(drv_adc)只负责底层寄存器操作,采集层(adc_collect)封装滤波、平均逻辑,业务层(poll)只负责数据换算和业务判断;
// ADC采集配置结构体(adc_collect.h) typedef struct { u8_t chx; // ADC通道 u16_t collect_cnt; // 总采集次数 u8_t skip_cnt; // 跳过前N次无效样本 u8_t extrema_en; // 是否剔除极值 } ADC_Collect_Config_t; // 通用ADC采集函数 u16_t adc_collect_average(ADC_Collect_Config_t *config) { // 入参校验 if (config == NULL || config->chx >= ADC_MAX_CHX || config->collect_cnt <= config->skip_cnt) { return 0; } // 采集逻辑(复用原有极值剔除,但参数可配置) u32_t tmpBuff = 0; u16_t AdcMAX = 0, AdcMIN = 0xffff; u16_t temp_value = 0; for (u8_t i = 0; i < config->collect_cnt; i++) { temp_value = drv_adc_read(config->chx); if (i < config->skip_cnt) continue; if (config->extrema_en) { AdcMAX = temp_value > AdcMAX ? temp_value : AdcMAX; AdcMIN = temp_value < AdcMIN ? temp_value : AdcMIN; } tmpBuff += temp_value; } // 极值剔除计算(按需) if (config->extrema_en) { tmpBuff -= (AdcMAX + AdcMIN); tmpBuff /= (config->collect_cnt - config->skip_cnt - 2); } else { tmpBuff /= (config->collect_cnt - config->skip_cnt); } return (u16_t)tmpBuff; }
3. poll 模块:补全初始化 + 封装全局变量,提升内聚性
  • 补全初始化流程:在poll_init中调用InitQueue16初始化队列,关联 CapBuf 缓存数组,让队列操作有合法的内存空间;
  • 封装全局变量:把全局变量封装为静态结构体,提供专用的访问接口,避免直接暴露;
  • 完善逻辑闭环:对g_ctrl_l2_time的超时判断补充实际处理逻辑,或注释说明设计意图;
// poll.c中封装全局变量 typedef struct { u16_t light_val; u16_t ctrl_l2; sys_tick_t ctrl_l2_time; } Poll_Data_t; static Poll_Data_t s_poll_data = {0}; static sqqueue16 s_filter_queue[RT_MAX_CHX] = {0}; // 队列实例 static u16_t s_cap_buf[RT_MAX_CHX][10] = {0}; // 队列缓存 // 补全初始化 void poll_init(void) { drv_adc_enable(); // 初始化队列(关键!) for (u8_t i = 0; i < RT_MAX_CHX; i++) { InitQueue16(&s_filter_queue[i], s_cap_buf[i], 10); } sys_timeout_set(poll_timer, 1000); } // 封装访问接口,替代直接读取全局变量 u16_t rt_parm_read(u8_t eid) { u16_t value = 0; switch(eid) { case RT_LIGHT_VAL: value = s_poll_data.light_val; break; case RT_IS_ANYBODY: value = s_poll_data.ctrl_l2; break; default: value = 0; // 非法ID返回默认值,避免随机值 break; } return value; }

三、模块化编程的核心:别让 “能用” 代替 “好用”

回到这个案例,最核心的问题不是 “代码能不能跑”,而是 “代码能不能维护、能不能复用”。嵌入式开发中,模块化编程的核心无非三点:

  1. 单一职责:一个模块只做一件事(队列模块只负责队列操作,ADC 采集只负责数据采集,业务模块只负责业务逻辑);
  2. 接口清晰:模块对外只暴露必要的接口,接口名、注释、功能必须一致,入参出参做合法性校验;
  3. 可配置化:避免硬编码,通过宏、结构体让模块参数可配置,提升复用性。

反观案例中的代码,正是违背了这三点:队列模块既做队列操作又藏语义陷阱,ADC 采集既做硬件读取又做业务换算,业务模块既做扫描又漏初始化 —— 最终导致代码 “能用但难用,可跑但难维护”。

写在最后:嵌入式代码的 “优雅”,从来不是用了多高级的语法,而是把基础模块做扎实、做通用,让后续开发者不用猜、不用改、直接用。希望这篇 “吐槽 + 重构” 的分享,能让你避开模块化编程的那些坑。

http://www.jsqmd.com/news/824160/

相关文章:

  • 基于ETL助睿平台的订单利润分流数据加工
  • AI技能库:结构化指令集提升智能体工作效率与一致性
  • 从零构建生产级AI助手:OpenClaw配置实战与自动化工作流指南
  • 基于Dify与微信的智能聊天机器人:从原理到部署实战
  • Rust跨平台像素级屏幕控制库mcpixy:自动化测试与RPA开发利器
  • Parabolic:简单高效的免费视频下载工具,yt-dlp图形界面终极方案
  • 第11章:C++ PGO与LTO优化
  • 条件查询-2
  • 终极二维码修复指南:如何用QrazyBox轻松恢复损坏的QR码数据
  • 联盟营销管理系统有哪些?如何选择?
  • Grid++Report设计器避坑指南:搞不定自动换行和字体缩小?看这篇就够了
  • WPF文本框进阶:打造优雅输入提示的三种实现策略
  • 告别臃肿!Dell G15散热控制开源替代方案全解析
  • 开源BaaS平台Nhost实战:基于PostgreSQL与GraphQL的Firebase替代方案
  • 从0到99.2%准确率:DeepSeek MATH竞赛测试通关路径图(含3个被忽略的归一化预处理陷阱)
  • QKeyMapper:Windows平台全能按键映射神器,游戏办公两不误
  • Qt网络调试助手实战指南:TCP/UDP调试与文件传输解决方案
  • 程序员该不该先去猪场接触业务
  • 基于模板匹配的自动化脚本开发:从原理到实战
  • AI编程技能库:用Scribe构建可复用的智能开发工作流
  • 3PEAK思瑞浦 TPA1811-SO1R SOP8 运算放大器
  • 为内部知识库问答系统集成Taotoken的多模型聚合能力
  • Obsidian Importer终极指南:如何一键迁移你的全部笔记到Obsidian知识库
  • 收藏!小白程序员必备:AI大模型时代,如何实现薪资翻倍?
  • 基于MicroPython的嵌入式射击计时器开发实战:从状态机到人机交互
  • CSS+JS实现鼠标跟随粒子爆炸特效:原理、集成与性能优化
  • AM243x多核MCU启动流程解析与OSPI Flash烧录实战
  • 从单仓到多租户GitOps:DeepSeek支撑200+业务线的分层仓库架构(含Git Submodule+OCI Registry双模设计图)
  • 2026年4月服务好的涂胶机公司推荐,单双向预浸机设备/碳纤维预浸料设备/碳纤维预浸料/涂膜机/涂胶机,涂胶机厂商推荐 - 品牌推荐师
  • PNG转Windows鼠标指针:开源工具png-to-cursor全解析