Price Prediction

Building a Factory Test Framework for ECU Low-Level APIs in CAN-Based HIL Systems

Modern automotive systems rely on complex Electronic Control Units (ECUs) to manage a range of functionalities. Ensuring the reliability and correctness of these systems requires robust testing strategies at the firmware level, especially for low-level drivers. This article explains how to build a Factory Test Code (FTC) framework to validate low-level software APIs in a Controller Area Network (CAN) environment, with a focus on Hardware-in-the-Loop (HIL) systems.

We’ll walk through practical C code examples, explore how to structure CAN communication for get/set operations, and explain how to simulate memory operations and diagnostics. While real ECUs use confidential modules, we’ll use example modules like PWM Controller, Frequency Modulator, and Motor Driver to demonstrate key ideas.


1. ECU and HIL Testing Overview

ECUs serve as the electronic brains of subsystems like power steering, engine management, or electronic braking. Each ECU interfaces with sensors and actuators through drivers implemented in low-level firmware. Testing these drivers ensures hardware-software integration is sound.

HIL (Hardware-in-the-Loop) testing introduces real-time simulation into the development cycle, letting developers test ECUs against virtual environments before physical deployment. The FTC allows automated and targeted testing of driver-level functions using CAN messages.


2. Example Modules

For demonstration, we’ll simulate the following modules:

  • PWM Controller – Controls Pulse Width Modulation outputs.
  • Frequency Modulator – Adjusts signal frequency.
  • Motor Driver – Simulates a motor interface with direction and speed control.

Each module provides APIs like:

  • set_pwm(channel, duty_cycle)
  • get_pwm(channel)
  • set_freq(channel, freq)
  • get_freq(channel)
  • read_diag(channel)

3. CAN Protocol Design for FTC

To test these modules, we design a CAN protocol with dedicated message IDs for different operations:

Operation

CAN ID (Hex)

SET_PWM

0x101

GET_PWM

0x102

SET_FREQ

0x103

GET_FREQ

0x104

READ_DIAG

0x105

READ_MEM

0x106

WRITE_MEM

0x107

We also use separate CAN IDs for TX and RX:

  • TX frame: Sent from the test host to the ECU.
  • RX frame: Response from the ECU.

Each CAN frame includes:

  • Byte 0: Command ID
  • Byte 1: Channel ID
  • Bytes 2–5: Payload (if any)
  • Byte 6–7: CRC or reserved

4. Low-Level Driver APIs in C

Let’s define simple driver interfaces.

// pwm_controller.h
#ifndef PWM_CONTROLLER_H
#define PWM_CONTROLLER_H

void set_pwm(uint8_t channel, uint8_t duty_cycle);
uint8_t get_pwm(uint8_t channel);

#endif
// pwm_controller.c
#include "pwm_controller.h"

static uint8_t pwm_duty[8];

void set_pwm(uint8_t channel, uint8_t duty_cycle) {
    if (channel < 8) pwm_duty[channel] = duty_cycle;
}

uint8_t get_pwm(uint8_t channel) {
    return (channel < 8) ? pwm_duty[channel] : 0;
}
// frequency_modulator.h
#ifndef FREQ_MODULATOR_H
#define FREQ_MODULATOR_H

void set_freq(uint8_t channel, uint16_t freq);
uint16_t get_freq(uint8_t channel);

#endif
// frequency_modulator.c
#include "frequency_modulator.h"

static uint16_t freq_val[8];

void set_freq(uint8_t channel, uint16_t freq) {
    if (channel < 8) freq_val[channel] = freq;
}

uint16_t get_freq(uint8_t channel) {
    return (channel < 8) ? freq_val[channel] : 0;
}

5. Implementing the Factory Test Code (FTC)

The FTC parses incoming CAN messages, invokes the corresponding API, and replies with results.

// ftc_handler.c
#include "pwm_controller.h"
#include "frequency_modulator.h"
#include "can.h"

void process_can_frame(CanMessage *msg) {
    uint8_t command = msg->data[0];
    uint8_t channel = msg->data[1];
    uint32_t value = (msg->data[2] << 24) | (msg->data[3] << 16) |
                     (msg->data[4] << 8)  | msg->data[5];

    CanMessage tx_msg = {0};
    tx_msg.id = msg->id + 0x80; // response ID

    switch (command) {
        case 0x01: // SET_PWM
            set_pwm(channel, (uint8_t)value);
            break;
        case 0x02: // GET_PWM
            tx_msg.data[0] = get_pwm(channel);
            break;
        case 0x03: // SET_FREQ
            set_freq(channel, (uint16_t)value);
            break;
        case 0x04: // GET_FREQ
            {
                uint16_t freq = get_freq(channel);
                tx_msg.data[0] = (freq >> 8) & 0xFF;
                tx_msg.data[1] = freq & 0xFF;
            }
            break;
        case 0x05: // READ_DIAG (mocked)
            tx_msg.data[0] = 0xAA; // example diag result
            break;
        default:
            tx_msg.data[0] = 0xFF; // unknown command
            break;
    }

    send_can_message(&tx_msg);
}

This handler supports dynamic testing through CAN tools or Python scripts.


6. CAN Communication Infrastructure

Assume a simple CAN abstraction:

// can.h
#ifndef CAN_H
#define CAN_H

typedef struct {
    uint32_t id;
    uint8_t data[8];
    uint8_t dlc;
} CanMessage;

void send_can_message(CanMessage *msg);
void receive_can_message(CanMessage *msg);

#endif

7. HIL Testing Flow

  1. Load firmware with FTC to ECU in HIL bench
  2. Connect CAN interface (PCAN, Vector, or NI-CAN)
  3. Send test cases via CAN using Python or CAPL scripts
  4. Observe responses and log pass/fail

Example Python test case:

import can

bus = can.interface.Bus(channel='can0', bustype='socketcan')
msg = can.Message(arbitration_id=0x101, data=[0x01, 0x02, 0x32, 0, 0, 0, 0, 0], is_extended_id=False)
bus.send(msg)

8. Memory Read/Write Simulation

Use dedicated commands to simulate reading and writing to internal memory:

uint8_t memory_space[256];

void handle_mem_commands(uint8_t command, uint8_t addr, uint8_t data, CanMessage *tx_msg) {
    switch (command) {
        case 0x06: // READ_MEM
            tx_msg->data[0] = memory_space[addr];
            break;
        case 0x07: // WRITE_MEM
            memory_space[addr] = data;
            break;
    }
}

9. Debugging and Troubleshooting Tips

  • CAN Sniffers: Use tools like Vector CANoe, PCAN-View, or Savvy CAN to monitor traffic.
  • Logging: Include serial print statements for each command processed.
  • CRC/Checksums: Add byte-level CRC to detect corrupt messages.
  • Watchdogs: Reset system if message processing hangs.
  • Loopback Tests: Verify CAN transceiver by echoing sent messages.

10. Conclusion

The Factory Test Code framework lets you validate low-level driver APIs through a CAN-based interface. This is especially useful in HIL environments where repeated automated testing is essential. With proper abstraction, the same FTC setup can support different modules and evolving requirements.

By structuring your CAN messages clearly, building driver-level API wrappers, and maintaining a test-driven approach, you can speed up validation and reduce firmware defects.

Let this framework serve as a starting point for building more advanced test harnesses for embedded automotive software.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button