SETCAN
руководство разработчика
Карта библиотеки ProtoCAN, формат кадров и практический маршрут переноса в существующую прошивку STM32.
Что находится в проекте
SETCAN — не готовый CubeIDE-проект, а подключаемый C-модуль поверх STM32 HAL. Его задача — фильтровать, принимать, разбирать и отправлять прикладные сообщения ProtoCAN.
Что ожидает модуль
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 не входят в ядро.
Состав библиотеки
Два файла образуют один модуль. Заголовок — контракт интеграции; C-файл — транспортная реализация и демонстрационные слабые обработчики.
protocan.h
Конфигурация текущего устройства, размеры буфера, enum типов сообщений, битовые представления ID/Body, структуры TX/RX и публичные функции.
Меняют при переносе: CURRENT_TYPE_DEVICE, CURRENT_ID_DEVICE, иногда PROTOCAN_RX_BUFFER_SIZE.
protocan.c
Инициализация, фильтры, кольцевой буфер, диспетчеризация, RTC sync, pulse и упаковка кадров.
Обычно не правят: прикладную логику лучше добавлять переопределением __weak-функций.
Внешний слой
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 битРеестр MsgType
| Код | Тип | Назначение | Статус |
|---|---|---|---|
| 0x0 | BROADCAST | Общие команды всем устройствам | stable |
| 0x1 | DISCRETE | Состояния, команды, флаги | stable |
| 0x2 | ANALOG | Датчики U / I / T и универсальные данные | stable |
| 0x3 | GAS | Общее 16-битное адресное пространство | stable |
| 0x4…0x7 | MODBUS | Coil, Discrete, Holding, Input | stable |
| 0x8 | ERROR | Код ошибки и дополнительная информация | stable |
| 0x9…0xD | BOOT | Управление, блоки A/B, статус, discovery | draft |
| 0xE | SETTINGS | Привязка 1-Wire ROM к локации Z/Y | stable |
| 0xF | PULSE | Присутствие устройства | stable |
Как портировать в свой STM32-проект
Рабочая последовательность для CubeMX/CubeIDE. Имена hcan, hrtc и htim2 замените на handles своего проекта.
Добавьте исходники
Inc/protocan.hв include pathSrc/protocan.cв сборку- Проверьте доступность
main.hиcan.h
Настройте адрес
В protocan.h задайте тип 0…7 и ID 0…15. Пара должна быть уникальной на шине.
#define CURRENT_TYPE_DEVICE 0b011
#define CURRENT_ID_DEVICE 0b0101Настройте CubeMX
- CAN: Extended ID, FIFO0
- RTC: если нужна синхронизация времени
- TIM: период pulse
- Проверьте filter banks 0, 1, 2 и границу 14
Инициализация и основной цикл
#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();
}
}Свяжите 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’ов.
Перенесите прикладную логику
Создайте, например, 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);
}
}