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

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

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

Итак, первый шаг заключается в настройке в Cube всей имеющейся периферии. Не буду подробно останавливаться на том, что не касается непосредственно 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 и генерируем код:

Настройка 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);

Собственно, за «пропускаемые» через фильтр 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’ов. Если есть хоть один свободный почтовый ящик, то начинаем формировать сообщение на передачу – устанавливаем параметры сообщения, идентификатор и, конечно же, непосредственно данные. Затем вызываем функцию HAL_CAN_AddTxMessage(), которая поместит наше новое сообщение в один из mailbox’ов и активирует соответствующий запрос на передачу.

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

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

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

Поделиться!

Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Июль 2020
Пн Вт Ср Чт Пт Сб Вс
« Июн    
 12345
6789101112
13141516171819
20212223242526
2728293031  

© 2013-2020 MicroTechnics.ru