http://habrahabr.ru/post/249975/
На всякий случай, а то вдруг санкции применят (смаил). Описываемый случай не имеет никакого отношения к реальности и является целиком и полностью выдумкой автора
Раньше было
про потоки,
семафоры,
очереди и
HAL
Как-то раз попросили меня посмотреть на одно очень дорогостоящее устройство. Проблема была одна: среди использующих это устройство возникло стойкое убеждение, что 99,99% его цены происходит от того факта, что производитель этого устройства монополист в своей сфере и деваться пользователям этого устройства некуда.
Вооружившись осциллографом, я полез внутрь.
Через некоторое время поиски привели к двум проводкам, которые были в жгуте, соединявшем блоки устройства. Осциллограмма показала, что в проводках почти обычный USART. Почти — потому что «туда» данные бежали на скорости 9600, а обратно на 115200.
Достал два usb-usart переходника, прицепил их входы к RX/TX и начал анализировать протокол. Задача осложнялась разными скоростями, асинхронностью и бинарностью протокола. В общем, через некоторое время я обнаружил, что у меня банально разъезжаются глаза, пытаясь отследить в терминалах, что за чем идет и что на что реагирует.
Что бы сберечь вам зрение и рассудок в аналогичной ситуации, давайте сделаем аппаратный сниффер usart. Причем сделаем его так, что бы ему было все равно на скорости и последовательности передач. Ну а разобравшись с usart, вы потом сможете добавить анализ ножек и всяких SPI одновременно.
Работа сниффера будет простая: у STM32 куча встроенных USART. Пусть 2 их них слушают свои RX ножки и потом через USB выдают их наружу. А мы эти ножки прицепим к RX/TX изучаемого прибора.
Для начала берем уже (надеюсь) привычную плату STM32F3DISCOVERY и с помощью STM32Cube создаем заготовку. В этой заготовке должно быть следующее
5 потоков.
defaultTask — будет мигать светодиодиком «устройство работает и не повисло»
Usart1Rx — будет заниматься приемом данных на 1м порту
Usart2Rx — то же самое, то на 2м
UsbSend — тут будут обрабатываться и посылаться данные наружу
Usart3Tx — а это будет тестовый поток, который будет рассылать цитаты знаменитых роботов. Используем для проверки работоспособности первых двух портов «на вход». Ну или для баловства.
И одну очередь — в которую будут складываться посылки от «приемщиков» данных. Дабы не заморачиваться, я сделал такую структуру
typedef struct {
uint8_t usart;
uint8_t byte;
} myMes;
Для начала напишем самую важную функцию — посылку тестовых данных.
for(;;)
{
// Wall-e: Eeeee... va?
uint8_t walle[]="Eeeee... va?\r\n";
// Short Circuit: Johny Five is Alive!
uint8_t johny[]="Johny Five is Alive! \r\n";
// StarWars
uint8_t c3po[]="Sir, the possibility of successfully navigating an asteroid field is approximately 3,720 to 1 \r\n";
HAL_UART_Transmit(&huart3,(uint8_t *)&walle,15,16); //PB10
HAL_UART_Transmit(&huart2,(uint8_t *)&johny,23,100); //PA2
HAL_UART_Transmit(&huart1,(uint8_t *)&c3po,97,100); //PC4
osDelay(1000);
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_15);
}
И проверим ее, просто поочередно зацепившись внешним usart-usb переходником за подходящие ножки. Это по крайней мере даст понимание того, чего ожидать на «приеме», если мы просто соединим ножки RX-TX прямо на плате.
Теперь перейдем к функции, которая принимает и оформляет полученное.
xQueueReceive( RecQHandle, &w, portMAX_DELAY );
if(oldusart!=w.usart || char_count>16)
{
oldusart=w.usart;
newline=1;
char_count=0;
}
buf[char_count++]=w.byte;
Из предыдущих статей и знания языка С становится понятно, что просто ждем очередной «посылки» и в зависимости от условий выставляем флаги.
Ну и дальше ждем, пока USB интерфейс будет готов и просто формируем (признаюсь, можно аккуратней) строку и выводим ее пользователю. Расписывать тут я ее не стал, ибо там работа с буфером и строками «в лоб», без каких-либо оптимизаций.
Теперь необходимо написать то, чем мы будем принимать данные.
void Usart1Rx(void const * argument)
{
/* USER CODE BEGIN Usart1Rx */
/* Infinite loop */
for(;;)
{
if(HAL_UART_Receive_IT(&huart1, &b1,1)==HAL_OK)
{
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_9);
}
osDelay(1);
}
/* USER CODE END Usart1Rx */
}
Usart2Rx абсолютно аналогична.
Если что, то в реальном коде я оставил комментарии для пытливых пользователей.
И наконец, обработчик прерывания от USART
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
myMes m;
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_14);
switch((uint32_t)UartHandle->Instance)
{
case (uint32_t)USART1:
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_10);
m.usart=1;
m.byte=b1;
xQueueSend( RecQHandle, &m, portMAX_DELAY );
// Do this need?
//HAL_UART_Receive_IT(&huart1, &b1,1);
break;
case (uint32_t)USART2:
HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_12);
m.usart=2;
m.byte=b2;
xQueueSend( RecQHandle, &m, portMAX_DELAY );
HAL_UART_Receive_IT(&huart2, &b2,1);
break;
}
}
Обратите внимание на комментарий посредине и почему для второго порта она не закомментированаПо просьбе одного из внимательных читателей: Так же обратите внимание на то, что я использовал в обработчике прерывания xQueueSend вместо xQueueSendFromISR. Это сделано специально, что бы контроллер в процессе испытаний «а так оно сможет» (или говоря простыми словами, при поднятии скорости на последовательном порту где-то до 57600-115200) начал регулярно зависать. Простое включение отладчика показало бы, что он завис в ожидании на попытке записать в переполненную очередь. Внутри реального кода есть еще пара таких «ловушек».
Компилируем, собираем и глядим в терминал
Кажется, то что надо. Цепляем к 1му порту другой «источник данных» и просто копипастим строку в него.
Кстати, очень хороший вопрос для разбирательства «как работают прерывания» — почему строки от обоих портов не прерывают друг друга?
Но для подтверждения того, что не все так плохо, привожу другой скриншот, где я просто стучал по клавиатуре
Как видите, если «есть шо», то один порт может «перебить» другой.
И наконец, самая интересная фишка получившегося анализатора. Если в коде инициализации физического USART порта добавить следующие строки
huart1.Init.OneBitSampling = UART_ONEBIT_SAMPLING_DISABLED ;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_AUTOBAUDRATE_INIT;
huart1.AdvancedInit.AutoBaudRateEnable = UART_ADVFEATURE_AUTOBAUDRATE_ENABLE;
huart1.AdvancedInit.AutoBaudRateMode = UART_ADVFEATURE_AUTOBAUDRATE_ONSTARTBIT;
То микроконтроллер будет сам, на ходу подстраиваться под скорость по стартовому биту. Очень полезно для случаев, когда сначала устройства «обнюхиваются» для идентификации на малой скорости, а потом переходят на большую.
В общем-то и все. Возвращаясь к затравке: полностью аналогичным способом был получен дамп обмена между модулями, довольно быстро методом «тыка» разобран по частям и через некоторое время дорогущий прибор был заменен на стобаксовую коробочку, превосходящую его по ключевому (для заказчика) параметру. Правда, деньги производителю все равно не перестали платить (там кучка других причин еще была), но зато избавились от боязни «счас сломается, а процесс контролировать надо».
Как обычно, полный комплект исходников
можно взять тут
PS На фотографии в заголовке макет прибора для лазерной хирургии, для которого мы в studiovsemoe.com писали прошивку. Он не имеет никакого отношения к
тому самому прибору. Просто красивая картинка получилась.