2. dSPACE原型与产品代码的接口映射
任务定义
本案例解决dSPACE快速控制原型(RCP)向产品级ECU移植时最核心的接口适配问题:dSPACE原型中的I/O接口(ADC、DAC、数字I/O、PWM、CAN)与产品代码中的硬件抽象层(HAL)接口存在命名、数据类型和调用方式差异。目标是为一个典型电机控制应用建立明确的接口映射表,并实现一个可编译验证的C代码适配层,使同一套控制算法代码无需修改即可在两种环境下运行。
接口映射策略
采用分层设计:控制算法层只调用统一的自定义接口(如Ctrl_ReadMotorSpeed()),底层通过条件编译分别实现dSPACE版本和产品版本。映射关系如下表所示:
| 功能 | dSPACE原型接口 | 产品代码接口 | 数据类型转换 |
|---|---|---|---|
| 相电流采样 | DS1103_ADC_Read(1) | HAL_ADC_GetValue(ADC_CH1) | int16 → float(A) |
| PWM输出 | DS1103_PWM_SetDuty(0, 0.75) | HAL_PWM_SetDuty(PWM_CH0, 75) | float(0~1) → uint16(0~1000) |
| 数字输入 | DS1103_DIO_Read(2) | HAL_GPIO_ReadPin(GPIO_PIN2) | uint8 → bool |
| CAN发送 | DS1103_CAN_Send(1, &msg) | HAL_CAN_Transmit(CAN1, &msg) | ID格式统一 |
适配层实现
以下代码实现接口映射核心逻辑,包含条件编译、数据类型转换和必要的初始化。该代码可直接编译(在无硬件环境下使用模拟函数验证)。
// io_adapter.h
#ifndef IO_ADAPTER_H
#define IO_ADAPTER_H
#include <stdint.h>
#include <stdbool.h>
// 统一接口(供控制算法调用)
float Ctrl_ReadMotorSpeed(void); // 返回电机转速(RPM)
void Ctrl_SetPWMOutput(float duty); // 输入0.0~1.0
bool Ctrl_ReadSwitch(void); // 返回开关状态
void Ctrl_SendCANMsg(uint32_t id, uint8_t* data, uint8_t len);
// 编译目标选择:默认编译产品版本
#ifndef TARGET_DSPACE
#define TARGET_PRODUCT
#endif
#endif
// io_adapter.c
#include "io_adapter.h"
// ========== dSPACE原型实现 ==========
#ifdef TARGET_DSPACE
#include "ds1103_io.h" // dSPACE库头文件
float Ctrl_ReadMotorSpeed(void) {
int16_t raw = DS1103_ADC_Read(1); // 通道1读取
return (float)raw * 0.1f; // 转换: 1LSB=0.1RPM
}
void Ctrl_SetPWMOutput(float duty) {
if (duty < 0.0f) duty = 0.0f;
if (duty > 1.0f) duty = 1.0f;
DS1103_PWM_SetDuty(0, duty); // PWM通道0
}
bool Ctrl_ReadSwitch(void) {
return DS1103_DIO_Read(2) != 0; // 数字输入2
}
void Ctrl_SendCANMsg(uint32_t id, uint8_t* data, uint8_t len) {
CAN_MSG msg;
msg.id = id;
msg.len = len;
for (int i = 0; i < len; i++) msg.data[i] = data[i];
DS1103_CAN_Send(1, &msg); // CAN通道1
}
// ========== 产品代码实现 ==========
#else
#include "hal.h" // 产品MCU的HAL头文件
float Ctrl_ReadMotorSpeed(void) {
uint16_t raw = HAL_ADC_GetValue(ADC_CH1);
return (float)raw * 0.1f; // 与dSPACE相同的标定
}
void Ctrl_SetPWMOutput(float duty) {
if (duty < 0.0f) duty = 0.0f;
if (duty > 1.0f) duty = 1.0f;
uint16_t period = 1000; // PWM周期1000个计数
uint16_t compare = (uint16_t)(duty * period);
HAL_PWM_SetDuty(PWM_CH0, compare); // 占空比0~1000
}
bool Ctrl_ReadSwitch(void) {
return HAL_GPIO_ReadPin(GPIO_PIN2) == GPIO_HIGH;
}
void Ctrl_SendCANMsg(uint32_t id, uint8_t* data, uint8_t len) {
CAN_TX_MSG msg;
msg.id = id;
msg.dlc = len;
for (int i = 0; i < len; i++) msg.data[i] = data[i];
HAL_CAN_Transmit(CAN1, &msg);
}
#endif
验证方法
由于dSPACE和产品硬件不可同时获得,采用模拟验证策略:
- 编译验证:分别定义
TARGET_DSPACE和不定义该宏,确认两种配置下代码均可无错误编译(需提供对应头文件的桩实现)。 - 逻辑一致性测试:编写测试桩(stub)替代真实硬件函数,记录调用参数,验证适配层是否正确完成数据类型转换和范围限制。
// test_stub.c —— 产品模式测试桩
#include <stdio.h>
#include "io_adapter.h"
// 模拟HAL函数
uint16_t HAL_ADC_GetValue(int ch) { return 500; } // 对应50.0 RPM
void HAL_PWM_SetDuty(int ch, uint16_t val) {
printf("PWM_CH%d duty=%d/1000\n", ch, val);
}
int HAL_GPIO_ReadPin(int pin) { return 1; } // 高电平
void HAL_CAN_Transmit(int ch, void* msg) {
printf("CAN transmit on ch%d\n", ch);
}
int main() {
float speed = Ctrl_ReadMotorSpeed();
printf("Speed = %.1f RPM\n", speed); // 预期输出: Speed = 50.0 RPM
Ctrl_SetPWMOutput(0.75f); // 预期输出: PWM_CH0 duty=750/1000
bool sw = Ctrl_ReadSwitch();
printf("Switch = %d\n", sw); // 预期输出: Switch = 1
uint8_t data[2] = {0x11, 0x22};
Ctrl_SendCANMsg(0x123, data, 2); // 预期输出: CAN transmit on ch1
return 0;
}
判定标准:编译无错误;测试程序输出与注释中的预期值一致;切换TARGET_DSPACE宏后,适配层调用的是dSPACE库函数(可通过桩函数打印日志验证)。