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格式统一
控制算法层 Ctrl_ReadMotorSpeed() Ctrl_SetPWMOutput() Ctrl_ReadSwitch() Ctrl_SendCANMsg() (不依赖硬件) 适配层(本案例) #ifdef TARGET_DSPACE DS1103_ADC_Read() DS1103_PWM_SetDuty() #else HAL_ADC_GetValue() HAL_PWM_SetDuty() #endif 硬件驱动层 dSPACE: DS1103 库函数 产品: MCU HAL 驱动库 物理 硬件

适配层实现

以下代码实现接口映射核心逻辑,包含条件编译、数据类型转换和必要的初始化。该代码可直接编译(在无硬件环境下使用模拟函数验证)。

// 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和产品硬件不可同时获得,采用模拟验证策略:

  1. 编译验证:分别定义TARGET_DSPACE和不定义该宏,确认两种配置下代码均可无错误编译(需提供对应头文件的桩实现)。
  2. 逻辑一致性测试:编写测试桩(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库函数(可通过桩函数打印日志验证)。