Меня давно "волновала" микроэлектроника, но все как-то не получалось. Простые поделки из Arduino и т.п. не заводили, не было какой-то идеи, которая бы приносила пользу.

Disclaimer

Стоит заметить, что в электронике в целом я дилетант, закон Ома регулярно гуглю, а для всех основных электронных компонентов держу в голове аналогии на "шлангах с водой".

Данный опус стоит расценивать как иллюстрацию "пути", набора ошибок и описания граблей. Опыт изготовления устройства был, как говорится, больше полезный, чем приятный. Многие вещи описываются очень упрощенно. Наверняка специалисты, варящиеся в этой каше сильно больше моего, найдут много неточностей в формулировках.

Идея

Началось все с сообщения в одном из Telegram-каналов любителей CAN-хакинга.

CAN - шина, применяемая в автомобилях для соединения блоков между собой. В современном автомобиле практически все мало-мальски управляемые компоненты связаны шиной и обмениваются друг с другом посылками. Конкретно в моем Opel Astra H 2011 года выпуска на этой шине сидят педали (тормоз и сцепление все же механические, но в шине видно когда педаль нажата), блок управления двигателем, блок предохранителей, ABS, ГУР, подрулевые переключатели, приборка, магнитола. Список можно продолжать.

Более того, в данном автомобиле шин не одна а целых три.

Завсегдатай канала писал...

Идея для LS модуля.

Когда выключаем двигатель (обороты = 0), ключ переведен в положение блокировки из ацц, если температура за бортом менее 4 градусов, нажимаем первое положение дворников на 1700 миллисекунд.

Это была классная, простая и полезная идея. Кто не мучался с примерзшими дворниками зимой? "Первое положение дворников" на Opel - это парковка. Они встают в произвольное (зависит от того, сколько держишь подрулевой переключатель нажатым) положение и там остаются, не возвращаются в нулевое положение. 1.7 сек это примерно вертикально. Стекло теплое, дворники встали вертикально что способствует лучшему стеканию воды. А если и примерзли - в таком положении они все в зоне обдува печки.

Да, можно "по дедовски" "отогнуть" их руками от стекла, но на мой взгляд это не эстетично. Ну и программисты же народ ленивый - зачем делать руками то, что за тебя может сделать автоматика? Да, еще говорят пружина "поводка" растягивается )

У меня к этому моменту уже был опыт сборки микроконтроллера и связи его с CAN-шиной на примере другого проекта (он пока не доделан, поэтому расскажу о нем позже) и я решил запилить.

Дизайн

Принципиальную схему нарисовал за пару часов. Для решения данной задачки нужен микроконтроллер (я использовал STM32), трансивер CAN-шины (сигналы по шине передаются несколько "специфическим" образом, об этом ниже, и чтобы подружить шину с контроллером нужен трансивер) и преобразователь питания (в авто, как мы знаем, 12 вольт, а STM32 работает от 3.3, нужно понижать).

На этом этапе решил заложить в устройство бОльшие возможности, чем просто подъем дворников.

Принципиальная схема. Итоговый вариант, после всех доработок, описанных далее
Принципиальная схема. Итоговый вариант, после всех доработок, описанных далее

В Opel Astra H три CAN-шины. Первая - HS, High Speed (скорость 500 kbit/sec). На ней сидит... а толком не известно что на ней сидит, потому что официальной информации нет и все собирается энтузиастами по крупицам путем реверс-инжинеринга и сниффинга шины. Высокая скорость обмена подразумевает большие объемы передаваемой информации, высокую скорость отклика. Насколько мне известно, на этой шине сидят жизненно важные (как для самого автомобиля так и для пассажиров) устройства. Лезть в нее просто так дело неблагодарное, ее вычеркнул сразу.

Вторая шина - MS, Medium Speed (95 kbit/sec). Тут уже много интересно. Тут и подрулевые переключатели, и магнитола, и куча данных о состоянии автомобиля: температура за бортом, температура двигателя. С помощью этой шины можно ловить все кнопки что есть на руле и на магнитоле, можно выводить данные на штатный экран. Ее полезно как слушать, так и использовать для записи. Задействуем ее на будущее, но для решения задачи этого будет недостаточно.

Третья шина - LS, Low Speed (33.3 kbit/sec). Тут частично продублированы данные, которые уже есть в MS. Также тут хорошо видно состояние двигателя (заведен или нет). И самое главное - тут сидят дворники. То есть отправив нужную телеграмму в эту шину, можно управлять положением дворников. Берем.

Принципиальную схему устройства и печатную плату под него я рисовал в KiCad. Первая версия выглядела как-то так:

Все выполнил на одной стороне. Левая часть - питание, преобразователь из 12 в 3.3, центр - микроконтроллер STM32F105 и всякая рассыпуха для него (большая часть - фильтрующие и питающие конденсаторы), правая часть - CAN-трансиверы и дополнительные разъемы для Serial Wire (для прошивки и отладки контроллера) и UART (для мониторинга и вывода отладочной информации).

Зоркий глаз заметит, что на плате отсутствует тактовый генератор. Изначально решил, что можно без него, в STM32 есть встроенный, частоты которого хватило бы, чтобы запустить чип и выдать нужную скорость на трансивер. Но умные люди подсказали, что встроенный генератор не отличается стабильностью. А для CAN-шины уплывшая в неподходящий момент скорость может оказаться губительной (об этом, кстати, будет интересная история). Ок, добавляем кварц.

Кстати, вот так выглядит экран настройки тактирования STM32:

А стоп, погодите, не тот скриншот. Хотя согласитесь, перепутать не сложно:

Плату хотелось развести так, чтобы "было красиво". Старался сделать все симметрично. Размеры посадочных мест выбирал такие, чтобы было удобно паять и в принципе реально запаять человеку без богатого опыта пайки. Вся мелочь выбиралась под 1206 (они же 3216). 3216 это размеры контактной площадки в миллиметрах (а 1206 в дюймах) - 3.2 мм в длину и 1.6 мм в ширину. Или наоборот... Не важно, короче они достаточно мелкие ) но вполне паяются руками обычным паяльником (обычным - это не тем которым батя в детстве чайники и тазы запаивал, а обычным для микроэлектроники). К слову есть еще 1608 (1.6 на 0.8) и 1105 (1.1 на 0.5).

Чтобы лучше представить размер - вот картинка (не моя). Еще можно погуглить SMD Challenge.

Монтаж в авто

Первый вариант дизайна предполагал монтаж "на проводах". Захотелось сделать более user friendly устройство. Чтобы не бояться за его изоляцию, отрыв проводов и т.п. Хотелось чтобы прямо plug-and-play.

Нашел на Aliexpress разные OBD разъемы под пайку (OBD-II, римская 2, не ЭлЭл, так называется типовой диагностический разъем в большинстве современных автомобилей), нашел примерные размеры. Сел переделывать плату...

Поставил себе задачу - уместить все на плате размером с сам OBD-разъем. Задачка получилась не из легких - OBD-разъем содержит 16 пинов которые стоят с довольно большим шагом чтобы занимать большую часть площади разъема, но в то же время с маленьким шагом, недостаточным для того, чтобы разместить компоненты между ними.

Где-то подсмотрел дизайн в форме стэка двух плат, соединенных вертикально контактными гребенками. Получилось как-то так:

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

Да, вот так примерно выглядит разводка, платы:

До первого заказа готовой платы было еще несколько итераций. Менял посадочные места компонентов чтобы все влезло и было удобно паять. Например кварц с through hole (когда из него торчат две длинные ноги которые надо пропустить через плату насквозь) заменил на SMD.

Заказываем плату

Тут ничего хитрого. Заказывал на JLCPCB в Китае. Заказал только верхнюю плату для отладки дизайна и пробной сборки. Минимальная партия - 5 штук. Вышла что-то около 200-300 рублей за партию. Отправляем архив с файлами, которые экспортирует KiCad, настраиваем параметры (цвет платы, толщина, обработка, и т.п.), засылаем деньги, ждем. От момента заказа до момента получения на почте проходит примерно 2 недели. Сам заказ выполняется очень быстро.

Паяем

Из оборудования для пайки у меня был только современный паяльник TS-100 купленный на Алике.

TS-100 обыкновенный, с дефолтным жалом
TS-100 обыкновенный, с дефолтным жалом

Чем хорош такой паяльник: он питается от 12 вольт, легкий, удобный, имеет возможность смены жала (много разных форм под разные задачи), быстро нагревается, имеет возможность настройки и калибровки. Более того, ему даже можно сменить прошивку. Да, у паяльника есть прошивка и ее можно обновить.

Изначально я заказал жало ровно как на фото выше. Оказалось что паять им SMD компоненты крайне неудобно. Умные люди посоветовали взять "топор".

Топор - это жало TS-K или TS-KU: 

TS-KU. Правильный топор
TS-KU. Правильный топор

Им можно даже при определенной сноровке за раз запаивать сразу несколько компонентов или пропаять сразу несколько ног у STM-ки.

Первая версия платы была спаяна топором.

Ошибки при пайке

При пайке допустил ошибку. По старинке сперва залудил посадочные места, затем пытался припаять к ним компоненты. В итоге получалось, что когда одна площадка нагрета и слой припоя расплавлен, на соседней площадке припой естественно застывший и компонент "перекашивает". Он как бы стоит немного на ребре. В результате это а) не эстетично и б) компонент может треснуть, как следствие - работать некорректно.

Правильно: сперва мажем площадки флюсом, на него сажаем компонент. У меня был индикаторный флюс TT, достаточно густой чтобы компонент как бы приклеился на него как на клей. Затем берем на жало припой, компонент прижимаем пинцетом и припаиваем одну из контактных площадок. Затем повторяем со второй. Крутые "паятели" умеют за одно прикосновение припаивать обе площадки, но я такую суперсилу еще не освоил.

После пайки излишки флюса лучше удалить во избежании потенциальных КЗ. Да, есть флюс "безотмывочный", который не проводит. Но мне просто не нравится как плата выглядит под слоем остатков флюса. После пайки мою ее спиртом.

Жгем чип или притча о земле

На удивление плата запустилась с первого раза. Прошивальщик увидел чип и смог залить в него прошивку (о ней чуть позже). Тестовая прошивка почти ничего не умела. Зажигала светодиод (их было целых три - на фото выше они под чипом, какая же микроэлектроника без светодиодов?), инициализировала CAN-шину и отправляла Hello World в UART.

Лампочки горели, UART присылал Hello World. Все шло как-то слишком гладко...

Изначально при отладке, я питал плату от 3.3 вольт, полученных от прошивальщика STLink. То есть в момент первого запуска и отладки преобразователь с 12 на 3.3 не был задействован вообще, на плату не приходило 12 В.

Решил проверить преобразователь. Отключил прошивальщик, отключил UART, подключил +12 V к плате. На удивление все прошло хорошо. Дым не пошел, померял мультиметром - на чипе были стабильные 3.3 V, лампочка горит. Отлично! Что может пойти не так? Подключил обратно UART - а там тихо. Дальше я потратил примерно час или два времени в попытке понять что же не так. Терминал молчал. Чип отвечал на прошивку, но подозрительно игнорировал попытку отладить себя, сваливался в какие-то дикие исключения. Был бы он Linux'ом, наверняка бы сдампил кору. Перевел обратно на 3.3 V, поменял UART-USB преобразователь, 100500 раз перепрошил - все безуспешно, не работает.

И тут я вспомнил, что папа в детстве всегда учил меня, чтобы у всех приборов была общая земля!

Штука тут вот в чем. Пока я подключал прошивальщик и UART все устройства у меня были включены в USB-порты одного компьютера, а значит имели общую землю.

Как только я запитал плату от внешнего блока питания 12 вольт, блок питания и UART оказались не связанными общей землей. Из-за этого разница потенциалов на пинах UARTа оказалась не 3.3 и даже не 5, а какой-то совсем иной. Иной в большую сторону.

Это привело к тому что часть чипа выгорела и не могла нормально функционировать. Проблема была именно в том, что для UART я развел только RX и TX пины, но не обеспечил UART преобразователь общей с контроллером землей. Пришлось перепроектировать плату - вывести дополнительный пин с землей для безопасного подключения UART.

Особенности LS-шины. Второе перепроектирование платы

Чтобы объяснить следующий факап, нужно немного углубиться в том, как работает CAN-шина и в особенности устройства шин в Opel Astra.

CAN-шина состоит обычно из двух проводников - High (H) и Low (L). Суть работы трансиверов заключается в управлении разницей напряжения между этими проводниками. Когда разница 0 - шина находится в рецессивном состоянии, когда больше или равна определенному значению - шина находится в доминантном состоянии.

Понятно, что выше чем свое напряжение питания уровень на линиях шины он не сделает.

Так как STM32 питается от 3.3 вольт, для обеих шин я выбрал трансиверы, работавшие от этого же напряжения. 

Когда я запустил устройство, обнаружилось, что а) я успешно могу читать обе использованные шины, но б) писать в LS-шину я почему-то не могу, авто игнорировал все посылки, отправляемые в шину.

Чем же LS-шина такая особенная? Отличие от MS не только в скорости, а еще и в том, что на LS-шине для создания доминантного состояния требуется разница между H и L равная 5 вольт (при этом для MS-шины это значение не превышает 3 вольт).

Таким образом трансивер, работающий от 3 вольт, никак не мог создать разницу H-L достаточную чтобы перейти в доминантное состояние. Но при этом вполне мог "читать" эту разницу и воспринимать идущие по шине посылки.

Тут уже пришлось выбрать другую модель трансивера, работающую от 5 вольт, и разместить на плате дополнительный преобразователь 12 - 5. Преобразование 12 - 5 удалось уместить на нижней плате пирога, непосредственно рядом с посадочным местом разъема OBD-II.

Разбираем-собираем

С Алика приехал OBD-II разъем, оказалось что я не угадал с размерами плат и текущая версия платы не влезает по габаритам. Ошибся примерно на 1 мм в каждом измерении. Ок, делаем еще одну ревизию платы, урезаем с каждой стороны на 0.5 мм, немного двигаем не влезающие компоненты, проверяем все еще раз, заказываем вторую ревизию платы со всеми исправлениями.

Решил купить себе паяльную станцию чтобы нормально демонтировать SMD-компоненты. Приобрел на Алике - станция с феном и паяльником. С помощью нее снял все с первой версии платы и перенес на новую версию, разместил разъем, немного подработал напильником слишком длинные контактные площадки так чтобы крышечка защелкивалась )

Пишем прошивку

Тут ничего особо хитрого. Прошивка пишется на C, используя библиотеку HAL (Hardware Abstraction Layer) от STM. Стартуем шину, фильтруем ее по нужным идентификаторам посылок, разбираем пришедшие данные.

Посылка в CAN-шине состоит из идентификатора и максимум 8 байт данных. В авто каждый идентификатор обычно описывает какое-то одно действие. Например, посылка с состоянием двигателя, посылка при нажатии на подрулевой переключатель, посылка с данными термодатчика за бортом. В данных каким-то образом зашифровано что собственно произошло.

В итоге фильтруем три посылки.

Первая - состояние двигателя. Фиксируем смену состояний между "заглушен" и "работает". При переходе из рабочего в нерабочее - выставляем флаг что нужно запарковать дворники. При переходе из нерабочего в рабочее - сбрасываем все состояние в начальные значения.

Вторая - температура за бортом. Если она ниже 4 градусов - выставляем флаг разрешающий парковку. Если выше - запрещаем парковку.

Третья - состояние подрулевых переключателей. Я решил что толку парковать дворники если ими не пользовались - нет. Пусть даже на улице и холодно, но если снега нет и водитель не чистит стекло - значит и осадков на нем нет, а значит парковать нет никакого толка. Еще я считаю количество использований. Если за время, пока мотор был заведен, было только одно использование стеклоочистителей - скорее всего это водитель вернул дворники из запаркованного состояния при запуске двигателя. А вот если срабатываний было 2 или больше - верный признак того что стекло чистят.

Почему я пишу что "выставляю флаг"? А где же сама парковка? Почему при получении телеграммы с состоянием двигателя "выкл" сразу не запустить парковку?

А все потому, что "прерывание". В моей программе в конце функции main можно увидеть код от которого привычный к другим языкам программирования (например к JS - такой как я) человек скорее всего покрутит пальцем у виска

while (true) {
   park_wipers_if_needed();
}

То есть мы постоянно крутимся в цикле и проверяем, не надо ли запарковать дворники? А где же тогда обработка состояния шины и т.п.?

А обработка состояния шины происходит в прерываниях. Когда внешняя аппаратура меняет состояния пинов микроконтроллера, внутри случается "прерывание" и управление передается на его обработчик, не важно в каком при этом while(true) мы находимся. В этом обработчике анализируется состояние аппаратуры и если получен готовый пакет его можно вычитать и проанализировать (то самое состояние двигателя).

Если в этой точке мы будем делать что-то длительное (например отправлять другие посылки в шину) или тем более делать какие-то паузы в коде ("удержание" подрулевого переключателя "нажатым" делается путем отправки специального пакета каждые Х миллисекунд) мы не дадим микроконтроллеру нормально работать и скорее всего он либо не сможет отправить нашу посылку либо мы пропустим какое-то другое важное прерывание и пропустим какое-нибудь сообщение от шины. Именно такой результат я и получил когда запустил парковку внутри обработчика события о получении нового пакета по шине.

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

"Заваливаем" шину

Еще один интересный факап - "обрушение" CAN-шины. В одной из прошивок я ошибся в настройках трансивера и выставил некорректную скорость шины. В итоге скорость, на которой пытался завестись трансивер не совпадала со скоростью, на которой работали все остальные устройства. Получился очень интересный эффект... Авто решило, что попало в аварию! Для мозгов автомобиля это, видимо, выглядело как потеря коннективности между различными участками системы. В момент подключения девайса в диагностический разъем, а точнее через пару секунд после этого Opel погасил все приборы, включил свет в салоне, врубил аварийку и разблокировал двери! Нет, подушки не сработали. Говорят "выстрелить" подушки через шину не получится да и сидят они, на сколько я знаю, на HS к которой я не подключался.

Выставление правильной скорости решило проблему, Opel больше не считал что пора спасать водителя. Но вот чтобы погасить свет в салоне пришлось скидывать клемму с аккумулятора.

Итог

Что получилось в итоге? Получилось рабочее устройство вида "включил и работает" которое может подключить неспециалист. Не надо морочиться с проводами, изоляцией, врезкой в существующую проводку. Удалось сделать его достаточно компактным чтобы разместить его в штатном диагностическом разъеме Opel Astra H и при этом закрыть (ну почти) крышку, которая этот разъем обычно скрывает от шаловливых ручек.

Девайс имеет возможности к расширению функционала за счет наличия второй шины - например можно вывести t двигателя на штатный экран (в Opel Astra H температура двигателя никак не выведена на приборную панель, доступна только в сервисном меню) или закрыть стекла автоматически при постановке на охрану (так конечно же умеют многие дополнительные сигнализации). Прошивку можно адаптировать для любого автомобиля, например похожая (но ручная, не автоматизированная) парковка дворников так же есть в автомобилях VAG и Lada.

Исходный код, схема и плата доступны на GitHub.

P.S. Выражаю огромную благодарность за консультации и ответы на бесконечные глупые вопросы сенсею Егору Давыденко из StarKit Robots