news 2026/5/12 16:27:20

嵌入式开发-桥接模式:应用与驱动层解耦

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式开发-桥接模式:应用与驱动层解耦

文章目录

    • 概要
    • AD采样功能解耦示例
    • 优势对比

概要

针对大型项目开发,将应用层与驱动层分离,通过函数指针桥接。将硬件相关的寄存器操作封装在驱动层的功能函数,并创建结构体声明一系列函数指针作为“桥”,而上层应用在初始化时为这些结构体内的函数指针(桥)指向(注册)具体的驱动实现函数,后续只允许通过指针接口调用,实现解耦功能。使项目得以多方协作并行开发、且易于移植。

AD采样功能解耦示例

ADC_HAL.C驱动层具体实现

uint8GetTempSensorAdcCheckCmpltFlag(void){uint8 u8Tmp;if(ADIF==1){u8Tmp=1;}// ... 一系列硬件寄存器操作return(u8Tmp);}voidClrTempSensorAdcConvertCmpltFlag(void){ADIF=0U;/* clear INTAD interrupt flag */}uint8GetTempSensorRegAdcsFlag(void){uint8 u8Tmp;if(ADCS==1){u8Tmp=1;}else{u8Tmp=0;}return(u8Tmp);}voidTempSensorAdcSampChannelSelect(uint8 u8Chn){switch(u8Chn){caseU8_AD_CH0:ADS=0;///<选择AIN0break;// ... 一系列硬件寄存器操作default:break;}}voidTempSensorStartAdc(uint8 u8Chn){volatileuint16_tw_count;ADCEN=1U;/* supply AD clock */ADMK=1U;/* disable INTAD interrupt */// ... 一系列硬件寄存器操作}uint16GetTempSensorConvertResult(uint8 u8AdcBitSel){uint16 rsl=0;// ... 一系列硬件寄存器操作return(rsl);}

ADC_Mld.C 桥接抽象层(声明结构体:数据成员、函数指针)

typedefstruct{uint8 u8AdChn;// ... 数据成员 ...uint8 fgSensorErr;uint8 fgSampOk;uint16 u16SampleAD_Max;uint16 u16SampleAD_Min;uint16 u16SampAdcVal;uint8 u8SampCnt;uint16 u16SampAdcSum;uint16 u16AveSampAdcVar;// ... 数据成员 ...uint8(*GetAdcCheckCmpltFlag)(void);// 桥1:检查完成标志void(*ClrAdcConvertCmpltFlag)(void);// 桥2:清除标志uint8(*GetRegAdcsFlag)(void);// 桥3:void(*AdcSampChannelSelect)(uint8 u8Chn);// 桥4:选择采样通道void(*StartAdc)(uint8 u8Chn);// 桥5:启动转换uint16(*GetConvertResult)(uint8 u8AdcBitSel);// 桥6:读取结果}stHalAdcType;stHalAdcType stHalAdcSamp[U8_CFG_HAL_AD_NUM];///<私有变量,只在本模块内使用uint8 u8HalAdcSampTim;//AD采样时基

ADC_App.C 应用层

voidHalAdcInit(void)// 初始化阶段,将驱动层的具体实现函数,注册到应用层的接口结构体中{uint8 i;for(i=0;i<U8_CFG_HAL_AD_NUM;i++){stHalAdcSamp[i].GetAdcCheckCmpltFlag=GetTempSensorAdcCheckCmpltFlag;stHalAdcSamp[i].ClrAdcConvertCmpltFlag=ClrTempSensorAdcConvertCmpltFlag;stHalAdcSamp[i].GetRegAdcsFlag=GetTempSensorRegAdcsFlag;stHalAdcSamp[i].AdcSampChannelSelect=TempSensorAdcSampChannelSelect;stHalAdcSamp[i].StartAdc=TempSensorStartAdc;stHalAdcSamp[i].GetConvertResult=GetTempSensorConvertResult;}}voidHalAdcProc(void)//全程通过 stHalAdcSamp[u8AdChanel] 结构体的函数指针操作硬件:{staticuint8 u8AdChanel=0;staticuint8 u8Staus=0;uint8 u8Tmp;if(0!=GetTimeBase4ms()){// ... 一系列操作}switch(u8Staus){case0:{///<判断是否满足采样触发条件if(0!=GetAdcSampCond(u8AdChanel)){//通过 stHalAdcSamp[u8AdChanel] 结构体的函数指针操作硬件:stHalAdcSamp[u8AdChanel].StartAdc(u8AdChanel);u8Staus=1;}}break;case1:{///<通过 stHalAdcSamp[u8AdChanel] 函数指针,判断是否转换完成if(0!=stHalAdcSamp[u8AdChanel].GetAdcCheckCmpltFlag()){u8Staus=0;stHalAdcSamp[u8AdChanel].ClrAdcConvertCmpltFlag();stHalAdcSamp[u8AdChanel].u16SampAdcVal=stHalAdcSamp[u8AdChanel].GetConvertResult(U8_CFG_HAL_ADC_SAMP_BIT);u8Tmp=HalGetAdcAveValue(u8AdChanel);if(0!=u8Tmp){stHalAdcSamp[u8AdChanel].fgSampOk=1;///<采样完成......}}}break;default:{u8Staus=0;}break;}}

优势对比

传统方法:在应用层直接调用驱动函数痛点:
a. 不同驱动的函数命名风格各异(GetTempSensorConvertResult vs SPI_ADC_Read),换芯片平台,应用层到处改函数名。
b. 参数不统一:有的要通道号,有的要设备地址,把每个硬件的具体访问方式作为形参,暴漏在应用层。
c. 无法批量处理:不能写 for(i=0; i<3; i++) 循环读取,因为每个都是不同的函数。

解耦方法:
a. 易于Mock,可注册假函数注入测试数据进行单元测试。
b. 编写函数指针时,通过规定接口契约(参数类型和个数),调用不同驱动时,即使输入形参都是uint8,(有的是物理通道,有的是设备地址,有的是片选号),而函数指针桥接可以将这些差异封装在驱动内部。

// 应用层完全统一(实际配置在初始化时完成)voidApp_ReadTemps(void){for(uint8 i=0;i<3;i++){// 无论底层是配置的哪种ADC,调用方式完全一致!// 参数U8_CFG_HAL_ADC_SAMP_BIT是配置,不是硬件地址temp[i]=stHalAdcSamp[i].GetConvertResult(U8_ADC_BIT_10);}}

c. 应对产品经理需求变更CH1从ADC换成I2C-ADC,只需更改 HalAdcInit 中注册的回调函数

voidHalAdcInit(void){stHalAdcSamp[1].GetConvertResult=I2C_ADC_ReadWrapper;// 换绑定//.........}

代码中的实际体现:

typedefstruct{uint8 u8AdChn;// ← 这就是"私有数据",存物理通道号// ...uint16(*GetConvertResult)(uint8 u8AdcBitSel);// ↑ 参数只有"采样位数"这个纯逻辑配置,没有硬件地址!}stHalAdcType;

桥接模式:硬件地址封装在stHalAdcSamp[i].u8AdChn中,应用层只传"我要10位精度"这个逻辑需求。
更重要的是让硬件寻址细节下沉到初始化阶段,应用层只处理业务逻辑参数(如采样精度、触发模式),实现真正的解耦。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/19 23:12:15

同步初始化Redis库的艺术

在编写一个小型Redis库时,我们常常会遇到一个棘手的问题:如何确保类在实例化时,所有的初始化操作都已经完成,包括异步的文件读取和脚本加载。让我们来探讨一个优雅的解决方案。 问题描述 假设我们有一个Redis类,其构造函数需要读取Lua脚本并加载到Redis中: constructo…

作者头像 李华
网站建设 2026/4/16 13:00:00

C语言和C++语言最大的不同是什么?

C语言是在C语言的基础上构建成的&#xff0c;C这个名称寓意着C是对C的超越和扩展。 但是&#xff0c;C语言和C语言在设计哲学、编程范式和应用场景上存在着根本性的差异。 今天来讲讲这些差异&#xff0c;以期对程序员在开发项目时选择合适的工具起到一点帮助的作用。 一、C语言…

作者头像 李华
网站建设 2026/4/16 23:10:37

# 016、AutoSAR CP操作系统(OS)配置与任务调度:那个让我加班到凌晨三点的调度死锁

上周在联调ECU唤醒流程时,遇到一个诡异现象:系统唤醒后运行几分钟就卡死,仿真器显示所有任务都停在WaitEvent状态。抓了三天Trace才发现,是OS任务优先级配反了——高优先级任务等低优先级任务释放资源,低优先级任务又被中等优先级任务抢占,经典的优先级反转没处理好。今天…

作者头像 李华
网站建设 2026/4/17 21:34:41

KDMapper终极指南:Windows内核驱动手动映射完全解析

KDMapper终极指南&#xff1a;Windows内核驱动手动映射完全解析 【免费下载链接】kdmapper KDMapper is a simple tool that exploits iqvw64e.sys Intel driver to manually map non-signed drivers in memory 项目地址: https://gitcode.com/gh_mirrors/kd/kdmapper 还…

作者头像 李华
网站建设 2026/4/15 12:46:00

数据结构中逻辑结构和存储结构对应有哪些

逻辑结构&#xff08;数据之间的抽象关系&#xff09; 存储结构&#xff08;这些关系在计算机内存中的具体实现方式&#xff09; 数据结构一、逻辑结构&#xff08;完整分类&#xff09;注&#xff1a;集合结构有时单独列出&#xff0c;有时归入非线性结构。类别子类型典型例…

作者头像 李华