CAN Bus Adapter
The CAN Bus Adapter is a collection of tools that lets you develop and integrate tests and simulations involving communication over a CAN bus within the miniHIL platform.
The set of tools includes:
-
Runtime and model support libraries
-
Easy sending and receiving can frames to the physical can bus from any eTrice actor
-
Supports Remote Transmission Requests (RTR)
-
Supports Extended CAN Frames
-
-
An DBC-to-eTrice model generator
-
Automatically creates ROOM ActorClasses that support automatic encoding, decoding, packing and unpacking of can frame signals
-
The CAN generator is part of the gradle build. It is part of the normal assemble process. If needed the generator can also be invoked separately: .\gradlew.bat generateDBCConf. The gradle task generates room models for all files with .dbcconf extension in the model/model-user folder in the miniHilProject as well as in all configured library projects.
CAN HAL
Currently, two separate CAN instances with a pair of physical pins on the MiniHiL board each, are available.
|
The sample point is currently fixed at 75%. If this leads to problems with high CAN-FD bitrates talk to us. |
Structure
Structure and dependencies of the can actors
How to use the HAL
-
Connect your DuT to the MiniHiL board. For blue miniHIL boards (v2.3 and higher) have a look at the pinout below. For older boards the following warning is important.
|
On old (pre V2.3, green) miniHIL boards the pinout of the CAN SUB-D connector is mirrored. I.e. the following pins are swapped: 1 <→ 5, 2 <→ 4, 3 <→ 3, 6 <→ 9, 7 <→ 8. |
-
Import the CAN actor.
import can.api.CANService.*
import can.platform.Stm32CANService.*
-
Create an instance of either AStm32CANService1 or AStm32CANService2 (or both) and connect the ports.
ActorRef can1: AStm32CANService1
ActorRef can2: AStm32CANService2
-
Initialize the CAN actor by sending an open(DCANParams) message to the CAN service ctl port. Make sure both nominal and data bitrate match with the DuT.
DCanParams params = {
.baudRate_kbs = 40,
.baudRate_kbs_fd = 125
};
// CANctl bound to ctl port of CAN service actor
CANctl.open(¶ms);
|
Both baudRate_kbs and baudRate_kbs_fd params must be set to valid values. Otherwise the initialization of the CAN service will fail and it will respond to the open request with a ctl.errorOpen notification message. |
-
Wait until you receive the opened() message in response.
-
You are now able to send and receive CAN messages through the CANData port.
CANMessage msg;
msg.id = 0x00;
msg.isExtendedFrame = false;
//Length has to be a value of {1,2,3,4,5,6,7,8,12,16,20,24,32}
msg.length = 16;
msg.data[0] = 0x00;
msg.data[1] = 0x01;
//...
msg.data[15] = 0x0f;
msg.isRemoteFrame = false;
msg.isBitRateSwitched = true;
msg.isFDFrame = true;
CANdata.send(&msg);
Example Application (HAL only)
This Application will initialize CAN and send one Message per second while waiting for incoming Messages.
RoomModel MiniHilProject {
import can.api.CANService.*
import can.platform.Stm32CANService.*
import etrice.api.timer.PTimer
ActorClass Application {
Structure {
conjugated Port CANctl: PCANCtrl
conjugated Port CANdata: PCANData
ActorRef can1: AStm32CANService1
ActorRef can2: AStm32CANService2
Binding CANdata and can1.bus
Binding CANctl and can1.ctl
SAP timer: PTimer
}
Behavior {
StateMachine {
State Active {
}
State Initializing
State FatalError
Transition init0: initial -> Initializing {
action '''
//initialize CAN with Nominal Bitrate = 40kb and Data Bitrate = 125kb
DCanParams params = {
.baudRate_kbs = 40,
.baudRate_kbs_fd = 125
};
CANctl.open(¶ms);
'''
}
Transition tr0: Initializing -> Active {
triggers {
<opened: CANctl>
}
action '''
timer.startTimer(1000);
'''
}
Transition tr1: Initializing -> FatalError {
triggers {
<errorOpen: CANctl>
}
action '''
etLogger_logError("Failed to initialize CANServiceTest!");
'''
}
Transition tr2: Active -> Active {
triggers {
<received: CANdata>
}
action '''
// received CAN Message
uint8 * d = transitionData->data;
'''
}
Transition tr3: Active -> Active {
triggers {
<errorRecv: CANctl>
}
action '''// received incorrect CAN Message'''
}
Transition tr4: Active -> Active {
triggers {
<errorSend: CANctl>
}
action '''// sending CAN Message failed'''
}
Transition tr5: Active -> Active {
triggers {
<timeout: timer>
}
action '''
// send CAN Message on timer timeout
CANMessage msg;
msg.id = 0x00;
msg.isExtendedFrame = 0;
//Length has to be a value of {1,2,3,4,5,6,7,8,12,16,20,24,32}
msg.length = 16;
msg.data[0] = 0x00;
msg.data[1] = 0x01;
//...
msg.data[15] = 0x0f;
msg.isRemoteFrame = 0;
msg.isBitRateSwitched = true;
msg.isFDFrame = true;
CANdata.send(&msg);
'''
}
}
}
}
}
DBC Generator - Signal-Level CAN Abstraction
While the CAN HAL (described above) works at the raw frame level, the DBC generator provides a signal-level abstraction. It reads a standard .dbc file and automatically generates eTrice ROOM actors that handle encoding, decoding, packing and unpacking of CAN signals.
This means you can send and receive individual signals (e.g. temperature, sw_version) without manually constructing CAN frames.
DBC File Format
A DBC file (.dbc) is a common format for describing CAN bus networks. It defines:
-
Nodes (BU_): ECUs on the bus
-
Messages (BO_): CAN frames with ID, length, and transmitting node
-
Signals (SG_): Data fields within messages with bit position, length, byte order, factor, offset, and receiving nodes
|
Example DBC file (complete, valid minimal DBC)
The following example is a complete DBC that can be loaded directly into DBC editors/tools. It is equivalent to a file exported with Vector CANdb++. |
VERSION ""
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
CAT_DEF_
CAT_
FILTER
BA_DEF_DEF_
EV_DATA_
ENVVAR_DATA_
SGTYPE_
SGTYPE_VAL_
BA_DEF_SGTYPE_
BA_SGTYPE_
SIG_TYPE_REF_
VAL_TABLE_
SIG_GROUP_
SIG_VALTYPE_
SIGTYPE_VALTYPE_
BO_TX_BU_
BA_DEF_REL_
BA_REL_
BA_DEF_DEF_REL_
BU_SG_REL_
BU_EV_REL_
BU_BO_REL_
SG_MUL_VAL_
BS_:
BU_: testbench ecu
BO_ 256 ecu_tx1: 3 ecu
SG_ sw_version : 0|8@1+ (1,0) [0|255] "" Vector__XXX
SG_ temp1 : 16|8@1+ (1,-25) [-25|215] "deg C" testbench
BO_ 250 ecu_tx2: 8 ecu
SG_ err_bool : 0|1@1+ (1,0) [0|1] "" testbench
SG_ float_val : 8|8@1- (0.125,0) [-5|26] "floats" testbench
BO_ 100 ecu_rx1: 2 testbench
SG_ temp_min : 0|8@1+ (1,-25) [-25|215] "deg C" ecu
SG_ temp_max : 8|8@1+ (1,-25) [-25|215] "deg C" ecu
Signal format 16|8@1+ (1,-25) [-25|215]:
-
16|8- start bit 16, length 8 bits -
@1+- little-endian (@1), unsigned (+); use@1-for signed -
(1,-25)- factor 1, offset -25 → physical value = raw * factor + offset -
[-25|215]- valid range min|max
Place the .dbc file in MiniHilProject/model-user/ (e.g. in a can/ subfolder).
DBC Configuration File (.dbcconf)
The .dbcconf file tells the code generator what to produce from a DBC file. It sits next to the .dbc file (e.g. in model-user/can/).
A complete .dbcconf file structure:
generate Example from "example.dbc" {
adapter ecu {
nodes = ecu
}
adapter testbench {
nodes = testbench
}
adapter FullBus {
nodes = ecu, testbench
}
portmapper ecu {
adapter = ecu
generateContainer {
canservice = can.platform.Stm32CANService.AStm32CANService1
}
}
}
generate block (top level)
The generate block wraps all adapters and portmappers:
generate Example from "example.dbc" {
// adapters and portmappers go here
}
-
Example- a name you choose. It becomes part of the generated file names (e.g.CANAdapters_Example.room). -
"example.dbc"- the DBC file to read (path relative to this.dbcconffile).
adapter - selects signals to send and receive over the CAN bus
The adapter block generates adapter ActorClasses in the ROOM model that represent one node or a group of nodes from the DBC file. The nodes property picks one or more ECU nodes from the DBC (BU_: line). The adapters act as proxies for this set of nodes on the actual CAN bus, forwarding messages and signals to and from the bus.
Given this DBC content:
BU_: testbench ecu BO_ 256 ecu_tx1: 3 ecu ← sent by node "ecu" SG_ temp1 ... SG_ sw_version ... BO_ 250 ecu_tx2: 8 ecu ← sent by node "ecu" SG_ err_bool ... SG_ float_val ... BO_ 100 ecu_rx1: 2 testbench ← sent by node "testbench" SG_ temp_min ... SG_ temp_max ...
The generator creates the following DUT adapters:
| Adapter | TX Messages | RX Messages |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
(none) |
|
|
|
(none) |
|
With a When all nodes are included in a single adapter declaration:
|
|
Although SG_sections can also define receiver nodes, this information is currently ignored by the generator. The generator only checks sender nodes at the message level when determining which direction the corresponding adapter port takes. |
portmapper - consolidates all signals into one port
A portmapper takes one adapter and flattens all its signals into a single eTrice protocol. Instead of wiring one port per CAN message, you get one port with all signals as direct messages.
portmapper ecu {
adapter = ecu
generateContainer {
canservice = can.platform.Stm32CANService.AStm32CANService1
}
}
-
adapter = ecu- which adapter to consolidate (must be defined above). -
generateContainer- generates a ready-to-use container actor (Aecu_SinglePortCANContainerPlant) that internally wires the CAN hardware service, the adapter, and the portmapper together. You only need to instantiate this single actor in your project. -
canservice = …- the platform-specific CAN service actor class to use inside the container. (AStm32CANService1 or AStm32CANService2 for the miniHIL board).
What the portmapper generates:
-
Pecu_SinglePortProtocol- a protocol where each DBC signal is an eTrice message:-
temp1(int32),float_val(float32),temp_min(int32), etc. -
setMultipleSignals(DSingleportMultiSignal_ecu)- for buffered multi-signal sends.
-
-
DSingleportMultiSignal_ecu- a data class with setter operations per signal, used for buffered sends. -
Aecu_SinglePortCANContainerPlant- the container actor (ifgenerateContainerwas specified).
Generated Files
The generator produces ROOM model files in model-gen/can/:
| File | Description |
|---|---|
|
Contains adapter actors. |
|
Signal data types and per-message multi-signal protocol classes. |
|
Single-port adapter and container actors (if |
|
Consolidated single-port protocol ( |
Build Integration
The DBC generator runs as part of the regular miniHIL build.
After changing .dbc or .dbcconf files, run the miniHIL build to regenerate the model files in model-gen/can/.
During generation, all .dbcconf and .dbc files in model and model-user directories are scanned.
|
Ensure model-gen is present in MiniHilProject/modelpath. |
Integration in MiniHilProject.room
The generator creates two container variants per portmapper:
-
Plant: an ActorClass that defines the interface (signal ports) that your test actor should implement.
-
non-Plant: the actual adapter container that sits between your test actor and the CAN service, acting as a signal/message proxy.
| Container | Purpose |
|---|---|
|
Abstract base class defining the test actor’s interface (port structure). |
|
The active adapter instance between test actor and CAN service. Handles signal encoding/decoding and message routing. |
Set up the test harness
To use the generated signals in your test actor, instantiate the adapter container and wire it to your test actor:
import can.api.CANService.PCANCtrl
import can.SinglePortAdapter_ecu.Aecu_SinglePortCANContainerPlant
import can.SinglePortSignals_ecu.Pecu_SinglePortProtocol
ActorClass Application {
Structure {
ActorRef canAdapter: Aecu_SinglePortCANContainer
ActorRef testcase: AMyTestActor
Binding testcase.canCtrl and canAdapter.serviceCtrl
Binding canAdapter.signals and testcase.can
}
}
ActorClass TestPortProvider {
Interface {
conjugated Port canCtrl: PCANCtrl
conjugated Port can: Pecu_SinglePortProtocol
}
}
Usage in CaGe Tests
With the generated single-port protocol, signals can be sent directly by name.
Opening the CAN bus
Step OpenCan(int bitrateKbs):
timeout 200 ms
action
``
DCanParams canParams = {
.baudRate_kbs = bitrateKbs,
.openStr = "",
.alwaysSendOpenedReply = true
};
``
canCtrl.open(``&canParams``)
reaction
expect canCtrl.opened
;
Direct send (single signal)
Sends immediately one CAN frame per signal:
Step testSend_direct:
action
can.temp_min(-5)
can.temp_max(80)
;
Buffered send (multiple signals)
Set multiple signal values and transmit them together. Useful when signals share the same CAN message:
Step testSend_buffered:
action
``
DSingleportMultiSignal_ecu multipleSignals;
DSingleportMultiSignal_ecu_init(&multipleSignals);
DSingleportMultiSignal_ecu_setTemp_min(&multipleSignals, -10);
DSingleportMultiSignal_ecu_setTemp_max(&multipleSignals, 85);
``
can.setMultipleSignals(``&multipleSignals``)
;
Receiving CAN signals
To receive signals sent by the DUT using a reaction block:
Step waitForTemperature:
timeout 500 ms
reaction
expect can.temp1 > 20
;
Quick Start
-
Place your
.dbcfile inMiniHilProject/model-user/can/. -
Create a
.dbcconffile next to it defining adapters and portmapper. -
Ensure
model-genis present inMiniHilProject/modelpath -
Run
.\gradlew.bat assemble(or.\gradlew.bat generateDBCConf). -
Import generated actors in your
MiniHilProject.room. -
Wire
canAdapter.serviceCtrlandcanAdapter.signalsto your test actor. -
Write CaGe tests using the generated signal-level protocol.
-
Build and flash to the miniHIL board.
Ingress filtering CAN messages by frame id 
If many actors are connected to the HAL canBus port each actor will receive a copy of the received can message. This might lead to performance issues. But, in many cases each connected actor only reacts to can messages with specific ids. In those cases ingress filtering can be used.
The PCANData Protocol supports message filtering for each connected user port. To enable filtering you have to first stop forwarding of all message by sending a disableForwarding message. Afterwards you may configure which messages your actor should receive using the enableForwardingWithFilter message with DCanFilterConfig payload. NOTE that the DCanFilterConfig only takes a pointer to a uint32 array. To cater for different filter needs (number of valid ids) the array of valid canIDs must be kept within the users actor. The PortClass directly accesses that array and does not create a copy.

