Merge K:/git_project/newProject/templates into codex/shared-plot-interaction
This commit is contained in:
@@ -34,6 +34,7 @@ templates/
|
||||
| [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 |
|
||||
| [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** |
|
||||
| [`c/set-protocol`](c/set-protocol) | единое ядро SETProtocol: SET v2, совместимые ProtoCAN/GUI v1, GAS, телеметрия, firmware flow и стабильный host ABI | C99 | COM/SLCAN/SocketCAN/USB/Ethernet или callbacks — **Windows, Android и STM32F4-порты в комплекте** |
|
||||
| [`c/set-protocol/ports/stm32-bxcan`](c/set-protocol/ports/stm32-bxcan) | порт прикладного ProtoCAN для STM32, бывший SETCAN; сохранён API `PROTOCAN_*` | STM32 HAL CAN/RTC/TIM + общее ядро `pcan_id` | classic bxCAN; настройки платы предоставляет прошивка |
|
||||
| [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot |
|
||||
| [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** |
|
||||
| [`c/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** |
|
||||
@@ -98,7 +99,6 @@ git subtree pull --prefix lib/templates https://git.rd12.ru/Andrey/templates.git
|
||||
|
||||
| Код | Почему не переносится | Что можно вытащить |
|
||||
|---|---|---|
|
||||
| `SETCAN/Src/protocan.c` | 59 вызовов `HAL_*`, привязка к `CAN_HandleTypeDef`, `RTC_HandleTypeDef`, `NVIC_SystemReset()` | разбиение длинных посылок на кадры и проверка даты — чистая логика, нужны два интерфейса: «отправить кадр» и «часы». Раскладка идентификатора уже вынесена в `pcan_id` |
|
||||
| `1921vk028/drivers/user/src/one_wire_user.c` | аппаратный 1-Wire `OWI_*` из `plib028`, мигание светодиодом прямо внутри опроса датчика | логика DS18B20 уже есть в `c/ds18b20` — переносится порт, а не алгоритм |
|
||||
| `1921vk028/drivers/GPIO`, `uart`, `optical`, `Interrupt` | целиком на регистрах `plib028` | переносить стоит не код, а приём: таблица описаний портов вместо `#define`, разбросанных по файлу |
|
||||
| `Drivers/` внутри прошивок | вендорный HAL и CMSIS | ничего, приходит со своим SDK |
|
||||
|
||||
22
c/can-sensor/CMakeLists.txt
Normal file
22
c/can-sensor/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(can_sensor C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(can_sensor STATIC can_sensor.c)
|
||||
target_include_directories(can_sensor PUBLIC .)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(can_sensor PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(can_sensor PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
option(CAN_SENSOR_BUILD_TESTS "Собирать тесты транспорта CAN sensor" ON)
|
||||
if(CAN_SENSOR_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_executable(test_can_sensor tests/test_can_sensor.c)
|
||||
target_link_libraries(test_can_sensor PRIVATE can_sensor)
|
||||
add_test(NAME can_sensor COMMAND test_can_sensor)
|
||||
endif()
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
| Файл | Что делает | Зависимости |
|
||||
|---|---|---|
|
||||
| `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, повторы передачи, счётчики обмена | `stdint.h` |
|
||||
| `can_sensor.h`, `can_sensor.c` | сборка и разбор SETTINGS, планировщик PULSE, повторы передачи, счётчики обмена | `stdint.h` |
|
||||
| `ports/stm32f1/` | опросный порт CAN1: GPIO, BTR, фильтр, mailbox/FIFO, тайм-аут ACK | CMSIS `stm32f10x.h` |
|
||||
|
||||
`ZZ` — номер сборки, `YY` — позиция. Нулевой ROM с DLC=8 очищает локацию.
|
||||
@@ -40,14 +40,20 @@ CanSensor link;
|
||||
CanSensor_Io io = { .send = bxcan_send, .receive = bxcan_receive, .context = &board };
|
||||
CanSensor_Config config;
|
||||
CanSensor_ConfigDefault(&config); /* TX 0x1FFE0000, RX 0x17FE0000 */
|
||||
config.pulse_period_ms = 1000U; /* 0 — PULSE выключен */
|
||||
|
||||
CanSensor_Init(&link, &io, &config);
|
||||
CanSensor_SendId(&link, position, assembly_serial, rom);
|
||||
CanSensor_Task(&link, now_ms);
|
||||
|
||||
CanSensor_Message message;
|
||||
if (CanSensor_Poll(&link, &message)) { /* принят идентификатор */ }
|
||||
```
|
||||
|
||||
Период можно менять во время работы через `CanSensor_SetPulsePeriod()`. Значение
|
||||
`0` именно выключает heartbeat: кадры PULSE больше не ставятся в очередь. При
|
||||
повторном включении первый кадр отправляется через полный заданный период.
|
||||
|
||||
## Порт STM32F1
|
||||
|
||||
Порт не использует STM32 HAL и не занимает прерывания. Скопируйте
|
||||
|
||||
@@ -40,6 +40,7 @@ void CanSensor_ConfigDefault(CanSensor_Config *config)
|
||||
}
|
||||
config->tx_id = CAN_SENSOR_DEFAULT_TX_ID;
|
||||
config->rx_id = CAN_SENSOR_DEFAULT_RX_ID;
|
||||
config->pulse_period_ms = 0U;
|
||||
config->extended = 1U;
|
||||
config->retries = CAN_SENSOR_DEFAULT_RETRIES;
|
||||
}
|
||||
@@ -72,6 +73,9 @@ uint8_t CanSensor_Init(CanSensor *link, const CanSensor_Io *io,
|
||||
link->send_errors = 0U;
|
||||
link->received_messages = 0U;
|
||||
link->dropped_frames = 0U;
|
||||
link->pulse_last_ms = 0U;
|
||||
link->pulse_counter = 0U;
|
||||
link->pulse_armed = 0U;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
@@ -201,3 +205,51 @@ uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out)
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
|
||||
void CanSensor_SetPulsePeriod(CanSensor *link, uint32_t period_ms)
|
||||
{
|
||||
if (link == 0) {
|
||||
return;
|
||||
}
|
||||
if (link->config.pulse_period_ms != period_ms) {
|
||||
link->config.pulse_period_ms = period_ms;
|
||||
/* После включения или смены периода отсчитываем полный новый период. */
|
||||
link->pulse_armed = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CanSensor_PulsePeriod(const CanSensor *link)
|
||||
{
|
||||
return (link != 0) ? link->config.pulse_period_ms : 0U;
|
||||
}
|
||||
|
||||
void CanSensor_Task(CanSensor *link, uint32_t now_ms)
|
||||
{
|
||||
CanSensor_Frame frame;
|
||||
const uint32_t period_ms = (link != 0) ? link->config.pulse_period_ms : 0U;
|
||||
|
||||
if ((link == 0) || (link->io.send == 0) || (period_ms == 0U)) {
|
||||
return;
|
||||
}
|
||||
if (link->pulse_armed == 0U) {
|
||||
link->pulse_last_ms = now_ms;
|
||||
link->pulse_armed = 1U;
|
||||
return;
|
||||
}
|
||||
if ((now_ms - link->pulse_last_ms) < period_ms) {
|
||||
return;
|
||||
}
|
||||
|
||||
frame.id = (link->config.tx_id & CAN_SENSOR_ADDRESS_MASK)
|
||||
| ((uint32_t)CAN_SENSOR_MSGTYPE_PULSE << 16U);
|
||||
frame.extended = 1U;
|
||||
frame.length = 1U;
|
||||
link->pulse_counter++;
|
||||
frame.data[0] = link->pulse_counter;
|
||||
if (can_sensor_send_frame(link, &frame) != 0U) {
|
||||
link->pulse_last_ms = now_ms;
|
||||
} else {
|
||||
/* Счётчик меняется только для реально поставленных в очередь кадров. */
|
||||
link->pulse_counter--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
|
||||
/** Назначенный проекту тип сообщения SETCAN SETTINGS. */
|
||||
#define CAN_SENSOR_MSGTYPE_SETTINGS 0xEU
|
||||
#define CAN_SENSOR_MSGTYPE_PULSE 0xFU
|
||||
|
||||
/** Маска полей SETCAN от Priority до MsgType; Body под маской не находится. */
|
||||
#define CAN_SENSOR_HEADER_MASK 0x1FFF0000UL
|
||||
#define CAN_SENSOR_ADDRESS_MASK 0x1FF00000UL
|
||||
#define CAN_SENSOR_BODY_MASK 0x0000FFFFUL
|
||||
|
||||
/** Базовые ID SETTINGS для выбранных DeviceType=0x7 и DeviceID=0xF. */
|
||||
@@ -88,6 +90,7 @@ typedef struct {
|
||||
uint32_t rx_id; /**< База запроса 0x17FE0000, Body должен быть нулевым. */
|
||||
uint8_t extended; /**< 1 — Extended CAN ID. */
|
||||
uint8_t retries; /**< Число попыток передачи. */
|
||||
uint32_t pulse_period_ms; /**< Период PULSE; 0 полностью отключает PULSE. */
|
||||
} CanSensor_Config;
|
||||
|
||||
/** Состояние транспорта и диагностические счётчики. */
|
||||
@@ -98,6 +101,9 @@ typedef struct {
|
||||
uint32_t send_errors;
|
||||
uint32_t received_messages;
|
||||
uint32_t dropped_frames;
|
||||
uint32_t pulse_last_ms;
|
||||
uint8_t pulse_counter;
|
||||
uint8_t pulse_armed;
|
||||
} CanSensor;
|
||||
|
||||
void CanSensor_ConfigDefault(CanSensor_Config *config);
|
||||
@@ -124,4 +130,17 @@ uint8_t CanSensor_HandleFrame(CanSensor *link, const CanSensor_Frame *frame,
|
||||
|
||||
uint8_t CanSensor_Poll(CanSensor *link, CanSensor_Message *out);
|
||||
|
||||
/**
|
||||
* @brief Меняет период PULSE во время работы.
|
||||
*
|
||||
* @param period_ms Период в миллисекундах; 0 выключает PULSE.
|
||||
*/
|
||||
void CanSensor_SetPulsePeriod(CanSensor *link, uint32_t period_ms);
|
||||
|
||||
/** Возвращает действующий период PULSE; 0 означает «выключено». */
|
||||
uint32_t CanSensor_PulsePeriod(const CanSensor *link);
|
||||
|
||||
/** Вызывает планировщик PULSE из главного цикла. */
|
||||
void CanSensor_Task(CanSensor *link, uint32_t now_ms);
|
||||
|
||||
#endif /* CAN_SENSOR_H */
|
||||
|
||||
79
c/can-sensor/tests/test_can_sensor.c
Normal file
79
c/can-sensor/tests/test_can_sensor.c
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "can_sensor.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct {
|
||||
CanSensor_Frame last_frame;
|
||||
uint32_t send_count;
|
||||
uint8_t accept;
|
||||
} FakeCan;
|
||||
|
||||
static uint8_t fake_send(void *context, const CanSensor_Frame *frame)
|
||||
{
|
||||
FakeCan *fake = (FakeCan *)context;
|
||||
|
||||
fake->send_count++;
|
||||
fake->last_frame = *frame;
|
||||
return fake->accept;
|
||||
}
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \
|
||||
#condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CanSensor link;
|
||||
CanSensor_Config config;
|
||||
CanSensor_Io io;
|
||||
FakeCan fake = {0};
|
||||
|
||||
fake.accept = 1U;
|
||||
io.send = fake_send;
|
||||
io.receive = 0;
|
||||
io.context = &fake;
|
||||
CanSensor_ConfigDefault(&config);
|
||||
|
||||
CHECK(config.pulse_period_ms == 0U);
|
||||
CHECK(CanSensor_Init(&link, &io, &config) != 0U);
|
||||
|
||||
/* Нулевой период означает выключенный PULSE при любых метках времени. */
|
||||
CanSensor_Task(&link, 0U);
|
||||
CanSensor_Task(&link, 0xFFFFFFFFUL);
|
||||
CHECK(fake.send_count == 0U);
|
||||
|
||||
CanSensor_SetPulsePeriod(&link, 1000U);
|
||||
CHECK(CanSensor_PulsePeriod(&link) == 1000U);
|
||||
CanSensor_Task(&link, 500U);
|
||||
CanSensor_Task(&link, 1499U);
|
||||
CHECK(fake.send_count == 0U);
|
||||
CanSensor_Task(&link, 1500U);
|
||||
CHECK(fake.send_count == 1U);
|
||||
CHECK(fake.last_frame.id == 0x1FFF0000UL);
|
||||
CHECK(fake.last_frame.extended == 1U);
|
||||
CHECK(fake.last_frame.length == 1U);
|
||||
CHECK(fake.last_frame.data[0] == 1U);
|
||||
|
||||
/* После period=0 дальнейшие вызовы не отправляют heartbeat. */
|
||||
CanSensor_SetPulsePeriod(&link, 0U);
|
||||
CHECK(CanSensor_PulsePeriod(&link) == 0U);
|
||||
CanSensor_Task(&link, 0xFFFFFFFFUL);
|
||||
CHECK(fake.send_count == 1U);
|
||||
|
||||
/* Повторное включение начинает новый полный период. */
|
||||
CanSensor_SetPulsePeriod(&link, 10U);
|
||||
CanSensor_Task(&link, 2000U);
|
||||
CanSensor_Task(&link, 2009U);
|
||||
CHECK(fake.send_count == 1U);
|
||||
CanSensor_Task(&link, 2010U);
|
||||
CHECK(fake.send_count == 2U);
|
||||
CHECK(fake.last_frame.data[0] == 2U);
|
||||
|
||||
puts("can_sensor pulse tests passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -36,6 +36,13 @@ multicast запрещены WRITE, FW_BEGIN и FW_ACTIVATE.
|
||||
|
||||
## CAN
|
||||
|
||||
Для существующего прикладного ProtoCAN доступен
|
||||
[порт STM32 bxCAN](ports/stm32-bxcan/README.md), перенесённый из SETCAN.
|
||||
Он сохраняет API `PROTOCAN_*`, использует HAL CAN/RTC/TIM и общий `pcan_id`.
|
||||
Порт рассчитан на classic bxCAN; он не реализует сегментацию SETProtocol v2.
|
||||
В проект устройства добавляются `ports/stm32-bxcan/protocan.c` и `src/pcan_id.c`,
|
||||
а в include paths — `ports/stm32-bxcan` и `include`.
|
||||
|
||||
Не вызывайте прикладной router из ISR. ISR принимает CAN-кадры в очередь,
|
||||
сегментация собирается в главном цикле, и только полный SETP-кадр передаётся
|
||||
parser/router. Сборка должна иметь timeout, контроль номера сегмента и одного
|
||||
|
||||
@@ -32,6 +32,7 @@ TCP, UART/DMA и аппаратный CAN подключаются портам
|
||||
| `src/` | общая реализация всех протокольных модулей |
|
||||
| `ports/android` | JNI и Kotlin facade |
|
||||
| `ports/stm32f4` | UART/DMA port для legacy byte stream |
|
||||
| [`ports/stm32-bxcan`](ports/stm32-bxcan/README.md) | STM32 HAL bxCAN/RTC/TIM, прикладной ProtoCAN; бывший отдельный SETCAN |
|
||||
| `docs/SETPROTOCOL.md` | архитектура, ABI, память и переносимость |
|
||||
| `docs/legacy` | нормативные документы ProtoCAN/SETGUI v1 |
|
||||
| `PROTOCOL.md` | нормативный wire contract SET protocol v2 |
|
||||
|
||||
@@ -48,7 +48,7 @@ uint16_t n = pcan_gas_read_block(&map, 0x0000, block, 4);
|
||||
Тип сообщения `PCAN_MSG_GAS` (`0b0011`). Адрес первого регистра лежит
|
||||
в `MsgBody`, данные — до 4 регистров подряд, младшим байтом вперёд.
|
||||
Это ровно то, что делает `PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()`
|
||||
в `SETCAN/Src/protocan.c`, поэтому обмен совместим с существующими
|
||||
в `ports/stm32-bxcan/protocan.c` (бывший SETCAN), поэтому обмен совместим с существующими
|
||||
устройствами.
|
||||
|
||||
| Кадр | DLC | Смысл |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Транспорт ProtoCAN (MsgType = 0b0011) кладёт в MsgBody адрес первого
|
||||
* регистра, а в данные - до 4 регистров подряд, младшим байтом вперёд.
|
||||
* Это ровно то, что делает PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()
|
||||
* в SETCAN/Src/protocan.c, поэтому обмен совместим с существующими
|
||||
* в ports/stm32-bxcan/protocan.c, поэтому обмен совместим с существующими
|
||||
* устройствами.
|
||||
*
|
||||
* Запрос на чтение кодируется кадром GAS с DLC = 0: в исходном коде
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @file pcan_id.h
|
||||
* @brief Упаковка и разбор 29-битного идентификатора ProtoCAN.
|
||||
*
|
||||
* Раскладка (соответствует ProtoCanId_t из SETCAN/Inc/protocan.h):
|
||||
* Раскладка (ProtoCanId_t из ports/stm32-bxcan/protocan.h, бывший SETCAN):
|
||||
*
|
||||
* биты 28 Priority
|
||||
* 27 Route
|
||||
|
||||
602
c/set-protocol/ports/stm32-bxcan/README.md
Normal file
602
c/set-protocol/ports/stm32-bxcan/README.md
Normal file
File diff suppressed because one or more lines are too long
1667
c/set-protocol/ports/stm32-bxcan/protocan.c
Normal file
1667
c/set-protocol/ports/stm32-bxcan/protocan.c
Normal file
@@ -0,0 +1,1667 @@
|
||||
#include "protocan.h"
|
||||
|
||||
ProtoCanDevice_t CurrentDevice;
|
||||
ProtoCanDevice_t Device_on_the_Network[8][16];
|
||||
struct controlflags ControlFlags;
|
||||
|
||||
CAN_HandleTypeDef *_HCAN = 0;
|
||||
RTC_HandleTypeDef *_HRTC = 0;
|
||||
TIM_HandleTypeDef *_HTIM = 0;
|
||||
|
||||
/* CurrentStep - индекс следующего сообщения на обработку (пишет основной код).
|
||||
LastStep - индекс последнего записанного сообщения (пишет ISR).
|
||||
Оба индекса volatile: без этого компилятор вправе закэшировать значение в
|
||||
регистре, т.к. в его модели исполнения прерывание в код не входит.
|
||||
Сам массив rxMsg volatile НЕ объявлен - это ухудшило бы кодогенерацию и
|
||||
мешало бы копированию структуры по значению. Порядок "заполнить слот, затем
|
||||
опубликовать индекс" обеспечивается барьерами __DMB() в точках публикации и
|
||||
чтения. */
|
||||
volatile uint16_t CurrentStep = 1;
|
||||
volatile uint16_t LastStep = 0;
|
||||
struct RXMsg rxMsg[PROTOCAN_RX_BUFFER_SIZE];
|
||||
|
||||
/**
|
||||
* @brief Проверяет, является ли год високосным.
|
||||
* В системе год хранится как смещение от 2000. Например, значение 24 соответствует 2024.
|
||||
* Эта функция добавляет 2000 к значению и проверяет, високосный ли получившийся год.
|
||||
* @param year Смещение от 2000, тип uint8_t (от 0 до 255).
|
||||
* @return _Bool true, если год високосный; false — иначе.
|
||||
*/
|
||||
_Bool IsLeapYear(uint8_t year)
|
||||
{
|
||||
year += 2000;
|
||||
return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Возвращает количество доступных сообщений в буфере приёма CAN.
|
||||
* Используется кольцевой буфер.
|
||||
* Возвращаемое значение — количество элементов между указателями LastStep и CurrentStep.
|
||||
* @return uint16_t Количество доступных сообщений, ожидающих обработки, в буфере.
|
||||
*/
|
||||
uint16_t AvailableCanRxMsg(void)
|
||||
{
|
||||
/* Однократное чтение индекса, обновляемого из ISR. */
|
||||
uint16_t last = LastStep;
|
||||
uint16_t current = CurrentStep;
|
||||
/* Индекс прочитан до обращения к данным слота. */
|
||||
__DMB();
|
||||
return (uint16_t)(((unsigned)PROTOCAN_RX_BUFFER_SIZE + last - current + 1u) % PROTOCAN_RX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void PROTOCAN_DEINIT(uint8_t stage)
|
||||
{
|
||||
switch(stage)
|
||||
{
|
||||
case 3:
|
||||
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
|
||||
HAL_TIM_UnRegisterCallback(_HTIM, HAL_TIM_PERIOD_ELAPSED_CB_ID);
|
||||
#endif
|
||||
case 2:
|
||||
case 1:
|
||||
#if (USE_HAL_CAN_REGISTER_CALLBACKS == 1)
|
||||
HAL_CAN_UnRegisterCallback(_HCAN, HAL_CAN_RX_FIFO0_MSG_PENDING_CB_ID);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Инициализация переферии
|
||||
* @details Инициализация указателей на HCAN, HRTC, установка фильтров CAN.
|
||||
* @note Фильтры CAN описаны в разделе PROTOCAN_CAN_FILTERS().
|
||||
*/
|
||||
PROTOCAN_INIT_StatusTypeDef PROTOCAN_INIT(CAN_HandleTypeDef *tmp_hcan, RTC_HandleTypeDef *tmp_hrtc, TIM_HandleTypeDef *tmp_tim)
|
||||
{
|
||||
unsigned initStage = 0;
|
||||
if(tmp_hcan)
|
||||
{
|
||||
_HCAN = tmp_hcan;
|
||||
#if (USE_HAL_CAN_REGISTER_CALLBACKS == 1)
|
||||
HAL_StatusTypeDef CAN_RC_RESULT = HAL_CAN_RegisterCallback(_HCAN, HAL_CAN_RX_FIFO0_MSG_PENDING_CB_ID, ProtoCanRxFifo0MsgPendingCallback);
|
||||
if(CAN_RC_RESULT != HAL_OK)
|
||||
{
|
||||
PROTOCAN_DEINIT(initStage);
|
||||
return PROTOCAN_INIT_HCAN_ERROR;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
PROTOCAN_DEINIT(initStage);
|
||||
return PROTOCAN_INIT_HCAN_ERROR;
|
||||
}
|
||||
initStage++;
|
||||
if(tmp_hrtc)
|
||||
{
|
||||
_HRTC = tmp_hrtc;
|
||||
}
|
||||
else
|
||||
{
|
||||
PROTOCAN_DEINIT(initStage);
|
||||
return PROTOCAN_INIT_HRTC_ERROR;
|
||||
}
|
||||
initStage++;
|
||||
if(tmp_tim)
|
||||
{
|
||||
_HTIM = tmp_tim;
|
||||
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
|
||||
HAL_StatusTypeDef TIM_RC_RESULT = HAL_TIM_RegisterCallback(_HTIM, HAL_TIM_PERIOD_ELAPSED_CB_ID, ProtoCanPulseCallback);
|
||||
if(TIM_RC_RESULT != HAL_OK)
|
||||
{
|
||||
PROTOCAN_DEINIT(initStage);
|
||||
return PROTOCAN_INIT_TIM_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
PROTOCAN_DEINIT(initStage);
|
||||
return PROTOCAN_INIT_TIM_ERROR;
|
||||
}
|
||||
initStage++;
|
||||
PROTOCAN_FILTERS();
|
||||
ControlFlags.IsPulse = 1;
|
||||
return PROTOCAN_INIT_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Бесконечный цикл обработки входящих CAN-сообщений.
|
||||
*
|
||||
* Эта функция постоянно проверяет наличие доступных сообщений в буфере CAN и
|
||||
* вызывает соответствующие обработчики в зависимости от типа полученного сообщения.
|
||||
* Обрабатываемые типы сообщений:
|
||||
* - Аналоговые данные
|
||||
* - Широковещательные сообщения
|
||||
* - Дискретные данные
|
||||
* - Общее адресное пространство
|
||||
* - Модбас сообщения
|
||||
* - Ошибки
|
||||
*
|
||||
* Обработка сообщений осуществляется внутри бесконечного цикла с использованием
|
||||
* функции AvailableCanRxMsg() для определения наличия новых сообщений.
|
||||
*
|
||||
* В конце каждого обработанного сообщения индекс текущего элемента буфера инкрементируется
|
||||
* с учетом размера буфера через операцию по модулю.
|
||||
*/
|
||||
void PROTOCAN_LoopProcessRxMsgs(void)
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
if(AvailableCanRxMsg())
|
||||
{
|
||||
if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ANALOG)
|
||||
{
|
||||
PROTOCAN_AnalogProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_BROADCAST)
|
||||
{
|
||||
PROTOCAN_BroadcastProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_DISCRETE)
|
||||
{
|
||||
PROTOCAN_DiscreticProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE)
|
||||
{
|
||||
ProtoCanMsgToGeneralAddressSpace(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_SETTINGS)
|
||||
{
|
||||
PROTOCAN_SettingsProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_COIL ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_DISCRETE ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_HOLDING ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_INPUT)
|
||||
{
|
||||
PROTOCAN_ModbusProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ERROR)
|
||||
{
|
||||
PROTOCAN_RequestError(rxMsg[CurrentStep]);
|
||||
}
|
||||
// Обновление индекса текущего сообщения с циклическим масштабированием
|
||||
CurrentStep = (uint16_t)(CurrentStep + 1) % PROTOCAN_RX_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обрабатывает все доступные входящие CAN-сообщения.
|
||||
*
|
||||
* Эта функция последовательно обрабатывает все сообщения, доступные в буфере,
|
||||
* вызывая соответствующие функции обработчики в зависимости от типа сообщения.
|
||||
* После обработки каждого сообщения индекс текущего элемента буфера обновляется
|
||||
* циклически.
|
||||
*
|
||||
* @return PROTOCAN_OK, если все сообщения были обработаны успешно.
|
||||
* В противном случае возвращается код ошибки, полученный при обработке сообщения.
|
||||
*
|
||||
* Обработка происходит до тех пор, пока есть доступные сообщения, полученные функцией
|
||||
* AvailableCanRxMsg().
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ProcessAllRxMsgs(void)
|
||||
{
|
||||
PROTOCAN_StatusTypeDef result = PROTOCAN_TIMEOUT;
|
||||
while(AvailableCanRxMsg())
|
||||
{
|
||||
if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ANALOG)
|
||||
{
|
||||
result = PROTOCAN_AnalogProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_BROADCAST)
|
||||
{
|
||||
result = PROTOCAN_BroadcastProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_DISCRETE)
|
||||
{
|
||||
result = PROTOCAN_DiscreticProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE)
|
||||
{
|
||||
result = ProtoCanMsgToGeneralAddressSpace(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_SETTINGS)
|
||||
{
|
||||
result = PROTOCAN_SettingsProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_COIL ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_DISCRETE ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_HOLDING ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_INPUT)
|
||||
{
|
||||
result = PROTOCAN_ModbusProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ERROR)
|
||||
{
|
||||
result = PROTOCAN_RequestError(rxMsg[CurrentStep]);
|
||||
}
|
||||
CurrentStep = (uint16_t)(CurrentStep + 1) % PROTOCAN_RX_BUFFER_SIZE;
|
||||
if(result != PROTOCAN_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обрабатывает одно доступное CAN-сообщение.
|
||||
*
|
||||
* Эта функция проверяет наличие доступных сообщений в буфере и, если таковое есть,
|
||||
* выполняет обработку текущего сообщения в зависимости от его типа. После обработки
|
||||
* индекс текущего сообщения обновляется циклически.
|
||||
*
|
||||
* @return Прошедший статус обработки:
|
||||
* - PROTOCAN_OK, если сообщение успешно обработано,
|
||||
* - PROTOCAN_TIMEOUT, если сообщений не было,
|
||||
* - или код ошибки, возвращенный функцией обработки.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ProcessSingleRxMsg(void)
|
||||
{
|
||||
PROTOCAN_StatusTypeDef result = PROTOCAN_TIMEOUT;
|
||||
if(AvailableCanRxMsg())
|
||||
{
|
||||
if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ANALOG)
|
||||
{
|
||||
result = PROTOCAN_AnalogProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_BROADCAST)
|
||||
{
|
||||
result = PROTOCAN_BroadcastProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_DISCRETE)
|
||||
{
|
||||
result = PROTOCAN_DiscreticProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE)
|
||||
{
|
||||
result = ProtoCanMsgToGeneralAddressSpace(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_SETTINGS)
|
||||
{
|
||||
result = PROTOCAN_SettingsProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_COIL ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_DISCRETE ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_HOLDING ||
|
||||
rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_MODBUS_INPUT)
|
||||
{
|
||||
result = PROTOCAN_ModbusProcessing(rxMsg[CurrentStep]);
|
||||
}
|
||||
else if(rxMsg[CurrentStep].eID.Fields.MsgType == PROTOCAN_MSGTYPE_ERROR)
|
||||
{
|
||||
result = PROTOCAN_RequestError(rxMsg[CurrentStep]);
|
||||
}
|
||||
CurrentStep = (uint16_t)(CurrentStep + 1) % PROTOCAN_RX_BUFFER_SIZE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция обработки широковещательных запросов.
|
||||
* @param struct RXMsg _rxMsg - структура для полученного сообщения.
|
||||
* @details Функция, выполняющая команды, переданные в широковещательном формате с головного (master) устройства. Типы команд: Запрос статуса, запрос на включение или выключение, рестарт устройств, установка времени.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_BroadcastProcessing(struct RXMsg _rxMsg)
|
||||
{
|
||||
msgBodyBroadcastType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
switch(msg.Fields.Type)
|
||||
{
|
||||
case PROTOCAN_BROADCAST_STATUS:
|
||||
{
|
||||
//Обработка запроса статуса устройства
|
||||
return ProtoCanMsgToBroadcastStatus(_rxMsg);
|
||||
}
|
||||
case PROTOCAN_BROADCAST_ONOFF:
|
||||
{
|
||||
//Обработка запроса на вкл/выкл
|
||||
ProtoCanMsgToBroadcastOnOff(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_BROADCAST_RESTARTDEVICE:
|
||||
{
|
||||
ProtoCanMsgToBroadcastRestart(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_BROADCAST_RTCSETUP:
|
||||
{
|
||||
//Обработка запроса на синхронизацию времени
|
||||
//С головным устройством
|
||||
ProtoCanMsgToBroadcastRtcSetup(_rxMsg);
|
||||
break;;
|
||||
}
|
||||
default:
|
||||
//RESERVE SENSOR TYPE.
|
||||
return PROTOCAN_ERROR;
|
||||
break;
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Формирует и отправляет широковещательное CAN-сообщение для ответа на запрос статуса.
|
||||
* Устанавливает идентификатор, собирает текущие параметры времени и даты, и отправляет сообщение.
|
||||
* @param _rxMsg Структура полученного по CAN запроса.
|
||||
* @return HAL_StatusTypeDef Статус операции HAL, например HAL_OK при успехе.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastStatus(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.DLC = 7;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
tmp_eID.Fields.DeviceType = CURRENT_TYPE_DEVICE;
|
||||
tmp_eID.Fields.DeviceID = CURRENT_ID_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
RTC_TimeTypeDef sTime = {0};
|
||||
HAL_RTC_GetTime(_HRTC, &sTime, RTC_FORMAT_BIN);
|
||||
data[0] = sTime.Hours;
|
||||
data[1] = sTime.Minutes;
|
||||
data[2] = sTime.Seconds;
|
||||
RTC_DateTypeDef DateToUpdate = {0};
|
||||
HAL_RTC_GetDate(_HRTC, &DateToUpdate, RTC_FORMAT_BIN);
|
||||
data[3] = DateToUpdate.Year;
|
||||
data[4] = DateToUpdate.Month;
|
||||
data[5] = DateToUpdate.Date;
|
||||
data[6] = DateToUpdate.WeekDay;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Виртуальная слабая функция для переключения режима пульсации ON/OFF.
|
||||
* Меняет состояние флага IsPulse в структуре ControlFlags.
|
||||
* В данном случае — просто инвертирует его логическое значение.
|
||||
* @param _rxMsg Структура полученного CAN-сообщения (Пока не используется в функции).
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastOnOff(struct RXMsg _rxMsg)
|
||||
{
|
||||
ControlFlags.IsPulse = !ControlFlags.IsPulse;
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak функция для обработки широковещательного сообщения перезапуска устройства.
|
||||
* Проверяет длину сообщения, извлекает ID из данных и, если она совпадает с текущим устройством,
|
||||
* вызывает системный перезапуск.
|
||||
* @param _rxMsg Структура полученного CAN-сообщения с командой перезапуска.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastRestart(struct RXMsg _rxMsg)
|
||||
{
|
||||
if(_rxMsg.DLC == 0)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
msgBodyBroadcastType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
if(msg.Fields.Body == (CURRENT_ID_DEVICE / (_rxMsg.DLC*8)))
|
||||
{
|
||||
uint64_t page = 0;
|
||||
for(uint32_t i = 0; i < _rxMsg.DLC; i++)
|
||||
{
|
||||
/* Приведение обязательно: Data[i] продвигается до int, и сдвиг на
|
||||
32..56 бит без него - неопределённое поведение. */
|
||||
page |= (uint64_t)_rxMsg.Data[i] << (i * 8u);
|
||||
}
|
||||
if((page >> CURRENT_ID_DEVICE) & 1u)
|
||||
{
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak функция для обработки запроса на синхронизацию RTC через широковещательное сообщение.
|
||||
* Проверяет длину сообщения и корректность данных (время и дата). Если все проверки пройдены,
|
||||
* вызывает функцию синхронизации RTC.
|
||||
* @param _rxMsg Структура полученного CAN-сообщения с данными для установки времени и даты.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastRtcSetup(struct RXMsg _rxMsg)
|
||||
{
|
||||
/* Нужны все 7 полей: чч, мм, сс, год, месяц, число, день недели.
|
||||
Прежняя проверка (DLC > 7) пропускала в том числе пустой кадр, и время
|
||||
устанавливалось из неинициализированных байт. */
|
||||
if(_rxMsg.DLC != 7)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const uint8_t DaysCount_Normal[2][12] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
|
||||
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
|
||||
/* Месяц нумеруется с 1, индекс массива - с 0. */
|
||||
if( _rxMsg.Data[0] > 23 ||
|
||||
_rxMsg.Data[1] > 59 ||
|
||||
_rxMsg.Data[2] > 59 ||
|
||||
_rxMsg.Data[3] > 99 ||
|
||||
_rxMsg.Data[4] == 0 || _rxMsg.Data[4] > 12 ||
|
||||
_rxMsg.Data[5] == 0 ||
|
||||
_rxMsg.Data[5] > DaysCount_Normal[IsLeapYear(_rxMsg.Data[3])][_rxMsg.Data[4] - 1] ||
|
||||
_rxMsg.Data[6] > 6)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
PROTOCAN_RTC_SYNC(_rxMsg.Data);
|
||||
}
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция обработки дискретных запросов.
|
||||
* @param struct RXMsg _rxMsg - структура для полученного сообщения.
|
||||
* @details Функция, формирующая и отправляющая ответ на запросы. Типы запросов: Аварии, Предупреждения, Управляющие сигналы, Флаги, Рестарт устройства, Изменение режима работы устройства, Запрос на устройство.
|
||||
* @note Запрос на устройство. Головное (master) устройство запрашивает некоторое колличество параметров. В Data - 64 битовых адресса параметров, тип которых задаётся в Sensor ID. Имеется возможность запрашивать непоследовательные параметры.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_DiscreticProcessing(struct RXMsg _rxMsg)
|
||||
{
|
||||
msgBodyDiscreteType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
switch(msg.Fields.Type)
|
||||
{
|
||||
case PROTOCAN_DISCRETE_ACCIDENT:
|
||||
{
|
||||
ProtoCanMsgToDiscreteAccident(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_WARNING:
|
||||
{
|
||||
ProtoCanMsgToDiscreteWarning(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_CONTROL_SIGNALS:
|
||||
{
|
||||
ProtoCanMsgToDiscreteControlSignals(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_FLAGS:
|
||||
{
|
||||
ProtoCanMsgToDiscreteFlags(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_RESET:
|
||||
{
|
||||
ProtoCanMsgToDiscreteReset(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_CHANGE_MODE:
|
||||
{
|
||||
ProtoCanMsgToDiscreteChangeMode(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_DISCRETE_REQUEST_LIST_OF_PARAMETERS:
|
||||
{
|
||||
ProtoCanMsgToDiscreteRequestListOfParameters(_rxMsg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//RESERVE SENSOR TYPE.
|
||||
return PROTOCAN_ERROR;
|
||||
break;
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса аварийных ситуаций.
|
||||
* В текущей реализации — заглушка, ничего не выполняет.
|
||||
* Можно реализовать отправку или обработку аварийных данных.
|
||||
* @param _rxMsg Входящее сообщение с данными запроса.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteAccident(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса предупреждений.
|
||||
* В текущей реализации — заглушка.
|
||||
* Можно реализовать обработку или ответ на предупреждения.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteWarning(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса управляющих сигналов.
|
||||
* В текущей реализации — заглушка.
|
||||
* Можно реализовать управление сигналами.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteControlSignals(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса флагов.
|
||||
* В текущей реализации — заглушка.
|
||||
* Можно реализовать работу с флагами.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteFlags(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка рестарта системы по CAN-запросу.
|
||||
* Выполняет программный рестарт системы.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteReset(struct RXMsg _rxMsg)
|
||||
{
|
||||
NVIC_SystemReset();
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса на изменение режима.
|
||||
* В текущей реализации — заглушка.
|
||||
* Можно реализовать изменение режима работы.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteChangeMode(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса на получение списка параметров.
|
||||
* В текущей реализации — заглушка.
|
||||
* Можно реализовать отправку списка параметров.
|
||||
* @param _rxMsg Входящее сообщение.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteRequestListOfParameters(struct RXMsg _rxMsg)
|
||||
{
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция обработки аналоговых запросов.
|
||||
* @param struct RXMsg _rxMsg - структура для полученного сообщения.
|
||||
* @details Функция, сортирующая запросы по типам и вызывающая соответствующие функции. Типы запросов: Универсальный, Уставки, Напряжение, Ток, Температура.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_AnalogProcessing(struct RXMsg _rxMsg)
|
||||
{
|
||||
msgBodyAnalogType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
switch (msg.Fields.Type)
|
||||
{
|
||||
case PROTOCAN_ANALOG_UNIVERSAL:
|
||||
{
|
||||
return ProtoCanMsgToAnalogUniversal(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_ANALOG_SETTINGS:
|
||||
{
|
||||
return ProtoCanMsgToAnalogSettings(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_ANALOG_U:
|
||||
{
|
||||
return ProtoCanMsgToAnalogUSens(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_ANALOG_I:
|
||||
{
|
||||
return ProtoCanMsgToAnalogISens(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_ANALOG_T:
|
||||
{
|
||||
return ProtoCanMsgToAnalogTSens(_rxMsg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//RESERVE SENSOR TYPE
|
||||
return PROTOCAN_ERROR;
|
||||
break;
|
||||
}
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция ответа на запрос данных со всех датчиков.
|
||||
* Создает сообщение с определенными данными и отправляет его через CAN-шину.
|
||||
* @param _rxMsg Структура входящего сообщения, используется для формирования ID.
|
||||
* @return HAL_StatusTypeDef Статус выполнения операции HAL, например HAL_OK.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogUniversal(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 6;
|
||||
data[0] = 'U';
|
||||
data[1] = 'N';
|
||||
data[2] = 'I';
|
||||
data[3] = 'V';
|
||||
data[4] = 'E';
|
||||
data[5] = 'R';
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция ответа на запрос уставок по CAN.
|
||||
* Создает сообщение с определенными данными и отправляет его через CAN-шину.
|
||||
* @param _rxMsg Структура входящего сообщения, используется только для формирования ID.
|
||||
* @return HAL_StatusTypeDef Статус выполнения операции HAL, например HAL_OK.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogSettings(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 7;
|
||||
data[0] = 'U';
|
||||
data[1] = 'S';
|
||||
data[2] = 'T';
|
||||
data[3] = 'A';
|
||||
data[4] = 'V';
|
||||
data[5] = 'K';
|
||||
data[6] = 'I';
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция ответа на запрос данных с датчиков напряжения.
|
||||
* Создает сообщение с указанными параметрами и отправляет его через CAN-шину.
|
||||
* @param _rxMsg Структура входящего сообщения, используемая для формирования идентификатора и данных.
|
||||
* @return HAL_StatusTypeDef Статус выполнения операции HAL, например HAL_OK при успешной отправке.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogUSens(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 6;
|
||||
msgBodyAnalogType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'U';
|
||||
data[1] = 'S';
|
||||
data[2] = '0' + msg.Fields.SensorID / 1000;
|
||||
data[3] = '0' + msg.Fields.SensorID / 100 % 10;
|
||||
data[4] = '0' + msg.Fields.SensorID / 10 % 10;
|
||||
data[5] = '0' + msg.Fields.SensorID % 10;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция ответа на запрос данных с датчиков тока.
|
||||
* Создает сообщение с определенными параметрами и отправляет его через CAN-шину.
|
||||
* @param _rxMsg Структура входящего сообщения, используется для формирования идентификатора и данных.
|
||||
* @return HAL_StatusTypeDef Статус операции HAL, например HAL_OK при успешной отправке.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogISens(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 6;
|
||||
msgBodyAnalogType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'I';
|
||||
data[1] = 'S';
|
||||
data[2] = '0' + msg.Fields.SensorID / 1000;
|
||||
data[3] = '0' + msg.Fields.SensorID / 100 % 10;
|
||||
data[4] = '0' + msg.Fields.SensorID / 10 % 10;
|
||||
data[5] = '0' + msg.Fields.SensorID % 10;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция ответа на запрос данных с датчиков температуры.
|
||||
* Создает сообщение с определенным заголовком, формирует данные по ID сенсора и отправляет его через CAN.
|
||||
* @param _rxMsg Структура полученного сообщения, которая используется для формирования идентификатора.
|
||||
* @return HAL_StatusTypeDef Статус выполнения функции HAL, например HAL_OK при успехе.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogTSens(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 6;
|
||||
msgBodyAnalogType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'T';
|
||||
data[1] = 'S';
|
||||
data[2] = '0' + msg.Fields.SensorID / 1000;
|
||||
data[3] = '0' + msg.Fields.SensorID / 100 % 10;
|
||||
data[4] = '0' + msg.Fields.SensorID / 10 % 10;
|
||||
data[5] = '0' + msg.Fields.SensorID % 10;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Отправляет сообщение по CAN-шине в соответствии с типом протокола.
|
||||
* Эта функция формирует и отправляет сообщение через CAN-шину, основываясь на типе
|
||||
* переданного ID и данных. Поддерживаются типы сообщений: широковещательные, дискретные
|
||||
* и аналоговые.
|
||||
* @param id Идентификатор сообщения, содержащий тип сообщения и другую информацию.
|
||||
* @param coreData Структура данных, включающая тип, тело и данные для передачи.
|
||||
* @return PROTOCAN_StatusTypeDef Статус операции.
|
||||
* @details
|
||||
* В зависимости от типа сообщения (панель broadcast, discrete или analog),
|
||||
* формируется соответствующий пакет данных и отправляется на шину CAN.
|
||||
* Для сообщений, превышающих длину 8 байт, передача выполняется пакетами.
|
||||
* В случае ошибок при добавлении сообщения в очередь передачи, возвращается ошибка.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_CORE(const ProtoCanId_t id, const struct ProtoCanCoreData coreData)
|
||||
{
|
||||
ProtoCanId_t localId = id;
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t canData[8];
|
||||
switch(id.Fields.MsgType)
|
||||
{
|
||||
case PROTOCAN_MSGTYPE_BROADCAST:
|
||||
{
|
||||
msgBodyBroadcastType body;
|
||||
body.Fields.Type = coreData.Type;
|
||||
body.Fields.Body = coreData.Body;
|
||||
localId.Fields.MsgBody = body.Body;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
TxHeader.DLC = 0;
|
||||
if(HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox) != HAL_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_DISCRETE:
|
||||
{
|
||||
msgBodyDiscreteType body;
|
||||
body.Fields.Type = coreData.Type;
|
||||
uint8_t regsRemaining = coreData.DataCount;
|
||||
uint8_t currentIndex = 0;
|
||||
while(regsRemaining > 0)
|
||||
{
|
||||
uint8_t regsInPacket = (regsRemaining > 8) ? 8 : regsRemaining;
|
||||
body.Fields.Body = coreData.Body + currentIndex;
|
||||
localId.Fields.MsgBody = body.Body;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
TxHeader.DLC = regsInPacket;
|
||||
for(int i = 0; i < regsInPacket; i++)
|
||||
{
|
||||
canData[i] = coreData.Data[currentIndex + i];
|
||||
}
|
||||
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox);
|
||||
if(status != HAL_OK)
|
||||
{
|
||||
return (PROTOCAN_StatusTypeDef)status;
|
||||
}
|
||||
regsRemaining -= regsInPacket;
|
||||
currentIndex += regsInPacket;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_ANALOG:
|
||||
{
|
||||
msgBodyAnalogType body;
|
||||
body.Fields.Type = coreData.Type;
|
||||
uint8_t regsRemaining = coreData.DataCount;
|
||||
uint8_t currentIndex = 0;
|
||||
while(regsRemaining > 0)
|
||||
{
|
||||
uint8_t regsInPacket = (regsRemaining > 8) ? 8 : regsRemaining;
|
||||
body.Fields.SensorID = coreData.Body + currentIndex;
|
||||
localId.Fields.MsgBody = body.Body;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
TxHeader.DLC = regsInPacket;
|
||||
for(int i = 0; i < regsInPacket; i++)
|
||||
{
|
||||
canData[i] = coreData.Data[currentIndex + i];
|
||||
}
|
||||
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox);
|
||||
if(status != HAL_OK)
|
||||
{
|
||||
return (PROTOCAN_StatusTypeDef)status;
|
||||
}
|
||||
regsRemaining -= regsInPacket;
|
||||
currentIndex += regsInPacket;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Разбирает однокадровый запрос SETTINGS для текущего устройства.
|
||||
*
|
||||
* Body[15:8] содержит номер сборки, Body[7:0] — позицию. DLC=0 означает
|
||||
* GET, DLC=8 с ненулевым ROM — WRITE/REPLACE, DLC=8 с нулями — CLEAR.
|
||||
* Проверка физического датчика и сохранение EEPROM выполняются приложением в
|
||||
* переопределённой функции ProtoCanMsgToSettings().
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SettingsProcessing(struct RXMsg _rxMsg)
|
||||
{
|
||||
ProtoCanSettingsMsg_t message;
|
||||
uint8_t index;
|
||||
uint8_t romIsZero = 1U;
|
||||
|
||||
if((_rxMsg.eID.BitAll & PROTOCAN_SETTINGS_HEADER_MASK)
|
||||
!= PROTOCAN_SETTINGS_REQUEST_BASE_ID)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
|
||||
message.AssemblySerial = (uint8_t)((_rxMsg.eID.Fields.MsgBody >> 8U) & 0xFFU);
|
||||
message.Position = (uint8_t)(_rxMsg.eID.Fields.MsgBody & 0xFFU);
|
||||
message.Result = PROTOCAN_SETTINGS_RESULT_OK;
|
||||
for(index = 0U; index < PROTOCAN_SETTINGS_ROM_SIZE; index++)
|
||||
{
|
||||
message.Rom[index] = 0U;
|
||||
}
|
||||
|
||||
if((_rxMsg.info.RTR != 0U)
|
||||
|| ((_rxMsg.DLC != 0U) && (_rxMsg.DLC != PROTOCAN_SETTINGS_ROM_SIZE)))
|
||||
{
|
||||
message.Operation = PROTOCAN_SETTINGS_INVALID;
|
||||
message.Result = PROTOCAN_SETTINGS_RESULT_INVALID_DLC;
|
||||
return PROTOCAN_SEND_SETTINGS_ERROR(message.AssemblySerial, message.Position,
|
||||
message.Result);
|
||||
}
|
||||
if(_rxMsg.DLC == 0U)
|
||||
{
|
||||
message.Operation = PROTOCAN_SETTINGS_GET;
|
||||
return ProtoCanMsgToSettings(&message);
|
||||
}
|
||||
|
||||
for(index = 0U; index < PROTOCAN_SETTINGS_ROM_SIZE; index++)
|
||||
{
|
||||
message.Rom[index] = _rxMsg.Data[index];
|
||||
if(message.Rom[index] != 0U)
|
||||
{
|
||||
romIsZero = 0U;
|
||||
}
|
||||
}
|
||||
message.Operation = (romIsZero != 0U) ? PROTOCAN_SETTINGS_CLEAR
|
||||
: PROTOCAN_SETTINGS_WRITE;
|
||||
return ProtoCanMsgToSettings(&message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Слабая точка подключения каталога датчиков приложения.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToSettings(
|
||||
const ProtoCanSettingsMsg_t *message)
|
||||
{
|
||||
(void)message;
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
|
||||
/** Отправляет один SETTINGS-кадр с Body из id и payload длиной 0..8. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS(
|
||||
ProtoCanId_t id, const struct ProtoCanSettingsData data)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0U;
|
||||
uint8_t canData[PROTOCAN_SETTINGS_ROM_SIZE] = {0U};
|
||||
uint8_t index;
|
||||
|
||||
if((id.Fields.MsgType != PROTOCAN_MSGTYPE_SETTINGS)
|
||||
|| (data.DataCount > PROTOCAN_SETTINGS_ROM_SIZE)
|
||||
|| ((data.DataCount != 0U) && (data.Data == 0)))
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
for(index = 0U; index < data.DataCount; index++)
|
||||
{
|
||||
canData[index] = data.Data[index];
|
||||
}
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&id);
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.DLC = data.DataCount;
|
||||
if(HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox) != HAL_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/** Отправляет успешный ответ SETTINGS с текущим ROM целевой локации. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS_RESPONSE(uint8_t assemblySerial,
|
||||
uint8_t position, const uint8_t *rom)
|
||||
{
|
||||
ProtoCanId_t id;
|
||||
ProtoCanData_t data;
|
||||
|
||||
if(rom == 0)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
id.BitAll = PROTOCAN_SETTINGS_RESPONSE_BASE_ID
|
||||
| ((uint32_t)assemblySerial << 8U) | position;
|
||||
data.SettingsData.Data = rom;
|
||||
data.SettingsData.DataCount = PROTOCAN_SETTINGS_ROM_SIZE;
|
||||
return PROTOCAN_SEND_SETTINGS(id, data.SettingsData);
|
||||
}
|
||||
|
||||
/** Отправляет однобайтовую ошибку SETTINGS с сохранением Body запроса. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS_ERROR(uint8_t assemblySerial,
|
||||
uint8_t position, ProtoCanSettingsResultType result)
|
||||
{
|
||||
ProtoCanId_t id;
|
||||
ProtoCanData_t data;
|
||||
const uint8_t error = (uint8_t)result;
|
||||
|
||||
if(result == PROTOCAN_SETTINGS_RESULT_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
id.BitAll = PROTOCAN_SETTINGS_RESPONSE_BASE_ID
|
||||
| ((uint32_t)assemblySerial << 8U) | position;
|
||||
data.SettingsData.Data = &error;
|
||||
data.SettingsData.DataCount = 1U;
|
||||
return PROTOCAN_SEND_SETTINGS(id, data.SettingsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка и ответ на запрос общего адресного пространства.
|
||||
* Формирует и передает сообщение с данными о статусе "GAS-XXXX", где XXXX — значение из сообщения _rxMsg.
|
||||
* @param _rxMsg Структура входящего сообщения, содержит идентификатор и тело сообщения.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToGeneralAddressSpace(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 8;
|
||||
data[0] = 'G';
|
||||
data[1] = 'A';
|
||||
data[2] = 'S';
|
||||
data[3] = '-';
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
unsigned sym = (_rxMsg.eID.Fields.MsgBody >> (12 - (i * 4))) & 0xF;
|
||||
if(sym >= 10)
|
||||
{
|
||||
data[4 + i] = sym % 10 + 'A';
|
||||
}
|
||||
else
|
||||
{
|
||||
data[4 + i] = sym + '0';
|
||||
}
|
||||
}
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Отправляет произвольный диапазон регистров в пакетах по, максимум, 4 регистра за раз через CAN.
|
||||
* Функция разбивает передаваемый массив регистров на пакеты по 4 или меньше и отправляет их по отдельности.
|
||||
* Для каждого пакета формируется уникальный MsgBody, равный адресу первого регистра в пакете.
|
||||
* @param priority Приоритет сообщения CAN. ProtoCan_Priority_TypeDef
|
||||
* @param regStartAdr Адрес первого регистра из всего массива.
|
||||
* @param data Указатель на массив регистров для отправки.
|
||||
* @param regCount Общее количество регистров для отправки.
|
||||
* @return Возвращает статус отправки: HAL_OK при успехе, или код ошибки HAL_StatusTypeDef при сбое.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_GENERAL_ADDRESS_SPACE(ProtoCanId_t id, uint16_t regStartAdr, uint16_t *data, uint8_t regCount)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t canData[8];
|
||||
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = id.BitAll;
|
||||
|
||||
uint8_t regsRemaining = regCount;
|
||||
uint8_t currentIndex = 0;
|
||||
|
||||
while(regsRemaining > 0)
|
||||
{
|
||||
uint8_t regsInPacket = (regsRemaining > 4) ? 4 : regsRemaining;
|
||||
|
||||
tmp_eID.Fields.MsgBody = regStartAdr + currentIndex;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = regsInPacket*2;
|
||||
|
||||
for(int i = 0; i < regsInPacket; i++)
|
||||
{
|
||||
canData[(i * 2)] = LowByteOfWord(data[currentIndex + i]);
|
||||
canData[(i * 2) + 1] = HighByteOfWord(data[currentIndex + i]);
|
||||
}
|
||||
|
||||
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox);
|
||||
|
||||
if(status != HAL_OK)
|
||||
{
|
||||
return (PROTOCAN_StatusTypeDef)status;
|
||||
}
|
||||
|
||||
regsRemaining -= regsInPacket;
|
||||
currentIndex += regsInPacket;
|
||||
}
|
||||
return (PROTOCAN_StatusTypeDef)HAL_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка запроса на основе типа Modbus.
|
||||
* В зависимости от типа сообщения, вызывает соответствующую функцию
|
||||
* для отправки Modbus-запроса по CAN-шине.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ModbusProcessing(struct RXMsg _rxMsg)
|
||||
{
|
||||
switch(_rxMsg.eID.Fields.MsgType)
|
||||
{
|
||||
case PROTOCAN_MSGTYPE_MODBUS_COIL:
|
||||
{
|
||||
return ProtoCanMsgToModbusCoil(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_MODBUS_DISCRETE:
|
||||
{
|
||||
return ProtoCanMsgToModbusDiscrete(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_MODBUS_HOLDING:
|
||||
{
|
||||
return ProtoCanMsgToModbusHolding(_rxMsg);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_MODBUS_INPUT:
|
||||
{
|
||||
return ProtoCanMsgToModbusInput(_rxMsg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//ERROR
|
||||
return PROTOCAN_ERROR;
|
||||
break;
|
||||
}
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak Функция отправки ответа на запрос Coil Modbus по CAN.
|
||||
* Формирует CAN ответ на запрос Coil Modbus и отправляет его.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToModbusCoil(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 8;
|
||||
msgBodyModbusType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'M';
|
||||
data[1] = 'C';
|
||||
data[2] = ' ';
|
||||
data[3] = 'S';
|
||||
data[4] = msg.Fields.StrAdr;
|
||||
data[5] = ' ';
|
||||
data[6] = 'C';
|
||||
data[7] = msg.Fields.RegCount;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak Функция отправки ответа на запрос Discrete Modbus по CAN.
|
||||
* Формирует CAN ответ на запрос Discrete Modbus и отправляет его.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToModbusDiscrete(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 8;
|
||||
msgBodyModbusType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'M';
|
||||
data[1] = 'D';
|
||||
data[2] = ' ';
|
||||
data[3] = 'S';
|
||||
data[4] = msg.Fields.StrAdr;
|
||||
data[5] = ' ';
|
||||
data[6] = 'C';
|
||||
data[7] = msg.Fields.RegCount;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak Функция отправки ответа на запрос Holding Modbus по CAN.
|
||||
* Формирует CAN ответ на запрос Holding Modbus и отправляет его.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToModbusHolding(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 8;
|
||||
msgBodyModbusType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'M';
|
||||
data[1] = 'H';
|
||||
data[2] = ' ';
|
||||
data[3] = 'S';
|
||||
data[4] = msg.Fields.StrAdr;
|
||||
data[5] = ' ';
|
||||
data[6] = 'C';
|
||||
data[7] = msg.Fields.RegCount;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak Функция отправки ответа на запрос Input Modbus по CAN.
|
||||
* Формирует CAN ответ на запрос Input Modbus и отправляет его.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN (успех или ошибка).
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef ProtoCanMsgToModbusInput(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 8;
|
||||
msgBodyModbusType msg;
|
||||
msg.Body = _rxMsg.eID.Fields.MsgBody;
|
||||
data[0] = 'M';
|
||||
data[1] = 'I';
|
||||
data[2] = ' ';
|
||||
data[3] = 'S';
|
||||
data[4] = msg.Fields.StrAdr;
|
||||
data[5] = ' ';
|
||||
data[6] = 'C';
|
||||
data[7] = msg.Fields.RegCount;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Отправляет данные Modbus через CAN-шину в соответствии с типом сообщения.
|
||||
* Эта функция формирует и отправляет Modbus-данные по CAN-шине, поддерживая
|
||||
* различные типы сообщений: Coil, Discrete, Holding Register и Input Register.
|
||||
* Для сообщений, превышающих 4 регистров, данные отправляются пакетами.
|
||||
* @param id Идентификатор сообщения, содержащий тип сообщения и другую информацию.
|
||||
* @param modbusData Структура с данными Modbus: адрес, количество регистров и сами данные.
|
||||
* @return PROTOCAN_StatusTypeDef Статус операции.
|
||||
* @details
|
||||
* В зависимости от типа Modbus (coil/discrete или holding/input), формируется
|
||||
* соответствующий CAN-пакет. Для coil/discrete отправляется один пакет, для holding/input —
|
||||
* несколько, по 4 регистра в пакете. В случае ошибок при добавлении сообщения API HAL возвращается ошибка.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_MODBUS(const ProtoCanId_t id, const struct ProtoCanModbusData modbusData)
|
||||
{
|
||||
ProtoCanId_t localId = id;
|
||||
uint8_t msgType = localId.Fields.MsgType;
|
||||
// Структура для отправки
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t canData[8];
|
||||
if(msgType == PROTOCAN_MSGTYPE_MODBUS_COIL || msgType == PROTOCAN_MSGTYPE_MODBUS_DISCRETE)
|
||||
{
|
||||
// Формируем body
|
||||
msgBodyModbusType body;
|
||||
body.Fields.StrAdr = modbusData.StrAdr;
|
||||
body.Fields.RegCount = modbusData.RegCount;
|
||||
// Устанавливаем коллизию ExtId с body.Body
|
||||
localId.Fields.MsgBody = (body.Body & 0xFFFF);
|
||||
// Обновляем ExtId
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
// Остальные настройки
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.DLC = modbusData.RegCount % 8 + 1;
|
||||
// Формируем CAN данные
|
||||
canData[0] = LowByteOfWord(modbusData.Data[0]);
|
||||
canData[1] = HighByteOfWord(modbusData.Data[0]);
|
||||
if (HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox) != HAL_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
}
|
||||
else if(msgType == PROTOCAN_MSGTYPE_MODBUS_HOLDING || msgType == PROTOCAN_MSGTYPE_MODBUS_INPUT)
|
||||
{
|
||||
uint16_t* dataPtr = modbusData.Data;
|
||||
unsigned totalRegs = modbusData.RegCount;
|
||||
unsigned regsProcessed = 0;
|
||||
unsigned startAddress = modbusData.StrAdr;
|
||||
while (regsProcessed < totalRegs)
|
||||
{
|
||||
uint8_t regsInPacket = (totalRegs - regsProcessed) > 4 ? 4 : (totalRegs - regsProcessed);
|
||||
// Формируем body
|
||||
msgBodyModbusType body;
|
||||
body.Fields.StrAdr = startAddress;
|
||||
body.Fields.RegCount = regsInPacket;
|
||||
// Обновляем ExtId
|
||||
localId.Fields.MsgBody = (body.Body & 0xFFFF);
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.DLC = regsInPacket * 2;
|
||||
// Добавляем регистры
|
||||
for (int i = 0; i < regsInPacket; i++)
|
||||
{
|
||||
canData[i * 2] = LowByteOfWord(dataPtr[regsProcessed + i]);
|
||||
canData[i * 2 + 1] = HighByteOfWord(dataPtr[regsProcessed + i]);
|
||||
}
|
||||
// Отправляем сообщение
|
||||
if(HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox) != HAL_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
regsProcessed += regsInPacket;
|
||||
startAddress += regsInPacket;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return PROTOCAN_ERROR; // Неверный тип сообщения
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief __weak Функция отправки сообщения об ошибке.
|
||||
* Посылает CAN сообщение с кодом ошибки. Используется, когда необходимо оповестить о неуспешной операции.
|
||||
* @param _rxMsg Структура входящего сообщения RXMsg, данными которого формируется ответ-ошибка.
|
||||
* @return HAL_StatusTypeDef Статус выполнения команды CAN.
|
||||
*/
|
||||
__weak PROTOCAN_StatusTypeDef PROTOCAN_RequestError(struct RXMsg _rxMsg)
|
||||
{
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
ProtoCanId_t tmp_eID;
|
||||
tmp_eID.BitAll = _rxMsg.eID.BitAll;
|
||||
tmp_eID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
tmp_eID.Fields.MsgType = PROTOCAN_MSGTYPE_ERROR;
|
||||
msgBodyErrorType msg;
|
||||
msg.Fields.ErrorCode = 0xFF;
|
||||
msg.Fields.Info = 0;
|
||||
tmp_eID.Fields.MsgBody = msg.Body;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&tmp_eID);
|
||||
TxHeader.DLC = 0;
|
||||
return (PROTOCAN_StatusTypeDef)HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Отправляет сообщение об ошибке по CAN-шине.
|
||||
* Эта функция формирует и отправляет сообщение об ошибке с кодом и информацией,
|
||||
* объединёнными в поле MsgBody идентификатора сообщения, и передаёт его через CAN.
|
||||
* @param id Идентификатор сообщения, содержащий базовую информацию.
|
||||
* @param errorData Структура с данными ошибки: код и дополнительная информация.
|
||||
* @return PROTOCAN_StatusTypeDef Статус операции.
|
||||
* @details
|
||||
* В качестве данных передаётся 2 байта: старшие биты — информация об ошибке, младшие — код ошибки.
|
||||
* Размер данных в CANFrames устанавливается нулевым, так как все данные инкапсулированы в MsgBody.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_ERROR(const ProtoCanId_t id, const struct ProtoCanErrorData errorData)
|
||||
{
|
||||
ProtoCanId_t localId = id;
|
||||
// Структура для отправки
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
uint8_t canData[8];
|
||||
localId.Fields.MsgBody = ((errorData.Info << 8) | errorData.Code);
|
||||
// Обновляем ExtId
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(&localId);
|
||||
// Остальные настройки
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.DLC = 0;
|
||||
if(HAL_CAN_AddTxMessage(_HCAN, &TxHeader, canData, &TxMailBox) != HAL_OK)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
return PROTOCAN_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Записывает полученное сообщение CAN в буфер rxMsg.
|
||||
* Копирует параметры расширенного ID, типы, длину данных и сам данные в указанный элемент буфера.
|
||||
* @param tmp_eID Расширенный ID сообщения.
|
||||
* @param tmp_IDE Тип идентификатора (например, CAN_ID_EXT).
|
||||
* @param tmp_RTR Тип запроса (например, CAN_RTR_DATA).
|
||||
* @param tmp_DLC Длина данных в сообщении.
|
||||
* @param tmp_DATA Указатель на массив данных, полученных из CAN.
|
||||
* @param tmp_LastStep Индекс позиции в буфере, куда записывать сообщение.
|
||||
*/
|
||||
void TakeRxMsgToBuffer(ProtoCanId_t tmp_eID, uint32_t tmp_IDE, uint32_t tmp_RTR, uint32_t tmp_DLC, uint8_t *tmp_DATA, uint16_t tmp_LastStep)
|
||||
{
|
||||
if(tmp_DLC > 8u)
|
||||
{
|
||||
tmp_DLC = 8u;
|
||||
}
|
||||
rxMsg[tmp_LastStep].eID.BitAll = tmp_eID.BitAll;
|
||||
rxMsg[tmp_LastStep].info.EXT = (tmp_IDE == CAN_ID_EXT) ? 1U : 0U;
|
||||
rxMsg[tmp_LastStep].info.RTR = (tmp_RTR == CAN_RTR_REMOTE) ? 1U : 0U;
|
||||
rxMsg[tmp_LastStep].DLC = tmp_DLC;
|
||||
for(uint32_t i = 0; i < tmp_DLC; i++)
|
||||
{
|
||||
rxMsg[tmp_LastStep].Data[i] = tmp_DATA[i];
|
||||
}
|
||||
/* Слот заполнен - только теперь публикуем его. Без барьера компилятору
|
||||
разрешено поднять запись LastStep выше записей в rxMsg, и обработчик
|
||||
увидит наполовину заполненное сообщение. */
|
||||
__DMB();
|
||||
LastStep = tmp_LastStep;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обработка сообщений, ожидающих в FIFO, по прерыванию HAL_CAN_RxFifo0MsgPendingCallback.
|
||||
* Читает все сообщения из FIFO, проверяет их тип, обновляет статус устройств сети или сохраняет сообщение в буфер.
|
||||
* @param hcan Указатель на структуру управления CAN-Hardware.
|
||||
*/
|
||||
void ProtoCanRxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
|
||||
{
|
||||
//Обработка всех сообщений в FIFO
|
||||
CAN_RxHeaderTypeDef RxHeader;
|
||||
uint8_t RCAN_Data[8];
|
||||
while(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RCAN_Data) == HAL_OK)
|
||||
{
|
||||
//Расширенный ID
|
||||
if(RxHeader.IDE == CAN_ID_EXT)
|
||||
{
|
||||
ProtoCanId_t ExtID_Of_RX_MSG;
|
||||
PROTOCAN_IdUnpack(RxHeader.ExtId, &ExtID_Of_RX_MSG);
|
||||
//Полученное сообщение - пульс устройств в сети.
|
||||
//Место в буфере не занимает, поэтому проверяется до проверки переполнения.
|
||||
if(ExtID_Of_RX_MSG.Fields.MsgType == PROTOCAN_MSGTYPE_PULSE)
|
||||
{
|
||||
Device_on_the_Network[ExtID_Of_RX_MSG.Fields.DeviceType][ExtID_Of_RX_MSG.Fields.DeviceID].Status = 1u;
|
||||
Device_on_the_Network[ExtID_Of_RX_MSG.Fields.DeviceType][ExtID_Of_RX_MSG.Fields.DeviceID].TimeFromLastPulse = 0;
|
||||
//continue, а не return: остаток FIFO нужно вычитать в этом же вызове,
|
||||
//иначе прерывание по непустому FIFO сработает повторно.
|
||||
continue;
|
||||
}
|
||||
//Полезная ёмкость буфера - (PROTOCAN_RX_BUFFER_SIZE - 1): при большем
|
||||
//заполнении счётчик AvailableCanRxMsg() переполняется и даёт 0 ("пусто").
|
||||
if(AvailableCanRxMsg() >= (PROTOCAN_RX_BUFFER_SIZE - 1u))
|
||||
{
|
||||
//Буфер переполнен, кадр отбрасывается. Кадр уже извлечён из
|
||||
//аппаратного FIFO, поэтому продолжаем вычитывать остаток.
|
||||
continue;
|
||||
}
|
||||
uint16_t tmp_LastStep = (uint16_t)((LastStep + 1u) % PROTOCAN_RX_BUFFER_SIZE);
|
||||
TakeRxMsgToBuffer(ExtID_Of_RX_MSG, RxHeader.IDE, RxHeader.RTR, RxHeader.DLC, RCAN_Data, tmp_LastStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция отправки пульса устройства.
|
||||
* @details Пульс устройства. Есть возможность отключить пульс по запросу.
|
||||
*/
|
||||
void ProtoCanPulseCallback(TIM_HandleTypeDef *htim)
|
||||
{
|
||||
if(ControlFlags.IsPulse)
|
||||
{
|
||||
static unsigned PulseStage = 0;
|
||||
CAN_TxHeaderTypeDef TxHeader;
|
||||
uint32_t TxMailBox = 0;
|
||||
ProtoCanId_t currentID;
|
||||
currentID.BitAll = 0;
|
||||
currentID.Fields.Priority = PROTOCAN_PRIORITY_STANDARD;
|
||||
currentID.Fields.Route = PROTOCAN_ROUTE_FROM_DEVICE;
|
||||
currentID.Fields.DeviceType = CURRENT_TYPE_DEVICE;
|
||||
currentID.Fields.DeviceID = CURRENT_ID_DEVICE;
|
||||
currentID.Fields.MsgType = PROTOCAN_MSGTYPE_PULSE;
|
||||
currentID.Fields.MsgBody = 0;
|
||||
TxHeader.ExtId = PROTOCAN_IdPack(¤tID);
|
||||
uint8_t data[8];
|
||||
TxHeader.IDE = CAN_ID_EXT;
|
||||
TxHeader.TransmitGlobalTime = DISABLE;
|
||||
TxHeader.RTR = CAN_RTR_DATA;
|
||||
TxHeader.DLC = 1;
|
||||
PulseStage++;
|
||||
if(PulseStage > 0xFF)
|
||||
{
|
||||
PulseStage = 0;
|
||||
}
|
||||
data[0] = PulseStage;
|
||||
HAL_CAN_AddTxMessage(_HCAN, &TxHeader, data, &TxMailBox);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция установки в RTC полученной даты/время из запроса.
|
||||
* @param uint8_t *data - Байтовый массив, 7 элементов. [0] - Часы. [1] - Минуты. [2] - Секунды. [3] - Год. [4] - Месяц. [5] - Дата. [6] - День недели.
|
||||
*/
|
||||
void PROTOCAN_RTC_SYNC(uint8_t *data)
|
||||
{
|
||||
__HAL_RTC_WRITEPROTECTION_DISABLE(_HRTC);
|
||||
RTC_TimeTypeDef sTime = {0};
|
||||
RTC_DateTypeDef DateToUpdate = {0};
|
||||
sTime.Hours = data[0];
|
||||
sTime.Minutes = data[1];
|
||||
sTime.Seconds = data[2];
|
||||
if(HAL_RTC_SetTime(_HRTC, &sTime, RTC_FORMAT_BIN) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
DateToUpdate.Year = data[3];
|
||||
DateToUpdate.Month = data[4];
|
||||
DateToUpdate.Date = data[5];
|
||||
DateToUpdate.WeekDay = data[6];
|
||||
if(HAL_RTC_SetDate(_HRTC, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
__HAL_RTC_WRITEPROTECTION_ENABLE(_HRTC);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Настройка CAN-Фильтра.
|
||||
* На основе переданных ID и маски настраивает фильтр CAN для приема только определённых сообщений.
|
||||
* @param filterBank Номер фильтра (обычно 0..27).
|
||||
* @param idFilter 32-битный идентификатор, используемый для фильтрации.
|
||||
* @param idMask 32-битная маска, определяющая биты, проверяемые фильтром.
|
||||
*/
|
||||
void PROTOCAN_CONFIG_FILTER(uint8_t filterBank, uint32_t idFilter, uint32_t idMask)
|
||||
{
|
||||
CAN_FilterTypeDef canFilterConfig;
|
||||
canFilterConfig.FilterBank = filterBank;
|
||||
canFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
|
||||
canFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
|
||||
canFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
|
||||
canFilterConfig.FilterActivation = ENABLE;
|
||||
canFilterConfig.SlaveStartFilterBank = 14;
|
||||
#define CAN_IDE_32 0b00000100 // Для 32-х битного масштаба
|
||||
// Разбиваем 32-битный ID и маску для фильтрации на High и Low 16 бит
|
||||
canFilterConfig.FilterIdHigh = (uint16_t)(((idFilter) >> 13)); // верхние 16 бит
|
||||
canFilterConfig.FilterIdLow = (uint16_t)((((idFilter) << 3)) | CAN_IDE_32); // низкие 16 бит,
|
||||
canFilterConfig.FilterMaskIdHigh = (uint16_t)(((idMask) >> 13));
|
||||
canFilterConfig.FilterMaskIdLow = (uint16_t)((((idMask) << 3)) | CAN_IDE_32);
|
||||
if(HAL_CAN_ConfigFilter(_HCAN, &canFilterConfig) != HAL_OK)
|
||||
{
|
||||
Error_Handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Конфигурирует три фильтра CAN для различных типов сообщений.
|
||||
*/
|
||||
void PROTOCAN_FILTERS()
|
||||
{
|
||||
#define BIT27 (1UL << 27)
|
||||
#define BITS_20_23_MASK (0xFUL << 20) // 4 бита (20..23)
|
||||
#define BITS_24_26_MASK (0x7UL << 24) // 3 бита (24..26)
|
||||
|
||||
/* Адресованный запрос приходит от ПМ, поэтому Route (бит 27) равен нулю. */
|
||||
uint32_t filter1_id = (CURRENT_TYPE_DEVICE << 24) | (CURRENT_ID_DEVICE << 20);
|
||||
uint32_t filter1_mask = BIT27 | BITS_24_26_MASK | BITS_20_23_MASK; // все проверяемые биты
|
||||
|
||||
#define BITS_16_19_MASK (0xFUL << 16)
|
||||
|
||||
uint32_t filter2_id = PROTOCAN_MSGTYPE_BROADCAST << 16;
|
||||
uint32_t filter2_mask = BITS_16_19_MASK;
|
||||
|
||||
uint32_t filter3_id = PROTOCAN_MSGTYPE_PULSE << 16;
|
||||
uint32_t filter3_mask = BITS_16_19_MASK;
|
||||
|
||||
// Первый фильтр: Route=0 (от ПМ), DeviceType и DeviceID текущего устройства.
|
||||
PROTOCAN_CONFIG_FILTER(0, filter1_id, filter1_mask);
|
||||
|
||||
// Второй фильтр: проверяем 16-19 биты, равны PROTOCAN_MSGTYPE_BROADCASTE
|
||||
PROTOCAN_CONFIG_FILTER(1, filter2_id, filter2_mask);
|
||||
|
||||
// Третий фильтр: проверяем 16-19 биты, равны PROTOCAN_MSGTYPE_PULSE
|
||||
PROTOCAN_CONFIG_FILTER(2, filter3_id, filter3_mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Отправляет CAN-сообщение в зависимости от типа сообщения.
|
||||
* Эта функция анализирует тип сообщения в идентификаторе и вызывает
|
||||
* соответствующую функцию отправки. В текущей реализации поддерживается только
|
||||
* тип PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE.
|
||||
* @param id Идентификатор CAN-сообщения, содержащий информацию о типе.
|
||||
* @param data Структура с данными, которые необходимо отправить. Зависит от типа сообщения.
|
||||
* @return Статус выполнения операции.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND(ProtoCanId_t id, ProtoCanData_t data)
|
||||
{
|
||||
switch(id.Fields.MsgType)
|
||||
{
|
||||
case PROTOCAN_MSGTYPE_BROADCAST:
|
||||
case PROTOCAN_MSGTYPE_DISCRETE:
|
||||
case PROTOCAN_MSGTYPE_ANALOG:
|
||||
{
|
||||
return PROTOCAN_SEND_CORE(id, data.CoreData);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE:
|
||||
{
|
||||
/* Если есть регистры для отправки, указатель data должен быть валиден. */
|
||||
if((data.GeneralAddressSpaceData.RegCount > 0U) && data.GeneralAddressSpaceData.Data == 0)
|
||||
{
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
return PROTOCAN_SEND_GENERAL_ADDRESS_SPACE(id, data.GeneralAddressSpaceData.RegStartAdr,
|
||||
data.GeneralAddressSpaceData.Data,
|
||||
data.GeneralAddressSpaceData.RegCount);
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_MODBUS_COIL:
|
||||
case PROTOCAN_MSGTYPE_MODBUS_DISCRETE:
|
||||
case PROTOCAN_MSGTYPE_MODBUS_HOLDING:
|
||||
case PROTOCAN_MSGTYPE_MODBUS_INPUT:
|
||||
{
|
||||
return PROTOCAN_SEND_MODBUS(id, data.ModbusData);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_ERROR:
|
||||
{
|
||||
return PROTOCAN_SEND_ERROR(id, data.ErrorData);
|
||||
break;
|
||||
}
|
||||
case PROTOCAN_MSGTYPE_SETTINGS:
|
||||
{
|
||||
return PROTOCAN_SEND_SETTINGS(id, data.SettingsData);
|
||||
}
|
||||
default:
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
return PROTOCAN_ERROR;
|
||||
}
|
||||
427
c/set-protocol/ports/stm32-bxcan/protocan.h
Normal file
427
c/set-protocol/ports/stm32-bxcan/protocan.h
Normal file
@@ -0,0 +1,427 @@
|
||||
//тест
|
||||
|
||||
#ifndef _protocan
|
||||
#define _protocan
|
||||
#include "main.h"
|
||||
#include "can.h"
|
||||
#include "pcan_id.h"
|
||||
|
||||
#define _DEMO
|
||||
|
||||
#define ONLINE 1;
|
||||
#define OFFLINE 0;
|
||||
|
||||
#define ID_MAIN_DEVICE 0x000
|
||||
#define CURRENT_TYPE_DEVICE 0b111
|
||||
#define CURRENT_ID_DEVICE 0b1111
|
||||
|
||||
#define PROTOCAN_RX_BUFFER_SIZE 128
|
||||
|
||||
#define SensorToModbusRegister(SensorType, SensorID) SensorType << 11 | SensorID
|
||||
#define HighByteOfWord(WORD) (WORD>>8)&0xFF
|
||||
#define LowByteOfWord(WORD) WORD&0xFF
|
||||
|
||||
/**
|
||||
* @brief Статус инициализации протокола CAN.
|
||||
*
|
||||
* Это перечисление описывает возможные статусы после попытки инициализации.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
PROTOCAN_INIT_OK = 0x00U, /**< Инициализация прошла успешно. */
|
||||
PROTOCAN_INIT_HCAN_ERROR = 0x01U, /**< Ошибка при инициализации модуля CAN. */
|
||||
PROTOCAN_INIT_HRTC_ERROR = 0x02U, /**< Ошибка при инициализации модуля RTC. */
|
||||
PROTOCAN_INIT_TIM_ERROR = 0x04U /**< Ошибка при инициализации таймера. */
|
||||
} PROTOCAN_INIT_StatusTypeDef;
|
||||
|
||||
/**
|
||||
* @brief Статус выполнения операции протокола CAN.
|
||||
*
|
||||
* Это перечисление описывает возможные результаты выполнения функций протокола.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
PROTOCAN_OK = 0x00U, /**< Операция прошла успешно. */
|
||||
PROTOCAN_ERROR = 0x01U, /**< Обнаружена ошибка во время выполнения. */
|
||||
PROTOCAN_BUSY = 0x02U, /**< Канал занят, выполнение невозможно в данный момент. */
|
||||
PROTOCAN_TIMEOUT = 0x03U /**< Истек таймаут ожидания. */
|
||||
} PROTOCAN_StatusTypeDef;
|
||||
|
||||
/**
|
||||
* @brief Уровень приоритета CAN-сообщения.
|
||||
*
|
||||
* Это перечисление определяет важность и приоритет обработки сообщения.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_PRIORITY_CRITICAL = 0, /**< Критический приоритет, рекомендуется обработка без задержек. */
|
||||
PROTOCAN_PRIORITY_STANDARD = 1 /**< Стандартный приоритет, менее важный. */
|
||||
} ProtoCanPriorityType;
|
||||
|
||||
/**
|
||||
* @brief Маршрут CAN-сообщения.
|
||||
*
|
||||
* Это перечисление задает направление маршрута отправки сообщения.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_ROUTE_FROM_PM = 0, /**< Сообщение отправляется от управляющего модуля (PM). */
|
||||
PROTOCAN_ROUTE_FROM_DEVICE = 1 /**< Сообщение отправляется от устройства (Device). */
|
||||
} ProtoCanRouteType;
|
||||
|
||||
/**
|
||||
* @brief Типы сообщений CAN.
|
||||
*
|
||||
* Перечисление определяет различные типы сообщений, используемые в протоколе.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_MSGTYPE_BROADCAST = 0b0000, /**< Широковещательное сообщение. */
|
||||
PROTOCAN_MSGTYPE_DISCRETE = 0b0001, /**< Дискретное цифровое сообщение. */
|
||||
PROTOCAN_MSGTYPE_ANALOG = 0b0010, /**< Аналоговое сообщение. */
|
||||
PROTOCAN_MSGTYPE_GENERAL_ADDRESS_SPACE = 0b0011, /**< Сообщение общего адресного пространства. */
|
||||
PROTOCAN_MSGTYPE_MODBUS_COIL = 0b0100, /**< Сообщение Modbus - Coil. */
|
||||
PROTOCAN_MSGTYPE_MODBUS_DISCRETE = 0b0101, /**< Сообщение Modbus - Discrete. */
|
||||
PROTOCAN_MSGTYPE_MODBUS_HOLDING = 0b0110, /**< Сообщение Modbus - Holding. */
|
||||
PROTOCAN_MSGTYPE_MODBUS_INPUT = 0b0111, /**< Сообщение Modbus - Input. */
|
||||
PROTOCAN_MSGTYPE_ERROR = 0b1000, /**< Сообщение об ошибке. */
|
||||
PROTOCAN_MSGTYPE_BOOT_CONTROL = 0b1001, /**< Команды ProtoCAN Boot. */
|
||||
PROTOCAN_MSGTYPE_BOOT_DATA_A = 0b1010, /**< Блок образа для слота A. */
|
||||
PROTOCAN_MSGTYPE_BOOT_DATA_B = 0b1011, /**< Блок образа для слота B. */
|
||||
PROTOCAN_MSGTYPE_BOOT_STATUS = 0b1100, /**< Статус команды загрузчика. */
|
||||
PROTOCAN_MSGTYPE_BOOT_DISCOVERY = 0b1101, /**< Идентификация загрузчика/приложения. */
|
||||
PROTOCAN_MSGTYPE_SETTINGS = 0b1110, /**< Привязка датчика к локации. */
|
||||
PROTOCAN_MSGTYPE_PULSE = 0b1111, /**< Импульсное сообщение. */
|
||||
}ProtoCanMsgType;
|
||||
|
||||
/** Длина ROM-кода 1-Wire и payload успешного ответа SETTINGS. */
|
||||
#define PROTOCAN_SETTINGS_ROM_SIZE 8U
|
||||
|
||||
/** Фиксированные базы выбранного DeviceType=0x7, DeviceID=0xF, MsgType=0xE. */
|
||||
#define PROTOCAN_SETTINGS_REQUEST_BASE_ID 0x17FE0000UL
|
||||
#define PROTOCAN_SETTINGS_RESPONSE_BASE_ID 0x1FFE0000UL
|
||||
#define PROTOCAN_SETTINGS_HEADER_MASK 0x1FFF0000UL
|
||||
|
||||
/** Операции SETTINGS, определяемые по направлению, DLC и payload. */
|
||||
typedef enum {
|
||||
PROTOCAN_SETTINGS_GET = 0,
|
||||
PROTOCAN_SETTINGS_WRITE = 1,
|
||||
PROTOCAN_SETTINGS_CLEAR = 2,
|
||||
PROTOCAN_SETTINGS_RESPONSE = 3,
|
||||
PROTOCAN_SETTINGS_ERROR = 4,
|
||||
PROTOCAN_SETTINGS_INVALID = 5
|
||||
} ProtoCanSettingsOperationType;
|
||||
|
||||
/** Коды результата SETTINGS в ответе с DLC=1. */
|
||||
typedef enum {
|
||||
PROTOCAN_SETTINGS_RESULT_OK = 0x00U,
|
||||
PROTOCAN_SETTINGS_RESULT_INVALID_DLC = 0x01U,
|
||||
PROTOCAN_SETTINGS_RESULT_INVALID_ROM_CRC = 0x02U,
|
||||
PROTOCAN_SETTINGS_RESULT_NOT_FOUND = 0x03U,
|
||||
PROTOCAN_SETTINGS_RESULT_ALREADY_ASSIGNED = 0x04U,
|
||||
PROTOCAN_SETTINGS_RESULT_EEPROM_ERROR = 0x05U,
|
||||
PROTOCAN_SETTINGS_RESULT_INVALID_LOCATION = 0x06U,
|
||||
PROTOCAN_SETTINGS_RESULT_BUSY = 0x07U
|
||||
} ProtoCanSettingsResultType;
|
||||
|
||||
/** Разобранная команда SETTINGS с локацией и ROM. */
|
||||
typedef struct {
|
||||
ProtoCanSettingsOperationType Operation;
|
||||
uint8_t AssemblySerial; /**< Body[15:8], номер сборки Z. */
|
||||
uint8_t Position; /**< Body[7:0], позиция Y. */
|
||||
ProtoCanSettingsResultType Result;
|
||||
uint8_t Rom[PROTOCAN_SETTINGS_ROM_SIZE];
|
||||
} ProtoCanSettingsMsg_t;
|
||||
|
||||
/**
|
||||
* @brief Типы широковещательных сообщений CAN.
|
||||
*
|
||||
* Перечисление задает различные типы широковещательных команд, используемых в протоколе.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_BROADCAST_STATUS = 0b000000000000, /**< Статус устройства. */
|
||||
PROTOCAN_BROADCAST_ONOFF = 0b000000000001, /**< Включение/выключение устройства. */
|
||||
PROTOCAN_BROADCAST_RESTARTDEVICE = 0b000000000010, /**< Перезагрузка устройства. */
|
||||
PROTOCAN_BROADCAST_RTCSETUP = 0b000000000011, /**< Настройка RTC (часы реального времени). */
|
||||
PROTOCAN_BROADCAST_END = 0b111111111111 /**< Конец диапазона широковещательных сообщений. */
|
||||
}ProtoCanBroadcastType;
|
||||
|
||||
/**
|
||||
* @brief Структура и представление широковещательного сообщения CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к данным сообщения либо как к отдельным полям, либо как к целому 16-битному значению.
|
||||
*/
|
||||
typedef union{
|
||||
struct msgBody_Broadcast{
|
||||
unsigned Body:4; /**< Поле для дополнительных данных или кода задачи (4 бита). */
|
||||
ProtoCanBroadcastType Type:12; /**< Тип широковещательного сообщения (12 бит). */
|
||||
} Fields; /**< Структура с разбивкой по полям. */
|
||||
unsigned Body; /**< Общее 16-битное представление сообщения. */
|
||||
} msgBodyBroadcastType;
|
||||
|
||||
/**
|
||||
* @brief Типы дискретных сообщений CAN.
|
||||
*
|
||||
* Перечисление задает различные виды дискретных команд или событий.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_DISCRETE_ACCIDENT = 0b0000, /**< Аварийное состояние. */
|
||||
PROTOCAN_DISCRETE_WARNING = 0b0001, /**< Предупредительный сигнал. */
|
||||
PROTOCAN_DISCRETE_CONTROL_SIGNALS = 0b0010, /**< Управляющие сигналы. */
|
||||
PROTOCAN_DISCRETE_FLAGS = 0b0011, /**< Флаги состояния. */
|
||||
PROTOCAN_DISCRETE_RESET = 0b0100, /**< Сброс устройства. */
|
||||
PROTOCAN_DISCRETE_CHANGE_MODE = 0b0101, /**< Смена режима работы. */
|
||||
PROTOCAN_DISCRETE_REQUEST_LIST_OF_PARAMETERS = 0b0110, /**< Запрос списка параметров. */
|
||||
PROTOCAN_DISCRETE_END = 0b1111 /**< Конец диапазона дискретных сообщений. */
|
||||
}ProtoCanDiscreteType;
|
||||
|
||||
/**
|
||||
* @brief Структура и представление дискретного сообщения CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к данным сообщения как к отдельным полям или как к целому 16-битному значению.
|
||||
*/
|
||||
typedef union{
|
||||
struct msgBody_Discrete{
|
||||
unsigned Body:12; /**< Поле данных или кода (12 бит). */
|
||||
ProtoCanDiscreteType Type:4; /**< Тип дискретного сообщения (4 бита). */
|
||||
} Fields; /**< Структура с разбивкой по полям. */
|
||||
unsigned Body; /**< Общее 16-битное представление сообщения. */
|
||||
} msgBodyDiscreteType;
|
||||
|
||||
/**
|
||||
* @brief Типы аналоговых сообщений CAN.
|
||||
*
|
||||
* Перечисление определяет различные типы аналоговых данных или команд.
|
||||
*/
|
||||
typedef enum {
|
||||
PROTOCAN_ANALOG_UNIVERSAL = 0b0000, /**< Универсальный тип. */
|
||||
PROTOCAN_ANALOG_SETTINGS = 0b0001, /**< Настройки. */
|
||||
PROTOCAN_ANALOG_U = 0b0010, /**< Напряжение (U). */
|
||||
PROTOCAN_ANALOG_I = 0b0011, /**< Ток (I). */
|
||||
PROTOCAN_ANALOG_T = 0b0100, /**< Температура (T). */
|
||||
PROTOCAN_ANALOG_END = 0b1111 /**< Конец диапазона типов. */
|
||||
}ProtoCanAnalogType;
|
||||
|
||||
/**
|
||||
* @brief Структура и представление аналогового сообщения CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к данным сообщения как к отдельным полям или как к целому 16-битному значению.
|
||||
*/
|
||||
typedef union{
|
||||
struct msgBody_Analog{
|
||||
unsigned SensorID:12; /**< Идентификатор датчика (12 бит). */
|
||||
ProtoCanAnalogType Type:4; /**< Тип аналоговых данных (4 бита). */
|
||||
} Fields; /**< Структура с разбивкой по полям. */
|
||||
unsigned Body; /**< Общее 16-битное представление сообщения. */
|
||||
}msgBodyAnalogType;
|
||||
|
||||
/**
|
||||
* @brief Структура и представление сообщения Modbus CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к данным как к отдельным полям или как к целому 16-битному значению.
|
||||
*/
|
||||
typedef union{
|
||||
struct msgBody_Modbus{
|
||||
unsigned RegCount:4; /**< Количество регистров (4 бита). */
|
||||
unsigned StrAdr:12; /**< Адрес строки или регистра (12 бит). */
|
||||
} Fields; /**< Поля структуры сообщения Modbus. */
|
||||
unsigned Body; /**< Общее 16-битное представление сообщения. */
|
||||
} msgBodyModbusType;
|
||||
|
||||
/**
|
||||
* @brief Структура и представление сообщения об ошибке CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к данным как к отдельным полям или как к целому 16-битному значению.
|
||||
*/
|
||||
typedef union{
|
||||
struct msgBody_Error{
|
||||
unsigned ErrorCode:8; /**< Код ошибки (8 бит). */
|
||||
unsigned Info:8; /**< Дополнительная информация (8 бит). */
|
||||
} Fields; /**< Поля ошибки. */
|
||||
unsigned Body; /**< Общее 16-битное представление сообщения об ошибке. */
|
||||
} msgBodyErrorType;
|
||||
|
||||
struct controlflags{
|
||||
unsigned IsPulse:1;
|
||||
unsigned IsRtrMode:1;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Поля идентификатора протокола CAN.
|
||||
*
|
||||
* Объединение позволяет обращаться к различным полям идентификатора, а также как к 29-битному целому числу.
|
||||
*/
|
||||
typedef union{
|
||||
struct protoCanIdFields{
|
||||
unsigned MsgBody:16; /**< Поле сообщения (16 бит). */
|
||||
ProtoCanMsgType MsgType:4; /**< Тип сообщения (4 бита). */
|
||||
unsigned DeviceID:4; /**< Идентификатор устройства (4 бита). */
|
||||
unsigned DeviceType:3; /**< Тип устройства (3 бита). */
|
||||
ProtoCanRouteType Route:1; /**< Маршрут (1 бит). */
|
||||
ProtoCanPriorityType Priority:1; /**< Приоритет (1 бит). */
|
||||
} Fields; /**< Структура с разбивкой по полям. */
|
||||
unsigned int BitAll:29; /**< Общее представление 29-битного идентификатора. */
|
||||
} ProtoCanId_t;
|
||||
|
||||
/**
|
||||
* Переходная граница legacy API -> переносимый формат линии.
|
||||
*
|
||||
* Прикладной код SETCAN пока может заполнять исторические Fields, но CAN ID
|
||||
* всегда собирается и разбирается общим pcan_id без зависимости от порядка
|
||||
* битовых полей конкретного компилятора.
|
||||
*/
|
||||
static inline uint32_t PROTOCAN_IdPack(const ProtoCanId_t *legacy)
|
||||
{
|
||||
pcan_id_t shared;
|
||||
shared.msg_body = (uint16_t)legacy->Fields.MsgBody;
|
||||
shared.msg_type = (uint8_t)legacy->Fields.MsgType;
|
||||
shared.device_id = (uint8_t)legacy->Fields.DeviceID;
|
||||
shared.device_type = (uint8_t)legacy->Fields.DeviceType;
|
||||
shared.route = (uint8_t)legacy->Fields.Route;
|
||||
shared.priority = (uint8_t)legacy->Fields.Priority;
|
||||
return pcan_id_pack(&shared);
|
||||
}
|
||||
|
||||
static inline void PROTOCAN_IdUnpack(uint32_t raw, ProtoCanId_t *legacy)
|
||||
{
|
||||
pcan_id_t shared;
|
||||
pcan_id_unpack(raw, &shared);
|
||||
legacy->BitAll = 0U;
|
||||
legacy->Fields.MsgBody = shared.msg_body;
|
||||
legacy->Fields.MsgType = (ProtoCanMsgType)shared.msg_type;
|
||||
legacy->Fields.DeviceID = shared.device_id;
|
||||
legacy->Fields.DeviceType = shared.device_type;
|
||||
legacy->Fields.Route = (ProtoCanRouteType)shared.route;
|
||||
legacy->Fields.Priority = (ProtoCanPriorityType)shared.priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Структура для общего адресного пространства CAN.
|
||||
*
|
||||
* Используется для хранения данных о регистре и его содержимом.
|
||||
*/
|
||||
typedef struct{
|
||||
struct ProtoCanCoreData{
|
||||
uint16_t Type;
|
||||
uint16_t Body;
|
||||
uint8_t *Data;
|
||||
unsigned DataCount;
|
||||
} CoreData;
|
||||
struct ProtoCanGeneralAddressSpaceData{
|
||||
uint16_t RegStartAdr; /**< Начальный адрес регистров. */
|
||||
uint16_t *Data; /**< Указатель на массив данных. */
|
||||
unsigned RegCount; /**< Количество регистров. */
|
||||
} GeneralAddressSpaceData;
|
||||
struct ProtoCanModbusData{
|
||||
uint16_t StrAdr;
|
||||
uint16_t *Data;
|
||||
unsigned RegCount:4;
|
||||
} ModbusData;
|
||||
struct ProtoCanErrorData{
|
||||
uint16_t Info;
|
||||
uint16_t Code;
|
||||
} ErrorData;
|
||||
struct ProtoCanSettingsData{
|
||||
const uint8_t *Data;
|
||||
uint8_t DataCount;
|
||||
} SettingsData;
|
||||
} ProtoCanData_t;
|
||||
|
||||
/**
|
||||
* @brief Структура данных устройства CAN.
|
||||
*
|
||||
* Включает статус, идентификатор, таймер последнего импульса и шаг последнего импульса.
|
||||
*/
|
||||
typedef struct{
|
||||
unsigned Status:1; /**< Статус устройства (флаг). */
|
||||
ProtoCanId_t Id; /**< Идентификатор CAN. */
|
||||
unsigned TimeFromLastPulse; /**< Время с последнего импульса (мс). */
|
||||
unsigned LastPulseStep; /**< Шаг последнего импульса. */
|
||||
} ProtoCanDevice_t;
|
||||
|
||||
/**
|
||||
* @brief Структура для хранения сообщения RXCAN.
|
||||
*/
|
||||
struct RXMsg {
|
||||
/**
|
||||
* @brief Структура, содержащая флаги информации.
|
||||
*/
|
||||
struct INFO {
|
||||
unsigned EXT : 1; /**< Бит расширенного идентификатора (EXT). */
|
||||
unsigned RTR : 1; /**< Бит запроса на передачу (RTR). */
|
||||
} info; /**< Информационные флаги сообщения. */
|
||||
ProtoCanId_t eID; /**< Идентификатор CAN-сообщения. */
|
||||
uint16_t DLC; /**< Длина данных (Data Length Code). */
|
||||
uint8_t Data[8]; /**< Массив данных (до 8 байт). */
|
||||
};
|
||||
|
||||
uint16_t AvailableCanRxMsg(void);
|
||||
|
||||
PROTOCAN_INIT_StatusTypeDef PROTOCAN_INIT(CAN_HandleTypeDef *tmp_hcan,
|
||||
RTC_HandleTypeDef *tmp_hrtc,
|
||||
TIM_HandleTypeDef *tmp_tim);
|
||||
void PROTOCAN_DEINIT(uint8_t stage);
|
||||
void PROTOCAN_FILTERS(void);
|
||||
void PROTOCAN_LoopProcessRxMsgs(void);
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ProcessAllRxMsgs(void);
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ProcessSingleRxMsg(void);
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND(ProtoCanId_t id, ProtoCanData_t data);
|
||||
|
||||
void ProtoCanPulseCallback(TIM_HandleTypeDef *htim);
|
||||
void ProtoCanRxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
|
||||
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_BroadcastProcessing(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastStatus(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastOnOff(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastRestart(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToBroadcastRtcSetup(struct RXMsg _rxMsg);
|
||||
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_DiscreticProcessing(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteAccident(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteWarning(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteControlSignals(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteFlags(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteReset(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteChangeMode(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToDiscreteRequestListOfParameters(struct RXMsg _rxMsg);
|
||||
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_AnalogProcessing(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogUniversal(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogSettings(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogUSens(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogISens(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToAnalogTSens(struct RXMsg _rxMsg);
|
||||
|
||||
/** Разбирает запрос SETTINGS и вызывает прикладной обработчик. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SettingsProcessing(struct RXMsg _rxMsg);
|
||||
|
||||
/**
|
||||
* Прикладной обработчик GET/WRITE/CLEAR. Приложение должно переопределить
|
||||
* слабую реализацию и после проверки датчика/EEPROM отправить ответ или ошибку.
|
||||
*/
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToSettings(const ProtoCanSettingsMsg_t *message);
|
||||
|
||||
/** Отправляет произвольный SETTINGS-кадр с уже заполненным Body. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS(ProtoCanId_t id,
|
||||
const struct ProtoCanSettingsData data);
|
||||
|
||||
/** Отправляет успешный ответ 0x1FFEZZYY, DLC=8, с текущим ROM локации. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS_RESPONSE(uint8_t assemblySerial,
|
||||
uint8_t position,
|
||||
const uint8_t *rom);
|
||||
|
||||
/** Отправляет ошибку 0x1FFEZZYY, DLC=1, Data[0]=result. */
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_SEND_SETTINGS_ERROR(uint8_t assemblySerial,
|
||||
uint8_t position,
|
||||
ProtoCanSettingsResultType result);
|
||||
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToGeneralAddressSpace(struct RXMsg _rxMsg);
|
||||
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_RequestError(struct RXMsg _rxMsg);
|
||||
|
||||
PROTOCAN_StatusTypeDef PROTOCAN_ModbusProcessing(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToModbusCoil(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToModbusDiscrete(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToModbusHolding(struct RXMsg _rxMsg);
|
||||
PROTOCAN_StatusTypeDef ProtoCanMsgToModbusInput(struct RXMsg _rxMsg);
|
||||
|
||||
void PROTOCAN_RTC_SYNC(uint8_t *data);
|
||||
|
||||
#endif
|
||||
@@ -10,12 +10,16 @@
|
||||
|---|---|
|
||||
| Однокадровые SETTINGS и bxCAN STM32F1 | [`c/can-sensor`](../../c/can-sensor) |
|
||||
| Единый SET protocol v2 | [`c/set-protocol`](../../c/set-protocol) |
|
||||
| Транспорт ProtoCAN | [`c/protocan-transport`](../../c/protocan-transport) |
|
||||
| Транспорт ProtoCAN | [`c/set-protocol`](../../c/set-protocol) |
|
||||
| STM32 HAL bxCAN/RTC/TIM, API `PROTOCAN_*` | [`c/set-protocol/ports/stm32-bxcan`](../../c/set-protocol/ports/stm32-bxcan/README.md) |
|
||||
| CAN-загрузчик | [`c/protocan-boot`](../../c/protocan-boot) |
|
||||
|
||||
Файлы `Inc/protocan.h` и `Src/protocan.c`, упомянутые в историческом
|
||||
[руководстве](index.html), больше не являются подключаемым исходным кодом.
|
||||
В новых проектах используются нормализованные модули из таблицы выше.
|
||||
[руководстве](index.html), перенесены в `c/set-protocol/ports/stm32-bxcan`
|
||||
с сохранением имён и API. Общий `pcan_id` подключается из этого же `templates`.
|
||||
Отдельный репозиторий SETCAN больше не требуется. Его история до `c8eec55`
|
||||
включена в Git-историю `templates`; документация здесь сохранена без
|
||||
перезаписи старым снимком.
|
||||
|
||||
## Документы
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class SettingsResult(IntEnum):
|
||||
EEPROM_ERROR = 0x05
|
||||
INVALID_LOCATION = 0x06
|
||||
BUSY = 0x07
|
||||
INVALID_VALUE = 0x08
|
||||
|
||||
|
||||
class BroadcastType(IntEnum):
|
||||
@@ -145,6 +146,7 @@ SETTINGS_RESULT_RU = {
|
||||
SettingsResult.EEPROM_ERROR: "ошибка EEPROM",
|
||||
SettingsResult.INVALID_LOCATION: "неверная локация",
|
||||
SettingsResult.BUSY: "устройство занято",
|
||||
SettingsResult.INVALID_VALUE: "значение настройки вне диапазона",
|
||||
}
|
||||
|
||||
BROADCAST_RU = {
|
||||
@@ -680,6 +682,30 @@ def _decode_settings(res: Decoded) -> None:
|
||||
(" SETTINGS.Position Y", "%d (0x%02X)" % (position, position)),
|
||||
]
|
||||
|
||||
# Зарезервированная локация прибора KONOR: runtime-настройки не являются
|
||||
# ROM датчика, хотя используют тот же однокадровый SETTINGS-транспорт.
|
||||
if assembly == 0xFF and position == 0x01:
|
||||
if res.dlc == 0 and res.id.pm == Route.FROM_PM:
|
||||
res.summary = "SETTINGS RUNTIME GET"
|
||||
return
|
||||
if res.dlc == 8:
|
||||
pulse_seconds = res.data[0]
|
||||
led_hz = int.from_bytes(res.data[1:3], "little")
|
||||
res.fields += [
|
||||
(" SETTINGS.Pulse period",
|
||||
"%d s" % pulse_seconds if pulse_seconds else "выключен"),
|
||||
(" SETTINGS.LED frequency", "%d Hz" % led_hz),
|
||||
]
|
||||
if led_hz > 500:
|
||||
res.warnings.append("Частота LED должна быть 0..500 Гц")
|
||||
if any(res.data[3:]):
|
||||
res.warnings.append("Резерв SETTINGS Data[3:8] должен быть нулевым")
|
||||
direction = "WRITE" if res.id.pm == Route.FROM_PM else "RESPONSE"
|
||||
period_text = "%d s" % pulse_seconds if pulse_seconds else "выключен"
|
||||
res.summary = ("SETTINGS RUNTIME %s: PULSE %s, LED %d Hz"
|
||||
% (direction, period_text, led_hz))
|
||||
return
|
||||
|
||||
if res.id.pm == Route.FROM_PM:
|
||||
if res.dlc == 0:
|
||||
operation = "GET"
|
||||
|
||||
Reference in New Issue
Block a user