Недавно на одном из профильных форумов наткнулся на крик души разработчика, который пытался настроить логику отправки пакетов в визуальной среде программирования контроллеров (CANNY):
«Помогите реализовать алгоритм. У меня 4 входа управления. При появлении единицы на трех первых отправляется посылка по одному и тому же ID, но с изменением значения в одном байте. Появляется единица на первом входе — начинается посылка со значением А. Появляется на втором (но не пропадает на первом) — значение этого байта меняется на Б. Появляется на третьем (не пропадает на первых двух) — значение меняется на В.
А при появлении единицы на 4-м входе — значение базовой посылки по этому ID не меняется, но в шину ДОБАВЛЯЮТСЯ еще около 15 других посылок с разными ID…»
Автор честно признался, что перечитал кучу инструкций, но построить такой алгоритм на «коммутаторах на тройку» и логических кубиках — это вынос мозга. Там действительно получается огромный, запутанный лабиринт из связей, в котором легко ошибиться.
Смотрите, как эта задача решается на нашем универсальном CAN-эмуляторе. Ровно десять минут с перекуром! Вместо рисования сотен блоков мы просто пишем понятную логику на языке C++ (Arduino).

Почему на нашем шилде это делается проще?
Для отправки сообщений будем использовать наш Кан Шилд с открытым исходным кодом. Шаблон кода для эмулятора Кан шины вы найдёте в репозиториях на Гитхабе и GitVerse. Сама же плата есть на озоне, как голая плата, так и полностью собранная и в корпусе, вам останется только припаять провода, написать код (Ардуино или HAL, или даже на регистрах если хотите) и загрузить код в плату.
На плате шилда разведено 6 независимых входов (кнопок/тумблеров). Нам понадобятся первые 4 (от PIN_IN1 до PIN_IN4).
Вся магия текстового кода в том, что мы можем проверять условия последовательно. Если активен третий вход — берем значение «В», если второй — «Б», если только первый — «А». А проверка 4-го входа просто запускает цикл, который за долю миллисекунды выкидывает в сеть пачку из 15 нужных пакетов.
Готовый рабочий скетч (Arduino IDE)
Просто скопируйте этот шаблон, установите библиотеку STM32_CAN и залейте в плату:
#include <Arduino.h>
#include <STM32_CAN.h>
const uint32_t BASE_CAN_ID = 0x100; // ID нашей основной посылки
const uint32_t CAN_SPEED = 500000; // Скорость шины (например, 500к)
const uint32_t SEND_PERIOD_MS = 100; // Период отправки базового пакета (100 мс)
// Назначаем физические входы нашего шилда
const uint8_t PIN_IN1 = PA2; // Вход 1 (Значение А)
const uint8_t PIN_IN2 = PA3; // Вход 2 (Значение Б)
const uint8_t PIN_IN3 = PA4; // Вход 3 (Значение В)
const uint8_t PIN_IN4 = PA5; // Вход 4 (+15 дополнительных пакетов)
STM32_CAN Can(CAN1, DEF);
unsigned long last_send_time = 0;
void setup() {
// Входы шилда используют внутреннюю подтяжку INPUT_PULLUP.
// Если на вход "прилетел" минус (GND) или сработал концевик -> digitalRead вернет LOW.
pinMode(PIN_IN1, INPUT_PULLUP);
pinMode(PIN_IN2, INPUT_PULLUP);
pinMode(PIN_IN3, INPUT_PULLUP);
pinMode(PIN_IN4, INPUT_PULLUP);
Can.begin();
Can.setBaudRate(CAN_SPEED);
}
void loop() {
// Читаем физическое состояние входов (LOW означает, что на входе есть сигнал/единица)
bool in1_active = (digitalRead(PIN_IN1) == LOW);
bool in2_active = (digitalRead(PIN_IN2) == LOW);
bool in3_active = (digitalRead(PIN_IN3) == LOW);
bool in4_active = (digitalRead(PIN_IN4) == LOW);
// Основной таймер отправки пакетов
if (millis() - last_send_time >= SEND_PERIOD_MS) {
last_send_time = millis();
// ---------------------------------------------------------------------
// ЛОГИКА 1-3 ВХОДОВ: Меняем значение в 0-м байте в зависимости от приоритета
// ---------------------------------------------------------------------
uint8_t target_byte_value = 0x00; // По умолчанию, если ничего не нажато
if (in3_active) {
target_byte_value = 0xCC; // Значение "В" (высший приоритет, так как по условию первые два не пропали)
} else if (in2_active) {
target_byte_value = 0xBB; // Значение "Б"
} else if (in1_active) {
target_byte_value = 0xAA; // Значение "А"
}
// Если хотя бы один из трех входов активен — отправляем базовый пакет
if (in1_active || in2_active || in3_active) {
CAN_message_t base_msg;
base_msg.id = BASE_CAN_ID;
base_msg.len = 8;
for(int i = 0; i < 8; i++) base_msg.buf[i] = 0x00;
base_msg.buf[0] = target_byte_value; // Записываем измененный байт
Can.write(base_msg);
}
// ---------------------------------------------------------------------
// ЛОГИКА 4-ГО ВХОДА: Массовый «залп» из 15 пакетов с разными ID
// ---------------------------------------------------------------------
if (in4_active) {
// Крутим быстрый цикл от 0 до 14
for (uint32_t i = 0; i < 15; i++) {
CAN_message_t extra_msg;
// Генерируем разные ID (например, от 0x200 до 0x20E)
extra_msg.id = 0x200 + i;
extra_msg.len = 8;
for(int b = 0; b < 8; b++) extra_msg.buf[b] = (uint8_t)i; // Какие-то данные
// Аппаратный CAN на STM32 мгновенно переварит эту пачку пакетов
Can.write(extra_msg);
}
}
}
}
Разбор полетов: Почему это красиво?
- Приоритеты «из коробки»: Конструкция
if - else ifсама решает проблему наложения сигналов. Если активен третий вход (in3_active), микроконтроллер запишет значение0xCCи даже не станет смотреть, что там происходит на первом и втором входах. Никаких громоздких логических схем И/ИЛИ городить не нужно. - Мгновенный «залп» пакетов: Когда срабатывает 4-й вход, цикл
forгенерирует и отправляет все 15 пакетов за несколько микросекунд. Благодаря встроенным Mailbox-ам процессора STM32, контроллер сам выстроит их в очередь и выдаст в шину один за другим без задержки основной программы. Обычная Ардуина на внешнем чипе MCP2515 на такой пачке могла бы запнуться из-за переполнения буфера, но наш шилд делает это на уровне железа.
Вывод простой: визуальное программирование хорошо для примитивного включения реле, но как только появляется реальная автомобильная логика переключения статусов и пакетные «залпы» — текстовый код на правильном железе выигрывает всухую.
Нужны качественные инструменты?
Не тратьте время на костыли, собирайте свои проекты сразу на надежной платформе:
- Голая плата на Ozon — для самостоятельной пайки.
- Собранный шилд в корпусе на Ozon — готовый фабричный блок.
- Код шаблонов и схемы: библиотеки на GitHub и GitVerse.
Делитесь своими кастомными алгоритмами и сложными задачами в комментариях — мы с удовольствием перепишем их под наш шилд в следующих статьях!