STM32 · Classic CAN 2.0B · HAL

SETCAN
руководство разработчика

Карта библиотеки ProtoCAN, формат кадров и практический маршрут переноса в существующую прошивку STM32.

29 бит Extended ID0…8 байт payload128 адресов устройств127 кадров в RX-очереди

Что находится в проекте

SETCAN — не готовый CubeIDE-проект, а подключаемый C-модуль поверх STM32 HAL. Его задача — фильтровать, принимать, разбирать и отправлять прикладные сообщения ProtoCAN.

Структура

Минимальное ядро + нормативные документы

SETCAN/ ├── Inc/protocan.h публичные типы, настройки и API ├── Src/protocan.c фильтры, RX-очередь, разбор и отправка ├── docs/protocan/ спецификация протокола │ ├── PROTOCOL.md 29-битный ID и типы сообщений │ ├── OAP.md общее адресное пространство │ ├── BOOTLOADER.md обновление прошивки по CAN (draft) │ └── examples/ эталонные кадры JSON ├── Протокол CAN и ОАП.xlsx редактируемый реестр ОАП └── doc/index.html эта страница
Зависимости

Что ожидает модуль

  • main.h и can.h из CubeMX
  • STM32 HAL CAN, RTC и TIM
  • CAN FIFO0 и Extended ID
  • STM32 GCC ABI для C bit-fields
Входящие

Путь кадра

IRQ вычитывает FIFO0. Pulse обрабатывается сразу, остальные Extended-кадры попадают в кольцевой буфер и разбираются в основном цикле.

Исходящие

Единая точка отправки

PROTOCAN_SEND() выбирает упаковщик по MsgType. GAS и Modbus автоматически разбиваются на кадры до 8 байт.

Граница

Что приложение реализует само

Прикладные действия, хранение SETTINGS, EEPROM, DS18B20, таймаут online/offline и полноценный bootloader не входят в ядро.

01 · FIFO0 IRQHAL сообщает о новом кадре
02 · RX bufferExtended ID помещается в очередь
03 · DispatcherРазбор по MsgType
04 · Weak handlerЛогика приложения

Состав библиотеки

Два файла образуют один модуль. Заголовок — контракт интеграции; C-файл — транспортная реализация и демонстрационные слабые обработчики.

Inc

protocan.h

Конфигурация текущего устройства, размеры буфера, enum типов сообщений, битовые представления ID/Body, структуры TX/RX и публичные функции.

Меняют при переносе: CURRENT_TYPE_DEVICE, CURRENT_ID_DEVICE, иногда PROTOCAN_RX_BUFFER_SIZE.

Src

protocan.c

Инициализация, фильтры, кольцевой буфер, диспетчеризация, RTC sync, pulse и упаковка кадров.

Обычно не правят: прикладную логику лучше добавлять переопределением __weak-функций.

HAL

Внешний слой

CAN_HandleTypeDef, RTC_HandleTypeDef и TIM_HandleTypeDef передаются в PROTOCAN_INIT(). Запуск периферии выполняет приложение.

Основной API

ФункцияНазначениеГде вызывать
PROTOCAN_INITСохраняет HAL handles, настраивает фильтры и callback’и при доступной регистрации.После MX_*_Init(), до HAL_CAN_Start().
PROTOCAN_ProcessAllRxMsgsОбрабатывает всю накопленную RX-очередь и возвращает первую ошибку.В основном цикле.
PROTOCAN_ProcessSingleRxMsgОбрабатывает максимум один кадр.В планировщике или ограниченном временном слоте.
PROTOCAN_SENDУпаковывает и отправляет сообщение выбранного типа.Из логики приложения, не удерживая изменяемые данные.
ProtoCanRxFifo0MsgPendingCallbackЗабирает кадры из CAN FIFO0.Из HAL callback, если авто-регистрация выключена.
ProtoCanPulseCallbackОтправляет периодический pulse со счётчиком.Из callback нужного базового таймера.
PROTOCAN_SEND_SETTINGS_RESPONSE / ERRORФормирует успешный либо ошибочный ответ привязки ROM.Из пользовательского обработчика SETTINGS.

Точки расширения

Переопределите нужную __weak-функцию в собственном C-файле: семейства ProtoCanMsgToBroadcast…, …Discrete…, …Analog…, ProtoCanMsgToSettings, ProtoCanMsgToGeneralAddressSpace и …Modbus…. Не редактируйте демонстрационную реализацию в ядре — так обновлять библиотеку проще.

Формат ProtoCAN

Протокол использует только CAN 2.0B Extended ID. Адрес устройства — пара DeviceType/DeviceID; назначение младших 16 бит зависит от типа сообщения.

29-битный идентификатор

28        27        26··24       23··20      19··16      15········0
Priority  Route     DeviceType   DeviceID    MsgType     MsgBody
1 бит     1 бит     3 бита       4 бита      4 бита      16 бит
Многобайтовые значения payload передаются little-endian. Разметка ID через C bit-fields требует проверки при смене компилятора или ABI.

Реестр MsgType

КодТипНазначениеСтатус
0x0BROADCASTОбщие команды всем устройствамstable
0x1DISCRETEСостояния, команды, флагиstable
0x2ANALOGДатчики U / I / T и универсальные данныеstable
0x3GASОбщее 16-битное адресное пространствоstable
0x4…0x7MODBUSCoil, Discrete, Holding, Inputstable
0x8ERRORКод ошибки и дополнительная информацияstable
0x9…0xDBOOTУправление, блоки A/B, статус, discoverydraft
0xESETTINGSПривязка 1-Wire ROM к локации Z/Ystable
0xFPULSEПрисутствие устройстваstable

Как портировать в свой STM32-проект

Рабочая последовательность для CubeMX/CubeIDE. Имена hcan, hrtc и htim2 замените на handles своего проекта.

Шаг 1

Добавьте исходники

  • Inc/protocan.h в include path
  • Src/protocan.c в сборку
  • Проверьте доступность main.h и can.h
Шаг 2

Настройте адрес

В protocan.h задайте тип 0…7 и ID 0…15. Пара должна быть уникальной на шине.

#define CURRENT_TYPE_DEVICE 0b011
#define CURRENT_ID_DEVICE   0b0101
Шаг 3

Настройте CubeMX

  • CAN: Extended ID, FIFO0
  • RTC: если нужна синхронизация времени
  • TIM: период pulse
  • Проверьте filter banks 0, 1, 2 и границу 14
Шаг 4

Инициализация и основной цикл

#include "protocan.h"

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

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

    if (PROTOCAN_INIT(&hcan, &hrtc, &htim2) != PROTOCAN_INIT_OK) {
        Error_Handler();
    }
    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();
    }
}
Шаг 5

Свяжите callback’и при отключённой регистрации HAL

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);
    }
}
Важно

Не дублируйте события

Если USE_HAL_*_REGISTER_CALLBACKS == 1, библиотека регистрирует callback’и сама. Не вызывайте те же функции ещё раз из стандартных callback’ов.

Шаг 6

Перенесите прикладную логику

Создайте, например, protocan_app.c и определите там только нужные weak handlers. Так ядро остаётся обновляемым.

#include "protocan.h"

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;
}

Проверка после переноса

  • Проект собирается без повторных определений HAL callback’ов.
  • CAN стартует и принимает только Extended ID.
  • Pulse появляется с периодом выбранного таймера.
  • Эталонный ID 0x13590702 декодируется как DeviceType=3, DeviceID=5, MsgType=9, Body=0x0702.
  • При нагрузке учтено, что буфер размера 128 хранит максимум 127 кадров.

Готовые шаблоны

Минимальные примеры формирования исходящего кадра и обработчика SETTINGS. Их можно вынести в прикладной слой проекта.

Отправка трёх регистров GAS

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 вернул ошибку. */
}

SETTINGS: прикладное хранение ROM

PROTOCAN_StatusTypeDef ProtoCanMsgToSettings(
    const ProtoCanSettingsMsg_t *message)
{
    uint8_t current_rom[PROTOCAN_SETTINGS_ROM_SIZE] = {0};

    switch (message->Operation) {
    case PROTOCAN_SETTINGS_GET:
        /* Загрузить ROM из EEPROM в current_rom. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, current_rom);

    case PROTOCAN_SETTINGS_WRITE:
        /* Проверить CRC/наличие и атомарно записать message->Rom. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, message->Rom);

    case PROTOCAN_SETTINGS_CLEAR:
        /* Очистить запись; current_rom должен содержать нули. */
        return PROTOCAN_SEND_SETTINGS_RESPONSE(
            message->AssemblySerial, message->Position, current_rom);

    default:
        return PROTOCAN_SEND_SETTINGS_ERROR(
            message->AssemblySerial, message->Position,
            PROTOCAN_SETTINGS_RESULT_INVALID_DLC);
    }
}