CAN Bus Integration: Difference between revisions
(added main content and script framework) |
|||
Line 1: | Line 1: | ||
=Introduction= | =Introduction= | ||
Mapping an existing CAN bus system can be easy if documentation is available. ECU manufacturers are especially keen on providing documentation as they want their devices to easily connect to other systems, such as data systems and dashboards. | |||
If documentation isn't available, the problem is just more interesting. Fortunately the CAN bus system is open in terms of it's messaging format and provides simple 8 byte message packets with a identifier, much like an internet address, but a smaller number. | |||
==Researching CAN Bus protocol== | ==Researching CAN Bus protocol== | ||
If you are working to integrate an ECU, first ask the manufacturer for their CAN bus specification. This documentation will provide a breakdown of channels - such as RPM, temperatures and pressures and describe how they appear in the CAN message stream. A good example is the [http://www.msextra.com/doc/pdf/Megasquirt_CAN_Broadcast.pdf Megasquirt CAN Broadcast Specification] | |||
===CAN Bus documentation=== | |||
This document will show for each channel the following information: | |||
* '''Channel''': (RPM, Engine Temperature, Throttle Position, etc) | |||
* '''CAN Identifier''': The CAN Identifier for each channel. This may appear in either 11 or the extended 29 bit format. Typically 11 bits are used. | |||
* '''Offset''': where the data starts within the 8 byte CAN message. An offset of 0 means the data starts on the first byte. | |||
* '''Length''': Typically 1 or two bytes | |||
* '''Scaling''': The raw value from the message is often scaled to a real world value. Typically this is provided as a multiplier and adder value. | |||
===MSB or LSB mode=== | |||
Additionally, the manufacturer will specify if multi-byte (16, 24 or 32 bit) values are presented in MSB (Most Significant Byte) or LSB (Least Significant Byte) mode. | |||
MSB mode data will have the high byte presented first in the data packet and is typically the most common format. | |||
Example: | |||
* Decimal value: 1000 | |||
* Hex Value in MSB: 0x03E8 | |||
* Hex Value in LSB: 0xE803 | |||
If provided in a CAN message, it might show up as follows in this hypothetical message: | |||
MSB format: | |||
* Offset 1 | |||
* Length 2 | |||
* Data: | |||
(Hex) [xx] [03] [E8] [xx] [xx] [xx] [xx] [xx] | |||
(Decimal) [xx] [3] [232] [xx] [xx] [xx] [xx] [xx] | |||
LSB format: | |||
* Offset 1 | |||
* Length 2 | |||
* Data: | |||
(Hex) [xx] [E8] [03] [xx] [xx] [xx] [xx] [xx] | |||
(Decimal) [xx] [232] [3] [xx] [xx] [xx] [xx] [xx] | |||
==Reverse Engineering== | ==Reverse Engineering== | ||
Reverse engineering is possible, providing you can log a data stream from the device and compare it against direct measurements. | |||
===Common Approach=== | |||
* Log all messages from the CAN bus | |||
* Directly measure or observe a sensor channel and compare it against changes in the CAN bus data, watching for patterns. | |||
===Example=== | |||
An example for decoding RPM: To detect which CAN message contains RPM, log CAN messages while simultaneously directly logging RPM, synchronizing the messages and the direct RPM measurement in time. Observing CAN message data changing in sync with direct RPM measurements will pin point which messages and data values contain RPM. | |||
===Logging Data=== | |||
You can use a commercial CAN data logger, or simply use RaceCapture/Pro's [[CAN_Bus_logger|CAN bus logger script]] to output all messages to the logging window. | |||
=Mapping CAN data to Virtual Channels= | |||
==Reading a CAN message== | ==Reading a CAN message== | ||
You can use the Lua scripting to read and process data from a CAN message. See the [[RaceCapturePro_Lua_Scripting#CAN_Bus_functions|Lua Scripting reference]] for a list of CAN bus functions. | |||
Example reading and printing the ID and first byte of a CAN message: | |||
id, ext, data = rxCAN(0, 100) | |||
print("ID: " ..id .."Data: " ..data[1]) | |||
==Mapping to a Virtual Channel== | ==Mapping to a Virtual Channel== | ||
You can create a virtual channel by using the ''[[RaceCapturePro_Lua_Scripting#Virtual_Channels|addChannel()]]'' lua function. The typical approach involves: | |||
* Set up the channel. Example "EngineTemp" | |||
* Read a CAN message | |||
* Extract a value from the data fields | |||
* Scale the extracted value to a real world value (Example: Degrees F) | |||
* Set the virtual channel with the scaled value | |||
* Example: Set '''MyChannel''' from CAN ID 1234. Data is in the first byte of the message. | |||
channelId = addChannel("MyChannel", 10) | |||
setTickRate(10) | |||
function onTick() | |||
id, ext, data = rxCAN(0) | |||
if id == 1234 then | |||
setChannel(channelId, data[1]) | |||
end | |||
end | |||
==CAN integration framework== | |||
Below is a script framework to ease mapping of CAN bus data to virtual channels. You can copy the script and modify as you need. This example showcases mapping some of the BMW E46 CAN bus channels: | |||
<pre> | |||
--This example configured for E46 CAN | |||
--how frequently we poll for CAN messages | |||
tickRate = 30 | |||
--the CAN baud rate | |||
CAN_baud = 500000 | |||
--CAN channel to listen on. 0=first CAN channel, 1=second | |||
CAN_chan = 0 | |||
--1 for Big Endian (MSB) mode; 0 for Little Endian mode (LSB) | |||
be_mode = 1 | |||
--add your virtual channels here | |||
tpsId = addChannel("TPS", 10, 0, 0, 100, "%") | |||
tempId = addChannel("EngineTemp", 1, 0, 0, 200, 'C') | |||
oilTempId = addChannel("OilTemp", 1, 0, 0, 200, 'C') | |||
rpmId = addChannel("RPM", 10, 0, 0, 10000) | |||
--customize here for CAN channel mapping | |||
--format is: [CAN Id] = function(data) map_chan(<channel id>, data, <CAN offset>, <CAN length>, <multiplier>, <adder>) | |||
CAN_map = { | |||
[809] = function(data) map_chan(tpsId, data, 6, 1 , 0.392156863, 0) map_chan_le(tempId, data, 2, 1 , 0.75, -48.373) end, | |||
[1349] = function(data) map_chan(oilTempId, data, 5, 1, 1, -48.373) end, | |||
[790] = function(data) map_chan(rpmId, data, 3, 2, 0.15625, 0) end | |||
} | |||
function onTick() | |||
processCAN(CAN_chan) | |||
end | |||
--===========do not edit below=========== | |||
function processCAN(chan) | |||
repeat | |||
local id, e, data = rxCAN(chan) | |||
if id ~= nil then | |||
local map = CAN_map[id] | |||
if map ~= nil then | |||
map(data) | |||
end | |||
end | |||
until id == nil | |||
end | |||
--Map CAN channel, little endian format | |||
function map_chan_le(cid, data, offset, len, mult, add) | |||
offset = offset + 1 | |||
local value = 0 | |||
local shift = 1 | |||
while len > 0 do | |||
value = value + (data[offset] * shift) | |||
shift = shift * 256 | |||
offset = offset + 1 | |||
len = len - 1 | |||
end | |||
setChannel(cid, (value * mult) + add) | |||
end | |||
--Map CAN channel, big endian format | |||
function map_chan_be(cid, data, offset, len, mult, add) | |||
offset = offset + 1 | |||
local value = 0 | |||
while len > 0 do | |||
value = (value * 256) + data[offset] | |||
offset = offset + 1 | |||
len = len - 1 | |||
end | |||
setChannel(cid, (value * mult) + add) | |||
end | |||
= | map_chan = (be_mode == 1) and map_chan_be or map_chan_le | ||
initCAN(CAN_chan, CAN_baud) | |||
setTickRate(tickRate) | |||
</pre> |
Revision as of 07:08, 26 February 2015
Introduction
Mapping an existing CAN bus system can be easy if documentation is available. ECU manufacturers are especially keen on providing documentation as they want their devices to easily connect to other systems, such as data systems and dashboards.
If documentation isn't available, the problem is just more interesting. Fortunately the CAN bus system is open in terms of it's messaging format and provides simple 8 byte message packets with a identifier, much like an internet address, but a smaller number.
Researching CAN Bus protocol
If you are working to integrate an ECU, first ask the manufacturer for their CAN bus specification. This documentation will provide a breakdown of channels - such as RPM, temperatures and pressures and describe how they appear in the CAN message stream. A good example is the Megasquirt CAN Broadcast Specification
CAN Bus documentation
This document will show for each channel the following information:
- Channel: (RPM, Engine Temperature, Throttle Position, etc)
- CAN Identifier: The CAN Identifier for each channel. This may appear in either 11 or the extended 29 bit format. Typically 11 bits are used.
- Offset: where the data starts within the 8 byte CAN message. An offset of 0 means the data starts on the first byte.
- Length: Typically 1 or two bytes
- Scaling: The raw value from the message is often scaled to a real world value. Typically this is provided as a multiplier and adder value.
MSB or LSB mode
Additionally, the manufacturer will specify if multi-byte (16, 24 or 32 bit) values are presented in MSB (Most Significant Byte) or LSB (Least Significant Byte) mode.
MSB mode data will have the high byte presented first in the data packet and is typically the most common format.
Example:
- Decimal value: 1000
- Hex Value in MSB: 0x03E8
- Hex Value in LSB: 0xE803
If provided in a CAN message, it might show up as follows in this hypothetical message:
MSB format:
- Offset 1
- Length 2
- Data:
(Hex) [xx] [03] [E8] [xx] [xx] [xx] [xx] [xx] (Decimal) [xx] [3] [232] [xx] [xx] [xx] [xx] [xx]
LSB format:
- Offset 1
- Length 2
- Data:
(Hex) [xx] [E8] [03] [xx] [xx] [xx] [xx] [xx] (Decimal) [xx] [232] [3] [xx] [xx] [xx] [xx] [xx]
Reverse Engineering
Reverse engineering is possible, providing you can log a data stream from the device and compare it against direct measurements.
Common Approach
- Log all messages from the CAN bus
- Directly measure or observe a sensor channel and compare it against changes in the CAN bus data, watching for patterns.
Example
An example for decoding RPM: To detect which CAN message contains RPM, log CAN messages while simultaneously directly logging RPM, synchronizing the messages and the direct RPM measurement in time. Observing CAN message data changing in sync with direct RPM measurements will pin point which messages and data values contain RPM.
Logging Data
You can use a commercial CAN data logger, or simply use RaceCapture/Pro's CAN bus logger script to output all messages to the logging window.
Mapping CAN data to Virtual Channels
Reading a CAN message
You can use the Lua scripting to read and process data from a CAN message. See the Lua Scripting reference for a list of CAN bus functions.
Example reading and printing the ID and first byte of a CAN message:
id, ext, data = rxCAN(0, 100) print("ID: " ..id .."Data: " ..data[1])
Mapping to a Virtual Channel
You can create a virtual channel by using the addChannel() lua function. The typical approach involves:
- Set up the channel. Example "EngineTemp"
- Read a CAN message
- Extract a value from the data fields
- Scale the extracted value to a real world value (Example: Degrees F)
- Set the virtual channel with the scaled value
- Example: Set MyChannel from CAN ID 1234. Data is in the first byte of the message.
channelId = addChannel("MyChannel", 10) setTickRate(10) function onTick() id, ext, data = rxCAN(0) if id == 1234 then setChannel(channelId, data[1]) end end
CAN integration framework
Below is a script framework to ease mapping of CAN bus data to virtual channels. You can copy the script and modify as you need. This example showcases mapping some of the BMW E46 CAN bus channels:
--This example configured for E46 CAN --how frequently we poll for CAN messages tickRate = 30 --the CAN baud rate CAN_baud = 500000 --CAN channel to listen on. 0=first CAN channel, 1=second CAN_chan = 0 --1 for Big Endian (MSB) mode; 0 for Little Endian mode (LSB) be_mode = 1 --add your virtual channels here tpsId = addChannel("TPS", 10, 0, 0, 100, "%") tempId = addChannel("EngineTemp", 1, 0, 0, 200, 'C') oilTempId = addChannel("OilTemp", 1, 0, 0, 200, 'C') rpmId = addChannel("RPM", 10, 0, 0, 10000) --customize here for CAN channel mapping --format is: [CAN Id] = function(data) map_chan(<channel id>, data, <CAN offset>, <CAN length>, <multiplier>, <adder>) CAN_map = { [809] = function(data) map_chan(tpsId, data, 6, 1 , 0.392156863, 0) map_chan_le(tempId, data, 2, 1 , 0.75, -48.373) end, [1349] = function(data) map_chan(oilTempId, data, 5, 1, 1, -48.373) end, [790] = function(data) map_chan(rpmId, data, 3, 2, 0.15625, 0) end } function onTick() processCAN(CAN_chan) end --===========do not edit below=========== function processCAN(chan) repeat local id, e, data = rxCAN(chan) if id ~= nil then local map = CAN_map[id] if map ~= nil then map(data) end end until id == nil end --Map CAN channel, little endian format function map_chan_le(cid, data, offset, len, mult, add) offset = offset + 1 local value = 0 local shift = 1 while len > 0 do value = value + (data[offset] * shift) shift = shift * 256 offset = offset + 1 len = len - 1 end setChannel(cid, (value * mult) + add) end --Map CAN channel, big endian format function map_chan_be(cid, data, offset, len, mult, add) offset = offset + 1 local value = 0 while len > 0 do value = (value * 256) + data[offset] offset = offset + 1 len = len - 1 end setChannel(cid, (value * mult) + add) end map_chan = (be_mode == 1) and map_chan_be or map_chan_le initCAN(CAN_chan, CAN_baud) setTickRate(tickRate)