CAN-USB адаптер из stm32vldiscovery



При разработке устройств с CAN-интерфейсом желательно иметь удобный инструмент для отслеживания сообщений в сети. Для RS232/485 существует множество бюджетных USB адаптеров и куча разнообразного софта, а вот для CAN мне не удалось найти недорогое готовое решение.

В то же самое время на сайтах автолюбителей находились самодельные устройства для подключения к CAN шине автомобиля. Одними из готовых проектов были USB<>CAN Bus Interface (CAN Hacker), реализованный на Atmega+SJA1000 и проект STM32-CAN-Busadapter, реализованный на STM32F105. Работают они с программой CAN Hacker, которая выглядит достаточно удобной для работы. Беглый анализ протокола команд по USB показал, что эти устройства представляются COM портом, и дальнейшее общение происходит в виде передачи команд из ASCII символов.

В закромах была найдена плата STM32VLDiscovery, которая и стала объектом испытаний. На ней мы будем повторять «STM32-CAN-Busadapter».

Первым делом придётся заменить микроконтроллер STM32F100, установленный на STM32VLDiscovery. Дело в том, что одновременная работа CAN и USB в серии F1 возможна только в микроконтроллерах STM32F105/107. Хорошо, что у STM заявлена pin-to-pin совместимость микроконтроллеров различных серий, но выполненном в одинаковом корпусе.

В местном магазине были приобретены:
1. STM32F105RBT6 297 руб.
2. PCA82C250T 115 руб.
3. TJA1055T 138 руб.
4. PBS-40, 2шт. 114 руб.
Макетная плата «с дырочками 2,54» уже давно ждала своего часа.

Попытка сделать всё по-быстрому


Стираем пыль с STM32VLDiscovery, проверяем, что она ещё работает, загрузив Demo-проект. Перепаиваем контроллер, проверяем, что пересадка прошла успешно, загрузив тот же самый проект.

С сайта проекта STM32-CAN-Busadapter загружаем (требуется регистрация) бинарный файл прошивки и при помощи «STM32 ST-LINK Utility» зашиваем в наш контроллер.

Упрощённая схема выглядит так. Более подробная — на сайте проекта STM32-CAN-Busadapter.



Припаиваем USB D+,D-,Vbus в соответствии со схемой. Добавляем джампер/переключатель на PA2, у автора названый «Bootloader».

Включаем и ...., ничего не работает, устройство по USB не определяется, совсем. При любых положениях «Bootloader».

Вспоминаем, что для определения подключения по USB необходимо линию D+ подтянуть на 5В через 1,5КОм резистор. После этого наше устройство начинает определяться как «неизвестное устройство» с vid/pid 0000.

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

Пишем тестовую прошивку, для проверки USB


Для написания тестовой прошивки используем STM32CubeMX, что позволит нам по-быстрому состряпать тестовую прошивку. По утверждениям ST и дистрибьютеров, использование CubeMX — это «модно, стильно, молодёжно», надо же когда-то попробовать разобраться с этим Cub-ом.

С сайта STMicroelectronic скачиваем STM32CubeMX. Версия периодически обновляется, у меня v4.7.0.

В установленном Cube входим в «Help»->«Install New Libraries» и устанавливаем «Firmware Package for Family STM32F1» (у меня V1.0.0).

В «Help»->«Updater Settings» можно посмотреть «Repository Folder» — место, куда скачалась наша «Firmware Package», там лежат в том числе и примеры с исходниками для различных отладочных плат.

Создаём новый проект в Cube


MCU — STM32F105RBTx.
В «Configuration»->«Peripheals»->«RCC» выбираем тактирование от внешнего кварцевого резонатора, HSE устанавливаем в «Crystal/Ceramic Resonator».
В «Configuration»->«Peripheals»->«USB_OTG_FS» выбираем режим «Device_Only», и устанавливаем галочку «Activate_VBUS» — для автоматического определения момента подключения к USB. После этого у нас автоматически назначаться ножки PA9, PA11 и PA12 на работу с USB.
В «Configuration»->«MiddleWares»->«USB_DEVICE»->«Class For FS IP» выбираем «Communication Device Class (Virtual Port Com)».



Дальше на закладке «Clock Configuration» настроим систему тактирования для нашего микроконтроллера. Подсмотреть готовые значения коэффициентов PLL и Prescaler-ов можно в примерах, посмотрев процедуру SystemClock_Config. У нас должна получиться такая «картина»:



Теперь можно сгенерировать проект для компиляции.

Перед первой генерацией запросит ввести название, место хранения проекта и IDE для которой будет формироваться проект. Я выбрал Keil 4, как более привычный. Возможны варианты Keil5, Keil4, IAR, TrueStudio, SW4STM32. После генерации нажимаем «Open Project» и открывается наша среда разработки. Ничего не меняя компилируем и загружаем.
И…, оно работает. Определилось устройство, драйвера нашлись на сайте ST. Теперь в «Диспетчере устройств» видим «STMicroelectronics Virtual COM Port (COM4)».

Далее было потрачено какое-то время, чтобы понять, почему железо работает, а чужая прошивка нет. В итоге было замечено, что бинарные файлы выглядят по-разному.

Помню, что в начале программы идут вектора прерываний и в нашей прошивке мы видим что-то похожее, а в скачанной прошивке данные совсем не похожи на команды перехода по адресам.



Более того, гугль подсказал, что первые 4 байта прошивки — адрес стека, следующие 4 байта — адрес первой команды программы.

Написал автору STM32-CAN-Busadapter. Описал, что прошивка «битая», не работает, что первые байты не такие, как должны быть. Andreas мне ответил. Написал, что прошивка-то рабочая, но требует его фирменного загрузчика. К письму был приложен .hex файл «personally version only for you».

ОК, смотрим как оно работает


Допаиваем микросхемы трансиверов CAN, получаем вот такую «красоту». Подтягивающий резистор к D+ линии USB можно убрать, он есть внутри микроконтроллера.





Прошиваем, запускаем CAN Hacker, изучаем. Здесь нам понадобится подключение к какой-нибудь CAN сети. У меня это была плата openmcu с STM32F107 контроллером, которая выдавала CAN посылки. Поигравшись с программой «CAN Hacker», понял, что штука подходящая, есть режимы monitor и tracer — сообщения выводятся или в таблицу или в список по мере поступления.

Вот небольшое видео, не моё.



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

Пишем свою прошивку...
Открываем в нашей IDE проект, сгенерированный Cube и дописываем недостающие куски кода.
Основное правило — писать в промежутках между
/* USER CODE BEGIN… */
и
/* USER CODE END… */
Иначе всё, что написано вне таких специально отведённых мест будет нещадно перезаписано Cub-ом при следующей генерации проекта.

Для начала сделаем эхо: всё, что отправили в наш виртуальный COM порт получаем обратно.
При приёме данных по USB вызывается процедура CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) в файле "usbd_cdc_if.c".

Добавим отправку обратно всего, что получили.

Эхо
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */

CDC_Transmit_FS(Buf, *Len); //отправляем всё, что получили

USBD_CDC_SetRxBuffer(hUsbDevice_0,&UserRxBufferFS[0]);
USBD_CDC_ReceivePacket(hUsbDevice_0); // разрешаем приём следующего пакета

return (USBD_OK);
/* USER CODE END 6 */
}


Компилируем, загружаем. Открываем любой терминальной программой наш виртуальный COM-порт. Параметры порта (скорость, чётность) могут быть любыми. Убеждаемся, что эхо работает.

Подстраиваемся под CAN Hacker


Теперь начнём реализацию протокола для работы с программой «CAN Hacker». Сам протокол можно посмотреть на странице проекта USB<>CAN Bus Interface (CAN Hacker), в файле «описание», или поискать на просторах интернета по названию «Lawicel Protokol».

Программой USBTrace был подсмотрен процесс инициализации адаптера.

Необходимо ответить на команду «запрос версии», на все остальные запросы просто отвечаем «ОК» (0x0D).

Изменим процедуру CDC_Receive_FS
static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */

uint32_t num_bytes;
uint8_t res;
uint8_t tmp_byte;

HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_9);

switch(Buf[0])
{
case 'V':
num_bytes = sprintf((char*)UserTxBufferFS,«V0101\r»);
break;

case 'v':
num_bytes = sprintf((char*)UserTxBufferFS,«vSTM32\r»);
break;

default:
num_bytes = sprintf((char*)UserTxBufferFS,"\r");
break;
}

USBD_CDC_SetTxBuffer(hUsbDevice_0, (uint8_t*)&UserTxBufferFS[0], num_bytes);
USBD_CDC_TransmitPacket(hUsbDevice_0);


// CDC_Transmit_FS(Buf, *Len);

USBD_CDC_SetRxBuffer(hUsbDevice_0,&UserRxBufferFS[0]);
USBD_CDC_ReceivePacket(hUsbDevice_0);

return (USBD_OK);
/* USER CODE END 6 */
}

После этого программа «CAN Hacker» сможет «увидеть» наш адаптер.

Добавим интерфейс CAN к нашему проекту


В Cube устанавливаем «Configuration»->«Peripheals»->«CAN1» галочку «Master mode». На закладке «Configuration» «CAN1» настраиваем скорость и разрешаем прерывание по приёму:



Скорость обмена по CAN получается настроена на 500 Кбит/с.
Частота тактирования модуля CAN от шины APB1 составляет 36 МГц, делим её на 6, получаем время одного кванта.
Один байт передаётся 1+6+5 квантов, и получается 36/6/(1+6+5)=0,5 МГц, или 500 Кбит/с.
Изменяя делитель «Prescaler (for Time Quantum)» можем получить стандартные скорости 125,250,500,1000 Кбит.


Генерируем проект в Cube, открываем в IDE.

В «main.c» необходимо добавить буферы для CAN, настроить фильтр входящих сообщений и добавить процедуру HAL_CAN_RxCpltCallback. Эта процедура будет вызываться из прерывания по приёму CAN. Название, разумеется, может быть только таким, т.к. именно оно прописано в «недрах» проекта, сгенерированного Cub-ом. Всё что приняли по CAN будем пересылать в USB, в соответствии с протоколом. Например по CAN с адреса 0x123 приняли 8 байт данных 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88, запаковываем это в посылку для USB «t12381122334455667788» добавляем в конце символ 0x0D и отправляем в наш виртуальный СОМ порт на ПК.

Добавим приём CAN посылок
буферы для приёма/передачи
/* USER CODE BEGIN PV */
static CanTxMsgTypeDef can1TxMessage;
static CanRxMsgTypeDef can1RxMessage;
/* USER CODE END PV */

процедура, вызываемая при приёме посылки
/* USER CODE BEGIN 0 */
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
uint32_t num_bytes;
uint8_t buf[200];

num_bytes = sprintf((char*)buf,«t%3.3X%1.1X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%4.4X\r», \
CanHandle->pRxMsg->StdId,\
CanHandle->pRxMsg->DLC,\
CanHandle->pRxMsg->Data[0],\
CanHandle->pRxMsg->Data[1],\
CanHandle->pRxMsg->Data[2],\
CanHandle->pRxMsg->Data[3],\
CanHandle->pRxMsg->Data[4],\
CanHandle->pRxMsg->Data[5],\
CanHandle->pRxMsg->Data[6],\
CanHandle->pRxMsg->Data[7]\
);
CDC_Transmit_FS(buf,num_bytes); // отправляем в USB то, что получили по CAN

HAL_CAN_Receive_IT(&hcan1, CAN_FIFO0); //ждём следующую посылку
}
/* USER CODE END 0 */

для использования внешней функции CDC_Transmit_FS подключим .h файл
/* USER CODE BEGIN Includes */
#include «usbd_cdc_if.h»
/* USER CODE END Includes */

в основном цикле main добавим инициализацию буферов и настроим приёмный фильтр
/* USER CODE BEGIN 2 */
hcan1.pTxMsg = &can1TxMessage;
hcan1.pRxMsg = &can1RxMessage;
/* USER CODE END 2 */

// настраиваем фильтр — приём всех посылок
CAN_FilterConfTypeDef canFilterConfig;
canFilterConfig.FilterNumber = 0;
canFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
canFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
canFilterConfig.FilterIdHigh = 0x0000;
canFilterConfig.FilterIdLow = 0x0000;
canFilterConfig.FilterMaskIdHigh = 0x0000 << 5;
canFilterConfig.FilterMaskIdLow = 0x0000;
canFilterConfig.FilterFIFOAssignment = 0;
canFilterConfig.FilterActivation = ENABLE;
canFilterConfig.BankNumber = 1;
HAL_CAN_ConfigFilter(&hcan1, &canFilterConfig);

HAL_CAN_Receive_IT(&hcan1, CAN_FIFO0); //разрешаем приём посылок
/* USER CODE END 2 */

дополнительно изменим размеры буферов USB
#define APP_RX_DATA_SIZE 1000 // было 4
#define APP_TX_DATA_SIZE 1000 // было 4

Ещё важно не забыть, что CAN трансивер у нас имеет вход для переключения в режим Standby.

В Cube назначаем PB7 на выход. По умолчанию на выходе будет установлен 0.

Компилируем, загружаем, проверяем. В принципе, работает. В CAN Hacker видим посылки из CAN, но если время между посылками CAN меньше 1 мс, то сообщения пропускаются, приходят через одно.

Картинка для примера. Каждую миллисекунду приходит две посылки, в CAN Hacker видим только одну.



канал 1 — CAN RX, принимаем в контроллер
канал 2 — CAN TX, видно подтверждения от нашего контроллера
канал 3,4 -USB D+, D-

К тому же, если в CAN шине идёт интенсивный обмен, то наш адаптер не определяется по USB, «неизвестное устройство» пишет.

Приходится сначала подключать USB, запускать CAN Hacker, после этого подключать CAN.

Добавляем кольцевой буфер для приёма CAN посылок


Очевидно, что пока формируем и отправляем посылку из CAN в USB, в это время успевает проскочить очередная посылка в CAN, и она остаётся не обработанной. Поэтому при приёме CAN посылки будем быстренько всё складывать в буфер, а где-нибудь в другом месте выгребать этот буфер и отправлять в USB.

Писать в буфер будем побайтно, поэтому легко следить за «закольцовыванием» — переходом от конца массива в памяти к началу.

Отправлять из буфера будем процедурами USBD_CDC_SetTxBuffer и USBD_CDC_TransmitPacket, указывая на начало данных для отправки и их количество. Если при оправке понадобится «сделать кольцо», то отправляем в два этапа, сначала данные до конца массива в памяти, потом остаток.


Реализация кольцевого буфера
буфер для отправки USB у нас уже определён
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];

в «usb_cdc_if.c» определим указатели в этот буфер
uint32_t ptrWriteUserTxBufferFS = 0; //запись в буфер при приёме из CAN
uint32_t ptrReadUserTxBufferFS = 0; //чтение из буфера, отправка в USB
/* USER CODE END 1 */

опишем процедуры добавления данных в буфер и отправки из буфера
к сожалению, нормального места в «usb_cdc_if.c» не нашлось, поэтому пришлось пихать в секцию «USBD_CDC_Private_Macros»
/* USER CODE BEGIN 2 */
extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
extern uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];
extern uint8_t interface_state;
extern USBD_HandleTypeDef *hUsbDevice_0;

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t CDC_add_buf_to_transmit(uint8_t* Buf, uint16_t Len) //добавление в буфер для передачи
{
uint16_t _cnt = Len;

while(_cnt)
{
UserTxBufferFS[ptrWriteUserTxBufferFS] = *Buf;
ptrWriteUserTxBufferFS++;
Buf++;
ptrWriteUserTxBufferFS %= APP_TX_DATA_SIZE; //закольцовываем
_cnt--;
}
return(0);
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
uint8_t CDC_periodic_callback(void) // периодически проверяем есть ли данные для передачи
{
uint32_t buffptr;
uint32_t buffsize;

if(ptrReadUserTxBufferFS != ptrWriteUserTxBufferFS)
{
__disable_irq();//при интенсивном обмене по CAN без этого всё «разваливается»
if(ptrReadUserTxBufferFS > ptrWriteUserTxBufferFS) // сделать кольцо?
{
buffsize = APP_TX_DATA_SIZE — ptrReadUserTxBufferFS; //этап 1, байт до конца массива
}
else
{
buffsize = ptrWriteUserTxBufferFS — ptrReadUserTxBufferFS; //все данные сразу
}
__enable_irq();

buffptr = ptrReadUserTxBufferFS;

if(interface_state != 1) return(1); //если интерфейс не сконфигурирован, то отправлять не будем

USBD_CDC_SetTxBuffer(hUsbDevice_0, (uint8_t*)&UserTxBufferFS[buffptr], buffsize);

if(USBD_CDC_TransmitPacket(hUsbDevice_0) == USBD_OK)
{
ptrReadUserTxBufferFS += buffsize;
if (ptrReadUserTxBufferFS == APP_TX_DATA_SIZE)
{
ptrReadUserTxBufferFS = 0; //закольцовываем
}
}
}
return(0);
}
/* USER CODE END 2 */

в «main.c» добавим переменную
uint8_t interface_state = 0;
пока от CAN Hacker не придёт команда «O» — переход в рабочий режим из настроечного, не будем ничего слать в USB, т.к. считаем что интерфейс ещё не сконфигурирован

заменим в прерывании CAN прямую отправку в USB на добавление в буфер отправки
CDC_Transmit_FS на CDC_add_buf_to_transmit

и добавим периодический опрос буфера для отправки
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
CDC_periodic_callback();
}
/* USER CODE END 3 */


Компилируем, загружаем. Видим, что теперь в CAN Hacker отображаются все сообщения, без пропусков.

Добавим Timestamp


В протоколе CAN Hаcker предусмотрены «отметки времени» для каждого сообщения. Диапазон значений 0..60000 мс.
Используем для этого TIM1.
В Cub-е, в «Configuration»->«Peripheals»->«TIM1» выбираем «Clock source»=«Internal Clock».
Для настройки частоты тактирования таймера 1000 Гц (1мс). Придётся понизить частоту тактирования APB2.
Убеждаемся, что от APB2 не тактируется ничего для нас важного по быстродействию.
Из «reference manual» на «STM32F1»:




Видим, что от APB2 тактируются таймер1, порты ввода-вывода, АЦП, SPI, USART, и мы можем смело понижать частоту APB2.

В Cub-е, на закладке «Clock Configuration» устанавливаем «APB2 Prescaler» равным 8, получаем частоту тактирования таймера 18 МГц.



На закладке «Configuration»->«TIM1» устанавливаем
Prescaler(PSC — 16 bit value) = 18000
Counter period (AutoReload Register) = 60000



Генерируем код, открываем в IDE.

Добавляем в main запуск таймера
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim1);
дописываем метку времени в сообщение из CAN в USB
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
uint32_t num_bytes;
uint8_t buf[200];
static uint32_t time;

time = __HAL_TIM_GetCounter(&htim1);

num_bytes = sprintf((char*)buf,«t%3.3X%1.1X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%4.4X\r», \
CanHandle->pRxMsg->StdId,\
CanHandle->pRxMsg->DLC,\
CanHandle->pRxMsg->Data[0],\
CanHandle->pRxMsg->Data[1],\
CanHandle->pRxMsg->Data[2],\
CanHandle->pRxMsg->Data[3],\
CanHandle->pRxMsg->Data[4],\
CanHandle->pRxMsg->Data[5],\
CanHandle->pRxMsg->Data[6],\
CanHandle->pRxMsg->Data[7],\
time
);
if(interface_state == 1) CDC_add_buf_to_transmit(buf,num_bytes);

HAL_CAN_Receive_IT(&hcan1, CAN_FIFO0);
}

Опять, вроде работает, но при интенсивном обмене в CAN обмен по USB «затыкается».
На этот раз виновата процедура sprintf, которая долго выполняется в прерывании CAN.

Перепишем формирование посылки из CAN в USB без использования sprintf.

Процедуры преобразования четырёх бит в HEX ASCII символ и наоборот
uint8_t halfbyte_to_hexascii(uint8_t _halfbyte)
{
_halfbyte &= 0x0F;
if(_halfbyte >= 10) return('A' + _halfbyte — 10);
else return('0' + _halfbyte);
}

uint8_t hexascii_to_halfbyte(uint8_t _ascii)
{
if((_ascii >= '0') && (_ascii <= '9')) return(_ascii — '0');
if((_ascii >= 'a') && (_ascii <= 'f')) return(_ascii — 'a');
if((_ascii >= 'A') && (_ascii <= 'F')) return(_ascii — 'A');
return(0xFF);
}
изменим процедуру HAL_CAN_RxCpltCallback
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
uint32_t num_bytes;
uint8_t buf[200];
static uint32_t time;

time = __HAL_TIM_GetCounter(&htim1);
/*
num_bytes = sprintf((char*)buf,«t%3.3X%1.1X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%4.4X\r», \
CanHandle->pRxMsg->StdId,\
CanHandle->pRxMsg->DLC,\
CanHandle->pRxMsg->Data[0],\
CanHandle->pRxMsg->Data[1],\
CanHandle->pRxMsg->Data[2],\
CanHandle->pRxMsg->Data[3],\
CanHandle->pRxMsg->Data[4],\
CanHandle->pRxMsg->Data[5],\
CanHandle->pRxMsg->Data[6],\
CanHandle->pRxMsg->Data[7],\
time
);
*/
num_bytes = 0;
buf[num_bytes++] = 't';
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->StdId)>>8);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->StdId)>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->StdId));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->DLC));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[0])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[0]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[1])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[1]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[2])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[2]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[3])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[3]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[4])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[4]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[5])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[5]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[6])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[6]));
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[7])>>4);
buf[num_bytes++] = halfbyte_to_hexascii((CanHandle->pRxMsg->Data[7]));
buf[num_bytes++] = halfbyte_to_hexascii((time)>>12);
buf[num_bytes++] = halfbyte_to_hexascii((time)>>8);
buf[num_bytes++] = halfbyte_to_hexascii((time)>>4);
buf[num_bytes++] = halfbyte_to_hexascii((time)>>0);
buf[num_bytes++] = '\r';

if(interface_state == 1) CDC_add_buf_to_transmit(buf,num_bytes);

HAL_CAN_Receive_IT(&hcan1, CAN_FIFO0);
}


Некоторые эксперименты по быстродействию на приём: в CAN формируем 1000000 посылок с адреса 0x321 и в CAN Hacker смотрим сколько из них примем.

Скорость 500 Кбит/сек, посылки без перерывов, потери 0,2%:



Скорость 1 Мбит/сек, посылки без перерывов, потери 50%:



Скорость 1 Мбит/сек, по две посылки каждые 1 мс, потери 0%:



По-моему неплохой результат.

Добавим возможность отправки сообщений в CAN


В файле «usbd_cdc_if.c», в процедуру USB CDC_Receive_FS добавим:

отправка в CAN
case 't':
i = 1;

hcan1.pTxMsg->StdId = hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->StdId = (hcan1.pTxMsg->StdId << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->StdId = (hcan1.pTxMsg->StdId << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->DLC = hexascii_to_halfbyte(Buf[i++]);
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[0] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[1] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[2] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[3] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[4] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[5] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[6] = tmp_byte;
tmp_byte = hexascii_to_halfbyte(Buf[i++]); tmp_byte = (tmp_byte << 4) + hexascii_to_halfbyte(Buf[i++]);
hcan1.pTxMsg->Data[7] = tmp_byte;
HAL_CAN_Transmit(&hcan1, 10);

num_bytes = sprintf((char*)UserTxBufferFS,"\r");
break;
Компилируем, загружаем, проверяем, работает.

Заключение


На этом, пожалуй, можно остановиться. Рубрика ведь «Сделай сам» называется. Если кто захочет, сможет самостоятельно добавить поддержку разных скоростей CAN, работу с 29 битными расширенными идентификаторами, фильтры сообщений, remote frames.

Хочу сказать, что принцип работы через ASCII команды мне понравился. В будущем планирую реализовать USB-SPI, USB-I2C функционал. Например, настраиваем наш виртуальный COM порт на 115200 бод — работаем с CAN, настраиваем на 57600 — работаем с I2C, настраиваем на 9600 — работаем с SPI. Разумеется, при работе с SPI или I2C «CAN Hacker» уже нельзя будет использовать и придётся какой-то свой протокол придумывать.

Готовый проект к данной статье можно скачать по ссылке.

Использованные материалы


1. USB<>CAN Bus Interface (CAN Hacker)
2. STM32-CAN-Busadapter
3. Описание и схема STM32VLDISCOVERY
4. STM32F105 Reference Manual
  • +25
  • 63.7k
  • 9
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 9

    +1
    Интересное решение завязаться на протокол существующего CANHacker под свое устройство для использования имеющегося интерфейса отображения. Я вот сейчас занимаюсь разработкой своего кроссплатформенного GUI интерфейса для отображения CAN пакетов со своего устройства.
      0
      А почему нельзя сделать такое устройство на f103 серии? Просто сам хотел сделать подобное устройство.
        0
        Потому что CAN и USB не смогут работать на одновременно 103 камне, а на 105-ом — смогут. Можно конечно сделать вариант 103 камень + FT232RL. Но это не то. Стоимость STM32F105 и FT232RL не очень сильно отличаются.
        0
        Всегда хотелось подержать в руках какое-нибудь готовое CAN-устройство. Например, банальный CAN-термометр или CAN-кнопку. Но что-то не гуглится. Если воткнуться в какой-нибудь автомобильный ODB2, то да, там там жизнь кипит, но идет от одного большого черного проприетарного блока к другому. А хочется поехать в автомагазин или, извиняюсь, на разборку и купить маленькую готовую коробочку с длинным номером, производителя NNN которая, о чудо, отзовется на CAN.

        Как я понял, надо копать в сторону грузовиков, стационарных дизелей или катеров, там CAN более «стандартный» чем в авто.
          0
          На разборке можно без проблем найти ЭБУ, который работает по CAN. Даже подключив его на столе, сам по себе он уже будет генерировать CAN трафик, который можно посмотреть тем же CANHacker. Если интересует какой конкретно ЭБУ нужно искать — пиши, подскажу.
            0
            Упс. Большой Черный Ящик и ЭБУ не хочется, на нем как правило висит туча старинных аналоговых датчиков и релюх. Хочется уже «оцифрованной» мелкой CAN-периферии.

            Еще поискал, нашел такое
            www.alibaba.com/product-detail/Automobile-CAN-BUS-Laboratory-Equipment_618628226.html

            Мне кажется, у современных машин на шине «салона» должно висеть много реальной CAN-мелочи.
          0
          Баг в функции:

          uint8_t hexascii_to_halfbyte(uint8_t _ascii)
          {
          if((_ascii >= '0') && (_ascii <= '9')) return(_ascii — '0');
          if((_ascii >= 'a') && (_ascii <= 'f')) return(_ascii — 'a' + 10);
          if((_ascii >= 'A') && (_ascii <= 'F')) return(_ascii — 'A' + 10);
          return(0xFF);
          }
            0
            А почему CANHacker 2.00.01 может не видеть виртуального ком порта под семёркой x64?

            При этом CAN232 Monitor Lite с сайта www.can232.com/?page_id=72 работает, как и «ручное» управление через терминал.
              0
              У меня так было, когда либо forscan был запущен, либо помимо сниффера был подключен ELM327

            Only users with full accounts can post comments. Log in, please.