00:00 |
- In this part of the course, we're going to move on from the pure theory and have a look at the more practical side of working with CAN systems.
|
00:06 |
While the background theory is important to have an understanding of, I think we can all agree that it is pretty dry to go through.
|
00:13 |
This section will bring those important theory lessons into the real world, demonstrating how they're applied in practice.
|
00:18 |
In this module, we're going to have an in depth look at a single CAN data frame, picking it apart to show how we define and use it to transmit data on the bus.
|
00:28 |
The example we'll have a look at is transmitting some basic engine operating parameters on the bus so they can be read by a dash and displayed to the driver.
|
00:37 |
A quick note here, you'll notice I haven't said that we want to transmit these parameters specifically to the dash.
|
00:44 |
We always need to remember that CAN is a broadcast network so we can't define a specific recipient for the data.
|
00:50 |
All we can do is program one of our modules to transmit data onto the bus and program another to read it from the bus.
|
00:57 |
But every connected device is also able to read that data if it finds it useful.
|
01:02 |
This usually works in our favour because it's quite likely other devices on the bus, like a dedicated logger, shift light module or lambda controller might also want access to this data.
|
01:12 |
They can all read it from the same transmitted message and this is much more efficient as now multiple devices have the data they need with only a single message being transmitted.
|
01:21 |
However to keep this first example simple, we're only going to be thinking about an engine control ECU transmitting some data on the bus and the dash display reading that data.
|
01:31 |
The data we'd like to transmit will be engine speed, coolant temperature, oil pressure, battery voltages and the statuses of a check engine light, alternator charge light and a traction control light.
|
01:44 |
The first thing we should do is determine the PID of the CAN message.
|
01:48 |
This is usually pretty arbitrary but we need to make sure that nothing else on the bus is trying to transmit any data on the same PID.
|
01:56 |
We should also remember that the lower we make this PID, the higher the priority of that message.
|
02:02 |
As this could be considered reasonably critical data, we're going to give it a relatively low PID of 100 or 0x64 if we're putting that in hexadecimal.
|
02:12 |
The data frame can have a maximum of 8 bytes of data within it and we're going to have to determine how we distribute these bytes to carry all of our parameters.
|
02:21 |
Which parameters can be sent with just a single byte and which might need 2 or more bytes? We'll start with our engine RPM and begin by thinking about the range of values this parameter might have and how precise we need our data transmission to be.
|
02:35 |
For more performance internal combustion engine applications, engine speed will vary between 0 and 10,000 RPM as a max.
|
02:43 |
So we're going to need to use this range of values to determine how we're going to send our data.
|
02:50 |
A single byte of data is 8 bits so can have a minimum value in decimal of 0 and a maximum value of 255.
|
02:57 |
This range isn't wide enough to let us transmit the complete range that we'd like to so we're going to have to fix this problem.
|
03:04 |
We can do this a couple of ways, the first is to introduce a factor into the value and transmit our engine RPM on a single byte divided by this factor.
|
03:14 |
The receiving devices all need to know this factor and can multiply by it once they receive the data on the other end to get back to the true engine speed.
|
03:24 |
In this example, if we used a factor of 50, this would let us transmit a range of 0 times 50 which is 0, to 255 times 50 which is 12,750.
|
03:35 |
This encompasses our desired range so will solve our initial problem.
|
03:40 |
There is a major downside to this approach though and that's the fact that we can now only transmit our engine speed in multiples of 50 which is not likely to be enough resolution.
|
03:50 |
The more likely way we would solve this problem is to use an additional byte of our data frame to transmit our engine speed.
|
03:58 |
Using 2 bytes gives us 16 bits which means we can now transmit a value between 0 and 65,535.
|
04:07 |
This is more than enough range to transmit our engine speed on, meaning we don't need to use any multiplication or division.
|
04:13 |
In an earlier course module, we talked about endianess and in this example we're going to use big endian transmission.
|
04:20 |
So we're going to transmit our high byte first in byte position 0 and our low byte second in byte position 1.
|
04:27 |
The next parameter to transmit is the engine coolant temperature.
|
04:31 |
Thinking about the possible range this parameter could take, we first have to determine the units we're going to use to transmit the value.
|
04:37 |
Metric units are almost universally used so we're going to stick with those.
|
04:42 |
This does give us a slight hurdle though as if our vehicle's operating in a particularly cold climate and has only just been started, our engine coolant temperature could well be a negative value.
|
04:53 |
We'll say a common range for engine coolant temperature would be -20°C to 150°C.
|
04:59 |
This range spans 170 individual values so we can transmit this parameter using only a single byte but we're going to need to use a defined offset to let us transmit negative values.
|
05:13 |
This is just a simple value that we're going to add to our coolant temperature before it's transmitted and then this will be subtracted by the receiving modules.
|
05:21 |
If we pick an offset value of 50, this lets us transmit a temperature value of between -50 and 205°C, giving us lots of space on either end of our expected range.
|
05:34 |
There is another way that we can send negative values but it involves defining how we translate from binary to decimal slightly differently and using a signed integer data type.
|
05:44 |
This isn't commonly seen in performance automotive applications though so it's outside the scope of the course.
|
05:49 |
The next parameter is engine oil temperature.
|
05:52 |
This has a somewhat similar range as engine coolant temperature so we'll transmit it in the same way to keep things nice and simple.
|
05:59 |
A single byte with an offset of 50 applied.
|
06:02 |
For transmitting the battery voltage, we're going to assume a required parameter range of between 0 and 18 volts.
|
06:08 |
This is well within the range of a single byte but it's a value where we need a little bit more resolution than just whole numbers.
|
06:15 |
To achieve this, we're going to use a factor but instead of transmitting our parameter value divided by this factor, we'll transmit it multiplied by this factor.
|
06:24 |
This lets us use more of the available transmission range one byte can have, giving us more precision.
|
06:30 |
To accomplish this, we'll take our battery voltage, say 13.5 volts and multiply it by a factor of 10.
|
06:38 |
We'll then send this out as 135.
|
06:41 |
The listening devices on the bus divide the received 135 by 10, getting back to 13.5.
|
06:48 |
This lets us transmit our battery voltage with 1 decimal place of precision.
|
06:53 |
Which for a parameter, like battery voltage, where the difference between 13 volts and 13.5 volts can be quite critical, is a huge help.
|
07:01 |
We now need to transmit the statuses of the check engine, alternator charge and traction control lights.
|
07:07 |
So far, we've used 5 of the 8 available bytes in our data frame.
|
07:12 |
2 for engine RPM, and one each for coolant temp, oil temp and battery voltage.
|
07:16 |
We have 3 left so we could use 1 for each status light with a 0 meaning the light should be off and 255 meaning the light should be on.
|
07:25 |
But a status light is just a binary value.
|
07:28 |
It's either on or off, there's only 2 states.
|
07:31 |
So using a full byte to transmit this is a bit of a waste.
|
07:35 |
Instead, we'll use just a single byte with bit position 0 being the state of the check engine light, bit position 1 being the state of the alternator charge light and bit position 2 being the state of the traction control light.
|
07:47 |
This means we can send out a value between 0 and 7 to control the states of the lights.
|
07:54 |
As there are 8 possible combinations of the states of these 3 lights.
|
07:58 |
Transmitting the states of these lights this way is more efficient because our CAN data frame will now be shorter, only having 6 data bytes instead of 8.
|
08:07 |
When the transmitting device constructs this message to send out onto the bus it'll put a 6 in the control section of the data frame, knowing the receivers to only expect 6 bytes of data.
|
08:18 |
This is a relatively simple example of a single CAN data frame but it includes the majority of different ways you'll see data transmitted.
|
08:25 |
Particularly in the performance aftermarket.
|
08:27 |
The procedure of applying offsets and multipliers to gain resolution or fit data into a transmission range is relatively universal and something that you will soon become comfortable with.
|
08:39 |
I'll just go over the data frame we've created quickly as it'll help you to hear some of the short hand language used when discussion CAN data frames.
|
08:46 |
We've created a data frame on PID 100, 0x64 in hex.
|
08:51 |
It has 6 data bytes total.
|
08:53 |
Data bytes 0 and 1 are the engine speed transmitted in big endian, no multiplier or offset applied.
|
09:01 |
The data byte in position 2 is coolant temp with an offset of 50 applied and no multiplier.
|
09:06 |
The data byte in position 3 is engine oil temp with the same offset of 50 and also no multiplier.
|
09:13 |
The data byte in position 4 is battery voltage transmitted with 1 decimal place, so a multiplier of 10.
|
09:20 |
The data byte in position 5 holds flags to signal the state of the check engine alternator charge and traction control lights.
|
09:28 |
These are transmitted in bit positions 0, 1 and 2 respectively.
|
09:32 |
You can see that by being efficient with our data frame structure, we can transmit quite a lot of useful data in a single frame.
|
09:39 |
In the next section, we'll have a look at what to do when you need to transmit more data than a single frame can hold.
|