2026-08-20 17:24:34 +03:00
2026-08-20 17:24:34 +03:00

SETCAN

SETCAN — небольшой модуль прикладного протокола поверх классического CAN для микроконтроллеров STM32 и библиотеки STM32 HAL. Модуль принимает только кадры с расширенным 29-битным идентификатором, складывает их в кольцевой буфер и передаёт обработчикам по типу сообщения. Также он умеет отправлять широковещательные, дискретные, аналоговые, адресные, Modbus-подобные сообщения, ошибки и периодический «пульс» устройства.

Это не самостоятельный проект STM32CubeIDE: здесь нет .ioc, startup-файлов, linker script или настроек тактирования. Каталоги Inc и Src нужно добавить в существующую прошивку STM32.

Состав

SETCAN/
├── Inc/
│   └── protocan.h   # типы протокола, настройки устройства и публичный API
└── Src/
    └── protocan.c   # приём, буферизация, разбор и отправка CAN-кадров

Модуль зависит от файлов main.h и can.h, сгенерированных STM32CubeMX, а также от HAL-драйверов CAN, RTC и TIM.

Формат расширенного CAN ID

Протокол размещает служебные поля в 29-битном Extended ID:

Биты Поле Размер Назначение
28 Priority 1 0 — критический, 1 — стандартный приоритет
27 Route 1 0 — от управляющего модуля, 1 — от устройства
26..24 DeviceType 3 тип устройства, 0..7
23..20 DeviceID 4 номер устройства, 0..15
19..16 MsgType 4 тип сообщения
15..0 MsgBody 16 тип команды, адрес, ID датчика или код ошибки

Типы сообщений:

Значение Тип
0x0 широковещательное (BROADCAST)
0x1 дискретное (DISCRETE)
0x2 аналоговое (ANALOG)
0x3 общее адресное пространство
0x4 Modbus Coil
0x5 Modbus Discrete
0x6 Modbus Holding
0x7 Modbus Input
0x8 ошибка
0xF пульс присутствия устройства

Разметка MsgBody зависит от типа сообщения:

  • broadcast: младшие 4 бита — дополнительное поле, старшие 12 бит — команда;
  • discrete: младшие 12 бит — данные/адрес, старшие 4 бита — подтип;
  • analog: младшие 12 бит — ID датчика, старшие 4 бита — тип величины;
  • Modbus: младшие 4 бита — количество регистров, старшие 12 бит — начальный адрес;
  • error: младший байт — код ошибки, старший байт — дополнительная информация.

Для регистров uint16_t в CAN payload используется порядок байтов little-endian: сначала младший байт, затем старший.

Разметка ID реализована C-битовыми полями. Она соответствует используемому STM32 GCC ABI, но не является переносимой между произвольными компиляторами без проверки фактического расположения битов.

Подключение к STM32-проекту

1. Добавить файлы

Скопировать или подключить к сборке:

  • Inc/protocan.h;
  • Src/protocan.c.

Каталог Inc добавить в include paths компилятора.

2. Настроить устройство

В Inc/protocan.h задать значения конкретного узла:

#define CURRENT_TYPE_DEVICE 0b001  /* 3 бита: 0..7 */
#define CURRENT_ID_DEVICE   0b0010 /* 4 бита: 0..15 */

При необходимости изменить размер программного RX-буфера:

#define PROTOCAN_RX_BUFFER_SIZE 128

Полезная ёмкость кольцевого буфера на один элемент меньше заданного размера, то есть при значении 128 в нём помещается 127 ожидающих кадров.

3. Настроить CubeMX

В проекте должны быть инициализированы:

  • CAN с поддержкой Extended ID;
  • RTC;
  • базовый таймер, задающий период отправки пульса.

Модуль использует CAN FIFO0 и фильтры с номерами 0, 1, 2. В PROTOCAN_CONFIG_FILTER() также жёстко задано SlaveStartFilterBank = 14. Если приложение уже использует фильтры, номера и границу банков нужно согласовать.

4. Инициализировать и запустить периферию

Пример для main.c:

#include "protocan.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_CAN_Init();
    MX_RTC_Init();
    MX_TIM2_Init();

    PROTOCAN_INIT_StatusTypeDef init_status =
        PROTOCAN_INIT(&hcan, &hrtc, &htim2);

    if (init_status != PROTOCAN_INIT_OK) {
        Error_Handler();
    }

    /* PROTOCAN_INIT() этого не делает. */
    if (HAL_CAN_Start(&hcan) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_CAN_ActivateNotification(
            &hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
        Error_Handler();
    }
    if (HAL_TIM_Base_Start_IT(&htim2) != HAL_OK) {
        Error_Handler();
    }

    while (1) {
        (void)PROTOCAN_ProcessAllRxMsgs();
    }
}

PROTOCAN_INIT() требует ненулевые указатели на CAN, RTC и TIM, настраивает CAN-фильтры и включает отправку пульса. Функцию следует вызвать после MX_CAN_Init(), MX_RTC_Init() и MX_TIMx_Init(), но до запуска CAN.

5. Подключить callbackи HAL

Если в HAL включены USE_HAL_CAN_REGISTER_CALLBACKS == 1 и USE_HAL_TIM_REGISTER_CALLBACKS == 1, PROTOCAN_INIT() зарегистрирует callbackи автоматически.

Если регистрация callbackов отключена, добавить перенаправление из стандартных HAL-callbackов:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    if (hcan_ptr == &hcan) {
        ProtoCanRxFifo0MsgPendingCallback(hcan_ptr);
    }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim_ptr)
{
    if (htim_ptr == &htim2) {
        ProtoCanPulseCallback(htim_ptr);
    }
}

Не следует одновременно регистрировать callback через HAL и вручную вызывать его из стандартного callback — иначе один кадр или событие таймера может быть обработано дважды.

Приём сообщений

В прерывании ProtoCanRxFifo0MsgPendingCallback() модуль вычитывает все кадры из FIFO0:

  • стандартные CAN ID игнорируются;
  • PULSE не занимает место в RX-буфере, а сразу обновляет внутреннюю таблицу присутствующих устройств;
  • остальные Extended-кадры помещаются в кольцевой буфер;
  • при переполнении новые кадры молча отбрасываются.

Обработку прикладной логики следует выполнять вне прерывания одним из трёх способов:

PROTOCAN_ProcessSingleRxMsg(); /* обработать не более одного кадра */
PROTOCAN_ProcessAllRxMsgs();   /* обработать все накопленные кадры */
PROTOCAN_LoopProcessRxMsgs();  /* бесконечный блокирующий цикл */

Для обычного while (1) удобнее PROTOCAN_ProcessAllRxMsgs(). Она возвращает PROTOCAN_TIMEOUT, если очередь была пуста, PROTOCAN_OK после успешной обработки или код первой ошибки.

Свои обработчики входящих команд

Большинство обработчиков объявлены как __weak. Приложение может определить функцию с тем же именем и заменить демонстрационную реализацию своей логикой.

Пример обработки запроса температуры:

PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogTSens(struct RXMsg msg)
{
    msgBodyAnalogType body = {0};
    body.Body = msg.eID.Fields.MsgBody;

    uint16_t sensor_id = body.Fields.SensorID;
    /* Прочитать датчик sensor_id и сформировать нужный ответ. */

    return PROTOCAN_OK;
}

Доступные точки переопределения находятся в protocan.h:

  • ProtoCanMsgToBroadcast...() — статус, включение/выключение, restart, RTC;
  • ProtoCanMsgToDiscrete...() — аварии, предупреждения, флаги и команды;
  • ProtoCanMsgToAnalog...() — универсальные данные, настройки, U/I/T;
  • ProtoCanMsgToGeneralAddressSpace();
  • ProtoCanMsgToModbus...();
  • PROTOCAN_RequestError().

Стандартные слабые обработчики в основном являются демонстрационными: часть возвращает PROTOCAN_OK без действий, часть отправляет текстовые ответы вроде TS0001 или GAS-0001. Для рабочего изделия их обычно нужно переопределить.

Отправка

Публичная точка отправки — PROTOCAN_SEND(id, data). Сначала заполняется общий Extended ID, затем соответствующая часть ProtoCanData_t.

Пример отправки диапазона 16-битных регистров общего адресного пространства:

uint16_t registers[] = { 0x1234, 0x5678, 0x9ABC };

ProtoCanId_t id = {0};
id.Fields.Priority   = PROTOCAN_PRIORITY_STANDARD;
id.Fields.Route      = PROTOCAN_ROUTE_FROM_DEVICE;
id.Fields.DeviceType = CURRENT_TYPE_DEVICE;
id.Fields.DeviceID   = CURRENT_ID_DEVICE;
id.Fields.MsgType    = PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE;

ProtoCanData_t tx = {0};
tx.GeneralAddressSpaceData.RegStartAdr = 100;
tx.GeneralAddressSpaceData.Data        = registers;
tx.GeneralAddressSpaceData.RegCount    = 3;

if (PROTOCAN_SEND(id, tx) != PROTOCAN_OK) {
    /* Очередь CAN занята или произошла ошибка HAL. */
}

Массив автоматически разбивается на кадры максимум по четыре регистра (8 байт) с увеличением адреса в MsgBody.

Пример сообщения об ошибке без payload:

ProtoCanId_t id = {0};
id.Fields.Priority   = PROTOCAN_PRIORITY_CRITICAL;
id.Fields.Route      = PROTOCAN_ROUTE_FROM_DEVICE;
id.Fields.DeviceType = CURRENT_TYPE_DEVICE;
id.Fields.DeviceID   = CURRENT_ID_DEVICE;
id.Fields.MsgType    = PROTOCAN_MSGTYPE_ERROR;

ProtoCanData_t tx = {0};
tx.ErrorData.Code = 0x12;
tx.ErrorData.Info = 0x34;

(void)PROTOCAN_SEND(id, tx);

Для BROADCAST, DISCRETE и ANALOG используется tx.CoreData; для Modbus-типов — tx.ModbusData. Если количество передаваемых элементов больше нуля, соответствующий указатель Data должен быть валиден до завершения вызова.

Пульс устройства

Каждое прерывание переданного в PROTOCAN_INIT() таймера вызывает ProtoCanPulseCallback() и отправляет кадр:

  • MsgType = PROTOCAN_MSGTYPE_PULSE;
  • DLC = 1;
  • Data[0] — циклический счётчик 0..255.

Период пульса полностью определяется настройками таймера. Broadcast-команда PROTOCAN_BROADCAST_ONOFF в стандартной реализации переключает его отправку.

Полученный пульс помечает удалённое устройство как активное и обнуляет TimeFromLastPulse. Увеличение этого времени и перевод устройства в offline в текущем модуле не реализованы — если это требуется, контроль таймаута нужно добавить в приложение.

Синхронизация RTC

Broadcast-команда PROTOCAN_BROADCAST_RTCSETUP ожидает ровно 7 байт:

Индекс Значение
0 часы, 0..23
1 минуты, 0..59
2 секунды, 0..59
3 год как смещение от 2000, 0..99
4 месяц, 1..12
5 число месяца
6 день недели в формате, ожидаемом данной прошивкой

Перед записью проверяются диапазоны и количество дней с учётом високосного года.

Важные ограничения текущей реализации

  • Модуль рассчитан на classic CAN с payload до 8 байт, не на CAN FD.
  • PROTOCAN_INIT() не запускает CAN/таймер и не включает CAN notification.
  • Все три аппаратных фильтра и номера банков заданы внутри библиотеки.
  • Адресованные сообщения фильтруются с установленным битом Route (бит 27). Это стоит сверить с принятой в системе трактовкой PROTOCAN_ROUTE_FROM_PM/PROTOCAN_ROUTE_FROM_DEVICE.
  • Таблица присутствующих устройств обновляется при пульсе, но автоматического offline-таймаута нет.
  • Буфер переполнения не ведёт счётчик потерь и не сообщает ошибку приложению.
  • В репозитории нет тестов, примера полного STM32-проекта и системы сборки.
  • Несколько внутренних функций отправки реализованы только в protocan.c и не входят в публичный заголовок; для прикладного кода следует использовать PROTOCAN_SEND().

Краткий порядок запуска

  1. Добавить protocan.c/.h в STM32-проект.
  2. Задать CURRENT_TYPE_DEVICE и CURRENT_ID_DEVICE.
  3. Настроить CAN, RTC и периодический TIM в CubeMX.
  4. Вызвать PROTOCAN_INIT() после MX_..._Init().
  5. Запустить CAN, активировать CAN_IT_RX_FIFO0_MSG_PENDING и запустить таймер с прерыванием.
  6. При необходимости подключить HAL-callbackи вручную.
  7. Переопределить нужные __weak-обработчики.
  8. Вызывать PROTOCAN_ProcessAllRxMsgs() в основном цикле.
Description
No description provided
Readme 155 KiB
Languages
C 100%