- PVSM.RU - https://www.pvsm.ru -

Реверс-инжиниринг протокола парктроника. Танец маленьких бит

Привет!
В попытках свести все жизненные рабочие показатели своего автомобиля на один экран головного устройства дошла очередь и до подключения парктроника. Многие возразят — ведь даже у дешевых парктроников есть свой экранчик, зачем выводить данные куда-то ещё? Да просто лишний экранчик в салоне ставить не хочется, и покопаться в железе повод есть…

В статье постараюсь описать приёмы и инструменты для реверс-инжиниринга недокументированного протокола обмена двух железок между собой.

Из содержания некоторых публикаций [1], создаётся впечатление, что, во-первых, стоит выбирать парктроник с радиоканалом между основным блоком и экраном, и во-вторых, ничего сложного в протоколах обмена не ожидается. Хм… ну да. Правдой это оказалось наполовину.

Наличие радиоканала между основным блоком и «блоком индикации» позволяет предположить, что протокол обмена между ними будет простым и последовательным с передачей измеренных расстояний в явном виде. Если бы экран цеплялся напрямую, то, скорее всего, вся логика была бы реализована в одном чипе и на экран шли бы уже команды типа «зажги вот этот пиксель/сегмент и погаси вон тот», без возможности получить непосредственно измеренные расстояния. Окей. Воодушевлённые, идём в «крупнейший кибермаркет» и покупаем там самый дешевый комплект парктроника с самолётами в логотипе. Купили, и тут началось…

Шаг первый. Вскрытие и считывание посылаемых данных

Для начала определимся со способом передачи данных по радиоканалу. Вскрыв основной блок, находим там кругленький чип R433A [2] с совершенно стандартной обвязкой. Приёмника в основном блоке нет, следовательно, канал передачи данных односторонний, с единственно возможной для R433 OOK-модуляцией [3]. Т.е. при наличии высокого уровня цифрового сигнала (логической «единицы») передатчик передаёт в эфир несущую на частоте 433,92 МГц. При отсутствии высокого уровня — передатчик молчит. Со стороны приёмника в блоке индикации аналогичным образом происходит декодирование: если приёмник видит несущую, выдаёт высокий уровень, если не видит — выдаёт низкий. Да, все помехи от парктроника соседа приёмник так же прекрасно ловит, поэтому его чувствительность сильно занижается. И к передаваемым данным, как правило, добавляется контрольная сумма (этот факт нам ещё пригодится ниже).

Нам понадобится цифровой осциллограф-самописец (удобны простые USB-осциллографы, типа DiSco). Находим дорожку, которая идёт от главного микроконтроллера «мозгов» к передатчику в основном блоке или от приёмника к микроконтроллеру в блоке индикации, находим любую «минусовую» дорожку, подпаиваем к ним проводочки, подключаем осциллограф, смотрим:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 1

Вот такую повторяющуюся посылку шлёт основной блок сразу при подаче питания. При внимательном её рассмотрении можно сделать крайне неочевидный вывод о том, что посылка состоит из трёх частей:

  • Первая часть — очевидно, некая синхронизация, чтобы «разбудить» приёмник и чип в блоке индикации. Состоит из пяти импульсов по 0.4 мс с паузами по 0.4 мс и следом одного импульса длительностью 1 мс с паузой 2 мс. Запомним информацию о длительности, она пригодится для реализации собственного декодера.
  • Вторая и третьи части — это непосредственно данные, закодированные в 16 импульсах разной ширины в каждой части (плюс ещё один 0.4 мс импульс в самом конце).

Выясним, каким образом во второй и третьей части закодированы биты. Чередование широких и узких импульсов похоже на Манчестерский код [4], который применяется, в частности, в сетях Ethernet. Но у манчестера два широких импульса не могут разделяться узким. Поэтому, заметив, что после широкого импульса всегда следует узкая «пауза» и наоборот (при общей длительности импульс+пауза = 1 мс), предположим более простое — ширина импульса непосредственно кодирует логическую единицу (узкий импульс) или ноль (широкий импульс).

Шаг второй. Декодирование ручками

Итак, у нас есть 32 бита данных и 4 датчика. Логично предположить, что на каждый датчик отводится «чуть менее» 8 бит, плюс, вероятно, контрольная сумма (мы ещё помним про контрольную сумму!). И нам нужно понять, каким образом в этих битах кодируются расстояния до препятствий и всё такое. Для начала отключим все датчики и вручную с показаний осциллографа запишем полученную последовательность бит:

10011100 10011100 10011101 01000100

хм… понятно, что ничего не понятно. В отсутствие датчиков логично бы получать все единицы или все нули. Здесь ничего похожего. Подключим датчик A, направив его в пустоту (показания — «бесконечность»):

10011100 10011100 10010011 01001100

изменились последние два байта. В 4-м байте поменялся 1 бит. Видимо, именно он показывает «наличие датчика A»? А в 3-м байте поменялось 3 бита. Причём, если рассматривать их как отдельное 3-битное число, записанное от младшего к старшему биту, можно заметить, что оно увеличилось на единицу: 011+1=100. А единица эта — добавилась к 4-му байту. Отсюда два предварительных вывода:

  • в 3-м байте есть биты, относящиеся к контрольной сумме,
  • контрольная сумма — это простая арифметическая сумма чего-то с чем-то, никаких там CRC и прочих сложностей,
  • данные в целом кодируются не байтами, а 4-битными «нибблами».

Попробуем отключить датчик A и подключить датчик B:

10011100 10011110 01011101 01000100

изменились 2-й и 3-й байты. Шестой бит второго байта (считая от нуля), похоже, «наличие датчика B». Младшие 4 бита третьего байта — тоже контрольная сумма, тоже увеличилась на единицу, появившуюся во втором байте. Но появилась она там не в нулевом и не в четвёртом, а в шестом бите! Уже становится интересно. Пробуем одновременно датчики A и B в «бесконечность»:

10011100 10011110 01010011 01001100

да, кое-что прояснилось. Имеем второй байт, как в посылке «только датчик B» и четвёртый байт из посылки «только датчик A». А вот в третьем байте две 4-битных части из двух предыдущих посылок. Предположение про 4-битные контрольные суммы начинает подтверждаться? Непривычно только то, что контрольная сумма засунута в середину посылки.

Попробуем теперь выставить перед датчиком A (отключив B) препятствие на расстоянии, скажем, 90 см:

10011100 10011100 10011111 01000110

о как, бит «наличие датчика A» больше не показывает нам наличие датчика A. Но в последних 4 битах появился бит в другом месте. И разительно поменялась контрольная сумма. Хотя если сравнить с исходной посылкой без датчиков: 1111-1011 = 100, а последние 4 бита: 0110-0010 = 100. Ура! Совпало!

Тем не менее, дальше становится понятно, что вручную с показаний осциллографа срисовывать эти биты тяжело и велика вероятность ошибиться. Поэтому…

Шаг третий. Декодирование ножками в микроконтроллере

У нас есть микроконтроллер. Ардуино или просто AVR на макетке, неважно. Он у нас есть, кому как не ему собирать все данные для головного устройства. Поэтому самое время написать программку для декодирования посылки от парктроника и передачи этой посылки через терминалку в компьютер для упрощения дальнейшего процесса реверсинга.

Поскольку уровень сигналов от парктроника составляет стандартные 5 вольт, то подключение к AVRке для отладки очень простое — проводом на любую неспециализированную ножку (хмм… может я зря зачеркнул в заголовке?).

Исходник программы доступен на гитхабе [5]. Декодированием занимается функция-обработчик прерывания PCINT3_vect в строке 119 и далее. Остальная часть программы делает много других интересных штук, может быть когда-нибудь я и про это напишу статью. А пока опишу вкратце алгоритм декодирования посылки от парктроника.

У нынешних AVRок почти на каждую ногу можно повесить прерывание, которое будет срабатывать каждый раз при изменении уровня на входе. Т.е. каждый раз при переходе от 0 до 5 вольт и каждый раз при переходе обратно от 5 до 0. Таким образом, достаточно при помощи таймера засекать время между срабатываниями прерывания и фиксировать внутреннее состояние. Состояний может быть несколько: ожидание первых 5 импульсов, ожидание широкого импульса, ожидание паузы, ожидание первых 16 бит (с последующим декодированием в зависимости от длительности импульса), ожидание паузы, ожидание вторых 16 бит, ожидание финального импульса, переход в начальное состояние. Причём всё это реализовано в обработчике прерывания, отнимает каждый раз буквально считанные такты и совсем не занимает главный цикл (правда, занимает отдельный таймер, но это исправимо).

Получившееся устройство по UART выдаёт в терминалку компьютера декодированные значения непосредственно в виде 4х байт. Для упрощения последующего анализа открываем Excel и пишем макрос:

Хабр умеет подсвечивать бейсик?

  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

генерирующий из 4 шестнадцатеричных байт вот такое (разноцветие и подписи, конечно, я уже добавил сам):

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 2

очень наглядно стало видно, что биты «наличие датчика» действительно имеются по всем 4-м датчикам и влияют на контрольные суммы соответственно. А пропадание бита датчика A при некоторых показаниях обусловлено чем-то ещё.

Обладая всем вышеописанным инструментарием, опытным путём получаем таблицу по датчику A:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 3

ну всё очень неплохо вырисовывается:

  • Последние 4 бита — десятки сантиметров датчика A. Причём если промоделировать расстояния вплоть до нуля, получится, что нулю десятков сантиметров соответствует 1111 и далее по убывающей, 10+ см = 1110, 20+ см = 1101, 30+ см = 1100 и т.д. вплоть до 0011, соответствующего 130+ см.
  • Отмеченные бледно-розовым два столбца по 2 бита соответствуют единицам сантиметров (заметьте, что для 105, 95 и 85 см биты одинаковы). Причём в первом столбце более старшие биты 4-битного значения. Принцип кодирования тот же: 0 см = 1111, 1 см = 1110 и т.д. вплоть до 9 см = 0110
  • Первая контрольная сумма остаётся неизменной, а вот вторая меняется хитро. Столбец десятков сантиметров влияет на сумму непосредственно, а вот оба столбца единиц сантиметров — влияют только на старшие два бита контрольной суммы.

Соберем аналогичную таблицу по датчику B:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 4

здесь интереснее получается:

  • Два столбца по два бита, кодирующие единицы сантиметров, остаются на своих местах (отмечены светлозеленым). Получается, что, скорее всего, единицы сантиметров выводятся каждый раз для ближайшего к препятствию датчика, а десятки сантиметров — отдельно по каждому датчику. Таким образом, на штатном экранчике выводится расстояние до ближайшего к препятствию датчика с точностью до сантиметра и, грубо, расстояние до остальных датчиков в виде полосочек перед бампером.
  • Десятки сантиметров для датчика B получаются также разбитыми на два столбца по два бита (отмечены «среднезеленым»).
  • Обратившись к инструкции к парктронику, выясняем, что заявленное максимальное фиксируемое расстояние до препятствия — 2.5 метра. А в 4 битах можно закодировать только 1.6 метра. Значит где-то есть пятый бит? И действительно, промоделировав расстояния 1.7 метра и дальше, выясняем, что это второй бит первого байта (отмечен тёмнозеленым). Таким образом, десятки сантиметров датчика B кодируются битами в следующем порядке (от старшего к младшему): 2,1,0,16,15.
  • Меняются значения обоих 4-битных контрольных сумм. Следовательно, биты, связанные с показаниями датчика B, влияют на обе суммы.
  • Первая контрольная сумма чётко изменяется на единицу вместе с изменениями на единицу значения десятков сантиметров. Видимо остальные столбцы, которые тоже включены в эту сумму — среди неизменяющихся по датчику B.

Очередь датчика C:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 5

я уже начинаю мыслить, как китаец:

  • гоньфень джи рёнран суньза ой, то есть единицы сантиметров по-прежнему на своих местах.
  • Десятки сантиметров для датчика C закодированы в пяти битах, которые на этот раз вместе, хоть и принадлежат разным байтам (сиреневый и тёмносиреневый). Принцип кодирования аналогичен предыдущим датчикам.
  • Первая контрольная сумма (первые 4 бита) чётко изменяется на единицу вместе с изменениями на единицу значения десятков сантиметров. Аналогично датчику B. Следовательно, предварительный вывод: в первую контрольную сумму входят значение десятков сантиметров датчика B и датчика C (вероятно, без пятого бита) и что-то ещё. Интуиция подсказывает, что это младшие 4 бита последнего байта. Проверим ниже.

По датчику D собирать подробную таблицу стало лениво, поэтому так:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 6

что ж, все гипотезы подтвердились. Первые 4 бита последнего байта кодируют десятки сантиметров датчика D.

Для проверки промоделируем несколько сочетаний датчиков A и B:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 7

да, всё совпадает.

На данном этапе мы можем полностью декодировать расстояния по каждому датчику, включая единицы сантиметров. И наличие/отсутствие датчиков. Может быть, этого достаточно? Хм. Кажется что-то ещё недораскопано…

Шаг четвёртый. Расчёт CRC (Chinese Redundancy Check)

Итак, что мы уже знаем про местные контрольные суммы:

  • Их две, по 4 бита, находятся почему-то не в последнем, а в третьем байте.
  • Каждая из них является простой арифметической суммой данных из других столбцов.
  • Предположительно известна принадлежность некоторых бит к конкретным контрольным суммам.

Отметим известную на данный момент принадлежность на выборке каких-нибудь произвольных показаний:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 8

попробуем просуммировать по первой строке, возьмём столбцы десятков сантиметров датчиков B, C и D:

1110 + 0111 + 0011 = 11000

хм, а контрольная сумма в третьем байте 0111. А что если минус один?

1110 + 0111 + 0011 — 1 = 10111

совпадает, если отбросить лишний бит. Проверим по другим строкам:

1110 + 0111 + 0011 — 1 = 10111 (ой, тут всё повторилось)
0101 + 0111 + 0011 — 1 = 0111 (тут без отбрасывания)
1111 + 1100 + 1100 — 1 = 100110 (тут аж два бита переполнилось)
0001 + 0101 + 0011 — 1 = 1000 (без отбрасывания)

ура, всё совпало!
У нас остались не отмеченные столбцы. Вероятно, они относятся ко второй контрольной сумме, поэтому попробуем просуммировать:

1010 + 1011 + 0011 = 11000
1110 + 0111 + 0101 = 11010
1110 + 0011 + 1000 = 11001
1111 + 1111 + 0111 = 100101
1111 + 1011 + 0111 = 100001

мда, маловато общего с второй контрольной суммой. Посмотрим, сколько нужно вычесть, чтобы совпало:

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

где-то я это уже видел… а, ну да, у первой контрольной суммы! Зависимость простая — от второй КС нужно отнять то, что мы отбросили как переполнение при расчёте первой КС, только xor'енное с 11. Т.е. отбрасывая 00 (ничего) от первой КС, от второй отнимаем 11 и т.д.

Уфф, вроде всё. Осталось два незадействованных бита, но они, похоже, всегда единицы.

Шаг пятый. Чистка радиоэфира

А вообще я не сторонник применения радиоканалов где попало. Эфир и так прилично загажен, так что работать это всё будет местами (географическими) довольно нестабильно. Поэтому займёмся тем, что выкинем из парктроника приёмник и передатчик, соединив базовый блок, блок индикации и наш микроконтроллер по проводам. Почему я упоминаю блок индикации, хотя не собирался его ставить? А из-за пищалки. Всё-таки передача от базового блока парктроника в наш микроконтроллер, там декодирование, затем пересылка в головное устройство, там снова декодирование и отрисовка внесёт некритичный, но заметный лаг в отображение расстояний. Поэтому блок индикации останется в недрах приборки и будет пищать заведомо быстрее (хотя в будущем, может быть, заставлю пищать свой микроконтроллер).

Можно было бы не париться и соединить все блоки проводочками прямо как в отладочном режиме, напрямую. Однако прокидывать через всю машину жалкие 5 вольт TTL, поверьте мне, не лучшая идея. Поэтому впаяем во все три устройства микросхемы MAX485, реализующие передачу по куда более надёжному интерфейсу RS-485 [6]. В общем как-то так (простите за неотмытый флюс). Базовый блок:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 9

на месте белого кружка в правом верхнему углу платы стоял чип R433A, из его обвязки также удалён транзистор Q11 и резистор, вместо которого припаян проводок. А в свободном месте удалось расположить микросхемку так, что ножки попали на минусовой контакт и несколько других подходящих контактов. Поскольку базовый блок всегда является передатчиком, ножки DE и RE можно постоянно замкнуть на +5 вольт. Линии A и B интерфейса RS485 выведены на дополнительную клемму.

Блок индикации:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 10

ну здесь вообще красота, MAX485 впаялась практически как родная вместо стоявшей микросхемы приёмника RF83C [7]. Совпали ножки выхода данных DO и минусовая GND, ножки DE и RE, поскольку эта часть всегда приёмник, посажены на землю. Остальное потребовало всего одной перемычки.

Работает, как и прежде:

Реверс-инжиниринг протокола парктроника. Танец маленьких бит - 11

фотку собственного микроконтроллера, пожалуй, опубликую в статье про остальную часть функционала KMENevoBT с гитхаба.

Напоследок, код полного декодирования посылки от парктроника из отладочной программки на Delphi:

Ну не бейте, это всего-лишь отладочный код на коленке

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;

Шаг шестой. Выводы

Возможно, в какой-то момент стоило отказаться от дальнейших раскопок и заказать с Ebay тот же парктроник, который расковырял итальянец с форума по первой ссылке, но мне понравился сам парктроник. Он весьма быстро и точно работает. Пришлось добить, уже из принципа.
Что курили китайцы, разрабатывая такой вот протокол, непонятно.

Лирическое отступление

Довелось мне несколько лет назад раскопать протокол диагностики автомобилей Форд с мозгами EEC-IV. Это такие мозги [8] на основе интеловского набора 8096, кажется. В общем, древность на уровне чуть-ли не 8086. Ставилось это на форды с середины 80х и по начало 2000х, где-то с начала 90х там был реализован протокол диагностики Data Communication Link (DCL). Так вот, скорее всего из-за ограничений производительности невозможно было сделать протокол обмена с асинхронным обменом данными, как COM-порт компьютера. Поэтому протокол был синхронный. Это означало, что при активации диагностики мозги [8] начинали слать по линии данных «фреймы», состоящие из строго синхронизированных по времени байтслов. Для общения с мозгами необходимо было с точностью в десятки микросекунд засылать свои слова в указанные промежутки между принимаемыми словами. Никакой компьютер с COM-портом не обеспечивал требуемой точности отправки байт. Пришлось делать на микроконтроллере…
Хм, так о чём я… А, вот. В том случае такой интересный протокол обмена был оправдан ограничениями чипа. В случае с этим парктроником я не вижу никакой логики именно такого «танца» бит. Что мешало расположить, к примеру, 4х4 бит десятков сантиметров, затем 4 бита единиц сантиметров, ещё 4 бита на расширение до 5 бит датчиков B и C и всякие служебные и в конце контрольная сумма всех бит (просто сумма, без извращений, в крайнем случае стандартная CRC8).

Кстати, зная протокол обмена, можно применить этот парктроник не только на автомобиле, а, к примеру, на самодельном роботе. Да, для роботов есть отдельные ультразвуковые датчики, но тут их сразу четыре и читаются они одной ножкой ардуины, хоть и с задержкой в несколько миллисекунд.

Всем дочитавшим всего наилучшего!

P.S. Хотел опрос добавить насчёт писать ли про раскопки протокола мозгов для газового впрыска KME Nevo Pro. Там несколько другие приёмы использовались… Но думаю, скорее надо спрашивать насчёт «а нужны ли на хабре такие околоавтомобильные изыскания?».

Автор: creat0r

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/arduino/81311

Ссылки в тексте:

[1] некоторых публикаций: http://www.mp3car.com/hardware-development/121995-custom-usb-parking-sensor-interface-formally-pdc-usb.html

[2] R433A: http://www.adsean.com/dl/saw5035.pdf

[3] OOK-модуляцией: http://en.wikipedia.org/wiki/On-off_keying

[4] Манчестерский код: https://ru.wikipedia.org/wiki/Физическое_кодирование#.D0.9C.D0.B0.D0.BD.D1.87.D0.B5.D1.81.D1.82.D0.B5.D1.80.D1.81.D0.BA.D0.BE.D0.B5_.D0.BA.D0.BE.D0.B4.D0.B8.D1.80.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D0.B5.C2.A0.28.D0.B0.D0.BD.D0.B3.D0.BB..29.D1.80.D1.83.D1.81.D1.81.D0.BA.

[5] гитхабе: https://github.com/doncreat0r/KMENevoBT/blob/master/KME_BT.c

[6] RS-485: http://ru.wikipedia.org/wiki/RS-485

[7] RF83C: http://www.hoperf.com/upload/rf/RF83.pdf

[8] мозги: http://www.braintools.ru

[9] Источник: http://habrahabr.ru/post/249409/