Работа с CAN и CAN FD на Arduino

В этом разделе рассматриваются примеры программ для передачи данных по CAN и CAN FD

Пример отправки и получения сообщения по ОБЫЧНОМУ CAN

Напишем на ардуино программу, которая постоянно шлет в CAN пакет, состоящий из 4 байт, а также при получении сообщения извне (будем считать оно тоже состоит не более, чем из 4 байт) отображает его в Serial порт.

Сам файл с программой:

Начнем с классического протокола CAN.

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

Класс CanFD - это управляющая структура. Он содержит следующие методы:

  • init() - метод, инициализирующий CAN

  • write_default_params_classic() - метод, который запишет параметры по умолчанию для CAN FD (настроены два битрейта 500000 и 4000000 если вы будете использовать raspberry для чтения/записи в can fd, то при запуске интерфейса используйте команду sudo ip link set can0 up txqueuelen 65535 type can bitrate 500000 dbitrate 4000000 fd on. Подробно о том как работать с CAN на RPI написано в разделе VB Can FD Raspberry HAT)

  • write_default_params() - метод, который запишет параметры по умолчанию для обычного CAN( здесь по умолчанию настроен битрейт на 1000000. Запуск интерфейса на RPI: sudo ip link set can0 up txqueuelen 65535 type can bitrate 1000000)

О том, как поменять тайминги и на что они влияют, здесь сказано не будет. Для целей обучения и простого использования мы предлагаем настройки по умолчанию. Если хотите углубиться - добро пожаловать в раздел Коммуникации-FDCAN

  • apply_config() - метод, который применит записанные ранее методы для нашего экземпляра класса

  • default_start() - метод, который запускает FD CAN на модуле, он обязателен.

  • get_hfdcan() - сохраняет всю конфигурацию и возвращает указатель на структуру FDCAN_HandleTypeDef

#include <VBCoreG4_arduino_system.h>


uint8_t data[4] = {222, 173, 190, 239}; //DE AD BE EF
FDCAN_HandleTypeDef*  hfdcan1; // создаем указатель на переменную типа FDCAN_HandleTypeDef
CanFD* can;  //в классе CanFD реализованы функции init(), запускающая can, 
             // get_hfdcan(), возвращающая указатель на структуру FDCAN_HandleTypeDef и
             //write_default_params() - функ

FDCAN_TxHeaderTypeDef TxHeader; // хидер сообщения, которое будет отправляться

В setup() вызывем функцию SystemClock_Config() - она обязательна для настройки тактирования, методы класса CanFD - init() , инициализирующую can, методы для записи и подтверждения настроек, функцию, запускающую CAN FD и get_hfdcan(), возвращающую инициализированнную управляющую can-ом структуру типа FDCAN_HandleTypeDef. Также укажем в хидере нашего сообщения его ID, размер и тип ID сообщения - он может быть расширенным, а может стандартным.

Если тип ID расширенный, то ID 0x64 будет выглядеть: 00000064, если стандартный: 064. Мы советуем использовать расширенный.

void setup() {
  Serial.begin(115200);
  pinMode(LED2, OUTPUT);
  /* Настройка FD CAN  */
  SystemClock_Config();  // Настройка тактирования
  can = new CanFD();  // Создаем управляющий класс
  can->init();  // Инициализация CAN
  can->write_default_params_classic();  // Записываем дефолтные параметры для classic CAN
  can->apply_config();  // Применяем их
  can->default_start();  // Запуск FDCAN модуля
  hfdcan1 = can->get_hfdcan();  // Сохраняем конфиг
  
  TxHeader.Identifier = 0x64;
  TxHeader.DataLength = FDCAN_DLC_BYTES_4; // длина сообщения 4 байта
  TxHeader.IdType = FDCAN_EXTENDED_ID; // тип ID расширенный
}

В основном цикле loop() ставим условие: если в очереди на отправку сообщения есть место, тогда отправляем сообщение. Если пакет отправлен и все хорошо будем мигать светодиодиком.

//------Отправка сообщения в can------
  if (HAL_FDCAN_GetTxFifoFreeLevel(hfdcan1) != 0){
    
    if (HAL_FDCAN_AddMessageToTxFifoQ(hfdcan1, &TxHeader, data) != HAL_OK){ Error_Handler(); } 
    else{digitalWrite(LED2, !digitalRead(LED2));} //помигаем светодиодом, если все ок
  }

Чтение пакетов из can происходит следующим образом: пока в очереди получаемых сообщений есть хотя бы одно, создаем хидер этого сообщения и массив, где будет лежать содержимое посылки. Если сообщение получено, выведем в Serial порт его ID, и само сообщение. Сейчас, для простоты, выведем первые 4 байта массива, считая, что больше отправлено не будет.

// -------Получение сообщений из can-------
  while(HAL_FDCAN_GetRxFifoFillLevel(hfdcan1, FDCAN_RX_FIFO0) > 0 )
    {
      FDCAN_RxHeaderTypeDef Header;  // хидер для входящего сообщения
      uint8_t RxData[4]; // максимальная длина сообщения - 64 байта 
      if (HAL_FDCAN_GetRxMessage(hfdcan1, FDCAN_RX_FIFO0, &Header, RxData) != HAL_OK){ Serial.println("error"); Error_Handler(); }  
      else{ // напечатаем первые 4 байта входящего сообщения, если все ок. Пример отправки сообщения cansend can0 00000123#DEADBEEF 
      Serial.print("ID ");
      Serial.print(Header.Identifier); // ID сообщения 
      Serial.print(" data: ");
      Serial.print(RxData[0]);
      Serial.print("  ");
      Serial.print(RxData[1]);
      Serial.print("  ");
      Serial.print(RxData[2]);
      Serial.print("  ");
      Serial.print(RxData[3]);
      Serial.println("  ");
    }
    }
  delay(100);

В конце цикла добавили задержку в 100 милисекунд.

Загрузите программу на плату. Проверить действительно ли все сообщения отправляются в can можно на Raspberry с помощью утилиты candump, а чтобы отправить сообщение воспользуйтесь утилитой cansend. О том как работают утилиты было расказано в главе Работа с CAN на Raspberry.

Также вы можете эту программу разделить на две части - отправку и чтение сообщения и загрузить на два модуля (для приемника закомментируйте часть с отправкой сообщения, для передатчика - часть с приемом), а потом проверить через сериал порт, что приемник действительно получает те сообщения, что отправляет ему второй модуль.

Пример отправки и получения сообщения по CAN FD

В целом код будет отличаться только настройками в setup() (названиями вызываемых функций):

  Serial.begin(115200);
  pinMode(LED2, OUTPUT);
  /* Настройка FD CAN
  */
  SystemClock_Config();  // Настройка тактирования
  canfd = new CanFD();  // Создаем управляющий класс
  canfd->init(); // Инициализация CAN
  canfd->write_default_params();  // Записываем дефолтные параметры для FDCAN (500000 nominal / 4000000 data)
  canfd->apply_config();  // Применяем их
  hfdcan1 = canfd->get_hfdcan();  // Сохраняем конфиг
  canfd->default_start();
 

  TxHeader.Identifier = 0x68;
  TxHeader.DataLength = FDCAN_DLC_BYTES_4;
  TxHeader.IdType = FDCAN_EXTENDED_ID;

Если у вас возникла ситуация, в которой одно устройство отправляет сообщения, а другое не принимает, почитайте как можно справиться с этой ошибкой здесь

Last updated