Top.Mail.Ru

STM32 и протокол CAN. Настройка в STM32CubeMx.

Недавно мы разбирались с теоретическими аспектами работы протокола CANконтролем ошибок, организацией арбитража сообщений на шине и т. д. Так вот, сегодня, как и обещал, займемся практической стороной вопроса – реализуем прием и передачу данных по CAN на микроконтроллере STM32.

Для настройки периферии будем использовать STM32CubeMx, в качестве среды разработки я, как обычно, беру IAR. Осталось упомянуть про выбранный контроллер – им сегодня будет STM32F103VE. Но, как вы помните, при работе с STM32 нет никакой проблемы в том, чтобы перейти на другой микроконтроллер или другую IDE 👍

Итак, первый шаг заключается в настройке в CubeMx всей имеющейся периферии. Не буду подробно останавливаться на том, что не касается непосредственно CAN, все можно найти в нашем курсе по этой вот ссылке.

В итоге у нас оказываются задействованы следующие пины контроллера:

STM32CubeMx CAN pinout.

Здесь мы уже активировали модуль CAN:

STM32 CAN module.

Но пока мы его только включили, необходимо еще выполнить настройку. И самым важным параметром является, конечно, скорость обмена данными. В случае с CAN ситуация не совсем такая, как с другими модулями STM32, для задания скорости мы должны настроить временные характеристики интерфейса, а не просто задать конкретное число:

Baudrate settings.

Давайте разберемся, что это за значения и за что они отвечают. Время передачи одного бита в CAN складывается из:

CAN timings.

Все длительности оцениваются через понятие кванта времени, для задания которого мы устанавливаем значение предделителя (в этом проекте он равен 8). Таким образом, мы получаем длительность 1 кванта:

T_{q}  = 222.2\medspace нс

Первый сегмент на этой схеме – SYNC_SEG – используется для синхронизации всех узлов сети CAN. Ожидается, что фронт сигнала должен находиться внутри именно этого сегмента.

Второй сегмент - BS1 (Bit segment 1). Стандарт CAN включает в себя два сегмента - PROP_SEG и PHASE_SEG1. Оба этих сегмента в STM32 относятся к сегменту BS1. PROP_SEG нужен для компенсации физических задержек в сети, а PHASE_SEG1 используется для компенсации ошибки смещения фазы сигнала.

Сегмент BS1 определяет положение sample point (точки захвата). В этой точке модуль CAN анализирует уровень сигнала на шине, то есть определяет, принят рецессивный или доминантный бит.

И третий сегмент - BS2 (Bit segment 2). Он представляет из себя сегмент PHASE_SEG2 интерфейса CAN. Его назначение такое же как и у PHASE_SEG1.

Сегмент BS2 определяет положение transmit point (точки передачи), то есть того момента времени, когда модуль CAN выдает на линию определенный бит.

Длительности этих сегментов таковы:

  • SYNC_SEG: 1 квант времени
  • Bit segment 1 (BS1): 1 - 16 квантов
  • Bit segment 2 (BS2): 1 – 8 квантов

Устанавливая длительности различных сегментов, мы получаем время передачи одного бита в квантах. Зная длительность самого кванта, мы легко получаем время передачи бита в секундах. А уже из этого мы рассчитываем скорость передачи данных по шине. Только при настройке CAN нужно пройти в обратном направлении.

Пусть мы хотим задать скорость обмена равной 500 Кбит/с. Тогда время передачи одного бита:

T_{bit}  = \frac{1\medspace с}{500000\medspace} = 2000\medspace нс

Длительность одного кванта мы уже задали равной 222,2 нс. Таким образом, время 1-го бита в квантах:

T = \frac{T_{bit}}{T_{q}} = 9

Эти 9 квантов нужно распределить между тремя сегментами. SYNC_SEG фиксирован (1 квант), значит остается 8 квантов на сегменты BS1 и BS2. Ставим в этом примере поровну – по 4 кванта и получаем нужную нам скорость обмена данными. И, наконец, можем двигаться дальше - включаем прерывания и генерируем код:

Настройка CAN для STM32.

В main() находим функцию инициализации CAN:

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_CAN_Init();

Но на самом деле, на этом настройка не заканчивается. Дело в том, что в STM32 есть очень полезная функция фильтрации сообщений по ID. Как вы помните, ID в CAN относится не к устройству в сети, а к сообщению. То есть одно и то же устройство может рассылать сообщения с разными ID. А поскольку сеть является широковещательной, то приемник будет получать кучу сообщений, среди которых ему нужны только некоторые. И вот для этого и существует фильтрация сообщений. Благодаря аппаратной поддержке не нужно программно проверять ID всех принятых сообщений, можно всего лишь изначально настроить периферию на прием только нужных сообщений.

Настраивается этот механизм следующим образом:

CAN_FilterTypeDef canFilterConfig;
canFilterConfig.FilterBank = 0;
canFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
canFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
canFilterConfig.FilterIdHigh = 0x0000;
canFilterConfig.FilterIdLow = 0x0000;
canFilterConfig.FilterMaskIdHigh = 0x0000;
canFilterConfig.FilterMaskIdLow = 0x0000;
canFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
canFilterConfig.FilterActivation = ENABLE;
canFilterConfig.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan, &canFilterConfig);

Для CAN2 (при наличии 2-х модулей CAN в контроллере) будет несколько отличаться, а именно:

canFilterConfig.FilterBank = 14;

Собственно, за "пропускаемые" через фильтр ID отвечают эти поля структуры CAN_FilterTypeDef:

canFilterConfig.FilterIdHigh = 0x0000;
canFilterConfig.FilterIdLow = 0x0000;
canFilterConfig.FilterMaskIdHigh = 0x0000;
canFilterConfig.FilterMaskIdLow = 0x0000;
  • FilterIdHigh – старшая часть ID
  • FilterIdLow– младшая часть ID
  • FilterMaskIdHigh– старшая часть маски
  • FilterMaskIdLow– младшая часть маски

В данном случае все значения равны 0x0000, это означает, что абсолютно все сообщения будут проходить через фильтр.

Значения FilterIdHigh и FilterIdLow определяют идентификатор, с которым будет сравниваться ID принятого сообщения. А FilterMaskIdHigh и FilterMaskIdLow отвечают за битовую маску, которая, в свою очередь, определяет, какие биты идентификатора будут проверяться, а какие – нет. Единица в маске означает, что бит, который соответствует положению этой единицы, будет проверен. Сейчас на примере все станет окончательно понятно.

Итак, пусть мы хотим принимать только сообщения с ID из диапазона – 0x200 – 0x20F. Тогда настройка будет такой:

canFilterConfig.FilterIdHigh = 0x200 << 5;
canFilterConfig.FilterIdLow = 0x0000;
canFilterConfig.FilterMaskIdHigh = 0x7F0 << 5;
canFilterConfig.FilterMaskIdLow = 0x0000;

Обратите внимание, что для стандартного идентификатора значения  необходимо сместить влево  на 5 бит. Запишем в двоичном виде значения 0x200 – 0x20F, смещенные на 5 битов:

Фильтрация CAN сообщений.

Получается, что мы должны проверять 7 старших битов ID, одинаковые во всех идентификаторах. И, в итоге, получаем значение битов маски равным 0x7F0 << 5 (единицы соответствуют тем битам, которые должны соответствовать заданному в FilterIdHigh значению).

После настройки фильтра мы включаем модуль CAN и разрешаем прерывания по приему данных в FIFO0:

HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

Теперь для приема сообщений CAN мы просто переопределяем соответствующую callback-функцию:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
  CAN_RxHeaderTypeDef msgHeader;
  uint32_t msgId = 0;
  uint8_t msgData[8];
  
  HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &msgHeader, msgData);
    
  if (msgHeader.IDE == CAN_ID_EXT)
  {
    msgId = msgHeader.ExtId;
  }
  else
  {
    msgId = msgHeader.StdId;
  }
}

В самой функции мы вызываем HAL_CAN_GetRxMessage() для получения принятых данных и, например, проверяем тип идентификатора (расширенный или стандартный). Дальше уже можно использовать принятые данные по назначению. С приемом все понятно, давайте рассмотрим и процесс передачи.

В STM32F10x реализованы 3 так называемых mailbox'а, то есть по сути "почтовых ящика", в которые мы можем складывать наши сообщения. Далее микроконтроллер аппаратно выбирает, какое сообщение отправить в линию. Можно настроить механизм аналогичный FIFO, то есть первое помещенное в mailbox сообщение и отправлено будет первым. А можно настроить периферию на отправку сообщений в соответствии с приоритетом самих сообщений.

Для каждого из mailbox'ов есть соответствующий callback, который вызывается по окончанию передачи:

  • void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan)
  • void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan)
  • void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan)

Отправим тестовое сообщение:

if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 0)
{
  CAN_TxHeaderTypeDef msgHeader;
  uint8_t msgData[8];

  msgHeader.StdId = 0x200;
  msgHeader.DLC = 8;
  msgHeader.TransmitGlobalTime = DISABLE;
  msgHeader.RTR = CAN_RTR_DATA;
  msgHeader.IDE = CAN_ID_STD;
  
  uint32_t mailBoxNum = 0;
  
  for (uint8_t i = 0; i < 8; i++)
  {
    msgData[i] = i;
  }
  
  HAL_CAN_AddTxMessage(&hcan, &msgHeader, msgData, &mailBoxNum);
}

Первым делом вызываем HAL_CAN_GetTxMailboxesFreeLevel(&hcan). Функция возвращает количество свободных mailbox'ов. Если есть хоть один свободный mailbox, то начинаем формировать сообщение на передачу - устанавливаем параметры сообщения, идентификатор и, конечно же, непосредственно данные. Затем вызываем функцию HAL_CAN_AddTxMessage(), которая поместит наше новое сообщение в один из mailbox'ов и активирует соответствующий запрос на передачу.

Обратите внимание, что помимо данных сообщения мы передаем в эту функцию адрес переменной mailBoxNum. Здесь функция сохранит номер почтового ящика, в который отправлено сообщение, этот номер можно использовать для дальнейшей работы, например, в callback'е по окончанию передачи.

Итак, в общем-то, на этом все - CAN настроили, прием и передачу осуществили ) Проект для статьи доступен по ссылке - MT CAN Example.

До скорых встреч и спасибо за внимание 🤝

Подписаться
Уведомить о
guest

20 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Юрий
Юрий
4 лет назад

Архив поврежден

Юрий
Юрий
Ответ на комментарий  Aveal
4 лет назад

Пишет архив поврежден

Алексей
Алексей
Ответ на комментарий  Юрий
4 лет назад

Я скачал. Архив не поврежден!

Сергей
Сергей
3 лет назад

Спасибо за статью. Помогла стартануть... Но нашёл баг в HAL_CAN_ConfigFiltr(), а конкретно, в режиме LIST, IDешники располагаются не правильно(перепутанно) т.е. если потом использовать Filter Match Index (FMI) как индекс массива данных, то данные сохраняются не ожидаемом порядке...

Maxim
Maxim
3 лет назад

при нажатии на ссылку пишет: Ничего не найдено...

Maxim
Maxim
3 лет назад

после отправки комментария файл скачался

Дмитрий
Дмитрий
1 год назад

Подскажите как открыть проект из Архива ?

Ярослав
Ярослав
5 месяцев назад

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

Ярослав
Ярослав
Ответ на комментарий  Aveal
5 месяцев назад

Там через callback реализуется или можно без него?

Максим
Максим
1 месяц назад

Здравствуйте! Скажите пожалуйста по какой формуле высчитывается длительность одного кванта (Tq
​=222.2нс)? Не очень ясно как рассчитывается это время.

Максим
Максим
Ответ на комментарий  Aveal
1 месяц назад

Спасибо большое, теперь понятно!

20
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x