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

张开发
2026/6/10 2:53:54 15 分钟阅读
嵌入式开发-桥接模式:应用与驱动层解耦
文章目录概要AD采样功能解耦示例优势对比概要针对大型项目开发将应用层与驱动层分离通过函数指针桥接。将硬件相关的寄存器操作封装在驱动层的功能函数并创建结构体声明一系列函数指针作为“桥”而上层应用在初始化时为这些结构体内的函数指针桥指向注册具体的驱动实现函数后续只允许通过指针接口调用实现解耦功能。使项目得以多方协作并行开发、且易于移植。AD采样功能解耦示例ADC_HAL.C驱动层具体实现uint8GetTempSensorAdcCheckCmpltFlag(void){uint8 u8Tmp;if(ADIF1){u8Tmp1;}// ... 一系列硬件寄存器操作return(u8Tmp);}voidClrTempSensorAdcConvertCmpltFlag(void){ADIF0U;/* clear INTAD interrupt flag */}uint8GetTempSensorRegAdcsFlag(void){uint8 u8Tmp;if(ADCS1){u8Tmp1;}else{u8Tmp0;}return(u8Tmp);}voidTempSensorAdcSampChannelSelect(uint8 u8Chn){switch(u8Chn){caseU8_AD_CH0:ADS0;///选择AIN0break;// ... 一系列硬件寄存器操作default:break;}}voidTempSensorStartAdc(uint8 u8Chn){volatileuint16_tw_count;ADCEN1U;/* supply AD clock */ADMK1U;/* disable INTAD interrupt */// ... 一系列硬件寄存器操作}uint16GetTempSensorConvertResult(uint8 u8AdcBitSel){uint16 rsl0;// ... 一系列硬件寄存器操作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);// 桥3void(*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(i0;iU8_CFG_HAL_AD_NUM;i){stHalAdcSamp[i].GetAdcCheckCmpltFlagGetTempSensorAdcCheckCmpltFlag;stHalAdcSamp[i].ClrAdcConvertCmpltFlagClrTempSensorAdcConvertCmpltFlag;stHalAdcSamp[i].GetRegAdcsFlagGetTempSensorRegAdcsFlag;stHalAdcSamp[i].AdcSampChannelSelectTempSensorAdcSampChannelSelect;stHalAdcSamp[i].StartAdcTempSensorStartAdc;stHalAdcSamp[i].GetConvertResultGetTempSensorConvertResult;}}voidHalAdcProc(void)//全程通过 stHalAdcSamp[u8AdChanel] 结构体的函数指针操作硬件{staticuint8 u8AdChanel0;staticuint8 u8Staus0;uint8 u8Tmp;if(0!GetTimeBase4ms()){// ... 一系列操作}switch(u8Staus){case0:{///判断是否满足采样触发条件if(0!GetAdcSampCond(u8AdChanel)){//通过 stHalAdcSamp[u8AdChanel] 结构体的函数指针操作硬件stHalAdcSamp[u8AdChanel].StartAdc(u8AdChanel);u8Staus1;}}break;case1:{///通过 stHalAdcSamp[u8AdChanel] 函数指针,判断是否转换完成if(0!stHalAdcSamp[u8AdChanel].GetAdcCheckCmpltFlag()){u8Staus0;stHalAdcSamp[u8AdChanel].ClrAdcConvertCmpltFlag();stHalAdcSamp[u8AdChanel].u16SampAdcValstHalAdcSamp[u8AdChanel].GetConvertResult(U8_CFG_HAL_ADC_SAMP_BIT);u8TmpHalGetAdcAveValue(u8AdChanel);if(0!u8Tmp){stHalAdcSamp[u8AdChanel].fgSampOk1;///采样完成......}}}break;default:{u8Staus0;}break;}}优势对比传统方法在应用层直接调用驱动函数痛点a. 不同驱动的函数命名风格各异GetTempSensorConvertResult vs SPI_ADC_Read,换芯片平台应用层到处改函数名。b. 参数不统一有的要通道号有的要设备地址把每个硬件的具体访问方式作为形参暴漏在应用层。c. 无法批量处理不能写 for(i0; i3; i) 循环读取因为每个都是不同的函数。解耦方法a. 易于Mock,可注册假函数注入测试数据进行单元测试。b. 编写函数指针时通过规定接口契约参数类型和个数调用不同驱动时即使输入形参都是uint8有的是物理通道有的是设备地址有的是片选号而函数指针桥接可以将这些差异封装在驱动内部。// 应用层完全统一实际配置在初始化时完成voidApp_ReadTemps(void){for(uint8 i0;i3;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].GetConvertResultI2C_ADC_ReadWrapper;// 换绑定//.........}代码中的实际体现typedefstruct{uint8 u8AdChn;// ← 这就是私有数据存物理通道号// ...uint16(*GetConvertResult)(uint8 u8AdcBitSel);// ↑ 参数只有采样位数这个纯逻辑配置没有硬件地址}stHalAdcType;桥接模式硬件地址封装在stHalAdcSamp[i].u8AdChn中应用层只传我要10位精度这个逻辑需求。更重要的是让硬件寻址细节下沉到初始化阶段应用层只处理业务逻辑参数如采样精度、触发模式实现真正的解耦。

更多文章