Files
motor_params/docs/TELEMETRY_PROTOCOL.md

454 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Протокол телеметрии
Документ описывает текущий бинарный пакет телеметрии из прошивки
`AD_Keil_Project`.
Основные файлы реализации:
| Файл | Назначение |
|---|---|
| `Core/Inc/simulink_interface.h` | Формат пакета, версия, размеры, API |
| `Core/Src/simulink_interface.c` | Заполнение пакета и CRC16 |
| `Core/Inc/ad_binary_transport.h` | Выбор протокола и транспорта |
| `Core/Src/ad_binary_transport.c` | Отправка готового пакета |
| `Core/Src/ad_usb_cdc.c` | USB FS CDC ACM транспорт |
| `Core/Src/ad_can_telemetry.c` | FDCAN1 classic CAN транспорт |
| `Core/Src/ad_modbus.c` | Modbus-регистры включения бинарной телеметрии |
## Кратко
Прошивка формирует один бинарный пакет `SimulinkTelemetryPacket_t`.
Пакет можно отдавать через USB CDC, через FDCAN1 classic CAN или одновременно
через оба транспорта.
По умолчанию бинарный поток выключен:
```c
AD_HOST_PROTOCOL_DEFAULT = 0
AD_BINARY_TELEMETRY_TRANSPORT_DEFAULT = 0
```
То есть после старта активен режим `0` - Modbus-контроль без бинарной
телеметрии. Чтобы включить поток, нужно через Modbus записать:
```text
0x001D / 29 = транспорт
0x001C / 28 = 1
```
Где транспорт:
| Значение | Транспорт |
|---:|---|
| `0` | USB CDC |
| `1` | CAN/FDCAN1 |
| `2` | USB CDC и CAN/FDCAN1 одновременно |
Чтобы выключить бинарный поток:
```text
0x001C / 28 = 0
```
## Частота
Пакет обновляется в `SimulinkInterface_StepSlow()`, который вызывается из
`AD_Board_Loop()` раз в `AD_BOARD_SLOW_PERIOD_MS = 10` мс.
Отправка из `AD_BinaryTransport_Loop()` также ограничена периодом:
```c
AD_BINARY_TELEMETRY_PERIOD_MS = 10
```
Итого штатная частота телеметрии - около `100 Гц`, если главный цикл успевает
обслуживать транспорт.
## Кодирование данных
| Параметр | Значение |
|---|---|
| Порядок байт | little-endian, как у STM32 Cortex-M |
| `float` | IEEE-754 binary32, 4 байта |
| Выравнивание | обычное C-выравнивание ARMCLANG, без `packed` |
| Текущий размер заголовка | `16` байт |
| Текущий размер payload | `136` байт |
| Текущий размер полного пакета | `152` байта |
| Максимум, разрешенный кодом | `SIMULINK_TELEMETRY_MAX_BYTES = 256` |
Если структура в C будет изменена, PC-парсер должен проверять
`header.version` и `header.payload_size`.
## Заголовок пакета
Тип: `SimulinkTelemetryHeader_t`.
Смещения указаны от начала пакета.
| Offset | Размер | Тип | Поле | Значение / описание |
|---:|---:|---|---|---|
| `0` | `4` | `uint32_t` | `magic` | `0x41444944` |
| `4` | `2` | `uint16_t` | `version` | `3` |
| `6` | `2` | `uint16_t` | `payload_size` | `136` для текущей версии |
| `8` | `4` | `uint32_t` | `sequence` | Увеличивается при каждой упаковке |
| `12` | `2` | `uint16_t` | `crc16` | CRC16-CCITT по payload |
| `14` | `2` | `uint16_t` | `reserved` | `0` |
`magic` сравнивать как `uint32_t` little-endian. На проводе байты идут так:
```text
44 49 44 41
```
## CRC16
CRC считается только по payload, без заголовка.
Параметры:
| Параметр | Значение |
|---|---|
| Алгоритм | CRC16-CCITT |
| Начальное значение | `0xFFFF` |
| Полином | `0x1021` |
| Reflection | нет |
| Final XOR | нет |
Проверка на стороне ПК:
```text
crc16_ccitt(packet[16 : 16 + payload_size]) == header.crc16
```
## Payload
Тип: `SimulinkTelemetryPayload_t`.
Смещения в таблице указаны от начала payload. Чтобы получить смещение от начала
пакета, прибавь `16`.
### Измерения
Блок `measurements`, тип `AD_Measurements_t`, размер `64` байта.
| Payload offset | Тип | Поле | Единицы |
|---:|---|---|---|
| `0` | `float` | `measurements.ia_A` | A |
| `4` | `float` | `measurements.ib_A` | A |
| `8` | `float` | `measurements.ic_A` | A |
| `12` | `float` | `measurements.ia_rms_A` | A RMS |
| `16` | `float` | `measurements.ib_rms_A` | A RMS |
| `20` | `float` | `measurements.ic_rms_A` | A RMS |
| `24` | `float` | `measurements.vdc_V` | V |
| `28` | `float` | `measurements.va_V` | V |
| `32` | `float` | `measurements.vb_V` | V |
| `36` | `float` | `measurements.vc_V` | V |
| `40` | `float` | `measurements.speed_rpm` | rpm |
| `44` | `float` | `measurements.torque_Nm` | N*m |
| `48` | `float` | `measurements.temperature_C` | град C |
| `52` | `uint32_t` | `measurements.timestamp_us` | us |
| `56` | `uint32_t` | `measurements.status_flags` | `AD_MEAS_STATUS_*` |
| `60` | `float` | `measurements.slip_percent` | % |
`measurements.status_flags`:
| Бит | Маска | Флаг |
|---:|---:|---|
| `0` | `0x00000001` | `AD_MEAS_STATUS_OVERCURRENT` |
| `1` | `0x00000002` | `AD_MEAS_STATUS_OVERVOLTAGE` |
| `2` | `0x00000004` | `AD_MEAS_STATUS_UNDERVOLTAGE` |
| `3` | `0x00000008` | `AD_MEAS_STATUS_OVERTEMPERATURE` |
| `4` | `0x00000010` | `AD_MEAS_STATUS_DRIVER_FAULT` |
| `5` | `0x00000020` | `AD_MEAS_STATUS_EMERGENCY_STOP` |
### Параметры двигателя
Блок `motor_parameters`, тип `AD_MotorParameters_t`, размер `60` байт.
| Payload offset | Тип | Поле | Единицы |
|---:|---|---|---|
| `64` | `float` | `motor_parameters.Rs_ohm` | Ohm |
| `68` | `float` | `motor_parameters.Rr_ohm` | Ohm |
| `72` | `float` | `motor_parameters.Ls_H` | H |
| `76` | `float` | `motor_parameters.Lr_H` | H |
| `80` | `float` | `motor_parameters.Lm_H` | H |
| `84` | `float` | `motor_parameters.Ll_H` | H |
| `88` | `float` | `motor_parameters.J_kg_m2` | kg*m2 |
| `92` | `float` | `motor_parameters.B_Nm_s` | N*m*s |
| `96` | `float` | `motor_parameters.pole_pairs` | пары полюсов |
| `100` | `float` | `motor_parameters.nominal_voltage_V` | V |
| `104` | `float` | `motor_parameters.nominal_current_A` | A |
| `108` | `float` | `motor_parameters.nominal_frequency_Hz` | Hz |
| `112` | `float` | `motor_parameters.nominal_power_W` | W |
| `116` | `float` | `motor_parameters.nominal_speed_rpm` | rpm |
| `120` | `uint32_t` | `motor_parameters.valid_mask` | `AD_MOTOR_PARAM_VALID_*` |
`valid_mask`:
| Бит | Маска | Флаг | Поле |
|---:|---:|---|---|
| `0` | `0x00000001` | `AD_MOTOR_PARAM_VALID_RS` | `Rs_ohm` |
| `1` | `0x00000002` | `AD_MOTOR_PARAM_VALID_RR` | `Rr_ohm` |
| `2` | `0x00000004` | `AD_MOTOR_PARAM_VALID_LS` | `Ls_H` |
| `3` | `0x00000008` | `AD_MOTOR_PARAM_VALID_LR` | `Lr_H` |
| `4` | `0x00000010` | `AD_MOTOR_PARAM_VALID_LM` | `Lm_H` |
| `5` | `0x00000020` | `AD_MOTOR_PARAM_VALID_LL` | `Ll_H` |
| `6` | `0x00000040` | `AD_MOTOR_PARAM_VALID_J` | `J_kg_m2` |
| `7` | `0x00000080` | `AD_MOTOR_PARAM_VALID_B` | `B_Nm_s` |
| `8` | `0x00000100` | `AD_MOTOR_PARAM_VALID_NOMINALS` | nominal fields |
| `9` | `0x00000200` | `AD_MOTOR_PARAM_VALID_POLE_PAIRS` | `pole_pairs` |
Перед использованием результата идентификации обязательно проверять
соответствующий бит `valid_mask`.
### Статус идентификации и команда
| Payload offset | Тип | Поле | Описание |
|---:|---|---|---|
| `124` | `uint32_t` | `param_id_status` | `AD_PARAM_ID_STATUS_*` |
| `128` | `uint32_t` | `param_id_faults` | `AD_PARAM_ID_FAULT_*` |
| `132` | `uint8_t` | `param_id_mode` | Текущий режим `AD_PARAM_ID_MODE_*` |
| `133` | `uint8_t` | `command_enable` | Последняя команда enable |
| `134` | `uint8_t` | `command_test_mode` | Последняя команда test mode |
| `135` | `uint8_t` | `reserved` | `0` |
`param_id_status`:
| Бит | Маска | Флаг |
|---:|---:|---|
| `0` | `0x00000001` | `AD_PARAM_ID_STATUS_ACTIVE` |
| `1` | `0x00000002` | `AD_PARAM_ID_STATUS_POWER_TEST_BLOCKED` |
| `2` | `0x00000004` | `AD_PARAM_ID_STATUS_POWER_STAGE_ARMED` |
| `3` | `0x00000008` | `AD_PARAM_ID_STATUS_FAULT_LATCHED` |
| `4` | `0x00000010` | `AD_PARAM_ID_STATUS_TIMEOUT` |
| `5` | `0x00000020` | `AD_PARAM_ID_STATUS_LOCKED_ROTOR_BLOCKED` |
| `6` | `0x00000040` | `AD_PARAM_ID_STATUS_SAFETY_LIMITS_UNKNOWN` |
| `7` | `0x00000080` | `AD_PARAM_ID_STATUS_DATA_VALID` |
| `8` | `0x00000100` | `AD_PARAM_ID_STATUS_COMPLETE` |
`param_id_faults`:
| Бит | Маска | Флаг |
|---:|---:|---|
| `0` | `0x00000001` | `AD_PARAM_ID_FAULT_OVERCURRENT` |
| `1` | `0x00000002` | `AD_PARAM_ID_FAULT_OVERVOLTAGE` |
| `2` | `0x00000004` | `AD_PARAM_ID_FAULT_UNDERVOLTAGE` |
| `3` | `0x00000008` | `AD_PARAM_ID_FAULT_OVERTEMPERATURE` |
| `4` | `0x00000010` | `AD_PARAM_ID_FAULT_DRIVER` |
| `5` | `0x00000020` | `AD_PARAM_ID_FAULT_EMERGENCY_STOP` |
| `6` | `0x00000040` | `AD_PARAM_ID_FAULT_TIMEOUT` |
| `7` | `0x00000080` | `AD_PARAM_ID_FAULT_NULL_INPUT` |
Режимы `param_id_mode` и `command_test_mode`:
| Значение | Режим |
|---:|---|
| `0` | `AD_PARAM_ID_MODE_IDLE` |
| `1` | `AD_PARAM_ID_MODE_STATOR_RESISTANCE` |
| `2` | `AD_PARAM_ID_MODE_NO_LOAD_MAGNETIZING` |
| `3` | `AD_PARAM_ID_MODE_LOCKED_ROTOR_LEAKAGE` |
| `4` | `AD_PARAM_ID_MODE_INERTIA_FRICTION` |
| `5` | `AD_PARAM_ID_MODE_DATA_LOGGING` |
| `6` | `AD_PARAM_ID_MODE_PWM_TEST_UH` |
| `7` | `AD_PARAM_ID_MODE_PWM_TEST_UL` |
| `8` | `AD_PARAM_ID_MODE_PWM_TEST_VH` |
| `9` | `AD_PARAM_ID_MODE_PWM_TEST_VL` |
| `10` | `AD_PARAM_ID_MODE_PWM_TEST_WH` |
| `11` | `AD_PARAM_ID_MODE_PWM_TEST_WL` |
| `12` | `AD_PARAM_ID_MODE_PWM_TEST_ALL` |
| `13` | `AD_PARAM_ID_MODE_AUTO_IDENTIFICATION` |
| `14` | `AD_PARAM_ID_MODE_ROTATION_3HZ` |
## USB CDC транспорт
USB реализован как CDC ACM устройство на USB FS:
| Параметр | Значение |
|---|---|
| Линии | `PA11 USB_DM`, `PA12 USB_DP` |
| Тактирование | `HSI48` |
| VID/PID | `0x0483 / 0x5740` |
| Product string | `AD Telemetry CDC` |
| Data IN endpoint | `0x81`, bulk, 64 байта |
| Data OUT endpoint | `0x01`, bulk, 64 байта |
| CMD endpoint | `0x82`, interrupt, 8 байт |
Код вызывает `HAL_PCD_EP_Transmit()` на весь пакет сразу, но CDC на стороне ПК
нужно считать потоком байт. Один `read()` на ПК не обязан совпадать с одним
пакетом прошивки.
Рекомендуемый парсер:
1. Искать в потоке magic bytes `44 49 44 41`.
2. Дочитать 16-байтный заголовок.
3. Проверить `version == 3`.
4. Проверить `payload_size == 136` или обработать совместимую новую версию.
5. Дочитать `payload_size` байт.
6. Проверить CRC16.
7. Передать payload в декодер.
## CAN/FDCAN транспорт
CAN реализован через FDCAN1 в режиме classic CAN, кадры по 8 байт.
| Параметр | Значение |
|---|---|
| Периферия | `FDCAN1` |
| RX/TX | `PB8 / PB9` |
| Frame format | Classic CAN |
| Bit rate switch | Off |
| Default base standard ID | `0x5A0` |
| Payload одного CAN-кадра | до 8 байт |
Базовый ID задается:
```c
AD_CAN_TELEMETRY_STD_ID_BASE = 0x5A0
```
Полный бинарный пакет режется последовательно:
| CAN ID | Данные |
|---:|---|
| `0x5A0` | bytes `0..7` |
| `0x5A1` | bytes `8..15` |
| `0x5A2` | bytes `16..23` |
| `...` | `...` |
Для текущего пакета `152` байта нужно `19` CAN-кадров:
```text
ceil(152 / 8) = 19
IDs: 0x5A0..0x5B2
```
Последний кадр несет полный 8-байтный блок.
Рекомендация для приемника CAN:
1. Считать кадр с `base_id`.
2. Начать буфер нового пакета.
3. Добавлять кадры `base_id + index`.
4. После первых 16 байт прочитать `payload_size`.
5. Ждать `16 + payload_size` байт.
6. Проверить `magic`, `version`, `sequence`, `crc16`.
7. При пропуске ID или смене `sequence` сбросить сборку пакета.
## Включение через Modbus
Modbus RTU остается каналом управления.
| Параметр | Значение |
|---|---|
| UART | `USART2` |
| TX/RX | `PA2 / PA3` |
| Скорость | `115200` |
| Формат | `8N1` |
| Slave address | `1` |
Регистры:
| Адрес hex / dec | Имя | Доступ | Значение |
|---:|---|---|---|
| `0x001C` / `28` | `control_host_protocol` | R/W | `0` = Modbus, `1` = binary telemetry |
| `0x001D` / `29` | `control_binary_transport` | R/W | `0` = USB, `1` = CAN, `2` = USB + CAN |
Пример включения USB CDC:
```text
write 0x001D / 29 = 0
write 0x001C / 28 = 1
```
Пример включения CAN:
```text
write 0x001D / 29 = 1
write 0x001C / 28 = 1
```
Пример одновременной отправки USB и CAN:
```text
write 0x001D / 29 = 2
write 0x001C / 28 = 1
```
Остановка бинарной телеметрии:
```text
write 0x001C / 28 = 0
```
## Состояние транспорта в отладчике
Основной debug-view:
```c
g_ad_debug.binary_transport
g_ad_debug.modbus
```
`g_ad_debug.binary_transport.status_flags`:
| Бит | Маска | Флаг |
|---:|---:|---|
| `0` | `0x00000001` | `AD_BINARY_TRANSPORT_STATUS_INITIALIZED` |
| `1` | `0x00000002` | `AD_BINARY_TRANSPORT_STATUS_USB_READY` |
| `2` | `0x00000004` | `AD_BINARY_TRANSPORT_STATUS_CAN_READY` |
| `3` | `0x00000008` | `AD_BINARY_TRANSPORT_STATUS_USB_CONFIGURED` |
| `4` | `0x00000010` | `AD_BINARY_TRANSPORT_STATUS_USB_TX_BUSY` |
| `5` | `0x00000020` | `AD_BINARY_TRANSPORT_STATUS_CAN_TX_BUSY` |
| `6` | `0x00000040` | `AD_BINARY_TRANSPORT_STATUS_LAST_DROPPED` |
Полезные счетчики:
| Поле | Значение |
|---|---|
| `tx_packets` | Сколько пакетов успешно поставлено хотя бы в один транспорт |
| `usb_packets` | Сколько пакетов принято USB TX |
| `can_packets` | Сколько пакетов принято CAN TX |
| `dropped_packets` | Сколько пакетов сброшено из-за busy/error/size |
| `last_size_bytes` | Последний размер пакета, сейчас должен быть `152` |
| `last_tx_tick_ms` | `HAL_GetTick()` последней успешной постановки |
## API прошивки
```c
void SimulinkInterface_PackTelemetry(void);
const SimulinkTelemetryPacket_t* SimulinkInterface_GetTelemetryPacket(void);
const uint8_t* SimulinkInterface_GetTelemetryBytes(void);
size_t SimulinkInterface_GetTelemetrySize(void);
void AD_BinaryTransport_Init(void);
void AD_BinaryTransport_Loop(void);
uint8_t AD_BinaryTransport_SetProtocol(uint16_t protocol);
uint8_t AD_BinaryTransport_SetTransport(uint16_t transport);
const AD_BinaryTransportState_t* AD_BinaryTransport_GetState(void);
```
## Минимальный Python-декодер CRC
```python
def crc16_ccitt(data: bytes) -> int:
crc = 0xFFFF
for value in data:
crc ^= value << 8
for _ in range(8):
if crc & 0x8000:
crc = ((crc << 1) ^ 0x1021) & 0xFFFF
else:
crc = (crc << 1) & 0xFFFF
return crc
```
Поля unpack для текущей версии:
```python
HEADER_FMT = "<IHHIHH"
PAYLOAD_SIZE_V3 = 136
PACKET_SIZE_V3 = 16 + PAYLOAD_SIZE_V3
MAGIC = 0x41444944
VERSION = 3
```