📡 Решаем задачу CAN-взаимодействия с нашим CAN-шилдом на STM32F103
Введение
На форумах часто встречаются задачи по обмену данными по CAN-шине, требующие не только монотонной отправки, но и интеллектуальной обработки входящих сообщений. Сегодня разберём реальный запрос пользователя и покажем, как его решить с помощью нашего CAN-шилда для STM32F103.
Постановка задачи
Устройство должно ожидать два разных сообщения с одинаковым идентификатором 0x1F4011F3:
-
Запрос №1 – данные:
03 19 02 01 00 00 00 00
Ответ – одно сообщение с ID0x1F40F311и данными:01 00 00 00 00 00 00 00 -
Запрос №2 – данные:
05 22 F1 91 F1 81 00 00
Ответ – семь сообщений подряд с ID0x1F40F311и данными: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()
Алгоритм решения
- Инициализация CAN на скорости 500 кбит/с .
- Настройка приёма – принимаем все сообщения с ID
0x1F4011F3(расширенный). - В основном цикле:
- Проверяем наличие входящего пакета.
- Если ID совпадает, анализируем первые два байта данных (это команда).
- В зависимости от команды формируем и отправляем ответ(ы).
- Для отправки серии из 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. ДРУГАЯ ЛОГИКА (если есть)
// -------------------------------------------------------------
// Например, чтение датчиков и отправка своих пакетов...
}
Как это работает – пошагово
- Инициализация – CAN-шина настраивается на скорость 500 кбит/с.
- Приём – в
loop()постоянно проверяем, не пришло ли новое сообщение. Если пришло – передаём его в обработчикhandleIncomingMessage(). - Обработчик:
- Сравнивает ID с
0x1F4011F3. - Смотрит на первые два байта данных.
- Для
03 19– формирует ответный пакет с01 00...и отправляет его один раз. - Для
05 22– запускает флагsending_series = trueи отправляет первый пакет серии.
- Сравнивает ID с
- Отправка серии – в основном цикле каждые 20 мс (проверка через
millis()) отправляется следующий пакет серии, пока не будут отправлены все 7. - Важно: флаг
extended = 1в структуреCAN_message_t– без него 29-битные ID не будут работать.
Проверка и отладка
Для тестирования вам понадобится второе CAN-устройство (например, USB-CAN адаптер) или ещё одна плата с таким же шилдом.
- Убедитесь, что скорость CAN совпадает на всех устройствах.
- Терминатор (сопротивление 120 Ом) должен быть установлен на обоих концах шины.
- Для просмотра трафика используйте программу типа PCAN-View или SocketCAN (под Linux).
Возможные доработки
- Фильтрация на уровне контроллера – чтобы не принимать лишние пакеты, можно настроить аппаратные фильтры STM32. Это снизит нагрузку на процессор.
- Динамическая скорость – если ваша шина работает на другой скорости (125k, 250k, 1M), просто измените
CAN_SPEED. - Обработка ошибок – добавьте проверку возвращаемого значения
Can.write()– если отправка не удалась (занят буфер), можно повторить попытку.
Заключение
Мы разобрали реальную задачу с форума и показали, как CAN-шилд для STM32F103 легко справляется с ней. Код компактен, не использует delay() и корректно обрабатывает как одиночные, так и серийные ответы.
Если у вас возникнут вопросы по подключению или настройке – пишите в комментариях, мы обязательно поможем!
Где купить наш CAN-шилд?
Он доступен на Ozon – переходите по ссылке