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().
Краткий порядок запуска
- Добавить
protocan.c/.hв STM32-проект. - Задать
CURRENT_TYPE_DEVICEиCURRENT_ID_DEVICE. - Настроить CAN, RTC и периодический TIM в CubeMX.
- Вызвать
PROTOCAN_INIT()послеMX_..._Init(). - Запустить CAN, активировать
CAN_IT_RX_FIFO0_MSG_PENDINGи запустить таймер с прерыванием. - При необходимости подключить HAL-callback’и вручную.
- Переопределить нужные
__weak-обработчики. - Вызывать
PROTOCAN_ProcessAllRxMsgs()в основном цикле.