📡 Решаем задачу CAN-взаимодействия с нашим CAN-шилдом на STM32F103

<кан шилд на STM32F103 Arduino

Введение

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

Постановка задачи

Устройство должно ожидать два разных сообщения с одинаковым идентификатором 0x1F4011F3:

  1. Запрос №1 – данные: 03 19 02 01 00 00 00 00
    Ответ – одно сообщение с ID 0x1F40F311 и данными: 01 00 00 00 00 00 00 00
  2. Запрос №2 – данные: 05 22 F1 91 F1 81 00 00
    Ответсемь сообщений подряд с ID 0x1F40F311 и данными:
    22 01 00 00 00 00 00 00
    22 02 00 00 00 00 00 00
    22 03 00 00 00 00 00 00
    22 04 00 00 00 00 00 00
    22 05 00 00 00 00 00 00
    22 06 00 00 00 00 00 00
    22 07 00 00 00 00 00 00
    с периодом 20 мс между ними.

Оба идентификатора имеют длину 29 бит (расширенный формат CAN). Наш CAN-шилд на базе STM32F103 идеально подходит для такой задачи благодаря аппаратной поддержке расширенных ID и гибкой настройке таймингов.

Наш CAN-шилд на STM32F103 – что умеет?

Плата построена на микроконтроллере STM32F103C8T6 с трансивером SIT1040, скетч заливается прямо в STM32. Код полностью открыт и доступен на GitHub и GitVerse

  • Поддержка стандартных (11‑бит) и расширенных (29‑бит) CAN-идентификаторов
  • Скорость до 1 Мбит/с
  • Аппаратные фильтры для приёма только нужных сообщений
  • 8‑байтовые данные на пакет (стандарт CAN 2.0B)
  • Точные временные задержки без delay() – на основе millis()

Алгоритм решения

  1. Инициализация CAN на скорости 500 кбит/с .
  2. Настройка приёма – принимаем все сообщения с ID 0x1F4011F3 (расширенный).
  3. В основном цикле:
    • Проверяем наличие входящего пакета.
    • Если ID совпадает, анализируем первые два байта данных (это команда).
    • В зависимости от команды формируем и отправляем ответ(ы).
  4. Для отправки серии из 7 пакетов используем неблокирующую задержку на millis(), чтобы не тормозить остальную логику.

Полный код скетча

Скетч написан для Arduino IDE с установленной библиотекой STM32_CAN. Скопируйте его и залейте в ваш CAN-шилд.

подключение кан шилда к ардуино
#include <Arduino.h>
#include <STM32_CAN.h>

// =========================================================================
// НАСТРОЙКИ CAN
// =========================================================================
const uint32_t CAN_SPEED = 500000;          // 500 кбит/с
const uint8_t  CAN_DLC   = 8;               // Длина данных – 8 байт

// Идентификаторы (29-битные, расширенные)
const uint32_t REQUEST_ID  = 0x1F4011F3;    // ID запроса
const uint32_t RESPONSE_ID = 0x1F40F311;    // ID ответа

// =========================================================================
// ПИНЫ (если нужно использовать внешние датчики – можно добавить)
// =========================================================================
// ...

STM32_CAN Can(CAN1, DEF);   // Используем CAN1 на пинах PA11 (RX), PA12 (TX)

// Переменные для управления отправкой серии
bool sending_series = false;
uint8_t series_index = 0;
uint32_t last_series_send_time = 0;

// Буфер для хранения данных ответа
uint8_t response_data[8] = {0};

// =========================================================================
// ФУНКЦИЯ ОТПРАВКИ ОДНОГО CAN-ПАКЕТА
// =========================================================================
void sendCanMessage(uint32_t id, const uint8_t* data, uint8_t len) {
    CAN_message_t tx_msg;
    tx_msg.id = id;
    tx_msg.len = len;
    tx_msg.flags.extended = 1;   // ВАЖНО: включаем расширенный ID
    for (int i = 0; i < len; i++) {
        tx_msg.buf[i] = data[i];
    }
    Can.write(tx_msg);
}

// =========================================================================
// ФУНКЦИЯ ОТПРАВКИ СЕРИИ ИЗ 7 ПАКЕТОВ (вызывается по таймеру)
// =========================================================================
void sendSeriesPacket() {
    if (!sending_series) return;
    
    // Формируем данные для текущего пакета
    memset(response_data, 0, 8);
    response_data[0] = 0x22;
    response_data[1] = series_index + 1;   // 01..07
    
    sendCanMessage(RESPONSE_ID, response_data, CAN_DLC);
    
    series_index++;
    if (series_index >= 7) {
        sending_series = false;            // Серия завершена
    } else {
        last_series_send_time = millis();  // Готовимся к следующему
    }
}

// =========================================================================
// ОБРАБОТЧИК ВХОДЯЩИХ СООБЩЕНИЙ
// =========================================================================
void handleIncomingMessage(const CAN_message_t& rx_msg) {
    // Проверяем ID и длину
    if (rx_msg.id != REQUEST_ID || rx_msg.len < 2) return;
    
    // Анализируем первые два байта команды
    uint8_t cmd1 = rx_msg.buf[0];
    uint8_t cmd2 = rx_msg.buf[1];
    
    if (cmd1 == 0x03 && cmd2 == 0x19) {
        // -------------------------------------------------------------
        // ЗАПРОС №1: 03 19 02 01 ...
        // Ответ: 01 00 00 00 00 00 00 00
        // -------------------------------------------------------------
        uint8_t reply[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        sendCanMessage(RESPONSE_ID, reply, CAN_DLC);
        
    } else if (cmd1 == 0x05 && cmd2 == 0x22) {
        // -------------------------------------------------------------
        // ЗАПРОС №2: 05 22 F1 91 F1 81 ...
        // Ответ: 7 пакетов с периодом 20 мс
        // -------------------------------------------------------------
        // Если предыдущая серия ещё не завершена – сбрасываем её
        sending_series = false;
        // Запускаем новую серию
        series_index = 0;
        sending_series = true;
        last_series_send_time = millis();
        // Отправляем первый пакет сразу
        sendSeriesPacket();
    }
}

// =========================================================================
// SETUP
// =========================================================================
void setup() {
    // Инициализация CAN
    Can.begin();
    Can.setBaudRate(CAN_SPEED);
    
    // Опционально: настроить аппаратный фильтр для приёма только нужного ID
    // Можно пропустить – будем проверять в коде.
}

// =========================================================================
// LOOP
// =========================================================================
void loop() {
    // -------------------------------------------------------------
    // 1. ПРИЁМ ВХОДЯЩИХ СООБЩЕНИЙ
    // -------------------------------------------------------------
    CAN_message_t rx_msg;
    if (Can.read(rx_msg)) {
        handleIncomingMessage(rx_msg);
    }
    
    // -------------------------------------------------------------
    // 2. ОТПРАВКА СЕРИИ (если активна)
    // -------------------------------------------------------------
    if (sending_series) {
        if (millis() - last_series_send_time >= 20) {   // 20 мс
            sendSeriesPacket();
        }
    }
    
    // -------------------------------------------------------------
    // 3. ДРУГАЯ ЛОГИКА (если есть)
    // -------------------------------------------------------------
    // Например, чтение датчиков и отправка своих пакетов...
}
Листинг 1 – Полный скетч для CAN-шилда

Как это работает – пошагово

  1. Инициализация – CAN-шина настраивается на скорость 500 кбит/с.
  2. Приём – в loop() постоянно проверяем, не пришло ли новое сообщение. Если пришло – передаём его в обработчик handleIncomingMessage().
  3. Обработчик:
    • Сравнивает ID с 0x1F4011F3.
    • Смотрит на первые два байта данных.
    • Для 03 19 – формирует ответный пакет с 01 00... и отправляет его один раз.
    • Для 05 22 – запускает флаг sending_series = true и отправляет первый пакет серии.
  4. Отправка серии – в основном цикле каждые 20 мс (проверка через millis()) отправляется следующий пакет серии, пока не будут отправлены все 7.
  5. Важно: флаг extended = 1 в структуре CAN_message_t – без него 29-битные ID не будут работать.

Проверка и отладка

работа каш шилда на STM32F103C8T6

Для тестирования вам понадобится второе CAN-устройство (например, USB-CAN адаптер) или ещё одна плата с таким же шилдом.

  • Убедитесь, что скорость CAN совпадает на всех устройствах.
  • Терминатор (сопротивление 120 Ом) должен быть установлен на обоих концах шины.
  • Для просмотра трафика используйте программу типа PCAN-View или SocketCAN (под Linux).

Возможные доработки

  • Фильтрация на уровне контроллера – чтобы не принимать лишние пакеты, можно настроить аппаратные фильтры STM32. Это снизит нагрузку на процессор.
  • Динамическая скорость – если ваша шина работает на другой скорости (125k, 250k, 1M), просто измените CAN_SPEED.
  • Обработка ошибок – добавьте проверку возвращаемого значения Can.write() – если отправка не удалась (занят буфер), можно повторить попытку.

Заключение

Мы разобрали реальную задачу с форума и показали, как CAN-шилд для STM32F103 легко справляется с ней. Код компактен, не использует delay() и корректно обрабатывает как одиночные, так и серийные ответы.

Если у вас возникнут вопросы по подключению или настройке – пишите в комментариях, мы обязательно поможем!

Где купить наш CAN-шилд?
Он доступен на Ozon – переходите по ссылке