Hi, Habr!
In an attempt to reduce all the
vital performance of your car on one screen of the head unit, it was the turn to connect the parking sensors. Many will argue - even cheap PDCs have their own screen, why output the data somewhere else? Yes, just do not want to put an extra screen in the cabin, and there is a reason to dig in the gland ...
In this article I will try to describe the techniques and tools for reverse engineering of an undocumented protocol for the exchange of two pieces of iron between each other.
From the content of
some publications , it seems that, firstly, it is worth choosing parking sensors with a radio channel between the main unit and the screen, and secondly, nothing complicated in the exchange protocols is expected. Hmm ... well, yes. It turned out to be half true.
')
The presence of a radio channel between the main unit and the “display unit” suggests that the exchange protocol between them will be simple and consistent with the transfer of the measured distances in an explicit form. If the screen would cling directly, then, most likely, all the logic would be implemented in one chip and commands like “light this pixel / segment and go out the same” would go to the screen, without being able to get the measured distances directly. Okay. Inspired, we go to the “largest cybermarket” and buy the cheapest set of parking sensors with airplanes in the logo. Bought, and then it began ...
Step one. Opening and reading sent data
To begin, we will determine the method of data transmission over the air.
Having opened the main unit, we find there a round chip
R433A with a completely standard strapping. There is no receiver in the main unit, therefore, the data transmission channel is one-way, with the only possible
modulation for R433
OOK . Those. If there is a high level of digital signal (logical “one”), the transmitter broadcasts a carrier at 433.92 MHz. In the absence of a high level - the transmitter is silent. On the receiver side, decoding occurs in a similar way in the display unit: if the receiver sees the carrier, it gives a high level, if it does not, it gives a low one. Yes, the receiver also perfectly catches all the interference
from the parking sensors of the neighbor , so its sensitivity is greatly underestimated. And, as a rule, a checksum is added to the transmitted data (this fact is still useful to us below).
We will need a digital oscilloscope recorder (simple USB oscilloscopes, such as DiSco, are convenient). We find the path that goes from the main microcontroller of the “brains” to the transmitter in the main unit or from the receiver to the microcontroller in the display unit, find any “minus” track, solder wires to them, connect an oscilloscope, look:

This repetitive package sends the main unit immediately when power is applied. With careful consideration, it is possible to make an
extremely non-obvious conclusion that the premise consists of three parts:
- The first part is obviously some kind of synchronization to “wake up” the receiver and the chip in the display unit. It consists of five pulses of 0.4 ms each with pauses of 0.4 ms and a single pulse of 1 ms duration with a 2 ms pause. Remember the information about the duration, it is useful for implementing your own decoder.
- The second and third parts are directly data encoded in 16 pulses of different widths in each part (plus another 0.4 ms pulse at the very end).
Find out how the bits are coded in the second and third parts. The alternation of wide and narrow pulses is similar to the
Manchester code , which is used, in particular, on Ethernet networks. But Manchester’s two broad impulses cannot be separated by narrow ones. Therefore, noting that a wide pause always follows a narrow “pause” and vice versa (with a total pulse duration + pause = 1 ms), suppose a simpler one — the pulse width directly encodes a logical unit (narrow pulse) or zero (wide pulse).
Step two. Pen decoding
So, we have 32 bits of data and 4 sensors. It is logical to assume that 8 bits are allocated to each sensor, plus, probably, a checksum (we still remember the checksum!). And we need to understand how distance to obstacles is encoded in these bits and so on. To begin with, turn off all sensors and manually write the resulting bit sequence from the oscilloscope readings:
10011100 10011100 10011101 01000100
hmm ... it is clear that nothing is clear. In the absence of sensors, it would be logical to receive all ones or all zeros. Nothing like this here. We connect sensor A, sending it to the void (readings - "infinity"):
10011100 10011100 10010011 01001100
changed the last two bytes. In the 4th byte 1 bit changed. Apparently, it is he who shows the presence of sensor A? And in the 3rd byte 3 bits changed. Moreover, if we consider them as a separate 3-bit number written from the low to high bit, we can see that it has increased by one: 011 + 1 = 100. And this unit is added to the 4th byte. From here two preliminary conclusions:
- in the 3rd byte there are bits related to the checksum,
- checksum is a simple arithmetic sum of something with something, there is no CRC and other difficulties,
- the data as a whole is not encoded by bytes, but by 4-bit “nibbles”.
Let's try to disconnect sensor A and connect sensor B:
10011100 10011110 01011101 01000100
changed 2nd and 3rd bytes. The sixth bit of the second byte (counting from zero) seems to be the “presence of sensor B”. The lower 4 bits of the third byte - also the checksum, also increased by one, which appeared in the second byte. But she appeared there not in the zero and not in the fourth, but in the sixth bit! Already getting interesting. Try simultaneously sensors A and B to "infinity":
10011100 10011110 01010011 01001100
yes, something cleared up. We have the second byte, as in the parcel “sensor B only” and the fourth byte from the parcel “sensor A only”. But in the third byte there are two 4-bit parts from the two previous packages. The assumption about 4-bit checksums begins to be confirmed? The only unusual thing is that the checksum is stuck in the middle of the package.
Let us now try to put an obstacle in front of sensor A (disabling B) at a distance of, say, 90 cm:
10011100 10011100 10011111 01000110
About how, the “presence of sensor A” bit no longer shows us the presence of sensor A. But in the last 4 bits a bit appeared in another place. And the checksum has changed dramatically. Although if you compare with the original parcel without sensors: 1111-1011 = 100, and the last 4 bits: 0110-0010 = 100. Hurray! Coincided!
Nevertheless, it becomes clear later that manually copying these bits from the oscilloscope readings is hard and there is a high probability of making a mistake. Therefore…
Step three. Decoding feet in the microcontroller
We have a microcontroller. Arduino or just AVR on breadboard, whatever. We have it, who does not like him to collect all the data for the head unit. Therefore, it's time to write a program to decode the parcel from the PDC and transfer this parcel through the terminalka to the computer to simplify the further process of reversing.
Since the level of signals from the PDC is standard 5 volts, the connection to the AVR for debugging is very simple - with a wire to any unspecialized leg (hmm ... maybe I shouldn't have crossed out in the header in vain?).
The program source is available on
github . The decoding is handled by the PCINT3_vect interrupt handler function in line 119 and on. The rest of the program does a lot of other interesting things, maybe someday I'll write an article about it. In the meantime, I will describe in brief the algorithm for decoding the parcel from parking sensors.
The current AVRok almost every foot can hang interruption, which will be triggered every time you change the level at the entrance. Those. each time when going from 0 to 5 volts and each time when going back from 5 to 0. Thus, it is enough to detect the time between the interrupt and activate the internal state using a timer. There can be several states: waiting for the first 5 pulses, waiting for a wide pulse, waiting for a pause, waiting for the first 16 bits (followed by decoding depending on the pulse duration), waiting for the pause, waiting for the second 16 bits, waiting for the final pulse, transition to the initial state. And all this is implemented in the interrupt handler, it takes literally a few clocks each time and does not occupy the main loop at all (although it takes a separate timer, but this is fixable).
The resulting UART device produces decoded values ​​to the terminalka of the computer directly in the form of 4 bytes. To simplify the subsequent analysis, open Excel and write a macro:
Habr can highlight BASIC?Dim lst As Worksheet Dim s As String Dim v, i, j As Long Set lst = ActiveWorkbook.ActiveSheet For k = 2 To 365 For i = 1 To 8 If i Mod 2 = 0 Then ii = i - 1 Else ii = i + 1 s = "&H0" + Mid(lst.Cells(k, 2).Value, ii, 1) v = CDec(s) For j = 0 To 3 If (v And (2 ^ j)) > 0 Then lst.Cells(k, 3 + (i - 1) * 4 + j) = "1" Else lst.Cells(k, 3 + (i - 1) * 4 + j) = "0" End If Next j Next i Next k
generating from 4 hex bytes like this (colors and signatures, of course, I already added):

it became very clear that the “sensor presence” bits are indeed available for all 4 sensors and affect the checksums, respectively. And the disappearance of the sensor bit A for some indications is due to something else.
Possessing all the above-described tools, we empirically get a table by sensor A:

Well, everything is very nice looming:
- The last 4 bits are tens of centimeters of sensor A. And if you simulate distances up to zero, it turns out that tens of centimeters correspond to 1111 and then decreasing further, 10+ cm = 1110, 20+ cm = 1101, 30+ cm = 1100, etc. d. up to 0011, corresponding to 130+ cm.
- The pale pink two columns of 2 bits correspond to units of centimeters (note that for 105, 95 and 85 cm the bits are the same). And in the first column of the higher bits of a 4-bit value. The coding principle is the same: 0 cm = 1111, 1 cm = 1110, etc. up to 9 cm = 0110
- The first checksum remains the same, but the second is changing slyly. A column of tens of centimeters affects the amount directly, but both columns of units of centimeters affect only the older two bits of the checksum.
Collect a similar table on sensor B:

here it is more interesting:
- Two columns of two bits each, encoding units of centimeters, remain in place (marked with light green). It turns out that, most likely, the units of centimeters are displayed each time for the sensor nearest to the obstacle, and tens of centimeters - for each sensor separately. Thus, on a standard screen, the distance to the sensor closest to the obstacle is displayed with an accuracy of one centimeter and, roughly, the distance to the other sensors in the form of stripes in front of the bumper.
- Dozens of centimeters for sensor B are also split into two columns, each divided into two bits (marked “medium green”).
- Referring to the instructions for Parktronic, we find out that the stated maximum fixed distance to the obstacle is 2.5 meters. And in 4 bits you can encode only 1.6 meters. So somewhere is the fifth bit? Indeed, by simulating distances of 1.7 meters and beyond, we find out that this is the second bit of the first byte (marked dark-green). Thus, tens of centimeters of sensor B are coded by bits in the following order (from the highest to the lowest): 2,1,0,16,15.
- The values ​​of both 4-bit checksums change. Therefore, the bits associated with sensor B readings affect both sums.
- The first checksum is clearly changed by one along with changes per unit of tens of centimeters. Apparently the remaining columns, which are also included in this amount - among the unchanged by sensor B.
Sensor queue C:

I'm starting to think like a Chinese:
gonfen ji rönran sun oh oh, that is, units of centimeters are still in their places.- Dozens of centimeters for the C sensor are encoded in five bits, which this time together, although they belong to different bytes (lilac and dark lilac). The coding principle is similar to the previous sensors.
- The first checksum (the first 4 bits) is clearly changed by one along with changes per unit of tens of centimeters. Similar to sensor B. Therefore, a preliminary conclusion: the first checksum includes the value of tens of centimeters of sensor B and sensor C (probably, without the fifth bit) and something else. Intuition suggests that this is the lower 4 bits of the last byte. Check below.
On the sensor D to collect a detailed table has become lazy, so:

Well, all the hypotheses were confirmed. The first 4 bits of the last byte encode tens of centimeters of sensor D.
To test, we simulate several combinations of sensors A and B:

yes, everything is the same.
At this stage, we can fully decode the distances for each sensor, including units of centimeters. And the presence / absence of sensors. Maybe this is enough? Hm Something else seems to be undersampled ...
Step Four. CRC (Chinese Redundancy Check) calculation
So, what we already know about local checksums:
- There are two of them, 4 bits each, for some reason not in the last, but in the third byte.
- Each of them is a simple arithmetic sum of data from other columns.
- It is assumed that certain bits belong to specific checksums.
We note the currently known membership in the sample of any arbitrary readings:

Let's try to sum up the first line, take the columns of tens of centimeters of sensors B, C and D:
1110 + 0111 + 0011 = 11000
hmm, and the checksum in the third byte 0111. And what if minus one?
1110 + 0111 + 0011 - 1 = 10111
matches if you drop the extra bit. Check in other lines:
1110 + 0111 + 0011 - 1 = 10111 (oh, it all happened again)
0101 + 0111 + 0011 - 1 = 0111 (here without dropping)
1111 + 1100 + 1100 - 1 = 100110 (here already two bits overflowed)
0001 + 0101 + 0011 - 1 = 1000 (without dropping)
Hurray, everything coincided!
We still have unmarked columns. They probably belong to the second checksum, so let's try to sum up:
1010 + 1011 + 0011 = 11000
1110 + 0111 + 0101 = 11010
1110 + 0011 + 1000 = 11001
1111 + 1111 + 0111 = 100101
1111 + 1011 + 0111 = 100001
mda, not enough in common with the second checksum. Let's see how much you need to subtract to coincide:
1010 + 1011 + 0011 - 10 = 10110
1110 + 0111 + 0101 - 10 = 11000
1110 + 0011 + 1000 - 11 = 10110
1111 + 1111 + 0111 - 01 = 100100
1111 + 1011 + 0111 - 11 = 11110
I have already seen it somewhere ... well, yes, the first checksum! The dependence is simple - from the second CS, it is necessary to take away what we have discarded as overflow when calculating the first CS, only xor'ennoe from 11. That is, discarding 00 (nothing) from the first COP, from the second subtracting 11, etc.
Whew, like everything. There are two unused bits left, but they seem to be always one.
Step five. Radio cleaning
In general, I am not a supporter of the use of radio channels anywhere. The air is already decently polluted, so that it will all work in places (geographically) rather unstable. Therefore, let us deal with the fact that we will eject the receiver and transmitter from the PDC by connecting the base unit, the display unit and our microcontroller by wires. Why do I mention the display unit, although I was not going to put it? And because of the tweeter. Still, the transfer from the PDC base unit to our microcontroller, there is decoding, then sending to the head unit, there again decoding and rendering will make an uncritical, but noticeable lag in the distance display. Therefore, the display unit will remain in the depths of tidy and will obviously squeak faster (although in the future, maybe I will make my microcontroller squeak).
It would be possible not to bathe and connect all the blocks with wires just like in debug mode, directly. However, throwing a miserable 5 volt TTL through the whole car, believe me, is not a good idea. Therefore, we solder into all three devices of the MAX485 microcircuit, implementing the transfer via a much more reliable
RS-485 interface. In general, something like that (sorry for the unwashed flux). Base unit:

on the place of the white circle in the upper right corner of the board there was an R433A chip, the Q11 transistor and the resistor, instead of which the wires were soldered, were also removed from its piping. And in the free space it was possible to arrange the microcircuit so that the legs fell on the negative contact and several other suitable contacts. Since the base unit is always a transmitter, the DE and RE legs can be permanently closed by +5 volts. Lines A and B of the RS485 interface are connected to an additional terminal.
Display Unit:

Well, here in general beauty, MAX485 soldered almost like a native instead of the standing
RF83C receiver
chip . The DO data output legs and the minus GND, the DE and RE legs, coincided, since this part is always the receiver, they are planted on the ground. The rest required just one jumper.
Works as before:

I’ll probably post a picture of my own microcontroller in the article about the rest of the KMENevoBT functionality from the github.
Finally, the full decoding code of the parcel from the PDC from the debugging program in Delphi:
Well, do not hit, it's just a debugging code on the knee procedure TfrmKMEmul.UpdatePT(a, b, c, d: Byte); var cm, la, lb, lc, ld, crc1, crc2: integer; begin cm := $F - ( (a shr 2) and $C + (b shr 4) and $3 ); la := (d shr 4) and $F; lb := ((a and 7) shl 2) + ((b shr 6) and $3); lc := ((a shr 6) and $3) + ((b and $7) shl 2); ld := (d) and $F; crc1 := ((lc and $F + lb and $F + ld) - 1); crc2 := ((a shr 2) and $F) + ((b shr 2) and $F) + ((d shr 4) and $F); crc2 := crc2 - ((crc1 shr 4) xor $3); la := $F - la; lb := $1F - lb; lc := $1F - lc; ld := $F - ld; lbCM.Caption := IntToStr(cm); lbA.Caption := Format('%.1f', [la/10]); lbB.Caption := Format('%.1f', [lb/10]); lbC.Caption := Format('%.1f', [lc/10]); lbD.Caption := Format('%.1f', [ld/10]); if la = $F - $2 then lbA.Font.Color := clRed else lbA.Font.Color := clGreen; if lb = $1F - $4 then lbB.Font.Color := clRed else lbB.Font.Color := clGreen; if lc = $1F - $4 then lbC.Font.Color := clRed else lbC.Font.Color := clGreen; if ld = $F - $2 then lbD.Font.Color := clRed else lbD.Font.Color := clGreen; if crc1 and $F = c and $F then lbCRC.Caption := 'OK' else lbCRC.Caption := 'BAD'; if (crc2 and $F = (c shr 4) and $F) then lbCRC2.Caption := 'OK' else lbCRC2.Caption := 'BAD'; end;
Step Six. findings
Perhaps at some point it was worth to abandon further excavations and order the same parking sensors from Ebay, which the Italian from the forum was picking up on the first link, but I liked the parking sensors themselves. It works very quickly and accurately. I had to finish, already out of principle.
What the Chinese smoked, developing such a protocol, is unclear.
Lyrical digressionI had a chance several years ago to unearth the diagnostic protocol for Ford cars with the brains of EEC-IV. These are such brains on the basis of Intel's set of 8096, it seems. In general, antiquity is at the level of a little bit of 8086. It was put on Fords from the mid 80s and up to the beginning of the 2000s, somewhere from the beginning of the 90s there was implemented the Data Communication Link Diagnostic Protocol (DCL). So, most likely due to performance limitations, it was impossible to make an exchange protocol with asynchronous data exchange, like the COM port of a computer. Therefore, the protocol was synchronous. This meant that when the diagnostic was activated, the brains started sending “frames” along the data line, consisting of words that are strictly synchronized in time. To communicate with the brains, it was necessary to send their words to the specified intervals between the received words with an accuracy of tens of microseconds. No computer with a COM port provided the required accuracy of sending bytes. I had to do on the microcontroller ...
Hmm, so what am I ... Ah, here. In that case, such an interesting exchange protocol was justified by the limitations of the chip. In the case of this PDC, I do not see any logic of just such a “dance” bit. What prevented to arrange, for example, 4x4 bits of tens of centimeters, then 4 bits of units of centimeters, 4 more bits for expanding up to 5 bits of sensors B and C and any service bits and at the end the checksum of all bits (just the sum, without distortions, as a last resort standard CRC8).
By the way, knowing the exchange protocol, you can apply this parking sensor not only on the car, but, for example, on a homemade robot. Yes, there are separate ultrasonic sensors for robots, but here there are four of them at once and they are read by one leg of the arduin, albeit with a delay of several milliseconds.
All read all the best!
PS I wanted to add a survey about whether to write about the excavation of the protocol of the brain for gas injection KME Nevo Pro. There, several other techniques were used ... But I think, rather, it is necessary to ask about "do we need such car searches at Habré?".