32 KiB
ProtoCAN: порт STM32 bxCAN (STM32 HAL)
stm32-bxcan — порт прикладного ProtoCAN для STM32 HAL, перенесённый из
SETCAN. Модуль принимает
кадры, управляет HAL CAN/RTC/TIM и вызывает обработчики приложения. Формат
29-битного идентификатора и его переносимая реализация принадлежат общему
ядру c/set-protocol в этом же репозитории templates.
Порт использует classic bxCAN (HAL_CAN_*), RTC и TIM. Для FDCAN на
STM32G4/H7 требуется другой аппаратный порт. Здесь нет .ioc, startup-файлов,
linker script или настроек тактирования: их предоставляет проект STM32CubeIDE.
Схема подключения: приложение → stm32-bxcan → STM32 HAL → bxCAN/RTC/TIM.
Для упаковки и разбора CAN ID порт вызывает общее ядро pcan_id.
Старый API PROTOCAN_* и имена protocan.c/.h сохранены для существующих
прошивок. Отдельный репозиторий и вложенный сабмодуль SETCAN больше не нужны.
Исходная версия: SETCAN c8eec559785ab809469483dc58d3976ec9db340a.
История SETCAN включена в историю templates отдельным родителем коммита
переноса; прежние пути можно посмотреть через
git show c8eec55:Inc/protocan.h и git log c8eec55.
Состав
c/set-protocol/
├── include/pcan_id.h
├── src/pcan_id.c
└── ports/stm32-bxcan/
├── protocan.h # типы протокола, настройки устройства и публичный API
├── protocan.c # приём, буферизация, разбор и отправка CAN-кадров
└── README.md
Модуль зависит от файлов main.h и can.h, сгенерированных STM32CubeMX, а также
от HAL-драйверов CAN, RTC и TIM. В сборку добавляются
c/set-protocol/ports/stm32-bxcan/protocan.c и c/set-protocol/src/pcan_id.c.
В include paths добавляются c/set-protocol/ports/stm32-bxcan,
c/set-protocol/include и каталог CubeMX с main.h/can.h.
Если прошивка уже собирает общее ядро SETProtocol, второй раз добавлять
pcan_id.c не нужно. HAL-порт не входит в host-сборку CMake общего ядра.
| Файл | Зависимости |
|---|---|
protocan.h |
main.h, can.h, общий pcan_id.h |
protocan.c |
protocan.h, STM32 HAL CAN/RTC/TIM, CMSIS |
../../src/pcan_id.c |
переносимое ядро C99, ../../include |
Документация протокола
Каноническое описание теперь находится рядом с переносимой реализацией:
- Структура ProtoCAN;
- Прошивка по CAN;
- Правила ведения ОАП;
- Машинные эталоны;
- Историческая HTML-документация SETCAN.
Файлы в doc/setcan/protocan сохранены как исторический снимок SETCAN и больше не
являются источником истины.
Формат расширенного 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 |
ошибка |
0x9 |
управление загрузчиком (BOOT_CONTROL, зарезервировано) |
0xA |
данные прошивки, слот A (BOOT_DATA_A, зарезервировано) |
0xB |
данные прошивки, слот B (BOOT_DATA_B, зарезервировано) |
0xC |
состояние загрузчика (BOOT_STATUS, зарезервировано) |
0xD |
обнаружение загрузчиков (BOOT_DISCOVERY, зарезервировано) |
0xE |
настройка привязки датчика (SETTINGS) |
0xF |
пульс присутствия устройства |
Разметка MsgBody зависит от типа сообщения:
- broadcast: младшие 4 бита — дополнительное поле, старшие 12 бит — команда;
- discrete: младшие 12 бит — данные/адрес, старшие 4 бита — подтип;
- analog: младшие 12 бит — ID датчика, старшие 4 бита — тип величины;
- Modbus: младшие 4 бита — количество регистров, старшие 12 бит — начальный адрес;
- SETTINGS: старший байт — номер сборки
Z, младший байт — позицияY; - error: младший байт — код ошибки, старший байт — дополнительная информация.
Для регистров uint16_t в CAN payload используется порядок байтов little-endian: сначала младший байт, затем старший.
Пара DeviceType/DeviceID образует уникальный адрес прибора. Три бита
DeviceType задают 8 типов, четыре бита DeviceID — 16 экземпляров каждого
типа; всего на одной шине можно адресовать до 128 приборов. Загрузочные кадры
так же адресуются этой парой и не требуют изменения 29-битного CAN ID.
Разметка ID реализована C-битовыми полями. Она соответствует используемому STM32 GCC ABI, но не является переносимой между произвольными компиляторами без проверки фактического расположения битов.
Подключение к STM32-проекту
1. Добавить файлы
Скопировать или подключить к сборке:
c/set-protocol/ports/stm32-bxcan/protocan.h;c/set-protocol/ports/stm32-bxcan/protocan.c;c/set-protocol/src/pcan_id.c(если ещё не собирается в составе ядра).
Каталоги c/set-protocol/ports/stm32-bxcan и c/set-protocol/include
добавить в include paths компилятора.
2. Настроить устройство
В protocan.h для прибора привязки DS18B20 заданы выбранные значения:
#define CURRENT_TYPE_DEVICE 0b111 /* DeviceType = 0x7 */
#define CURRENT_ID_DEVICE 0b1111 /* DeviceID = 0xF */
При необходимости изменить размер программного RX-буфера:
#define PROTOCAN_RX_BUFFER_SIZE 128
Полезная ёмкость кольцевого буфера на один элемент меньше заданного размера, то есть при значении 128 в нём помещается 127 ожидающих кадров.
3. Настроить CubeMX
В проекте должны быть инициализированы:
- CAN с поддержкой Extended ID;
- RTC;
- базовый таймер, задающий период отправки пульса.
Модуль использует CAN FIFO0 и фильтры с номерами 0, 1, 2. Адресованный фильтр принимает Route=0 (запрос от ПМ) для текущих DeviceType/DeviceID; отдельные фильтры принимают broadcast и pulse. В 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;ProtoCanMsgToSettings()— GET/WRITE/CLEAR привязки ROM к локации;ProtoCanMsgToGeneralAddressSpace();ProtoCanMsgToModbus...();PROTOCAN_RequestError().
Стандартные слабые обработчики в основном являются демонстрационными: часть возвращает PROTOCAN_OK без действий, часть отправляет текстовые ответы вроде TS0001 или GAS-0001. Для рабочего изделия их обычно нужно переопределить.
SETTINGS: привязка DS18B20 к локации
Для прибора DeviceType=0x7, DeviceID=0xF используется MsgType=0xE:
Body = (Z << 8) | Y
запрос от ПМ = 0x17FEZZYY
ответ прибора = 0x1FFEZZYY
| Запрос | Операция |
|---|---|
DLC=0 |
GET: прочитать ROM локации |
DLC=8, ROM не нулевой |
WRITE/REPLACE: записать или заменить датчик |
DLC=8, восемь нулей |
CLEAR: очистить локацию |
| другой DLC или RTR | автоматический ответ INVALID_DLC |
Успешный ответ имеет DLC=8 и содержит текущее состояние локации: ROM после
GET/WRITE либо восемь нулей после CLEAR. Ошибка сохраняет тот же Body, имеет
DLC=1 и передаёт ProtoCanSettingsResultType в Data[0].
PROTOCAN_SettingsProcessing() выполняет транспортный разбор и вызывает
слабую функцию ProtoCanMsgToSettings(). Приложение переопределяет её и
выполняет проверку family code/CRC8, поиск ROM на 1-Wire и запись EEPROM:
PROTOCAN_StatusTypeDef ProtoCanMsgToSettings(
const ProtoCanSettingsMsg_t *message)
{
uint8_t current_rom[PROTOCAN_SETTINGS_ROM_SIZE] = {0};
switch (message->Operation) {
case PROTOCAN_SETTINGS_GET:
/* Загрузить ROM локации в current_rom; свободная локация = нули. */
return PROTOCAN_SEND_SETTINGS_RESPONSE(
message->AssemblySerial, message->Position, current_rom);
case PROTOCAN_SETTINGS_WRITE:
/* Проверить ROM и атомарно записать его в каталог/EEPROM. */
return PROTOCAN_SEND_SETTINGS_RESPONSE(
message->AssemblySerial, message->Position, message->Rom);
case PROTOCAN_SETTINGS_CLEAR:
/* Удалить локацию из каталога/EEPROM. */
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);
}
}
Слабая реализация специально не подтверждает операцию: без прикладного каталога библиотека не должна сообщать, что ROM был сохранён.
Отправка
Публичная точка отправки — 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, для SETTINGS — tx.SettingsData. Для успешных ответов и ошибок SETTINGS удобнее использовать готовые функции PROTOCAN_SEND_SETTINGS_RESPONSE() и PROTOCAN_SEND_SETTINGS_ERROR(). Если количество передаваемых элементов больше нуля, соответствующий указатель 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 | день недели в формате, ожидаемом данной прошивкой |
Перед записью проверяются диапазоны и количество дней с учётом високосного года.
Прошивка приборов по CAN
Ниже зафиксирован формат планируемого загрузочного сервиса. Значения
MsgType=0x9..0xD зарезервированы в протоколе, но обработчики загрузчика в
текущих protocan.c/.h ещё не реализованы.
Загрузчик работает поверх classic CAN 2.0B с Extended ID. Управляющий модуль
использует Route=0, прибор отвечает с Route=1. Команды стирания и записи
всегда должны быть адресованы конкретной паре DeviceType/DeviceID;
широковещательный режим допустим только для обнаружения.
Карта загрузочных сообщений
MsgType |
Имя | Назначение MsgBody |
CAN payload |
|---|---|---|---|
0x9 |
BOOT_CONTROL |
SessionID:8 | Command:8 |
параметры команды |
0xA |
BOOT_DATA_A |
номер 8-байтового блока слота A | 8 байт образа |
0xB |
BOOT_DATA_B |
номер 8-байтового блока слота B | 8 байт образа |
0xC |
BOOT_STATUS |
SessionID:8 | Command:8 |
статус и прогресс |
0xD |
BOOT_DISCOVERY |
подтип запроса/ответа | идентификация прибора |
MsgBody в кадрах данных является не байтовым адресом, а номером блока:
block_offset = (uint32_t)MsgBody * 8U;
if (MsgType == PROTOCAN_MSGTYPE_BOOT_DATA_A) {
address = SLOT_A_BASE + block_offset;
} else if (MsgType == PROTOCAN_MSGTYPE_BOOT_DATA_B) {
address = SLOT_B_BASE + block_offset;
}
Диапазон MsgBody=0x0000..0xFFFF адресует 65536 блоков:
65536 блоков * 8 байт = 524288 байт = 512 КиБ на слот
Таким образом, BOOT_DATA_A и BOOT_DATA_B адресуют два логических слота
по 512 КиБ, всего 1 МиБ пространства образов. Физические SLOT_A_BASE и
SLOT_B_BASE задаёт конкретный загрузчик. Если внутренняя Flash имеет ровно
1 МиБ, два полных слота в ней не поместятся вместе с загрузчиком и метаданными:
нужно уменьшить слоты, выбрать MCU с большей Flash или хранить staging-образ
во внешней памяти.
Кадр данных
Extended CAN ID
Priority = STANDARD
Route = FROM_PM
DeviceType = тип целевого прибора
DeviceID = экземпляр целевого прибора
MsgType = 0xA (слот A) или 0xB (слот B)
MsgBody = BlockIndex, 0x0000..0xFFFF
DATA[0..7] = очередные 8 байт образа
Например, MsgBody=0x0123 задаёт смещение 0x0123 * 8 = 0x0918 от
начала выбранного слота. Последний неполный блок дополняется значениями
0xFF; фактический размер передаётся командой BEGIN_UPDATE, поэтому CRC32
считается только по байтам образа.
Управляющие команды
В BOOT_CONTROL поле MsgBody имеет формат:
15........8 7.........0
SessionID Command
Рекомендуемые команды:
| Код | Команда | Назначение |
|---|---|---|
0x01 |
IDENTIFY |
прочитать тип, аппаратную и программную версии |
0x02 |
ENTER_BOOT |
перейти из приложения в загрузчик |
0x03 |
BEGIN_IMAGE |
передать размер и CRC32 образа |
0x04 |
BEGIN_COMPAT |
передать тип, аппаратную и программную версии |
0x05 |
ERASE |
подготовить неактивный слот |
0x06 |
VERIFY |
проверить размер, CRC32 и подпись |
0x07 |
COMMIT |
назначить проверенный слот кандидатом на запуск |
0x08 |
CONFIRM |
подтвердить успешный запуск новой программы |
0x09 |
REBOOT |
перезагрузить прибор |
0x0A |
ABORT |
отменить текущую сессию |
0x0B |
QUERY_PROGRESS |
запросить слот и следующий ожидаемый блок |
BEGIN_IMAGE содержит размер и CRC образа:
DATA[0..3] ImageSize, uint32 little-endian
DATA[4..7] ImageCRC32, uint32 little-endian
BEGIN_COMPAT содержит совместимость и версию:
DATA[0..1] ProductType, uint16 little-endian
DATA[2] минимальная HardwareRevision
DATA[3] максимальная HardwareRevision
DATA[4..7] FirmwareVersion, uint32 little-endian
Обе команды передаются с одним SessionID. До стирания Flash загрузчик обязан
получить обе части метаданных и проверить ProductType, аппаратную ревизию,
размер образа, границы выбранного слота и допустимость версии.
Ответ состояния
BOOT_STATUS возвращает результат команды и точку продолжения:
MsgBody[15..8] = SessionID
MsgBody[7..0] = команда, на которую дан ответ
DATA[0] Status
DATA[1] TargetSlot: 0 = A, 1 = B
DATA[2..3] NextBlock, uint16 little-endian
DATA[4..7] RunningCRC32, uint32 little-endian
Минимальный набор статусов:
| Код | Статус |
|---|---|
0x00 |
OK |
0x01 |
BUSY |
0x02 |
INVALID_COMMAND |
0x03 |
WRONG_DEVICE |
0x04 |
WRONG_HARDWARE |
0x05 |
INVALID_SIZE |
0x06 |
CRC_ERROR |
0x07 |
FLASH_ERROR |
0x08 |
SEQUENCE_ERROR |
0x09 |
SIGNATURE_ERROR |
0x0A |
SESSION_ERROR |
0x0B |
VOLTAGE_ERROR |
NextBlock позволяет возобновить загрузку после разрыва связи. Для первой
реализации допустимо подтверждать каждый блок. Для рабочей скорости лучше
передавать окна по 16 кадров и подтверждать окно одним BOOT_STATUS; при
необходимости протокол статуса можно расширить битовой картой потерянных
блоков.
Выбор слота и безопасное обновление
GUI не должен самостоятельно перезаписывать активный слот. После получения
BEGIN_IMAGE и BEGIN_COMPAT загрузчик выбирает неактивный слот и сообщает его в
BOOT_STATUS:
активен A -> принимать BOOT_DATA_B
активен B -> принимать BOOT_DATA_A
Рекомендуемый цикл обновления:
- Обнаружить прибор и сверить
DeviceType/DeviceID,ProductType, UID и версии. - Выполнить адресную команду
ENTER_BOOTи получить новыйSessionID. - Передать
BEGIN_IMAGEиBEGIN_COMPAT; загрузчик выберет неактивный слот. - Стереть выбранный слот и передать блоки
BOOT_DATA_AилиBOOT_DATA_B. - Выполнить
VERIFY: проверить размер, CRC32 и цифровую подпись образа. - Выполнить
COMMITи перезагрузить устройство. - Новое приложение вызывает
CONFIRMпосле успешной самопроверки. - При отсутствии подтверждения загрузчик возвращается к предыдущему слоту.
CRC32 обнаруживает случайное повреждение, но не защищает от подмены. Для
серийных изделий образ следует подписывать, а открытый ключ проверки хранить
в неизменяемой части загрузчика. Сам загрузчик не должен обновляться обычными
командами BOOT_DATA_A/B.
Важные ограничения текущей реализации
- Модуль рассчитан на classic CAN с payload до 8 байт, не на CAN FD.
PROTOCAN_INIT()не запускает CAN/таймер и не включает CAN notification.- Все три аппаратных фильтра и номера банков заданы внутри библиотеки.
- Фильтр адресованных сообщений рассчитан на устройство: он принимает Route=0 от ПМ. Для использования библиотеки на стороне ПМ потребуется отдельная конфигурация фильтров Route=1.
- Проверку ROM, физического датчика и EEPROM выполняет приложение в переопределённом
ProtoCanMsgToSettings(). - Таблица присутствующих устройств обновляется при пульсе, но автоматического offline-таймаута нет.
- Буфер переполнения не ведёт счётчик потерь и не сообщает ошибку приложению.
- У порта нет полного примера прошивки STM32; его сборку и проверку на плате выполняет проект устройства. Host-тесты общего ядра находятся в
../../tests. - Несколько внутренних функций отправки реализованы только в
protocan.cи не входят в публичный заголовок; для прикладного кода следует использоватьPROTOCAN_SEND().
Краткий порядок запуска
- Подключить
templatesк проекту устройства. - Добавить
protocan.c/.hи общийpcan_id.cв 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()в основном цикле.