CAN-сниффер на STM32F0x2

(заранее извиняюсь за отсутствие кусков кода в тексте: редактор пикабу не позволяет воткнуть достаточно длинные посты, да и нет никаких тегов для оформления исходников, оригинал можно прочитать у меня в ЖЖ; и вообще, на пикабу крайне неудобный редактор постов, в ЖЖ все намного удобней).

Саму основу для сниффера я сделал еще давно — когда необходимо было разработать девборду для "тренировок" с USB и CAN (под контроллеры термодатчиков):

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


Итак, первое, что нам нужно для работающего CAN-сниффера — это возможность одновременной работы и CAN, и USB. В дешевой нише STM32 это умеют STM32F0x2 (072 и 042; на работе для термодатчиков я закупал 042, для дома же купил на али 2 десятка 072, тогда это было примерно 60 рублей за штучку).

В отличие от 103-х, где служебный буфер в памяти CAN и USB делять не "по-братски", и он может принадлежать лишь одной из периферий, в 0x2 CAN забирает лишь 256 последних байт буфера USB. Поэтому для USB доступно 768 байт буфера (в принципе, этого вполне достаточно, если не делать сложное составное устройство).

Начнем с USB. Сниффер будет "выдавать" себя за PL2303 (мне нравится, что модуль ядра выделяет для него устройство /dev/ttyUSBx, а не позорный /dev/ttyACMx, как под обычный USB-CDC; кроме того, в некоторых некошерных дистрибутивах вроде бубунты могут быть проблемы с USB-CDC: при подключении запускается modemd и захватывает файл устройства в свое личное распоряжение).

В заголовочном файле usb_defs.h определяем USB_BTABLE_SIZE как 768 байт. Там же определяем размеры буферов конечных точек 0 и 1 (конечная точка 1 — interrupt IN — использоваться при работе не будет, и в принципе можно было бы ее не определять). Там же нам понадобится определить структуры служебных регистров и регистров описания конечных точек.

В файлах usb_lib.c и usb_lib.h разместим "низкоуровневые" функции USB. В принципе, эту иерархию не я придумал: когда я только начал заниматься USB, вменяемой реализации на просторах интернета не нашел. Один из пользователей easyelectronix выложил простую реализацию USB-HID. Собственно, на основе ее я и сделал свои экземпляры USB-HID, CDC и эмуляцию PL2303.

В заголовочном файле определим различные стандартные типы запросов и прочее, что нам понадобится (строки с 35 по 77). Простые макросы для понимания, является ли пакет входящим или исходящим и есть ли там данные SETUP. Для работы с регистрами конечных точек нам понадобится определить пару макросов: KEEP_DTOG_STAT и KEEP_DTOG.


Здесь сразу скажу: надо помнить, что в регистрах EPnR некоторые флаги имеют свойство toggle. Поэтому не повторяйте моих ошибок: держите это всегда в уме, и когда нужно лишь какой-то флаг установить/сбросить, обнуляйте биты всех ненужных toggle-флагов! Эти макросы собственно и занимаются тем, что оставляют нетронутыми флаги DTOG (они нам не нужны, т.к. мы не пользуемся двойной буферизацией) и STAT (а это важно, когда мы получаем данные, но не хотим сразу же отправлять ACK, пока буфер не будет обработан).Еще для работы конечного автомата состояния USB понадобится определить его состояния. Там же определяем макросы для задания строковой информации (_USB_STRING_, _USB_LANG_ID_) и вспомогательные структуры для разбора конфигурационного пакета (config_pack_t), настройки конечной точки (ep_t) самого USB(usb_dev_t — эта структура используется, чтобы поменьше глобальных переменных заводить). Из /usr/include/linux/usb/cdc.h копируем определение структуры usb_LineCoding (в принципе, можно обрабатывать SET_LINE_CODING, меняя скорость, скажем, USART1; но в данном случае это не нужно, а USART1 у меня использовался исключительно для отладочных сообщений).

В файле usb_lib.c определяем дескрипторы устройства (достать их несложно, если "натравить" на "настоящий китайский" PL2303 утилиту lsusb -v). Здесь же как WEAK определены заглушки для обработчиков стандартных запросов SET_LINE_CODING (изменение параметров последовательного порта: скорости, четности и т.п.), SET_CONTROL_LINE_STATE (аппаратное управление потоком) и SEND_BREAK (конец связи).

В отличие от "обычного" USB CDC у pl2303 есть еще и vendor-запросы. Нафиг они нужны — непонятно, однако, благодаря тому, что кто-то уже отреверсил подобную железяку и написал для нее модуль ядра, в исходниках /usr/src/linux/drivers/usb/serial/pl2303.c можно посмотреть, как оно работает. Собственно, оттуда я и утащил функцию-обработчик vendor_handler(config_pack_t *packet).

Возможно, в оригинале эти запросы таили что-то эдакое, но в ядре они используются лишь для идентификации — что на том конце действительно PL2303. Во всяком случае, с таким минимумом устройство работает и под android (возможно, будет работать и под игровыми приставками, мне это безразлично).

При работе со строковыми дескрипторами иногда может оказаться, что дескриптор не влезает в стандартный объем 64-байтной посылки (особенно большими размерами славятся HID-дескрипторы), поэтому такие вещи надо разбить на несколько посылок. Отправляются такие дескрипторы только в начале коннекта, поэтому я решил не заморачиваться с конечным автоматом, а сделать блокирующую запись: static void wr0(const uint8_t *buf, uint16_t size). Здесь еще надо было учесть, что если мы отправляем в последней посылке ровно 64 байта, то нужно еще и ZPL — посылку нулевой длины — отправить.

Работать с USB мы будем на прерываниях, поэтому все самое интересное начинается в обработчике прерывания usb_isr().

Здесь мы обрабатываем такие прерывания, как RESET — хост говорит устройству, что нужно заново инициализировать USB, CTR (correct transfer) — прерывание по приему или передаче данных, а также вспомогательные SUSP и WKUP — "засни" и "проснись".


В обработке OUT-запросов (т.е. входящих для устройства) пришлось пойти на хитрость: т.к. некоторые вещи (как тот же LINECODING) передаются в "два захода", необходимо отдельно заполнять данными setup_packet и вспомогательный ep0databuf (где и лежат эти данные по установке LINECODING). Данные для LINECODING приходят так: сначала без флага SETUP приходит нужная информация, а потом уже с этим флагом — команда запроса SET_LINECODING. И, соответственно, вызывается процедура обработчика запроса.

Чтобы USB корректно работало, сначала нам надо настроить все конечные точки: выдать им адреса и размеры буферов, определить направление, задать функцию-обработчик. Это делается в EP_Init.

Кому-то нравится руками задавать адреса буферов данных, я же решил все упростить — в переменной lastaddr хранится последний свободный адрес в буфере (при RESET эта переменная реинициируется на начало буфера). Дальше все по коду понятно.

Для разбора данных, поступающих на управляющую точку EP0, вызывается функция EP0_Handler. Из остальных конечных точек, как я уже говорил, точка EP1 у нас хоть и определена, но не используется. А для приема/передачи заводим односторонние точки EP2 и EP3 (все эти данные хранятся в дескрипторе устройства, поэтому размеры буферов и направление передачи конечных точек нужно согласовывать с данными в дескрипторе). Их обработчики будут уже в другом файле — с более высокоуровневыми вещами.

У STM32F0x2 регистры данных USB_TX пишутся по 16 бит (в STM32F103 эмулируется 32-битное хранилище, т.е. писать надо как 32-битный блок, но активны там только 16 бит), а USB_RX вообще можно читать побайтно (у 103 они тоже эмулируют 32-битные блоки). В общем, здесь все проще. EP_WriteIRQ отличается от EP_Write тем, что вызывается внутри обработчиков прерывания, поэтому в ней флаги EPnR не меняются.

В файле usb.c лежат сравнительно высокоуровневые функции (правда, я и USB_setup зачем-то здесь оставил).

Собственно, настройка USB и начинается с USB_setup. Тактируем USB от HSI48, что позволяет не цеплять внешний кварц. Используем автокоррекцию HSI48 от USB SOF. Для начала разрешаем только прерывания RESET и WKUP (остальное будем разрешать уже после настройки конечных точек).

IN/OUT запросы данных обрабатываем фукнциями transmit_Handler() и receive_Handler(). Обработчик IN (выходящие данные) ощичает флаг CTR_TX и выставляет внутренний флаг tx_succesfull (говорящий о том, что предыдущая посылка отправлена и можно отправлять следующую). А в обработчике OUT (входящие данные) мы выствляем внутренний флаг rxNE (говорящий, что в буфере есть данные и их можно считать) и очищаем флаг CTR_RX. Выставлять ACK мы будем лишь после того, как данные из буфера будут обработаны!

Для того, чтобы не блокировать МК на время отправки мелких объемов данных (а в основном они значительно меньше 64 байт), в функции USB_send есть возможность "отложенной" отправки данных (последняя строчка просто помещает маленькие объемы данных во вспомогательный буфер, который будет отправлен в последующем при помощи функции send_next()).

Из основного цикла main на каждом проходе надо запускать функцию usb_proc. Здесь анализируются состояния КА USB. Скажем, в состоянии USB_STATE_CONFIGURED (EP0 настроена, все дескрипторы отправлены) нужно настроить остальные конечные точки. В состояниях USB_STATE_DEFAULT (начальное) и USB_STATE_ADDRESSED (только прошла процедура адресации) USB еще пользоваться нельзя — поэтому снимаем флаг usbON. А в состоянии USB_STATE_CONNECTED вызываем ту самую send_next().

Более высокоуровневую функцию USB_receive вызываем уже откуда-нибудь извне. Она возвращает количество считанных данных и заполняет ими буфер. Ну, а т.к. данные теперь надежно сохранены, можно отправлять хосту ACK.


Уже в main.c размещаем функцию буферизованного чтения из USB с выставлением флага готовности по '\n'. Здесь происходит эхо введенных символов и обработка backspace (чтобы можно было удалять неправильно набранные символы).


ОК, с USB закончили. Пора переходить к CAN.


В can.h определим структуру CAN_message. В ней содержатся данные (до 8 байт) для стандартной CAN-посылки, длина данных и идентификатор получателя этих данных. И определим флаги состояния CAN.

В can.c определяем основные функции для работы с CAN. Входящие данные буферизуются в массиве messages[CAN_INMESSAGE_SIZE].

Настройку CAN делаем по сниппетам. Разве что аргументом этой функции является скорость в кбод, поэтому проверяем в начале, допустимым ли является значение скорости.

Поначалу фильтр позволяет принимать абсолютно все сообщения: нечетные в FIFO0 и четные в FIFO1. Далее фильтры можно будет перенастроить.

В отличие от USB, у CAN в прерывании анализируются лишь ошибки, а вот для работы с данными из main постоянно запускается can_proc(). Здесь рассматриваются различные флаги и обрабатываются ошибки линии: если на шине нет никого, либо накапливается много ошибок, CAN переинициализируется.

Функция can_send(uint8_t *msg, uint8_t len, uint16_t target_id) отправляет сообщение msg длиной len с идентификатором target_id. В принципе, можно было бы уменьшить количество аргументов этой функции, если поместить эти данные в обертку — структуру CAN_message. Функция находит первый свободный "почтовый ящик", заполняет в нем регистры данных и инициализирует посылку.

Функция can_process_fifo(uint8_t fifo_num) запускается при наличии данных в соответствующем буфере FIFO. Все данные помещаются в массив-буфер, откуда впоследствии их можно считать. Если буфер полон, то функция "надеется", что к следующем запуску можно будет его опустошить (иначе происходит переполнение FIFO — ничего с этим не поделать).


Как все это работает.


При получении определенной команды по USB, она анализируется (proto.c), и если это команда на передачу данных (s), запускается функция sendCANcommand. В ней происходит парсинг введенных пользователем данных, и если все ОК, то отправляется сообщение.

При поступлении сообщения с CAN, его содержимое выводится на терминал.

Для упрощения создания фильтров я добавил еще и софтовый фильтр — для возможности отклонения сообщений с ID из списка (иначе пришлось бы лепить аппаратный фильтр с нужной маской и ID, а считать-то лень!).

Функция add_filter позволяет добавить или удалить (если фильтр не содержит данных) фильтр с номером от 0 до 27. Т.е. можно удалить фильтры по умолчанию и заменить их своими. В функции идет довольно-таки длинный парсинг текстовых данных: разбирается, в каком режиме фильтр (список или маска), а далее заполняются сами данные фильтра.

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

Лига Радиолюбителей

1.5K постов10K подписчика

Правила сообщества

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

Постарайтесь не быть снобами в отношении новичков. Все мы когда-то ничего не знали и ничего не умели.

За попытку приплести политику или религию - предупреждение. 2 предупреждения - бан.

1
Автор поста оценил этот комментарий
А почему не на хабре? Там вроде публика как раз под это дело заточена.
раскрыть ветку (1)
1
DELETED
Автор поста оценил этот комментарий

В том болоте я принципиально ничего не пишу. Политика "кармы" — это самый настоящий фашизм!

показать ответы
1
Автор поста оценил этот комментарий

тут демократичней.....
если проседает, на фото платы можно котов накидать :)

раскрыть ветку (1)
0
DELETED
Автор поста оценил этот комментарий

Их есть у меня! ☺

Иллюстрация к комментарию
показать ответы
2
Автор поста оценил этот комментарий

На хабре сейчас в основном реклама, элеронная коммерция и всякая хрень.

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Ну вот я туда периодически заглядываю: за последние три месяца вообще ничего интересного не было.

показать ответы
0
Автор поста оценил этот комментарий
А можно подробное практическое применение?
раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Основная цель, для которой я его делал — анализ CAN-сетей без дорогущих PCI'ных плат. Причем, получается совершенно кроссплатформенная штука: хоть в планшет подключай, да смотри в терминале, что там в шине происходит.

Ну и как побочное применение — замена этой самой дорогущей PCI'ной платы CAN-контроллера.

показать ответы
0
Автор поста оценил этот комментарий

Это профессиональный инструмент, он не может стоить дешево. И вы опять не учитываете потраченное на разработку время.

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Ну ОК, если мою зарплату приплюсовать, то пусть будет 10000 рублей!

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

Я работал с парой PCI'ных контроллеров — это просто жесть какая-то! Для обоих производитель вместо исходных кодов модуля ядра давал блоб! Понятное дело, что блоб этот был рассчитан на какое-то дико бородатое ядро (то ли 3.2, то ли вообще 2.4, в общем, то, что уже давно никем не используется). И приходилось держать очень древний линукс на компьютере, работающем с CAN-шиной.

В общем, в данном случае как раз такая ситуация, что свое — оно намного лучше, чем чужое. Да и я могу при необходимости внести изменения в протокол работы железяки, какие-то дополнения сделать…

1
Автор поста оценил этот комментарий

Ну у IXXAT очень хорошая поддержка. Есть софт из коробки - CAN analyzer mini или miniMON (зависит от версии драйверов). Дружелюбный интерфейс, выгрузка логов. Без проблем работает как под виндой так и под линуксом.

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Но 500 баксов за такое — это уж чересчур… У моего себестоимость меньше трехсот рублей!

показать ответы
1
Автор поста оценил этот комментарий

Я там вижу что можно почитать раз в пару недель. Ну вот последнее что там было интересного это про распознавание объектов нейросетью пару дней назад.

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Про нейросети там другой прикол: 99% пишущих про нейросети понятия не имеют, что это такое! Во многих случаях там что-то, сильно напоминающее абдуринщиков: "что-то скачал в инете, почитал инструкцию, жамкнул мышкой три раза — во как круто получилось!".

показать ответы
0
Автор поста оценил этот комментарий

А чем вас IXXAT не устроил? Он конечно тоже не дешевый, но сопоставим с оплатой ваших трудозатрат.

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

А он тоже себя выдает за эмулятор последовательного порта? Потому что я встречал несколько моделей CAN<>USB и у всех них главной проблемой было то, что они выдавали себя за custom device и тупо не работали из-за этого!

У меня никакого желания заниматься реверсом USB, запуская в виртуалбоксе "родное" ПО и мониторя, что там в шине при помощи wireshark, нет!

показать ответы
4
Автор поста оценил этот комментарий

А тут карма по другому работает? За пост спасибо, как раз этой темой за интерисовался! )

раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Я только недавно узнал, что здесь тоже это говно есть. Прискорбно.

показать ответы
2
Автор поста оценил этот комментарий
Ломать проприетарные протоколы и делать свои собственные прибомбасы для анализа. Дилеры в сад.
раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

Да, для реверса тоже можно использовать.

0
Автор поста оценил этот комментарий
Нихуя не понял, но ОЧЕНЬ интересно!
раскрыть ветку (1)
DELETED
Автор поста оценил этот комментарий

У меня на ютубе подобные отзывы на лекции были =D

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества