OpenXC Vehicle Interface Firmware¶

Version: | 5.1.3-dev |
---|---|
Web: | http://openxcplatform.com |
Documentation: | http://vi-firmware.openxcplatform.com |
Source: | http://github.com/openxc/vi-firmware |
The OpenXC vehicle interface (VI) firmware runs on a microcontroller connected to one or more CAN buses. It receives either all CAN messages or a filtered subset, performs any unit conversion or factoring required and outputs a generic version to a USB, Bluetooth or network interface.
Warning
This portion of the site covers advanced topics such as writing and compiling your own firmware from source. These steps are NOT required for flashing a pre-compiled binary firmware.
If you’ve downloaded a pre-built binary firmware for your car, locate your VI in the list of supported interfaces to find instructions for programming it. You don’t need anything from the VI firmware documentation itself - most users don’t need anything in this documentation. Here be dragons!
Getting Started¶
If you’ve downloaded a pre-built binary firmware for your car, locate your VI in the list of supported interfaces to find instructions for programming it. You don’t need anything from the VI firmware documentation itself - most users don’t need anything in this documentation. Really, you can stop here!
The first steps for building custom firmware are to create a configuration file for your car and compile the firmware with that configuration. Make sure you have one of the supported hardware platforms.
Configuring, Compiling and Testing¶
Firmware Configuration¶
The open source repository does not include the implementation of the functions declared in signals.h and these are required to compile and program a vehicle interface. These functions are dependent on the specific vehicle and message set, which is often proprietary information to the automaker.
If you cannot use a pre-built binary firmware from an automaker, you can either:
- Recommended: Create a VI configuration file and use the code generation tool in the OpenXC Python library. Many examples of configuration files are included in the docs, as well as a complete reference for all configuration options.
- Implement the functions in signals.h manually. Knowledge of the vehicle’s CAN message is required for this method. The documentation of those functions describes the expected effect of each. Implement these in a file called signals.cpp and the code should now compile. The configuration file method is strongly recommended as it still allows flexibility while removing boilerplate.
Knowledge of the vehicle’s CAN messages is required for both options - this is advanced territory, and if you’re just looking to get some data out of your car you most likely want one of the binary firmwares from an automaker
Getting Started with VI Configuration¶
In this example, we’ll pick a simple use case and walk through how to configure and compile the firmware. You’ll need to be comfortable getting around at the command line, but you don’t need to know any C++. The VI firmware can be configured and built in Windows, Linux (Ubuntu and Arch are tested) or Mac OS X.
Let’s say we have a vehicle with a high speed CAN bus on the standard HS pins, connecting to the “CAN1” controller on our vehicle interface. There’s a CAN message on this bus sent by an ECU, and we want to read one numeric signal from the message - for this example, let it be the accelerator pedal position as a percentage.
CAN Message and Signal Details¶
The message contains driver control signals, so we’ll give it the name Driver_Controls so we can keep track of it. The message ID is 0x102.
In the message, there is a signal we’ll call Accelerator_Pedal_Pos that starts at bit 5 and is 7 bits wide - enough to represent pedal positions from 0 to 100.
The value on the bus is exactly how we want it to appear in the translated version over USB or Bluetooth. We want the name to be accelerator_pedal_position and we want to hide the rest of the details.
JSON Configuration¶
The configuration file format used for the VI firmware lis what we call a JSON mapping file. JSON is a human-readable data format that’s a alternative to XML - we use it because it’s easy to parse and easy to write by hand and the syntax is fairly obvious. Each configuration file, or mapping, is a single JSON object.
CAN Bus Definition¶
We’ll start by defining the CAN buses that we want to connect - save this in a file called accelerator-config.json:
{ "name": "accelerator",
"buses": {
"hs": {
"controller": 1,
"speed": 500000
}
}
}
- We gave this configuration the name accelerator - that will show up when we query for the query from the VI.
- We defined 1 CAN bus and called it hs for “high speed” - the name is arbitrary but we’ll use it later on, so make it short and sweet. hs, ms, info - these are good names.
- We configured this bus to be connected to the #1 controller on the VI - that’s typically what’s connected to the high speed bus in most vehicles.
- We set the speed of this CAN bus at 500Kbps - the speed attribute is in bytes per second, so we set it to 500000.
CAN Message Definition¶
Next up, let’s define the CAN message we want to translate from the bus. Modify the file so it looks like this:
{ "name": "accelerator",
"buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"name": "Driver_Controls",
"bus": "hs"
}
}
}
- We added a messages field to the JSON object.
- We added a 0x102 field to messages - that’s our CAN message’s ID and we use it here as a “key” for the object.
- Within the 0x102 message object:
- We set the name of the message. This is just used in comments so we can keep track of which message is which, rather than memorizing the ID.
- We set the bus field to hs, so this message will be pulled from the bus we defined (and named hs).
CAN Signal Definition¶
Don’t stop yet...we have to define our CAN signal before anything will be translated. Modify the file again:
{ "name": "accelerator",
"buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"name": "Driver_Controls",
"bus": "hs",
"signals": {
"Accelerator_Pedal_Pos": {
"generic_name": "accelerator_pedal_position",
"bit_position": 5,
"bit_size": 7
}
}
}
}
}
- We added a signals field to the 0x102 message object, after the name. The order doesn’t matter, just watch out for the commas required after each field and value pair. There’s no comma after the last field in an object.
- We added an Accelerator_Pedal_Pos field in the signals object - that’s the name of the signal, and like the message name, this is just for human readability.
- The generic_name is what the name field will be in the translated format over USB and Bluetooth - we set it to accelerator_pedal_position.
- We set the bit_position and bit_size for the signal.
That’s it - the configuration is finished. When we compile the VI firmware with this configuration, it will read our CAN message from the bus, parse and translate it into a JSON output message with a name and value, and send it out over USB and Bluetooth. Next, we’ll walk through how to do the compilation.
Basic Configuration Examples¶
If you haven’t created a custom firmware for the OpenXC VI yet, we recommend the getting started with custom data guide.
For all examples, the name field for message is optional but strongly encouraged to help keep track of the mapping.
When an example refers to “sending” a translated or raw message, it means sending to the app developer via one of the output interfaces (e.g. USB, Bluetooth) and not sending to the CAN bus. For examples of configuring writable messages and signals that do write back to the CAN bus, see the write configuration examples.
- One Bus, One Numeric Signal
- Transformed Numeric Signal
- One Bus, One Boolean Signal
- One Bus, One State-based Signal
- Combined State-based Signal
- Two Buses, Two Signals
- Limited Translated Signal Rate
- Limited Translated Signal Rate if Unchanged
- Send Signal on Change Only
- Separate Files for Message Sets
- Mapped from a DBC File
One Bus, One Numeric Signal¶
We want to read a single, numeric signal from a high speed bus on controller 1. The signal is 7 bits wide, starting from bit 5 in message ID 0x102. We want the name of the signal for OpenXC app developers to be my_openxc_measurement.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7
}
}
}
}
}
Transformed Numeric Signal¶
We want to read the same signal as in the One Bus, One Numeric Signal example, but we want to transform the value with a factor and offset before sending it to eh app developer. The value on CAN must be multiplied by -1.0 and offset by 1400.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400
}
}
}
}
}
We added the factor and offset attributes to the signal.
One Bus, One Boolean Signal¶
We want to read a boolean signal from a high speed bus on controller 1. The signal is 1 bits wide, starting from bit 32 in message ID 0x103. We want the name of the signal for OpenXC app developers to be my_boolean_measurement. Because it is a boolean type, the value will appear as true or false in the JSON for app developers.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x103": {
"bus": "hs",
"signals": {
"My_Boolean_Signal": {
"generic_name": "my_boolean_measurement",
"bit_position": 32,
"bit_size": 1,
"handler": "booleanHandler"
}
}
}
}
}
We set the handler for the signal to the booleanHandler, one of the built-in signal handler functions - this will transform the numeric value from the bus (a 0 or 1) into first-class boolean values (true or false).
One Bus, One State-based Signal¶
We want to read a signal from a high speed bus on controller 1 that has numeric values corresponding to a set of states - what we call a state-based signal
The signal is 3 bits wide, starting from bit 28 in message ID 0x104. We want the name of the signal for OpenXC app developers to be active_state. There are 6 valid states from 0-5, and we want those to appears as the state strings a through f in the JSON for app developers.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x104": {
"bus": "hs",
"signals": {
"My_State_Signal": {
"generic_name": "active_state",
"bit_position": 28,
"bit_size": 3,
"states": {
"a": [0],
"b": [1],
"c": [2],
"d": [3],
"e": [4],
"f": [5]
}
}
}
}
}
}
We set the states field for the signal to a JSON object mapping the string value for each state to the numerical values to which it corresponds. This automatically will set the handler to the stateHandler, one of the built-in signal handler functions.
Combined State-based Signal¶
We want to read the same state-based signal from One Bus, One State-based Signal but we want the values 0-3 on the bus to all correspond with state a and values 4-5 to the string state b.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x104": {
"bus": "hs",
"signals": {
"My_State_Signal": {
"generic_name": "active_state",
"bit_position": 28,
"bit_size": 3,
"states": {
"a": [0, 1, 2, 3],
"b": [4, 5]
}
}
}
}
}
}
Each state string maps to an array - this can seem unnecessary when you only have 1 numeric value for each state, but it allows combined mappings as in this example.
Two Buses, Two Signals¶
We want to read two numeric signals - one from a message on a high speed bus on controller 1, and the other from a message on a medium speed bus on controller 2.
The signal on the high speed bus is 12 bits wide, starting from bit 11 in message ID 0x108. We want the name of the signal for OpenXC app developers to be my_first_measurement.
The signal on the medium speed bus 14 bits wide, starting from bit 0 in message ID 0x90. We want the name of the signal for OpenXC app developers to be my_second_measurement.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
},
"messages": {
"0x108": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_first_measurement",
"bit_position": 11,
"bit_size": 12
}
}
},
"0x90": {
"bus": "ms",
"signals": {
"My_Other_Signal": {
"generic_name": "my_second_measurement",
"bit_position": 0,
"bit_size": 14
}
}
}
}
}
We added the second bus to the buses field and assigned it to controller 2. We added the second message object and made sure to set its bus field to ms.
Limited Translated Signal Rate¶
We want to read the same signal as in the One Bus, One Numeric Signal example, but we want it to be sent at a maximum of 5Hz. We want the firmware to pick out messages at a regular period, but we don’t care which data is dropped in order to stay under the maximum.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"max_frequency": 5
}
}
}
}
}
We set the max_frequency field of the signal to 5 (meaning 5Hz) - the firmware will automatically handle skipping messages to stay below this limit.
Limited Translated Signal Rate if Unchanged¶
We want the same signal from Limited Translated Signal Rate at a limited rate, but we don’t want to lose any information - if the value of the signal changes, we want it to be sent regardless of the max frequency. Repeated, duplicate signal values are fairly common in vehicles, where a signal is sent at a steady frequency even if the value hasn’t changed. For this example, we want to preserve all information - if a signal changes, we want to make sure the data is sent.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"max_frequency": 5,
"force_send_changed": true
}
}
}
}
}
We added the force_send_changed field to the signal, which will make sure the signal is sent immediately when the value changes. This rate limiting is lossless.
Send Signal on Change Only¶
We want to limit the rate of a signal as in Limited Translated Signal Rate if Unchanged, but we want to be more strict - the signal should only be translated and sent to app developers if it actually changes.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"send_same": false
}
}
}
}
}
We accomplish this by setting the send_same field to false. This is most appropriate for boolean and state-based signals where the transition is most important. Considering that a host device may connect to the VI after the message has been sent, using this field has the potential of making it difficult to tell the current state of the vehicle on startup - you have to wait for a state change before knowing any values. For that reason, we’ve moved away from using this for most firmware (using a combination of a max_frequency of 1Hz and force_send_changed == true) but the option is still available.
Separate Files for Message Sets¶
Starting from the Two Buses, Two Signals example, we want to split up the configuration into mutiple files because it’s getting too big and hard to follow. This will especially be true as we add more message and signals.
Starting from this complete configuration:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
},
"messages": {
"0x108": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_first_measurement",
"bit_position": 11,
"bit_size": 12
}
}
},
"0x90": {
"bus": "ms",
"signals": {
"My_Other_Signal": {
"generic_name": "my_second_measurement",
"bit_position": 0,
"bit_size": 14
}
}
}
}
}
we move the messages that we want to read from the hs bus to the file hs.json:
{
"messages": {
"0x108": {
"signals": {
"My_Signal": {
"generic_name": "my_first_measurement",
"bit_position": 11,
"bit_size": 12
}
}
}
}
}
and we move the messages that we want to read from the ms bus to the file ms.json:
{
"messages": {
"0x90": {
"signals": {
"My_Other_Signal": {
"generic_name": "my_second_measurement",
"bit_position": 0,
"bit_size": 14
}
}
}
}
}
Notice in both of these files, the messages no longer have the bus attribute - we’re instead going to specify that in the top level configuration:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
},
"mappings": [
{"mapping": "hs.json", "bus": "hs"},
{"mapping": "ms.json", "bus": "ms"}
]
}
The primary advantage of using separate files is readability, but it also makes the message definitions more re-usable between vehicle platforms and buses. For example, we could quickly parse all of the messages from the ms.json mapping file from the hs bus instead of ms by flipping the bus attribute in the top-level config file.
Mapped from a DBC File¶
If you use Vector DBC files to store your “gold standard” CAN signal definitions, you can save some effort by exporting the DBC to an XML file and merging it with your VI configuration file. You won’t need to manually copy the bit_position, bit_size, factor and offset attributes.
If we are to implement One Bus, One Numeric Signal manually, we would use this configuration file:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400
}
}
}
}
}
If the message and signal is defined in a DBC file, we can save some effort. Using a program like Vector CANdb++, export the DBC file to XML. Place the XML file in the same directory as your JSON configuration file. We need to first split up the configuration into a mapped messages file and a top-level config, as in Separate Files for Message Sets example.
In our config.json:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"mappings": [
{"mapping": "hs.json", "bus": "hs"}
]
}
and in hs.json:
{
"messages": {
"0x102": {
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400
}
}
}
}
}
Now that we have the DBC exported to an XML file (we’ll assume it’s named exported-hs.xml), we can remove the bit_position, bit_size, factor and offset fields and let them be imported from the XML - the only thing required is a generic_name:
In our config.json:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"mappings": [
{"mapping": "hs.json", "bus": "hs",
"database": "exported-hs.xml"}
]
}
and in hs.json:
{
"messages": {
"0x102": {
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement"
}
}
}
}
}
It’s not huge savings for 1 signal, but once you get a dozen it can save a lot of effort and opportunities for bugs.
Low-level CAN Configuration Examples¶
Unfiltered Raw CAN¶
We want to read all raw CAN messages from a bus at full speed. Be aware that the VI hasn’t been optimized for this level of throughput, and it’s not guaranteed at this time that messages will not be dropped. We recommend using rate limiting, which can dramatically decrease the bandwidth required without losing any information.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000,
"raw_can_mode": "unfiltered"
}
}
}
Filtered Raw CAN¶
We want to read only the message with ID 0x21 from a high speed bus on controller 1.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000,
"raw_can_mode": "filtered"
}
},
"messages": {
"0x21": {
"bus": "hs"
}
}
}
We added the 0x21 message and assigned it to bus hs, but didn’t define any signals (it’s not necessary when using the raw CAN mode).
Unfiltered Raw CAN with Limited, Variable Data Rate¶
We want to read all raw CAN messages from a bus, but we don’t want the output interface to be overwhelmed by repeated duplicate messages. This is fairly common in vehicles, where a message is sent at a steady frequency even if the value hasn’t changed. For this example, we want to preserve all information - if a message changes, we want to make sure the data is sent.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000,
"raw_can_mode": "unfiltered",
"max_message_frequency": 1,
"force_send_changed": true
}
}
}
We combine two attributes to both limit the data rate from raw CAN messages, and also make sure the transfer is lossless. The max_message_frequency field sets the maximum send frequency for CAN messages that have not changed to 1Hz. We also set the force_send_changed field to true, which will cause a CAN message with a new value to be sent to the output interface immediately, even if it would go above the 1Hz frequency. The result is that each CAN message is sent at a minimum of 1Hz and a maximum of the true rate of change for the message.
Unfiltered Raw CAN with Strict, Limited Data Rate¶
We want to read all raw CAN messages as in Unfiltered Raw CAN with Limited, Variable Data Rate but we want to set a strict limit on the read frequency of each CAN message. We don’t care if we skip some CAN messages, even if they have new data - the maximum frequency is the most important thing.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000,
"raw_can_mode": "unfiltered",
"max_message_frequency": 1
}
}
}
We left the force_send_changed field out - by default it is set to false and the firmware will strictly enforce the max message frequency.
Translated and Raw CAN Together¶
We want to read the same signal as in the One Bus, One Numeric Signal example, but we also want to receive all unfiltered raw CAN messages simultaneously.
{ "buses": {
"hs": {
"controller": 1,
"raw_can_mode": "unfiltered",
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7
}
}
}
}
}
We added set the raw_can_mode for the bus to unfiltered, as in Unfiltered Raw CAN. No other changes are required - the raw and translated message co-exist peacefully. If we set raw_can_mode to filtered, it would only send the raw message for 0x102, where we’re getting the numeric signal.
Custom Firmware Code Examples¶
Sometimes a bit of C++ is required beyond the JSON configuration to implement advanced features. You can optionally inject your own code into the generated C++ file, so you can still take advantage of the configuration file for the simple signals.
Custom Transformed Numeric Signal¶
Similar to the Transformed Numeric Signal example, we want to modify a numeric value read from a CAN signal before sending it to the app developer, but the the desired transformation isn’t as simple as an offset. We want to read the same signal as before, but if it’s below 100 it should be rounded down to 0. We want our custom transformation to happen after using the existing factor and offset.
To accomplish this, we need to know a little C - we will write a custom signal handler to make the transformation. Here’s the JSON configuration:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400,
"handler": "ourRoundingHandler"
}
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
We set the handler for the signal to ourRoundingHandler, and we’ll define that in a separate file named my_handlers.cpp. We also added the extra_sources field, which is a list of the names of C++ source files on our path to be included with the generated firmware code.
In my_handlers.cpp:
/* Round the value down to 0 if it's less than 100. */
float ourRoundingHandler(CanSignal* signal, CanSignal* signals,
int signalCount, float value, bool* send) {
if(value < 100) {
value = 0;
}
return value;
}
After being transformed with the factor and offset for the signal from the configuration file, the value is passed to our handler function. We make whatever custom transformation required and return the new value.
There are a few other valid type signatures for these custom value handlers - for converting numeric values to boolean or state-based signals.
Transformed with Signal Reference¶
We need to combine the values of two signals from a CAN message to create a single value - one signal is the absolute value, the other is the sign.
Both signals are on the high speed bus in the message with ID 0x110. The absolute value signal is 5 bits wide, starting from bit 2. The sign signal is 1 bit wide, starting from bit 12 - when the value of the sign signal is 0, the final value should be negative. We want to the final value to be sent to app developers with the name my_signed_measurement.
We will use a custom value handler for the signal to reference the sign signal’s last value when transforming the absolute value signal.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x110": {
"bus": "hs",
"signals": {
"My_Value_Signal": {
"generic_name": "my_signed_measurement",
"bit_position": 2,
"bit_size": 5,
"handler": "ourSigningHandler"
},
"My_Sign_Signal": {
"generic_name": "sign_of_signal",
"bit_position": 12,
"bit_size": 1,
"handler": "ignoreHandler"
}
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
We don’t want to the sign signal to be sent separately on the output interfaces, but we need the firmware to read and store its value so we can refer to it from our custom handler. We set the sign signal’s handler to ignoreHandler which will still process and store the value, but withold it from the output data stream.
For the absolute value signal, we set the handler to a custom function where we look up the sign signal and use its value to transform the absolute value. In my_handlers.cpp:
/* Load the last value for the sign signal and multiply the absolute value
by it. */
float ourRoundingHandler(CanSignal* signal, CanSignal* signals,
int signalCount, float value, bool* send) {
CanSignal* signSignal = lookupSignal("sign_of_signal",
signals, signalCount);
if(signSignal == NULL) {
debug("Unable to find sign signal");
*send = false;
} else {
if(signSignal->lastValue == 0) {
// left turn
value *= -1;
}
}
return value;
}
We use the lookupSignal` function to load a struct representing the sign_of_signal CAN signal we defined in the configuration, and check the lastValue attribute of the struct. If for some reason we aren’t able to find the configured sign signal, lookupSignal will return NULL and we can stop hold the output of the final value by flipping *send to false. The firmware will check the value of *send after each call to a custom handler to confirm if the translation pipeline should continue.
One slight problem with this approach: there is currently no guaranteed ordering for the signals. It’s possible the lastValue for the sign signal isn’t from the same message as the absolute value signal you are current handling in the function. With a continuous value, there’s only a small window where this could happen, but if you must be sure the values came from the same message, you may need to write a Composite Signal.
Composite Signal¶
We want complete control over the output of a measurement from the car. We have a CAN message that includes 3 different signals that represent a GPS latitude value, and want to combine them into a single value in degrees.
The three signals are in the message 0x87 on a high speed bus connected to controller 1. The three signals:
- The whole latitude degrees signal starts at bit 10 and is 8 bits wide. The value on CAN requires an offset of -89.0.
- The latitude minutes signal starts at bit 18 and is 6 bits wide.
- The latitude minute fraction signal starts at bit 24 and is 14 bits wide. The value on CAN requires a factor of .0001.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x87": {
"bus": "hs",
"handler": "latitudeMessageHandler",
"signals": {
"Latitude_Degrees": {
"generic_name": "latitude_degrees",
"bit_position": 10,
"bit_size": 8,
"offset": -89,
"ignore": true
},
"Latitude_Minutes": {
"generic_name": "latitude_minutes",
"bit_position": 18,
"bit_size": 6,
"ignore": true
},
"Latitude_Minute_Fraction": {
"generic_name": "latitude_minute_fraction",
"bit_position": 24,
"bit_size": 14,
"factor": 0.0001,
"ignore": true
},
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
We made two changes to the configuration from a simple translation config:
- We set the ignore field to true for each of the component signals in the message. The signal definitions (i.e. the position, offset, etc) will be included in the firmware build so we can access it from a custom message handler, but the signals will not be processed by the normal translation stack.
- We set the handler for the 0x87 message (notice that unlike in other examples the handler is set on the message object in the config, not any of the signals) to our custom message handler, latitudeMessageHandler.
In my_handlers.cpp:
/* Combine latitude signals split into their components (degrees,
* minutes and fractional minutes) into 1 output message: latitude in
* degrees with with decimal precision.
*
* The following signals must be defined in the signal array, and they must
* all be contained in the same CAN message:
*
* * latitude_degrees
* * latitude_minutes
* * latitude_minutes_fraction
*
* This is a message handler, and takes care of sending the output message.
*
* messageId - The ID of the received GPS latitude CAN message.
* data - The CAN message data containing all GPS latitude information.
* signals - The list of all signals.
* signalCount - The length of the signals array.
* send - (output) Flip this to false if the message should not be sent.
* pipeline - The pipeline that wraps the output devices.
*
* This type signature is required for all custom message handlers.
*/
void latitudeMessageHandler(int messageId, uint64_t data,
CanSignal* signals, int signalCount, Pipeline* pipeline) {
// Retrieve the CanSignal struct representations of the 3 latitude
// component signals. These are still included in the firmware build
// when the 'ignore' flag was true for the signals.
CanSignal* latitudeDegreesSignal =
lookupSignal("latitude_degrees", signals, signalCount);
CanSignal* latitudeMinutesSignal =
lookupSignal("latitude_minutes", signals, signalCount);
CanSignal* latitudeMinuteFractionSignal =
lookupSignal("latitude_minute_fraction", signals, signalCount);
// Confirm that we have all required signal components
if(latitudeDegreesSignal == NULL ||
latitudeMinutesSignal == NULL ||
latitudeMinuteFractionSignal == NULL) {
debug("One or more GPS latitude signals are missing");
return;
}
// begin by assuming we will send the message, no errors yet
bool send = true;
// Decode and transform (using any factor and offset defined in the
// CanSignal struct) each of the component signals from the message data
// preTranslate is intended to be used in conjunction with postTranslate
// - together they keep metadata about the receive signals in memory.
float latitudeDegrees = preTranslate(latitudeDegreesSignal, data, &send);
float latitudeMinutes = preTranslate(latitudeMinutesSignal, data, &send);
float latitudeMinuteFraction = preTranslate(
latitudeMinuteFractionSignal, data, &send);
// if we were able to decode all 3 component signals (i.e. none of the
// calls to preTranslate flipped 'send' to false
if(send) {
float latitude = (latitudeMinutes + latitudeMinuteFraction) / 60.0;
if(latitudeDegrees < 0) {
latitude *= -1;
}
latitude += latitudeDegrees;
// Send the final latitude value to the output interfaces (via the
// pipeline)
sendNumericalMessage("latitude", latitude, pipeline);
}
// Conclude by updating the metadata for each of the component signals
// with postTranslate
postTranslate(latitudeDegreesSignal, latitudeDegrees);
postTranslate(latitudeMinutesSignal, latitudeMinutes);
postTranslate(latitudeMinuteFractionSignal, latitudeMinuteFraction);
}
A more complete, functional example of a message handler is included in the VI firmware repository - one that handles both latitude and longitude in a CAN message. There is also additional documentation on the message handler type signature.
Initializer Function¶
We want to initialize a counter when the VI powers up that we will use from some custom CAN signal handlers.
{ "buses": {
"hs": {
"controller": 1,
"raw_can_mode": "unfiltered",
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7
}
}
}
},
"initializers": [
"initializeMyCounter"
],
"extra_sources": [
"my_initializers.cpp"
]
}
We added an initializers field, which is an array containing the names of C functions matching the initializer type signature.
In my_initializers.cpp:
int MY_COUNTER;
void initializeMyCounter() {
MY_COUNTER = 42;
}
This isn’t a very useful initializer, but there much more you could do - you’ll want to look into the lowest level APIs in the firmware source. Look through the .h files, where most functions are documented.
Looper Function¶
We want to increment a counter every time through the main loop of the firmware, regardless of whatever CAN messages we may have received.
{ "buses": {
"hs": {
"controller": 1,
"raw_can_mode": "unfiltered",
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7
}
}
}
},
"loopers": [
"incrementMyCounter"
],
"extra_sources": [
"my_loopers.cpp"
]
}
We added a loopers field, which is an array containing the names of C functions matching the looper type signature.
In my_loopers.cpp:
void incrementMyCounter() {
static int myCounter = 0;
++myCounter;
}
As with the initializer, this isn’t a very functional example, but there much more you could do - you’ll want to look into the lowest level APIs in the firmware source. Look through the .h files, where most functions are documented.
Ignore Depending on Value¶
We want to ignore a signal (and not translate it and send over USB/Bluetooth) if the value matches certain critera. We’ll use a custom handler as in Custom Transformed Numeric Signal but instead of modifying the value of the signal, we’ll use the send flag to tell the VI if it should process the value or not.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400,
"handler": "ourFilteringHandler"
}
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
In my_handlers.cpp:
/* Ignore the signal if the value is less than 100 */
float ourFilteringHandler(CanSignal* signal, CanSignal* signals,
int signalCount, float value, bool* send) {
if(value < 100) {
*send = false;
}
return value;
}
Firmware Write Configuration Examples¶
A “write” to the VI is an OpenXC formatted message sent in reverse - from an application running on a host device back through e.g. USB or Bluetooth to the VI. By default, any data sent back to the VI is ignored.
For applications that need to write data back to the CAN bus, whether for actuation, personalization or diagnostics, you can configure a range of writing styles to be permitted. At a high level, you can configure the VI to accept writes for raw CAN messages, translated signals, translated signals with custom logic on the VI to perform transformations, and completely arbitrary sets of CAN writes from a single request from the user.
Most of these examples build on configurations started for reading data from the bus, so you are strongly encouraged to read, understand and try the read-only configurations before continuing.
Translated Numeric Signal Write Request¶
We want to send a single numeric value to the VI, and have it translated back into a CAN signal in a message on a high speed bus attached to controller 1. The signal is 7 bits wide, starting from bit 5 in message ID 0x102. We want the name of the signal that will be sent to the VI to be my_openxc_measurement.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"writable": true
}
}
}
}
}
This configuration is the same as the read-only example One Bus, One Numeric Signal with the addition of the writable flag to the signal. When this flag is true, an OpenXC message sent back to the VI from an app with the name my_openxc_measurement will be translated to a CAN signal in a new message and written to the bus.
Translated Boolean Signal Write Request¶
We want to send a single boolean value to the VI, and have it translated back into a CAN signal in a message on a high speed bus attached to controller 1. The signal is 1 bits wide, starting from bit 3 in message ID 0x201. We want the name of the signal that will be sent to the VI to be my_boolean_request.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_boolean_request",
"bit_position": 3,
"bit_size": 1,
"writable": true,
"write_handler": "booleanWriter"
}
}
}
}
}
In addition to setting writable to true, We set the write_handler for the signal to the built-in booleanWriter. This will handle converting a true or false value from the user back to a 1 or 0 in the outgoing CAN message.
Translated State-based Signal Write Request¶
We want to send a state as a string to the VI, and have it translated back into a numeric CAN signal in a message on a high speed bus attached to controller 1. As in One Bus, One State-based Signal, the signal is 3 bits wide, starting from bit 28 in message ID 0x104. We want the name of the signal for OpenXC app developers to be active_state. There are 6 valid states from 0-5 in the CAN signal, but we want the app developer to send the strings a through f to the VI.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_state_request",
"bit_position": 28,
"bit_size": 3,
"states": {
"a": [0],
"b": [1],
"c": [2],
"d": [3],
"e": [4],
"f": [5]
},
"writable": true
}
}
}
}
}
The writable field is all that is required - the signal will be automatically configured to use the built-in stateWriter as its write_handler because the signal has a states array. If a user sends the VI the value c in a write request with the name my_state_request, it will be encoded as 2 in the CAN signal in the outgoing message.
Translated, Transformed Written Signal¶
We want to write the same signal as Translated Numeric Signal Write Request but round any values below 100 down to 0 before sending (similar to the read-only example Custom Transformed Numeric Signal).
To accomplish this, we need to know a little C - we will write a custom signal handler to make the transformation. Here’s the JSON configuration:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400,
"write_handler": "ourRoundingWriteHandler"
}
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
We set the write_handler for the signal to ourRoundingWriteHandler, and we’ll define that in a separate file named my_handlers.cpp. The extra_sources field is also set, meaning that our custom C/C++ code will be included with the firmware build.
In my_handlers.cpp:
/* Round the value down to 0 if it's less than 100 before writing to CAN. */
uint64_t ourRoundingWriteHandler(CanSignal* signal, CanSignal* signals,
int signalCount, double value, bool* send) {
if(value < 100) {
value = 0;
}
// encodeSignal pulls the CAN signal definition from the CanSignal struct
// and encodes the value into the right bits of a 64-bit return value.
return encodeSignal(signal, value);
}
Signal write handlers are responsible for encoding the value into a 64-bit value, to be used in the outgoing message.
Composite Write Request¶
When the app developer sends a numeric measurement to the VI, we want to send:
- 1 arbitrary CAN message with the ID 0x34 on a high speed bus connected to controller 1, with the value 0x1234.
- The value sent by the developer encoded into the message ID 0x35 in a signal starting at bit 0, 4 bits wide on the same high speed bus. We don’t want this value to be writable by the app developer unless a part of these 3 writes combined.
- A boolean signal in the message 0x101 on a medium speed bus connected to controller 2, starting at bit 12 and 1 bit wide. If the numeric value from the user is greater than 100, the boolean value should be true.
{ "name": "passthrough",
"buses": {
"hs": {
"controller": 1,
"raw_writable": true,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
},
"messages": {
"0x35": {
"bus": "hs",
"signals": {
"My_Numeric_Signal": {
"generic_name": "my_number_signal",
"bit_position": 0,
"bit_size": 4
}
}
}
"0x101": {
"bus": "ms",
"signals": {
"My_Other_Signal": {
"generic_name": "my_value_is_over_100_signal",
"bit_position": 12,
"bit_size": 1
}
}
}
},
"commands": [
{"name": "my_command",
"handler": "handleMyCommand"}
],
"extra_sources": [
"my_handlers.cpp"
]
}
We added a commands field, which contains an array of JSON objects with name and handler fields. The name of the command, my_command is what app developers will send to the VI. The handler is the name of a C++ function will define in one of the files listed in extra_sources.
In the configuration, also note that:
- The raw CAN message that we want to send isn’t included. Since raw_writable is true for the hs bus, there’s no need to define it in the configuration.
- The my_number_signal signal doesn’t have the writable flag set to true (it’s omitted, and the default is false). This means an app developer will not be able to send write requests for my_number_signal directly.
In my_handlers.cpp:
bool handleMyCommand(const char* name, cJSON* value, cJSON* event,
CanSignal* signals, int signalCount) {
// Look up the numeric and boolean signals we need to send and abort if
// either is missing
CanSignal* numericSignal = lookupSignal("my_number_signal", signals,
signalCount);
CanSignal* booleanSignal = lookupSignal("my_value_is_over_100_signal",
signals, signalCount);
if(numericSignal == NULL) {
debug("Unable to find numeric signal, can't send trio");
// return false, indicating that we didn't successfully handle this
// command
return false;
}
// Send the arbitrary CAN message:
// Build and enqueue the arbitrary CAN message to be sent - note that none
// of the CAN messages we enqueue in the handler will be sent until after
// it returns - interaction with the car via CAN must be asynchronous.
CanMessage message = {0x34, 0x12345};
// TODO need a lookupCanBus function to make sure we get the bus we wanted
can::write::enqueueMessage(getCanBuses()[0], &message);
// Send the numeric value:
// The write API accepts cJSON objects right now as a way to accept
// multiple types, so we create a cJSON number object wrapping the value
// provided by the user
cJSON* numberObject = cJSON_CreateNumber(value);
can::write::sendSignal(numericSignal, numberObject, signals, signalCount,
// the last parameter is true, meaning we want to force sending
// this signal even though it's not marked writable in the
// config
true);
// Make sure to free the cJSON object we created, otherwise it will leak
// memory and quickly kill the VI
cJSON_Delete(numberObject);
// Send the boolean value:
// Like above, create a cJSON object that wraps a boolean - true if the
// value sent by the user is greater than 100
cJSON* boolObject = cJSON_CreateBool(value > 100);
// Send that boolean value in in the boolean signal on the bus, using the
// booleanWriter write handler to convert it from a boolean to a number in
// the message data
can::write::sendSignal(booleanSignal, boolObject, booleanWriter,
signals, signalCount,
true);
// again, make sure to free the cJSON object we created
cJSON_Delete(boolObject);
// we successfully processed the command, so return true to the VI stack
return true;
}
FAQ¶
While building custom firmware, if you find yourself thinking:
- “I need to run some code in the main loop, regardless of any CAN signals.” - use a Looper Function.
- “I need to run initialization code at startup.” User an Initializer Function.
- “I need to do some extra processing on a CAN signal before sending it out.” Use a custom signal handler.
- “I need to combine the value of multiple signals in a message to generate a value.” Use a message handler.
- “I need to send less data.” Control the send frequency.
- “I don’t want this signal to send unless it changes.” Configure it to Send Signal on Change Only.
Bit Numbering¶
Because of different software tools and conventions in the industry, there are multiple ways to refer to bits within a CAN message. This doesn’t change the actual data representation (like a different byte order would) but it changes how you refer to different bit positions for CAN signals.
The vehicle interface C++ source assumes the number of the highest order bit of a 64-bit CAN message is 0, and the numbering continuous left to right:
Hex: 0x83 46
Binary: 10000011 01000110
Bit pos: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...etc.
The tool used at Ford to document CAN messages (Vector DBC files) uses an “inverted” numbering by default. In each byte of a CAN message, they start counting bits from the rightmost bit, e.g.:
Hex: 0x83 46
Binary: 10000011 01000110
Bit pos: 7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8 ...etc.
When building CanSignal structs manually, you must use the normal, non-inverted bit numbering.
When using JSON mapping format and the code generation tools, you can control the bit numbering with the bit_numbering_inverted flag. By default it assumes normal bit ordering unless you are using a database-backed mapping, in which case it defaults to true - the DBC files we’ve seen so far have all stored signal information in the inverted format.
Configuration Options¶
There are many configuration options - we recommend looking for your use case in the list of read or write to find a starting point, and refer to this section as a reference on particular configuration options.
The root level JSON object maps CAN bus addresses to CAN bus objects, CAN message IDs to CAN message objects in each bus, and CAN signal name to signal object within each message. Other top-level sections are available to list one-time initialization functions and to list arbitrary functions that should be added to the main loop.
In all cases when we refer to a “path,” either an absolute or relative path will work. If you use relative paths, however, they must be relative to the root of wherever you run the build scripts.
Once you’ve defined your message set in a JSON file, run the openxc-generate-firmware-code tool from the OpenXC Python library to create an implementation of signals.cpp:
vi-firmware/ $ openxc-generate-firmware-code --message-set mycar.json > src/signals.cpp
Message Set¶
Each JSON mapping file defines a “message set,” and it should have a name. Typically this identifies a particular model year vehicle, or possibly a broader vehicle platform. The name field is required.
bit_numbering_inverted - (optional, false by default, true by default for database-backed mappings) This flag controls the default bit numbering for all messages included in this message set. You can override the bit numbering for any particular message or mapping, too.
max_message_frequency - Set a default value for all buses for this attribute - see the Can Bus section for a description.
raw_can_mode - Set a default value for all buses for this attribute - see the Can Bus section for a description.
Parent Message Sets¶
Message sets are composable - you can extend a set by adding a path to the parent(s) to the parents key.
Initializers¶
The key initializers should have as its value an array of strings. Each string should be the name of a function with the type signature:
void function();
These functions will be called once at the beginning of execution, before reading any CAN messages.
Loopers¶
The key loopers should have as its value an array of strings. Each string should be the name of a function with the type signature:
void function();
These functions will be called once each time through the main loop function, after reading and processing any CAN messages.
CAN Buses¶
The key buses must be an object, where each field is a CAN bus uses by this message set, and which CAN controllers are attached on the microcontroller. The
controller - The integer ID of the CAN controller to which this bus is attached. The platforms we are using now only have 2 CAN controllers, identified here by 1 and 2 - these are the only acceptable bus addresses. If this field is not defined, the bus and any messages associated with it will be ignored (but it won’t cause an error, so you can swap between buses very quickly).
speed - The CAN bus speed in Kbps, most often 125000 or 500000.
raw_can_mode - Controls sending raw CAN messages (encoded as JSON objects) from the bus over the output channel. Valid modes are off (the default if you don’t specify this attribute), filtered (if messages are defined for the bus, will enable CAN filters and only transmit those messages), or unfiltered (disable acceptance filters and send all received CAN messages). If this attribute is set on a CAN bus object, it will override any default set at the message set level (e.g. you can have all buses configured to send filtered raw CAN messages, but override one to send unfiltered).
raw_writable - Controls whether or not raw CAN messages from the user can be written back to this bus, without any sort of translation. This is false by default. Even when this is false, messages may still be written to the bus if a signal is configured as writable, but they will translated from the user’s input first.
max_message_frequency - The default maximum frequency for all CAN messages when using the raw passthrough mode. To put no limit on the frequency, set this to 0 or leave it out. If this attribute is set on a CAN bus object, it will override any default set at the message set level. This value cascades to all CAN message objects for their max_frequency attribute, which can also be overridden at the message level.
force_send_changed - (default: true) Meant to be used in conjunction with max_message_frequency, if this is true a raw CAN message will be sent regardless of the given frequency if the value has changed (when using raw CAN passthrough). Setting the value here, on the CAN bus object, will cascade down to all CAN messages unless overridden.
CAN Messages¶
The messages key is a object with fields mapping from CAN message IDs to signal definitions. The fields must be hex IDs of CAN messages as strings (e.g. 0x90).
Message¶
The attributes of each message object are:
bus - The name of one of the previously defined CAN buses where this message can be found.
bit_numbering_inverted - (optional, defaults to the value of the mapping, then default of the message set) This flag controls the default bit numbering for the signals in this message.
signals - A list of CAN signal objects (described in the Signal section) that are in this message, with the name of the signal as the key. If this is a database-backed mappping, this value must match the signal name in the database exactly - otherwise, it’s an arbitrary name.
name - (optional) The name of the CAN message - this is not required and has no meaning in code, it can just be handy to be able to refer back to an original CAN message definition in another document.
handlers - (optional) An array of names of functions that will be compiled with the firmware and should be applied to the entire raw message value (see Message Handlers).
enabled - (optional, true by default) Enable or disable all processing of a CAN message. By default, a message is enabled. If this flag is false, the CAN message and all its signals will be left out of the generated source code.
max_frequency - (default: 0, no limit) If sending raw CAN messages to the output interfaces, this controls the maximum frequency (in Hz) that the message will be process and let through. The default value (0) means that all messages will be processed, and there is no limit imposed by the firmware. If you want to make sure you don’t miss a change in value even when rate limiting, see the force_send_changed attribute.
max_signal_frequency - (default: 0, no limit) Setting the max signal frequency at the message level will cascade down to all of the signals within the message (unless overridden). The default value (0) means that all signals will be processed, and there is no limit imposed by the firmware. See the max_frequency flag documentation for the signal mapping for more information. If you want to make sure you don’t miss a change in value even when rate limiting, see the force_send_changed_signals attribute.
force_send_changed - (default: true) Meant to be used in conjunction with max_frequency, if this is true a raw CAN message will be sent regardless of the given frequency if the value has changed (when using raw CAN passthrough).
force_send_changed_signals - (default: false) Setting this value on a message will cascade down to all of the signals within the message (unless overridden). See the force_send_changed flag documentation for the signal mapping for more information.
Message Handlers¶
If you need additional control, you can provide custom handlers for the entire message to combine multiple signals into a single value (or any other arbitrary processing). You can generate 0, 1 or many translated messages from each call to a custom handler function.
void handleSteeringWheelMessage(int messageId, uint64_t data,
CanSignal* signals, int signalCount, Pipeline* pipeline);
float steeringWheelAngle = decodeCanSignal(&signals[1], data);
float steeringWheelSign = decodeCanSignal(&signals[2], data);
float finalValue = steeringWheelAngle;
if(steeringWheelSign == 0) {
// left turn
finalValue *= -1;
}
char* message = generateJson(signals[1], finalValue);
sendMessage(usbDevice, (uint64_t*) message, strlen(message));
}
Using a custom message handler will not automatically stop the normal translation workflow for individual signals. To mute them (but still store their values in signal->lastvalue), specify ignoreHandler as the handler. This is not done by default because not every signal in a message is always handled by a message handler.
Signal¶
The attributes of a signal object within a message are:
generic_name - The name of the associated generic signal name (from the OpenXC specification) that this should be translated to. Optional - if not specified, the signal is read and stored in memory, but not sent to the output bus. This is handy for combining the value of multiple signals into a composite measurement such as steering wheel angle with its sign.
bit_position - (required only if not a database-backed mapping) The staring bit position of this signal within the message.
bit_size - (required only if not a database-backed mapping) The width in bits of the signal.
factor - (required only if not a database-backed mapping) The signal value is multiplied by this if set. Optional.
offset - (required only if not a database-backed mapping) This is added to the signal value if set. Optional.
handler - (optional) The name of a function that will be compiled with the firmware and should be applied to the signal’s value after the normal translation. See the Value Handlers section for details.
ignore - (default: false) Setting this to true on a signal will silence output of the signal. The VI will not monitor the signal nor store any of its values. This is useful if you are using a custom handler for an entire message, want to silence the normal output of the signals it handles, and you don’t need the VI to keep track of the values of any of the signals separately (in the lastValue field). If you need to use the previously stored values of any of the signals, you can use the ignoreHandler as a value handler for the signal.
states - (required only for state-based signals) This is a mapping between the desired descriptive states (e.g. off) and the corresponding numerical values from the CAN message (usually an integer). The raw values are specified as a list to accommodate multiple raw states being coalesced into a single final state (e.g. key off and key removed both mapping to just “off”).
max_frequency - (default: 0, no limit) Some CAN signals are sent at a very high frequency, likely more often than will ever be useful to an application. This attribute sets the maximum frequency (Hz) that the signal will be processed and let through. The defualt value (0) means that all values will be processed, and there is no limit imposed by the firmware. If you want to make sure you don’t miss a change in value even when dropping messages, see the force_send_changed attribute. You probably don’t want to combine this attribute with send_same or else you risk missing a status change message if wasn’t one of the messages the VI decided to let through.
send_same - (default: true) By default, all signals are translated every time they are received from the CAN bus. By setting this to false, you can force a signal to be sent only if the value has actually changed. This works best with boolean and state based signals.
force_send_changed - (default: false) Meant to be used in conjunction with max_frequency, if this is true a signal will be sent regardless of the given frequency if the value has changed. This is useful for state-based and boolean states, where the state change is the most important thing and you don’t want that message to be dropped.
writable - (default: false) Set this attribute to true to allow this signal to be written back to the CAN bus by an application. OpenXC JSON-formatted messages sent back to the VI that are writable are translated back into raw CAN messages and written to the bus. By default, the value will be interpreted as a floating point number.
write_handler - (optional, default is a numerical handler) If the signal is writable and is not a plain floating point number (i.e. it is a boolean or state value), you can specify a custom function here to encode the value for a CAN messages. This is only necessary for boolean types at the moment - if your signal has states defined, we assume you need to encode a string state value back to its original numerical value.
enabled - (optional, true by default) Enable or disable all processing of a CAN signal. By default, a signal is enabled; if this flag is false, the signal will be left out of the generated source code.
Value Handlers¶
The default value handler for each signal is a simple passthrough, translating the signal’s value from engineering units to something more usable (using the defined factor and offset). Some signals require additional processing that you may wish to do within the VI and not on the host device. Other signals may need to be combined to make a composite signal that’s more meaningful to developers.
An good example is steering wheel angle. For an app developer to get a value that ranges from e.g. -350 to +350, we need to combine two different signals - the angle and the sign. If you want to make this combination happen inside the VI, you can use a custom handler.
You may also need a custom handler to return a value of a type other than float. A handler is provided for dealing with boolean values, the booleanHandler - if you specify that as your signal’s handler the resulting JSON will contain true for 1.0 and false for 0.0. If you want to translate integer state values to string names (for parsing as an enum, for example) you will need to write a value handler that returns a char*.
For this example, we want to modify the value of steering_wheel_angle by setting the sign positive or negative based on the value of the other signal (steering_angle_sign). Every time a CAN signal is received, the new value is stored in memory. Our custom handler handleSteeringWheelAngle will use that to adjust the raw steering wheel angle value. Modify the input JSON file to set the handler attribute for the steering wheel angle signal to handleSteeringWheelAngle.
Add this to the top of signals.cpp (or if using the mapping file, add it to a separate .cpp file and then add that filename to the extra_sources field):
float handleSteeringWheelAngle(CanSignal* signal, CanSignal* signals,
int signalCount, float value, bool* send) {
if(signal->lastValue == 0) {
// left turn
value *= -1;
}
return value;
}
The valid return types for value handlers are bool, float and char* - the function prototype must match one of:
char* customHandler(CanSignal* signal, CanSignal* signals, int signalCount,
float value, bool* send);
float customHandler(CanSignal* signal, CanSignal* signals, int signalCount,
float value, bool* send);
bool customhandler(cansignal* signal, cansignal* signals, int signalCount,
float value, bool* send);
where signal is a pointer to the CanSignal this is handling, signals is an array of all signals, value is the raw value from CAN and send is a flag to indicate if this should be sent over USB.
The bool* send parameter is a pointer to a bool you can flip to false if this signal value need not be sent over USB. This can be useful if you don’t want to keep notifying the same status over and over again, but only in the event of a change in value (you can use the lastValue field on the CanSignal object to determine if this is true). It’s also good practice to inspect the value of send when your custom handler is called - the normal translation workflow may have decided the data shouldn’t be sent (e.g. the value hasn’t changed and sendSame == false). Handlers are called every time a signal is received, even if send == false, so that you have the flexibility to implement custom processing that depends on receiving every data point.
A known issue with this method is that there is no guarantee that the last value of another signal arrived in the message or before/after the value you’re current modifying. For steering wheel angle, that’s probably OK - for other signals, not so much.
Mappings¶
The mappings field is an optional field allows you to move the definitions from the messages list to separate files for improved composability and readability.
For an detailed explanation of mapped message sets, see the example of a message set using mappings, see the Separate Files for Message Sets configuration example.
The mappings field must be a list of JSON objects with:
mapping - a path to a JSON file containing a single object with the key messages, containing objects formatted as the CAN Messages section documents. In short, you can pull out the messages key from the main file and throw it into a separate file and link it in here.
bus - (optional) The name of one of the defined CAN buses where these messages can be found - this value will be set for all of the messages contained the mapping file, but can be overridden by setting bus again in an individual message.
database - (optional) a path to a CAN message database associated with these mappings. Right now, XML exported from Vector CANdb++ is supported. If this is defined, you can leave the bit position, bit size, factor, offset, max and min values out of the mapping file - they will be picked up automatically from the database.
bit_numbering_inverted - (optional, defaults to the value of the message set, or true if this mapping is database-backed) This flag controls the default bit numbering for the messages contained in this mapping. Messages in the mapping can override the bit numbering by explicitly specifying their own value for this flag.
enabled - (optional, true by default) Enable or disable all processing of the CAN messages in a mapping. By default, a mapping is enabled; if this flag is false, all CAN message and signals from the mapping will be excluded from the generated source code.
Extra Sources¶
The extra_sources key is an optional list of C++ source files that should be injected into the generated signals.cpp file. These may include value handlers, message handlers, initializers or custom loopers.
Commands¶
The commands field is a mapping of arbitrary command names to functions that should be called to run arbitrary code in the VI on-demand (e.g. sending multiple CAN signals at once). The value of this attribute is a list of objects with these attributes:
name - The name of the command to be recognized on the OpenXC translated interface.
enabled - (optional, true by default) Enable or disable all processing of a command. By default, a command is enabled. If this flag is false, the command will be excluded from the generated source code.
handler - The name of a custom command handler function (that matches the CommandHandler function prototype from canutil.h) that should be called when the named command arrives over the translated VI interface (e.g. USB or Bluetooth).
bool (*CommandHandler)(const char* name, cJSON* value, cJSON* event,
CanSignal* signals, int signalCount);
Any message received from the USB host with that given command name will be passed to your handler. This is useful for situations where there isn’t a 1 to 1 mapping between OpenXC command and CAN signal, e.g. if the left and right turn signal are split into two signals instead of the 1 state-based signal used by OpenXC. You can use the sendCanSignal function in canwrite.h to do the actual data sending on the CAN bus.
Building from Source¶
For a detailed walkthrough, see Getting Started with VI Compilation.
The build process uses GNU Make and works with Linux (tested in Arch Linux and Ubuntu), OS X and Cygwin in Windows. For documentation on how to build for each platform, see the supported platform details.
Before you can compile, you will need to define your CAN messages define your CAN messages. When compiling you need to specify which board you are compiling for with the PLATFORM flag. All other flags are optional.
Getting Started with VI Compilation¶
At this point, we’ll assume you already have a VI configuration file - either one you wrote yourself, or the configuration from the tutorial.
Environment Setup¶
Before we can compile, we need to set up our development environment. Bear with it...there’s a few steps but you only have to do it once!
When you see $ it means this is a shell command - run the command after the $ but don’t include it. The example shell commands may also be prefixed with a folder name, if it needs to be run from a particular location.
Windows¶
Download Cygwin and run the installer - during the installation process, select these packages:
make, gcc-core, patchutils, git, unzip, python, check, curl, libsasl2, python-setuptools
After it’s installed, open a new Cygwin terminal and configure it to ignore Windows-style line endings in scripts by running this command:
$ set -o igncr && export SHELLOPTS
Linux¶
We need to install Git from our distribution’s package manager.
In Ubuntu:
$ sudo apt-get install git
In Arch Linux:
$ [sudo] pacman -S git
OS X¶
Open the Terminal app and install Homebrew by running this command:
$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"
Once Homebrew is installed, use it to install Git:
$ brew install git
All Platforms¶
If we’re on a network that requires an Internet proxy (e.g. at work on a corporate network) set these environment variables.
$ export http_proxy=<your proxy>
$ export https_proxy=$http_proxy
$ export all_proxy=$http_proxy
Clone the vi-firmware repository:
$ git clone https://github.com/openxc/vi-firmware
Run the bootstrap.sh script:
$ cd vi-firmware
vi-firmware/ $ script/bootstrap.sh
If there were no errors, we are ready to compile. If there are errors, try to follow the recommendations in the error messages. You may need to manually install the dependencies if your environment is not in a predictable state. The bootstrap.sh script is tested in Cygwin, OS X Mountain Lion, Ubuntu 12.04 and Arch Linux.
Testing Compilation¶
Let’s confirm the development environment is set up correctly by compiling the emulator version of the firmware. Move to the vi-firmware/src directory and compile the emulator for the reference VI from Ford:
vi-firmware/ $ cd src
vi-firmware/src $ PLATFORM=FORDBOARD make emulator
Compiling for FORDBOARD...
...
Compiled successfully for FORDBOARD running under a bootloader.
There will be a lot more output when you run this but it should end with Compiled successfully.... If you got an error, try and follow what it recommends, then look at the troubleshooting section, and finally ask for help on the Google Group.
Compiling, False Start¶
Assuming the emulator compiled successfully , we’re ready to build for an actual live CAN bus. Clean up the build to make sure the emulator doesn’t conflict:
vi-firmware/src $ make clean
and then compile for a real vehicle - just leave off the emulator option:
vi-firmware/src $ PLATFORM=FORDBOARD make
Whoa - we just a bunch of errors about an undefined reference like this:
vi_firmware.cpp:55: undefined reference to `openxc::signals::getCanBuses()'
What’s going on? The open source VI firmware doesn’t include any CAN message definitions by default. In fact there is no implementation of the C++ file signals.cpp, one that’s required to build - we need to implement the functions defined in signals.h before the firmware will compile.
Compiling with the Missing Link¶
Didn’t we say that we didn’t have to know how to write C++, though? Correct! We’ve reached the final piece - the OpenXC Python library includes a tool that will generate the C++ for signals.cpp from the JSON configuration file we wrote earlier.
From the environment setup, we already have the OpenXC Python library installed. Assuming the accelerator-config.json file is in our home directory, run this to generate a valid signals.cpp for our CAN signal:
vi-firmware/src $ openxc-generate-firmware-code --message-set ~/accelerator-config.json > signals.cpp
and then try compiling again:
vi-firmware/src $ PLATFORM=FORDBOARD make
Compiling for FORDBOARD...
15 Compiling build/FORDBOARD/signals.o
Producing build/FORDBOARD/vi-firmware-FORDBOARD.elf
Producing build/FORDBOARD/vi-firmware-FORDBOARD.bin
Compiled successfully for FORDBOARD running under a bootloader.
Success! The compiled firmware is located at build/FORDBOARD/vi-firmware-FORDBOARD.bin. We can use the same VI re-flashing procedure that we used for a binary firmware from an automaker with our custom firmware - the process is going to depend on the VI you have, so see the list of supported VIs to find the right instructions.
There’s a lot more you can do with the firmware - many more CAN signals simultaneously, raw CAN messages, advanced data transformation, etc. For complete details, see the VI Firmware docs. YOu can find the right PLATFORM value for your VI in the VI firmware supported platforms page.
Dependencies¶
If the bootstrap.sh script didn’t work, see below for more information on the dependencies.
To compile the VI firmware, you need:
- Git
- vi-firmware source code cloned with Git - not from a .zip file
- OpenXC Python library
- MPIDE
- Digilent’s USB and CAN libraries for the chipKIT
- FTDI driver
- Mini-USB cable
If instead of the chipKIT, you are compiling for the Blueboard (based on the NXP LPC1768/69), instead of MPIDE you will need:
- GCC for ARM toolchain
- OpenOCD
- JTAG programmer compatible with openocd - we’ve tested the Olimex ARM-OCD-USB programmer.
The easiest way to install these dependencies is to use the script/bootstrap.sh script in the vi-firmware repository. Run the script in Linux, Cygwin in Windows or OS X and if there are no errors you should be ready to go:
$ script/bootstrap.sh
If there are errors, continue reading in this section to install whatever piece failed manually.
Clone the repository from GitHub:
$ git clone https://github.com/openxc/vi-firmware
Some of the library dependencies are included in this repository as git submodules, so before you go further run:
$ git submodule update --init
If this doesn’t print out anything or gives you an error, make sure you cloned this repository from GitHub with git and that you didn’t download a zip file. The zip file is missing all of the git metadata, so submodules will not work.
Building the source for the VI for the chipKIT microcontroller requires MPIDE (the development environment and compiler toolchain for chipKIT provided by Digilent). Installing MPIDE can be a bit quirky on some platforms, so if you’re having trouble take a look at the installation guide for MPIDE.
Although we just installed MPIDE, building via the GUI is not supported. We use GNU Make to compile and upload code to the device. You still need to download and install MPIDE, as it contains the PIC32 compiler.
You need to set an environment variable (e.g. in $HOME/.bashrc) to let the project know where you installed MPIDE (make sure to change these defaults if your system is different!):
# Path to the extracted MPIDE folder (this is correct for OS X)
export MPIDE_DIR=/Applications/Mpide.app/Contents/Resources/Java
Remember that if you use export, the environment variables are only set in the terminal that you run the commands. If you want them active in all terminals (and you probably do), you need to add these export ... lines to the file ~/.bashrc (in Linux) or ~/.bash_profile (in OS X) and start a new terminal.
It also requires some libraries from Microchip that we are unfortunately unable to include or link to as a submodule from the source because of licensing issues:
- Microchip USB device library (download DSD-0000318 from the bottom of the Network Shield page)
- Microchip CAN library (included in the same DSD-0000318 package as the USB device library)
You can read and accept Microchip’s license and download both libraries on the Digilent download page.
Once you’ve downloaded the .zip file, extract it into the libs directory in this project. It should look like this:
- /Users/me/projects/vi-firmware/
---- libs/
-------- chipKITUSBDevice/
chipKitCAN/
... other libraries
If you’re using Mac OS X or Windows, make sure to install the FTDI driver that comes with the MPIDE download. The chipKIT uses a different FTDI chip than the Arduino, so even if you’ve used the Arduino before, you still need to install this driver.
$ pacman -S openocd
Install Homebrew. Then:
$ brew install libftdi libusb
$ brew install --enable-ft2232_libftdi openocd
Remove the Olimex sections from the FTDI kernel module, and then reload it:
$ sudo sed -i "" -e "/Olimex OpenOCD JTAG A/{N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;d;}" /System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist
$ sudo kextunload /System/Library/Extensions/FTDIUSBSerialDriver.kext/
$ sudo kextload /System/Library/Extensions/FTDIUSBSerialDriver.kext/
Download the binary version of the toolchain for your platform (Linux, OS X or Windows) from this Launchpad site.
In Arch Linux you can alternatively install the gcc-arm-none-eabi package from the AUR.
Makefile Options¶
These options are passed as shell environment variables to the Makefile, e.g.
$ PLATFORM=FORDBOARD make
Note
Try adding the -j4 flag to your calls to make to build 4 jobs in parallel - the speedup can be quite dramatic.
- PLATFORM - Select the target microcontroller platform (see the platform specific pages for valid options).
- DEBUG - Set to 1 to compile with debugging symbols and to enable debug logging. See the platform docs for details on how to read this output.
- LOG_STATS - Set to 1 to enable logging CAN message and output message statistics over the normal DEBUG output.
- BENCHTEST - Set to 1 to enable write mode on the CAN controllers so messages are ACKed. SEe the testing section for more details.
- NETWORK - By default, TCP output of OpenXC vehicle data is disabled. Set this to 1 to enable TCP output on boards that have an Network interface (only the chipKIT Max32 right now).
- NETWORK_ALLOW_RAW_WRITE - By default, raw CAN message write requests are not allowed from the network interface even if the CAN bus is configured to allow raw writes - set this to 1 to accept them.
- BLUETOOTH_ALLOW_RAW_WRITE - By default, raw CAN message write requests are not allowed from the Bluetooth interface even if the CAN bus is configured to allow raw writes - set this to 1 to accept them.
- USB_ALLOW_RAW_WRITE - By default, raw CAN message write requests are allowed from the wired USB interface (if the CAN bus is also configured to allow raw writes) - set this to 0 to block them.
- BINARY_OUTPUT - By default, the output format is JSON. Set this to 1 to use a binary output format, described more in Binary Output Format.
- BOOTLOADER - By default, the firmware is built to run on a microcontroller with a bootloader (if one is available for the selected platform), allowing you to update the firmware without specialized hardware. If you want to build to run on bare-metal hardware (i.e. start at the top of flash memory) set this to 0.
Troubleshooting¶
If the compilation didn’t work:
- Make sure the submodules are up to date - run git submodule update --init and then git status and make sure there are no modified files in the working directory.
- Did you download the .zip file of the vi-firmware project from GitHub? Use git to clone the repository instead - the library dependencies are stored as git submodules and do not work when using the zip file.
- If you get a lot of errors about undefined reference to getSignals()' and other functions, you need to make sure you defined your CAN messages - read through Firmware Configuration before trying to compile.
Supported Platforms¶
For information on how to add support for another platform, see the board support section.
Ford Reference Vehicle interface¶
The Ford Reference VI is an open source hardware implementation of a VI, and the complete documentation is available at vi.openxcplatform.com.
To build for the Ford reference VI, compile with the flag PLATFORM=FORDBOARD.
Flashing a Pre-compiled Firmware¶
Pre-compiled binaries (built with the BOOTLOADER flag enabled, see all compiler flags) are compatible with the OpenLPC USB bootloader - follow the instructions for Flashing User Code to update the vehicle interface.
Compiling¶
USB Bootloader¶
If you are running a supported bootloader, you don’t need any special programming hardware. Compile the firmware to run under the bootloader:
$ make clean
$ PLATFORM=FORDBOARD make -j4
The compiled firmware will be located at build/lpc17xx/vi-firmware-lpc17xx.bin. See reference VI programming instructions to find out how to re-flash the VI.
Bare Metal¶
Attach a JTAG adapter to your computer and the VI, then compile and flash:
$ make clean
$ export PLATFORM=FORDBOARD
$ export BOOTLOADER=0
$ make -j4
$ make flash
The config files in this repository assume your JTAG adapter is the Olimex ARM-USB-OCD unit. If you have a different unit, modify the src/lpc17xx/lpc17xx.mk Makefile to load your programmer’s OpenOCD configuration.
UART¶
The software configuration is identical to the Blueboard. The reference VI includes an RN-41 on the PCB attached to the RX, TX, CTS and RTS pins, in addition to the UART status pin.
When a Bluetooth host pairs with the RN-42 and opens an RFCOMM connection, pin 0.18 will be pulled high and the VI will being streaming vehicle data over UART.
Debug Logging¶
Logging will be on UART0, which is exposed on the bottom of the board at J3, a 5-pin ISP connector.
LED Lights¶
The reference VI has 2 RGB LEDs. If the LEDs are a dim green and red, then the firmware was not flashed properly and the board is not running.
LED A
- CAN activity detected - Blue
- No CAN activity on either bus - Off
LED B
- USB connected, Bluetooth not connected - Green
- Bluetooth connected, USB in either state - Blue
- Neither USB or Bluetooth connected - Off
Bootloader¶
The OpenLPC USB bootloader is tested and working, and enables the LPC17xx to appear as a USB drive. See the documentation in that repository for instructions on how to flash the bootloader (a JTAG programmer is required). The reference VI from Ford is pre-programmed with this bootloader.
NGX Blueboard LPC1768-H¶
To build for the Blueboard, compile with the flag PLATFORM=BLUEBOARD.
UART¶
On the LPC17xx, UART1 is used for OpenXC output at the 230000 baud rate. Like on the chipKIT, hardware flow control (RTS/CTS) is enabled, so CTS must be pulled low by the receiving device before data will be sent.
- Pin 2.0 - UART1 TX, connect this to the RX line of the receiver.
- Pin 2.1 - UART1 RX, connect this to the TX line of the receiver.
- Pin 2.2 - UART1 CTS, connect this to the RTS line of the receiver.
- Pin 2.7 - UART1 RTS, connect this to the CTS line of the receiver.
UART data is sent only if pin 0.18 is pulled high. If you are using a Bluetooth module like the BlueSMiRF from SparkFun, you need to hard-wire 5v into pin 0.18 to actually enabling UART. Other hardware implementations (like the Ford Reference VI) may be able to hook the Bluetooth connection status to this pin instead, to make the status of UART more dynamic.
Debug Logging¶
On the Blueboard LPC1768H, logging will be on UART0 at 115200 baud:
- Pin 0.2 - UART0 TX, connect this to the RX line of the receiver
- Pin 0.3 - UART0 RX, connect this to the TX line of the receiver
LED Lights¶
LEDs are not currently supported on the Blueboard.
Digilent chipKIT Max32¶
To build for the chipKIT-based Vehicle Interface, compile with the flag PLATFORM=CHIPKIT. The chipKIT is also the default platform, so the flag is optional.
The chipKIT VI supports up to 2 of the CAN1, CAN2-1 or CAN2-2 buses simultaneously.
For more details, see the chipKIT’s documentation.
For instructions on flashing a new firmware version to the chipKIT, see the chipKIT firmware programming documentation.
USB¶
The micro-USB port on the Digilent Network Shield is used to send and receive OpenXC messages. The mini-USB cable on the Max32 itself is only used for re-programming.
UART¶
On the chipKIT, UART1A is used for OpenXC output at the 230000 baud rate. Hardware flow control (RTS/CTS) is enabled, so CTS must be pulled low by the receiving device before data will be sent. There are a few tricky things to watch out for with UART (i.e. Bluetooth) output on the chipKIT, so make sure to read this entire section.
UART1A is also used by the USB-Serial connection, so in order to flash the PIC32, the Tx/Rx lines must be disconnected. Ideally we could leave that UART interface for debugging, but there are conflicts with all other exposed UART interfaces when using flow control.
- Pin 0 - U1ARX, connect this to the TX line of the receiver.
- Pin 1 - U1ATX, connect this to the RX line of the receiver.
- Pin 18 - U1ARTS, connect this to the CTS line of the receiver.
- Pin 19 - U1ACTS, connect this to the RTS line of the receiver.
UART data is sent only if pin A1 is pulled low (to ground). If you are using a Bluetooth module like the BlueSMiRF from SparkFun, you need to hard-wire GND into this pin to actually enabling UART. To disable UART, pull A1 high (hard-wire to 5v) or leave it floating.
An additional item to consider when using UART: typically you will want to rig the chipKIT to be self-powered (either from an external power source or the vehicle) if you’re going to use UART for adding Bluetooth support. There’s not much point in being wireless if you still need power from USB.
In that case, move the power jumper from the 5v input on the Network Shield to A0 (analog input 0). Instead of using 5v to power the board, the firmware can use it to detect if USB is actually attached or not. The benefit of this is that if you connect USB, then disconnect it, we can detect that in the firmware and stop wasting time trying to send data over USB. This will dramatically increase the throughput over UART.
Debug Logging¶
On the chipKIT Max32, logging will be on UART2 (Pin 16 - Tx, Pin 17 - Rx) at 115200 baud (if the firmware was compiled with DEBUG=1).
LED Lights¶
The chipKIT has 1 user controllable LED. When CAN activity is detected, the LED will be enabled (it’s green).
Compiling and Flashing¶
Attach the chipKIT to your computer with a mini-USB cable, cd into the src subdirectory, build and upload to the device.
$ make clean
$ make
$ make flash
If the flash command can’t find your chipKIT, you may need to set the SERIAL_PORT variable if the serial emulator doesn’t show up as /dev/ttyUSB* in Linux, /dev/tty.usbserial* in Mac OS X or com3 in Windows. For example, if the chipKIT shows up as /dev/ttyUSB4:
$ SERIAL_PORT=/dev/ttyUSB4 make flash
and if in Windows it appeared as COM4:
$ SERIAL_PORT=com4 make flash
IDE Support¶
It is possible to use an IDE like Eclipse to edit and compile the project.
- Follow the directions in the above “Installation” section.
- Install Eclipse with the CDT project
- In Eclipse, go to File -> Import -> C/C++ -> Existing Code as Makefile Project and then select the vi-firmware/src folder.
- In the project’s properties, under
C/C++ General -> Paths and Symbols, add these to the include
paths for C and C++:
- ${MPIDE_DIR}/hardware/pic32/compiler/pic32-tools/pic32mx/include
- ${MPIDE_DIR}/hardware/pic32/cores/pic32
- /src/libs/CDL/LPC17xxLib/inc (add as a “workspace path”)
- /src/libs/chipKITCAN (add as a “workspace path”)
- /src/libs/chipKITUSBDevice (add as a “workspace path”)
- /src/libs/chipKITUSBDevice/utility (add as a “workspace path”)
- /src/libs/chipKITEthernet (add as a “workspace path”)
- /usr/include (only if you want to use the test suite, which requires the check C library)
- In the same section under Symbols, if you are building for the chipKIT define a symbol with the name __PIC32__
- In the project folder listing, select
Resource Configurations -> Exclude from Build for these
folders:
- src/libs
- build
If you didn’t set up the environment variables from the Installation section (e.g. MPIDE_HOME), you can also do that from within Eclipse in C/C++ project settings.
There will still be some errors in the Eclipse problem detection, e.g. it doesn’t seem to pick up on the GCC __builtin_* functions, and some of the chipKIT libraries are finicky. This won’t have an effect on the actual build process, just the error reporting.
Bootloader¶
All stock chipKITs are programmed with a compatible bootloader at the factory. The PIC32 avrdude bootloader is also tested and working and allows flashing over USB with avrdude.
CrossChasm C5 Interface¶
CrossChasm’s C5 OBD interface is compatible with the OpenXC VI firmware. To build for the C5, compile with the flag PLATFORM=CROSSCHASM_C5.
CrossChasm has made the C5 available for purchase from their website, and it comes pre-loaded with the correct bootloader, so you don’t need any additional hardware to load the OpenXC firmware.
The C5 connects to the CAN1 bus pins on the OBD-II connector.
Flashing a Pre-compiled Firmware¶
Assuming your C5 has the bootloader already flashed, once you have the USB cable attached to your computer and to the C5, follow the same steps to upload as for the chipKIT Max32.
The C5 units offered directly from the CrossChasm website are pre-programmed with the bootloader.
Bootloader¶
The C5 can be flashed with the same PIC32 avrdude bootloader, as the chipKIT.
The OpenXC fork of the bootloader (the previous link) defines a CROSSCHASM_C5 configuration that exposes a CDC/ACM serial port function over USB. Once the bootloader is flashed, there is a 5 second window when the unit powers on when it will accept bootloader commands.
In Linux and OS X it will show up as something like /dev/ACM0, and you can treat this just as if it were a serial device.
In Windows, you will need to install the stk500v2.inf <https://raw.github.com/openxc/PIC32-avrdude-bootloader/master/Stk500v2.inf> driver before the CDC/ACM modem will show up - download that file, right click and choose Install. The C5 should now show up as a COM port for for 5 seconds on bootup.
The C5 units offered directly from the CrossChasm website are pre-programmed with the bootloader.
If you need to reflash the bootloader yourself, a ready-to-go .hex file is available in the GitHub repository and you can flash it with MPLAB IDE/IPE and an ICSP programmer like the Microchip PICkit 3. You can also build it from source in MPLAB by using the CrossChasm C5 configuration.
Compiling¶
The instructions for compiling from source are identical to the chipKIT Max32 except that PLATFORM=CROSSCHASM_C5 instead of CHIPKIT.
If you will not be using the avrdude bootloader and will be flashing directly via ICSP, make sure to also compile with BOOTLOADER=0 to enable the program to run on bare metal.
USB¶
The micro-USB port on the board is used to send and receive OpenXC messages.
UART¶
On the C5, UART1A is used for OpenXC output at the 230000 baud rate. Hardware flow control (RTS/CTS) is enabled, so CTS must be pulled low by the receiving device before data will be sent.
TODO add pinout of expansion header, probably a picture
UART data is sent only if pin 0.58 (or PORTB BIT 4, RB4) is pulled high (to 5vv). If you are using a Bluetooth module like the BlueSMiRF from SparkFun, you need to hard-wire 5v into this pin to actually enabling UART. To disable UART, pull this pin low or leave it floating.
Debug Logging¶
On the C5, logging is on UART3A at 115200 baud (if the firmware was compiled with DEBUG=1).
LED Lights¶
The C5 has 2 user controllable LEDs. When CAN activity is detected, the green LED will be enabled. When USB or Bluetooth is connected, the blue LED will be enabled.
Troubleshooting PIC32 Boards¶
- No data received over USB?
- If you have UART enabled for a while, then disconnect the UART receiver (i.e. pull the status pin low and stop touching RTS/CTS), it can cause the firmware to block trying to write data to UART. Power cycle the board or leave the UART receiver attached even if nobody is reading data (i.e. keep the CTS/RTS lines active).
- USB data arriving in bursts?
- Are you also reading data over UART, or do you have something pulling the UART connect pin high? It’s not always possible to read both USB and UART at full data rates at the same time.
Testing¶
Windows USB Device Driver¶
If you want to send and receive vehicle data in Windows via USB, you must install the VI Windows Driver <https://github.com/openxc/vi-windows-driver>.
Python Library¶
The OpenXC Python library, in particular the openxc-dashboard tool, is useful for testing the VI with a regular computer, to verify the data received from a vehicle before introducing an Android device. A quick “smoke test” using the Python tools is described in the Getting Started Guide for Python developers at the OpenXC website.
Keep in mind when bench testing - the VI will go into a low power suspend mode if no CAN activity has been detected for 30 seconds. If you compile with the DEBUG flag, it will not suspend.
Debugging information¶
Viewing Debugging data¶
To view debugging information, first compile the firmware with the debugging flag:
$ make clean
$ DEBUG=1 make
$ make flash
When compiled with DEBUG=1, two things happen:
- Debug symbols are available in the .elf file generated in the build directory.
- Log messages will be output over a UART port (no hardware flow control is required) - see supported platforms for details.
View this output using an FTDI cable and any of the many available serial terminal monitoring programs, e.g. screen, minicom, etc.
Emulator¶
The repository includes a rudimentary vehicle emulator version of the firmware:
$ make clean
$ make emulator
The emulator generates fakes values for many OpenXC signals and sends out translated OpenXC messages as if it were plugged into a real vehicle.
Test Suite¶
The non-embedded platform specific code in this repository includes a unit test suite. It’s a good idea to run the test suite before committing any changes to the git repository.
Dependencies¶
The test suite uses the check library.
Ubuntu¶
$ sudo apt-get install check
Arch Linux¶
$ sudo pacman -S check
Running the Suite¶
vi-firmware/src $ make clean && make test -s
Advanced Reference¶
Advanced Firmware Features¶
Low-level CAN Features¶
The OpenXC message format specification defines a “raw” CAN message type. You can configure the VI firmware to output raw CAN messages using this format.
For example, this JSON configuration will output all CAN messages received on a high speed bus connecetd to the CAN1 controller:
{ "name": "passthrough",
"buses": {
"hs": {
"controller": 1,
"raw_can_mode": "unfiltered",
"speed": 500000
}
}
}
The only change from a typical configuration is the addition of the raw_can_mode attribute to the bus, set to unfiltered. When using the raw CAN configuration, there’s no need to configure any messages or signals.
You may use both translated and raw output simultaneously - the 2 message types will be interleaved on the output interfaces, so you’ll need to check for the right fields before reading the output.
If you’re only interested in a few CAN messages, you can send a filtered set of raw messages. Change the raw_can_mode to filtered and add the messages ID’s you want:
{ "name": "passthrough",
"buses": {
"hs": {
"controller": 1,
"raw_can_mode": "filtered",
"speed": 500000
}
},
"messages": {
"0x21": {
"bus": "hs"
}
}
}
This will read and send the message with ID 0x21 only.
The raw_can_mode flag can be applied to all active CAN buses at once by defining it at the top level of the configuration. For example, this configuration will enable unfiltered raw CAN output from 2 buses simultaneously:
{ "name": "passthrough",
"raw_can_mode": "filtered",
"buses": {
"hs": {
"controller": 1,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
}
}
When defined at the top level, the raw_can_mode can be overridden by any of the individual buses (e.g. hs could inherit the unfiltered setting but ms could override and set it to filtered).
There are yet more ways to configure and control the low-level output (e.g. limiting the data rate as to not overwhelm the VI’s output channels) - see the raw configuration examples for more information.
Writing to CAN¶
By default, the CAN controllers on the VI are configured to be in a read-only mode - they won’t even send ACK frames. If you configure one of the buses to be raw_writable in the firmware configuration, the controller will be write-enabled for raw CAN messages, e.g.:
{ "name": "passthrough",
"buses": {
"hs": {
"controller": 1,
"raw_can_mode": "unfiltered",
"raw_writable": true,
"speed": 500000
}
}
}
With a writable bus, you can send CAN messages (in the OpenXC “raw” message JSON format) to the VI’s input interfaces (e.g. USB, Bluetooth) and they’ll be written out to the bus verbatim.
Obviously this is an advanced feature with many security and safety implications. The CAN controllers are configured as read-only by default for good reason - make sure you understand the risks before enabling raw CAN writes.
For additional security, by default the firmware will not accept raw CAN write requests from remote interfaces even if raw_writable is true. Write requests from Bluetooth and network connections will be ignored - only USB is allowed by default. If you wish to write raw CAN messages wirelessly (and understand that those words make security engineers queasy), compile with the NETWORK_ALLOW_RAW_WRITE or BLUETOOTH_ALLOW_RAW_WRITE flags (see all compile-time flags).
The raw CAN write support is intended soley for protoyping and advanced development work - for any sort of consumer-level app, it’s much better to use writable translated messages.
Binary Output Format¶
For applications that need to maximize the amount of data transferred from the vehicle, the firmware includes an experimental binary output format. Instead of JSON, it uses Google Protocol Buffers to more efficiently pack the data. Translated-style OpenXC messages are on average 30% smaller when using protobufs instead of JSON, and raw messages are around 60% smaller. This space savings comes at the cost of decreased flexibility and increased complexity in reciving and parsing the data.
The firmware does not currently support receiving binary-encoded messages - CAN write requests must still be sent in JSON.
This output format is supported by the official OpenXC Android library and OpenXC Python library, both of which will auto-detect the output format being used by an attached VI. The protobuf objects are defined in an experimental branch in the openxc-message-format repository if you wish to add support to another language or environment.
The output stream uses the common delimiting technique of writing the length of each protobuf message before the message itself in the stream.
Compiling with Binary Output¶
To use the binary output format, compile with the BINARY_OUTPUT flag (see all compile-time flags).
Motivation¶
The default output format encodes data from the vehicle as JSON, using the OpenXC message format. There are JSON formats for both translated and raw messages. The format is human-readable and very simple to parse, but it’s not very compact.
For one, it representes everything in human-readable text, so for example, the number 999 (which could be stored in only 10 bits) takes 3 bytes - more than twice the space.
This isn’t an issue for most users, as the output interfaces have plenty of bandwidth (USB is around 125-160 KB/s and the popular RN-42 Bluetooth tops out at 23KB/s) and the data rate of most release VI firwmares is on the order of only 3-6KB/s using JSON.
However, some applications need to pull a lot more data out of the car, perhaps by reading raw CAN messages or reading many signals at high frequencies. These can quickly overwhelm the output pipe using JSON.
CAN Bench Testing¶
Normally, the CAN controllers are initialized in a “listen only” mode. They are configured as writable only if using raw CAN passthrough with a CanBus that is marked writable or if a CanSignal is writable.
This works fine in a vehicle, but when testing on a bench with a simulated CAN network (e.g. another VI sending CAN messages directly to your VI under test), there’s a problem - every CAN message must be acknowledged by the controller, and in “listen only” mode it does not send these ACKs. With nobody ACKing on the bus, the messages never propagate up from the network layer to the VI firmware.
When compiling the VI firmware to use to receive data on a bench test CAN bus, use the BENCHTEST flag to make sure CAN messages are ACked:
$ make clean
$ BENCHTEST=1 make
The CAN controllers will also be write-enabled if you use the DEBUG flag, or the transmitter Makefile target. The BENCHTEST flag is useful if you want to bench test normal, non-debug operation but still send ACKs.
Simulating a CAN Network¶
Speaking of CAN bus bench testing, it’s possible to create a 2-node CAN network on your desk with 2 VIs to test your firmware. This is especially handy for prototyping new translated signals, since you can record a raw CAN bus trace from a vehicle and do all of your work inside where it’s warm and comfortable.
Requirements¶
- 2 VIs
- A female-female OBD-II cable, or whatever combination of cable ends you need to connect one VI directly to another
- 120 ohm resistors across the H/L CAN wires in that cable or...
- If one VI is a chipKIT, there are jumpers you can flip to enable 120 ohm resistors on each CAN controller (only one of the VIs needs the resistors)
- If one VI is a Ford prototype, that has the termination resistors enabled by default on version 1.0. In an updated version that may change, as this is actually a mistake - they should have been only available if you short a solder jumper.
We recommend building your own cable with female OBD-II connectors from Molex (with the matching crimp connectors and a crimper wired together with 120ohm resistors crimped directly into one end of the cable (some pictures of a wired cable). We also recommend including the 12v and a ground wire, and shaving off a little ring of insulation on each at one end of the connector so you can inject bus power if needed. Make sure to offset the sections of shaved off insulation by a half inch so they don’t short.
Alternatively, there are OBD-II splitter cables available online that may also work, but these have not been tested.
Preparing the Transmitter¶
- Decide which of the VIs is the transmitter, and which is the receiver (the receiver is most like your VI under test).
- Follow the normal firmware build process (the CAN signals defined don’t matter, just the bus speeds) but instead of just make run make transmitter. This changes 2 things: it configures the CAN controllers to be write-enabled and changes the USB product ID from 1 to 2, so you can address the transmitter VI independently from the receiver.
Preparing the Receiver¶
Compile the VI firmware for the receiver as usual, but instead of just make, run BENCHTEST=1 make This configures the CAN controllers as write-enabled, so that your VI under test can ACK the CAN messages. If nobody on the bus ACKs, you will receive nothing. In a car there are usually many other things ACKing, so we can be “listen only”.
Sending Data on the Bus¶
You need a pre-recorded raw CAN trace file from a vehicle. To create one, compile the firmware in the passthrough mode (see the low-level CAN docs) and flash a VI, then use openxc-dump to receive the raw messages - point that at a file on disk to create a trace.
With a raw CAN trace file named raw-can-trace.json:
$ openxc-control writeraw --usb-product 2 -f raw-can-trace.json
That will send all of the message in the file, correctly spaced according to the timestamps. If you want to send continuously, you can loop the file like this:
$ watch -n 0 openxc-control writeraw --usb-product 2 -f raw-can-trace.json
Receiving Data¶
Receive data as usual from the VI under test, as if it was in real car.
Board Support¶
The code base is expanding very organically from supporting only one board to supporting multiple architectures and board variants. The strategy we have now:
- Switch between “platforms” with the PLATFORM flag - a platform encapsulates a micro architecture and a board variant.
- Implement different architecture-specific code in a subfolder for the micro
- Switch pins for board variants in in those same architecture-specific files (like in lights.cpp)
Output Interfaces & Format¶
The OpenXC message format is specified and versioned separately from any of the individual OpenXC interfaces or libraries, in the OpenXC Message Format repository.
UART Output¶
You can optionally receive the output data over a UART connection in addition to USB. The data format is the same as USB - a stream of newline separated JSON objects.
In the same way that you can send OpenXC writes over USB using the OUT direction of the USB endpoint, you can send identically formatted messages in the opposite direction on the serial device - from the host to the VI. They’ll be processed in exactly the same way. These write messages are accepted via serial even if USB is connected. One important difference between reads and writes - write JSON messages must be separated by a NULL character instead of a newline.
For details on your particular platform like the pins and baud rate, see the supported platforms.
USB Device Driver¶
Most users do not need to know the details of the device driver, but for reference it is documented here.
The VI initializes its USB 2.0 controller as a USB device with three endpoints. The Android tablet or computer you connect to the translator acts as the USB host, and must initiate all transfers.
Endpoint 0¶
This is the standard USB control transfer endpoint. The VI has a few control commands:
Version¶
Version control command: 0x80
The host can retrieve the version of the VI using the 0x80 control request. The data returned is a string containing the software version of the firmware and the configured vehicle platform in the format:
Version: 1.0 (c346)
where 1.0 is the software version and c346 is the configured vehicle.
Reset¶
Reset control command: 0x81
The CAN transceivers can be re-initialized by sending the 0x81 control request. This command was introduced to work around a bug that caused the VI to periodically stop responding. The bug still exists, but there are now workarounds in the code to automatically re-initialize the transceivers if they stop receiving messages.
Endpoint 1 IN¶
Endpoint 1 is configured as a bulk transfer endpoint with the IN direction (device to host). OpenXC JSON messages read from the vehicle are sent to the host via IN transactions. When the host is ready to receive, it should issue a request to read data from this endpoint. A larger sized request will allow more messages to be batched together into one USB request and give high overall throughput (with the downside of introducing delay depending on the size of the request).
Endpoint 1 OUT¶
OpenXC JSON messages created by the host to send to the vehicle (i.e. to write to the CAN bus) are sent via OUT transactions. The CAN translator is prepared to accept writes from the host as soon as it initializes USB, so they can be sent at any time. The messages must be separated by a NULL character.
There is no special demarcation on these messages to indicate they are writes - the fact that they are written in the OUT direction is sufficient. Write messages must be no more than 4 USB packets in size, i.e. 4 * 64 = 256 bytes.
In the same way the VI is pre-configured with a list of CAN signals to read and parse from the CAN bus, it is configured with a whitelist of messages and signals for which to accept writes from the host. If a message is sent with an unlisted ID it is silently ignored.
Contributing¶
Contributing¶
Please see our Contributing Guide.
Mailing list¶
For discussions about the usage, development, and future of OpenXC, please join the OpenXC mailing list.
Bug tracker¶
If you have any suggestions, bug reports or annoyances please report them to our issue tracker at http://github.com/openxc/vi-firmware/issues/
Authors and Contributors¶
A complete list of all authors is stored in the repository - thanks to everyone for the great contributions.
Python Library¶
The OpenXC Python library, in particular the openxc-dashboard tool, is useful for testing the VI with a regular computer, to verify the data received from a vehicle before introducing an Android device. Documentation for this tool (and the list of required dependencies) is available on the OpenXC vehicle interface testing page.
Android Library¶
The OpenXC Android library is the primary entry point for new OpenXC developers. More information on this library is available at in the applications section of the OpenXC website.
License¶
Copyright (c) 2012-2013 Ford Motor Company
Licensed under the BSD license.
This software depends on other open source projects, and a binary distribution may contain code covered by other licenses.