запущен проект motor identification c терминалкой

This commit is contained in:
2026-06-05 12:15:36 +03:00
commit 177431f3d2
1383 changed files with 840275 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
# Чтение датчиков токов через ADC
## Где реализовано
Чтение токов добавлено в:
```text
AD_Keil_Project/Core/Src/ad_board.c
```
Главная функция:
```c
void AD_Board_ReadMeasurements(AD_Measurements_t *meas);
```
Она вызывается из `AD_Board_Loop()` на каждой итерации основного цикла. Внутри теперь вызывается ADC-слой, который заполняет:
```c
meas->ia_A;
meas->ib_A;
meas->ic_A;
meas->vdc_V;
```
## Распиновка IHM08M1 по схеме ST
Каналы взяты не произвольно. Для `X-NUCLEO-IHM08M1` использована официальная схема ST `x-nucleo-ihm08m1_schematic.pdf`: на рисунке 6 указаны сигналы `PA0 - PhA`, `PC1 - PhB`, `PC0 - PhC`, `PA1 - BUSV`, `PC2 - Temp. Sens.`.
Источник схемы: `https://www.st.com/resource/en/schematic_pack/x-nucleo-ihm08m1_schematic.pdf`.
В коде сейчас читаются три фазных тока и напряжение DC-звена:
| Сигнал | Пин STM32 | ADC-канал | Поле измерений |
|---|---|---:|---|
| Ток фазы A | `PA0` | `ADC1_IN1` | `ia_A` |
| Ток фазы B | `PC1` | `ADC1_IN7` | `ib_A` |
| Ток фазы C | `PC0` | `ADC1_IN6` | `ic_A` |
| DC-звено | `PA1` | `ADC1_IN2` | `vdc_V` |
| Температура IHM08M1 | `PC2` | `ADC1_IN8` | пока не читается |
Эти пины настраиваются прямо в `ad_board.c`: включается тактирование `GPIOA` и `GPIOC`, пины `PA0`, `PA1`, `PC0`, `PC1` переводятся в аналоговый режим, затем `ADC1` читает каналы `1`, `7`, `6`, `2`.
Важно: это пока не CubeMX-конфигурация `.ioc`, а ручной аппаратный слой в коде. После подтверждения схемы надо перенести назначение в CubeMX или аккуратно синхронизировать `.ioc`.
## Масштабирование
По умолчанию используются безопасные макросы:
```c
AD_BOARD_ADC_VREF_V 3.3f
AD_BOARD_ADC_CURRENT_ZERO_V AD_BOARD_ADC_VREF_V * 0.5f
AD_BOARD_ADC_DEFAULT_PHASE_SHUNT_OHM (0.1f)
AD_BOARD_ADC_CURRENT_A_PER_V 1.0f / AD_BOARD_ADC_DEFAULT_PHASE_SHUNT_OHM
AD_BOARD_ADC_VDC_BUS_V_PER_ADC_V 12.1770f
AD_BOARD_ADC_VDC_LOW_CAL_ADC_V 0.618f
AD_BOARD_ADC_VDC_LOW_CAL_BUS_V 10.0f
AD_BOARD_ADC_VDC_HIGH_CAL_BUS_V 14.5f
AD_BOARD_ADC_VDC_AVG_SAMPLES 16
AD_BOARD_ADC_VDC_FILTER_ALPHA 0.125f
```
Формулы:
```text
Uadc = raw * AD_BOARD_ADC_VREF_V / 4095
Iphase = (Uadc - AD_BOARD_ADC_CURRENT_ZERO_V) * AD_BOARD_ADC_CURRENT_A_PER_V * (0.1 / Rshunt)
Udc = adc_vdc_from_voltage(Uadc)
```
`Rshunt` задаётся через Modbus register `0x0041` / `H065_param_phase_shunt_mohm`
с масштабом `1 мОм`. Дефолт `0.1 Ом` читается как `100`; запись `0` возвращает
прошивку к дефолту.
Для DC-звена `Uadc` берется после сглаживания, а не из одного одиночного преобразования:
```text
vdc_raw = последний одиночный ADC-код PA1/BUSV
vdc_raw_avg = средний ADC-код PA1/BUSV
vdc_adc_avg_V = vdc_raw_avg * AD_BOARD_ADC_VREF_V / 4095
vdc_adc_V = IIR(vdc_adc_avg_V, AD_BOARD_ADC_VDC_FILTER_ALPHA)
vdc_V = adc_vdc_from_voltage(vdc_adc_V)
```
По умолчанию `AD_BOARD_ADC_VDC_AVG_SAMPLES=16`, а `AD_BOARD_ADC_VDC_FILTER_ALPHA=0.125`. Для более быстрой реакции можно поднять `AD_BOARD_ADC_VDC_FILTER_ALPHA` до `0.25`; для более спокойной индикации можно опустить до `0.05`.
Для DC-звена сейчас задан измеренный коэффициент по реальной плате:
```text
реальное Udc = 10.0 В
измеренное напряжение на ADC/PA1 = 0.618 В
AD_BOARD_ADC_VDC_LOW_CAL_ADC_V = 0.618
AD_BOARD_ADC_VDC_LOW_CAL_BUS_V = 10.0
AD_BOARD_ADC_VDC_BUS_V_PER_ADC_V = 12.1770
AD_BOARD_ADC_VDC_HIGH_CAL_BUS_V = 14.5
```
После этой калибровки `g_ad_debug.measurements.vdc_V` должен показывать около `10 В`, когда `g_ad_debug.adc.vdc_adc_V` около `0.618 В`.
Если DC-звено прыгает почти на `1 В`, это примерно `77` отсчетов ADC на входе `PA1`, потому что один ADC-код после масштабирования равен примерно:
```text
3.3 / 4095 * 12.1770 = 0.0098 В DC-шины
```
Основные причины такого разброса: одиночная выборка ADC, пульсации DC-звена, наводки от PWM/силовой земли, несинхронный запуск ADC относительно ШИМ, длинные провода/земля измерения или шумный делитель. В коде одиночная выборка DC-звена заменена усреднением и IIR-фильтром.
Для реальной платы нужно также задать настоящие коэффициенты датчиков тока в настройках компиляции Keil или в коде. Коэффициент DC-звена можно переопределить в настройках компиляции, если будет новая точка калибровки.
Пример:
```text
AD_BOARD_ADC_CURRENT_A_PER_V=20.0f
AD_BOARD_ADC_DEFAULT_PHASE_SHUNT_OHM=0.1f
AD_BOARD_ADC_VDC_BUS_V_PER_ADC_V=12.1770f
AD_BOARD_ADC_VDC_LOW_CAL_ADC_V=0.618f
AD_BOARD_ADC_VDC_LOW_CAL_BUS_V=10.0f
AD_BOARD_ADC_VDC_HIGH_CAL_BUS_V=14.5f
AD_BOARD_ADC_VDC_AVG_SAMPLES=16
AD_BOARD_ADC_VDC_FILTER_ALPHA=0.125f
```
## Что смотреть в Keil
В окно наблюдения добавьте:
```c
g_ad_debug.adc
g_ad_debug.measurements
```
Поля `g_ad_debug.adc`:
| Поле | Смысл |
|---|---|
| `initialized` | ADC успешно запущен |
| `faults` | ошибки init или ожидания преобразования |
| `ia_raw`, `ib_raw`, `ic_raw` | сырые 12-битные значения ADC токовых каналов |
| `vdc_raw` | последний одиночный 12-битный ADC-код `PA1/BUSV` |
| `vdc_raw_avg` | средний ADC-код `PA1/BUSV` после `AD_BOARD_ADC_VDC_AVG_SAMPLES` преобразований |
| `ia_adc_V`, `ib_adc_V`, `ic_adc_V` | напряжения на входах ADC токовых каналов |
| `vdc_adc_avg_V` | напряжение `PA1/BUSV` после среднего по нескольким преобразованиям |
| `vdc_adc_V` | отфильтрованное напряжение `PA1/BUSV`, которое используется для расчета `vdc_V` |
Поля `g_ad_debug.measurements.ia_A`, `ib_A`, `ic_A`, `vdc_V` показывают уже пересчитанные физические величины.
## Что осталось проверить на железе
- Соответствует ли подключенная плата именно распиновке `X-NUCLEO-IHM08M1`: `PA0/PhA`, `PC1/PhB`, `PC0/PhC`, `PA1/BUSV`.
- Нужно ли добавить чтение температуры `PC2 / ADC1_IN8`.
- Какой ноль у датчиков тока: обычно около `Vref/2`, но это надо измерить.
- Какой коэффициент `А/В` у датчиков тока.
- Уточнить коэффициент делителя DC-звена второй точкой, например на 20-30 В, если питание и безопасность позволяют.
- Нет ли инверсии знака токов.
- Нужно ли запускать ADC синхронно от `TIM1`, а не из основного цикла.
Для первого включения с силовой частью держать `AD_INVERTER_ENABLE_OUTPUTS=0`, смотреть только сырые ADC и пересчитанные значения.

View File

@@ -0,0 +1,43 @@
# Binary Telemetry Transport
The firmware initializes two physical transports for the existing binary
telemetry packet:
- USB FS CDC ACM on PA11/PA12, clocked from HSI48.
- FDCAN1 classic CAN on PB8/PB9, default 500 kbit/s with an external CAN transceiver.
Modbus RTU on USART2 remains active as the control channel. This is intentional:
the host can always switch the telemetry mode back without reflashing.
## Modbus Selection Registers
Modbus protocol/register-map version is `7`.
| Address hex / dec | Field | Access | Purpose |
|---:|---|---|---|
| `0x001C` / `28` | `H028_control_host_protocol` | R/W | `0`=Modbus control only, `1`=binary telemetry output enabled |
| `0x001D` / `29` | `H029_control_binary_transport` | R/W | `0`=USB CDC, `1`=CAN/FDCAN1, `2`=USB CDC and CAN |
## USB CDC
USB is exposed as a CDC ACM serial stream. The stream does not guarantee message
boundaries, so the PC side should parse by the telemetry header:
- `magic`
- `payload_size`
- `sequence`
- `crc16`
## CAN/FDCAN
CAN carries the same binary packet split into classic 8-byte frames:
| Standard ID | Payload |
|---:|---|
| `0x5A0` | packet bytes `0..7` |
| `0x5A1` | packet bytes `8..15` |
| `0x5A2` | packet bytes `16..23` |
The base standard ID is controlled by `AD_CAN_TELEMETRY_STD_ID_BASE`, default
`0x5A0`. The receiver reconstructs the packet from sequential frame IDs and
uses the normal binary telemetry header to determine the full packet length.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,176 @@
# Как начать тест и что смотреть в отладке
## Главное
В проект добавлены три уровня запуска:
- безопасное логирование без силовых ключей;
- тестовый PWM на каждый отдельный вход драйвера `UH/UL/VH/VL/WH/WL`;
- начальный алгоритм идентификации, который сейчас оценивает `Rs`, `Ls` и `Ll`.
По умолчанию физические PWM-выходы выключены на уровне сборки. Это сделано намеренно: прошивку можно залить и смотреть ADC без риска включить ключи.
## Где вход в приложение
Основной проект Keil:
```text
F:\set\workspace\setcorp\set506\AD\AD_Keil_Project\MDK-ARM\IHM08M.uvprojx
```
Основная Simulink-модель:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl\inu_im_1wnd_3lvl.slx
```
В коде запуск находится в `main.c`:
```c
AD_Project_Init();
while (1)
{
AD_Project_Loop();
}
```
## Безопасный первый запуск
Для проверки измерений без ключей:
```c
AD_Board_StartDataLogging();
```
Останов:
```c
AD_Board_StopParamTest();
```
Кнопка `B1 / PC13` переключает только `DATA_LOGGING`, силовые ключи она не включает.
## Как разрешить реальные PWM-импульсы
Перед этим нужно проверить питание, `BKIN`, полярности входов драйвера, dead-time и ограничения тока/напряжения.
В Keil, в `Options for Target -> C/C++ -> Define`, можно включить один общий макрос:
```text
AD_PROJECT_POWER_TEST_ENABLE=1
```
Он внутри включает:
```text
AD_PARAM_ID_ENABLE_POWER_TESTS=1
AD_INVERTER_ENABLE_OUTPUTS=1
```
Если `AD_PROJECT_POWER_TEST_ENABLE=0`, запрос силового режима будет переведён в безопасное логирование с флагом `AD_PARAM_ID_STATUS_POWER_TEST_BLOCKED`.
## PWM-тест каждого канала
Для проверки входов драйвера осциллографом:
```c
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_PWM_TEST_UH, 0.08f);
AD_Board_StopParamTest();
```
Второй аргумент это duty. Если передать `0`, будет использовано значение по умолчанию `AD_PARAM_ID_DEFAULT_PWM_DUTY_LIMIT = 0.08`. Внутренний тестовый алгоритм дополнительно ограничивает duty до `0.20`.
Рекомендуемый порядок проверки:
| Режим | Код | Пин IHM08M1 |
|---|---:|---|
| `AD_PARAM_ID_MODE_PWM_TEST_UH` | 6 | `PA8 / TIM1_CH1 / UH` |
| `AD_PARAM_ID_MODE_PWM_TEST_UL` | 7 | `PA7 / TIM1_CH1N / UL` |
| `AD_PARAM_ID_MODE_PWM_TEST_VH` | 8 | `PA9 / TIM1_CH2 / VH` |
| `AD_PARAM_ID_MODE_PWM_TEST_VL` | 9 | `PB0 / TIM1_CH2N / VL` |
| `AD_PARAM_ID_MODE_PWM_TEST_WH` | 10 | `PA10 / TIM1_CH3 / WH` |
| `AD_PARAM_ID_MODE_PWM_TEST_WL` | 11 | `PB1 / TIM1_CH3N / WL` |
| `AD_PARAM_ID_MODE_PWM_TEST_ALL` | 12 | все три плеча, duty фаз `0.5` |
В режиме одного канала проект не перезапускает таймер каждые 10 мс: выбранный канал удерживается, а последующие шаги только обновляют compare.
## Режимы идентификации
| Режим | Код | Что делает сейчас |
|---|---:|---|
| `AD_PARAM_ID_MODE_IDLE` | 0 | выключено |
| `AD_PARAM_ID_MODE_STATOR_RESISTANCE` | 1 | оценивает `Rs_ohm` коротким DC-вектором |
| `AD_PARAM_ID_MODE_NO_LOAD_MAGNETIZING` | 2 | оценивает `Lm_H` вращающимся полем без нагрузки |
| `AD_PARAM_ID_MODE_LOCKED_ROTOR_LEAKAGE` | 3 | оценивает `Ll_H` и `Rr_ohm`; требует `AD_ParamID_SetLockedRotorAllowed(1)` |
| `AD_PARAM_ID_MODE_INERTIA_FRICTION` | 4 | оценивает `J` и `B` по разгону/выбегу; нужен ненулевой `speed_rpm` |
| `AD_PARAM_ID_MODE_DATA_LOGGING` | 5 | только измерения |
| `AD_PARAM_ID_MODE_AUTO_IDENTIFICATION` | 13 | последовательно делает `Rs`, `Ll`, `Rr`, `Lm`, затем `J/B` |
| `AD_PARAM_ID_MODE_ROTATION_3HZ` | 14 | 6-step вращение с настраиваемой частотой и модуляцией |
Запуск автоидентификации:
```c
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_AUTO_IDENTIFICATION, 0.08f);
```
Запуск проверки вращения 3 Гц:
```c
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_ROTATION_3HZ, 0.35f);
```
Через Modbus для этого режима:
```text
write 0x000D / 13 = 30 ; частота 3.00 Hz
write 0x000E / 14 = 3500 ; модуляция 0.35
```
`J` и `B` помечаются действительными только если во время разгона и выбега приходит скорость `speed_rpm`; при нулевой скорости алгоритм не записывает фиктивные механические параметры.
## Что смотреть в Keil
Добавить в Watch:
```c
g_ad_debug
```
Основные поля:
| Поле | Смысл |
|---|---|
| `g_ad_debug.adc.ia_raw`, `ib_raw`, `ic_raw` | сырые ADC-коды токов |
| `g_ad_debug.adc.vdc_raw_avg` | усреднённый ADC-код `BUSV` |
| `g_ad_debug.adc.vdc_adc_V` | отфильтрованное напряжение на входе `PA1/BUSV` |
| `g_ad_debug.measurements.vdc_V` | пересчитанное напряжение DC-звена |
| `g_ad_debug.command.test_mode` | запрошенный режим |
| `g_ad_debug.param_id_mode` | фактический режим |
| `g_ad_debug.inverter.pwm_running` | TIM1 PWM реально запущен |
| `g_ad_debug.inverter.service_pwm_running` | включён одиночный/сервисный PWM-тест |
| `g_ad_debug.inverter.service_output` | какой выход проверяется: `1..7` |
| `g_ad_debug.inverter.id_stage` | стадия алгоритма измерения |
| `g_ad_debug.motor_parameters.valid_mask` | какие параметры действительны |
| `g_ad_debug.motor_parameters.Rs_ohm` | измеренное сопротивление статора |
| `g_ad_debug.motor_parameters.Ls_H` | измеренная индуктивность статора |
| `g_ad_debug.motor_parameters.Ll_H` | оценка индуктивности рассеяния |
## Флаги состояния
| Флаг | Смысл |
|---|---|
| `AD_PARAM_ID_STATUS_ACTIVE` | режим активен |
| `AD_PARAM_ID_STATUS_POWER_TEST_BLOCKED` | силовой тест заблокирован |
| `AD_PARAM_ID_STATUS_POWER_STAGE_ARMED` | силовая часть программно разрешена |
| `AD_PARAM_ID_STATUS_FAULT_LATCHED` | авария защёлкнута |
| `AD_PARAM_ID_STATUS_TIMEOUT` | истекло время теста |
| `AD_PARAM_ID_STATUS_LOCKED_ROTOR_BLOCKED` | locked-rotor режим не разрешён |
| `AD_PARAM_ID_STATUS_SAFETY_LIMITS_UNKNOWN` | не заданы пределы тока или напряжения |
| `AD_PARAM_ID_STATUS_DATA_VALID` | есть актуальные измерения/данные |
| `AD_PARAM_ID_STATUS_COMPLETE` | алгоритм измерения завершён |
| `AD_PARAM_ID_STATUS_PARTIAL_COMPLETE` | автоидентификация завершилась частично: часть параметров действительна |
| `AD_PARAM_ID_STATUS_STEP_FAILED` | этап измерения не смог получить достаточный ток/напряжение/скорость |
| `AD_PARAM_ID_STATUS_LOCKED_ROTOR_SKIPPED` | locked-rotor этап пропущен, потому что `locked_rotor_allowed=0` |
Любой `Stop`, fault или переход в блокировку вызывает аппаратное выключение PWM через `AD_ParamID_HardwareDisable()`.

View File

@@ -0,0 +1,29 @@
# Файлы, добавленные в Keil
В собранном проекте `AD_Keil_Project/MDK-ARM/IHM08M.uvprojx` уже добавлена группа:
```text
Application/User/AD Param ID
```
В неё включены:
| Файл | Назначение |
|---|---|
| `Core/Src/ad_project.c` | Общая инициализация проекта, единая точка `AD_Project_Init()`, цикл `AD_Project_Loop()` и структура `g_ad_debug` |
| `Core/Src/ad_board.c` | Слой платы, кнопка, LED, безопасный цикл приложения |
| `Core/Src/ad_inverter.c` | Инициализация и управление ключами инвертора, безопасное выключение PWM |
| `Core/Src/ad_parameter_identification.c` | Состояние идентификации, защиты, команды тестов |
| `Core/Src/simulink_interface.c` | Обмен измерениями, командами и телеметрией |
Заголовочные файлы лежат в `Core/Inc`, этот путь уже есть в путях включения проекта.
Файл `Core/Src/ad_debug.c` оставлен как пустая совместимая единица трансляции. Для сборки он не обязателен: `AD_Debug_Init()`, `AD_Debug_Update()` и `g_ad_debug` реализованы в `Core/Src/ad_project.c`. Это сделано специально, чтобы проект не падал линковкой, если Keil ещё не подхватил новый файл в списке исходников.
Если проект будет перенесён в новый `.uvprojx`, нужно добавить те же `.c` файлы и путь:
```text
Core/Inc
```
Сгенерированные файлы Simulink вручную не править, если это не предусмотрено процессом генерации.

View File

@@ -0,0 +1,97 @@
# Карта слоя драйвера двигателя
## Что сейчас реализовано
Проект содержит рабочий слой TIM1 для IHM08M1:
- `PA8 / TIM1_CH1 / UH`;
- `PA7 / TIM1_CH1N / UL`;
- `PA9 / TIM1_CH2 / VH`;
- `PB0 / TIM1_CH2N / VL`;
- `PA10 / TIM1_CH3 / WH`;
- `PB1 / TIM1_CH3N / WL`;
- `PA6 / TIM1_BKIN / BKIN`.
TIM1 PWM timing: center-aligned mode, `ARR=32767`, `PSC=0`; carrier remains about 2.6 kHz.
`MX_TIM1_Init()` настраивает PWM1 на трёх каналах, комплементарные выходы, dead-time `AD_TIM1_DEADTIME_TICKS=100` и Break input.
## Безопасность включения
По умолчанию физические выходы запрещены:
```text
AD_PROJECT_POWER_TEST_ENABLE=0
```
Для стендового включения можно задать:
```text
AD_PROJECT_POWER_TEST_ENABLE=1
```
Это включает внутренние макросы:
```text
AD_PARAM_ID_ENABLE_POWER_TESTS=1
AD_INVERTER_ENABLE_OUTPUTS=1
```
При `Stop`, fault, блокировке силового теста или `enable=0` вызывается `AD_ParamID_HardwareDisable()`, который в реализации `ad_inverter.c` гасит TIM1 через `AD_Inverter_Disable()`.
## API инвертора
| Функция | Назначение |
|---|---|
| `AD_Inverter_Init()` | инициализация состояния inverter слоя |
| `AD_Inverter_Disable()` | останов всех PWM и сброс duty |
| `AD_Inverter_SetDuty(a,b,c)` | запись duty трёх фаз |
| `AD_Inverter_Enable()` | запуск всех трёх каналов и комплементарных выходов |
| `AD_Inverter_StartPwmOutput(output,duty)` | сервисный PWM на один вход драйвера или на все плечи |
| `AD_Inverter_ApplyCommand()` | применить трёхфазную команду |
## PWM-тест каналов
Сервисные режимы:
| Режим | Канал |
|---|---|
| `AD_PARAM_ID_MODE_PWM_TEST_UH` | `PA8 / UH` |
| `AD_PARAM_ID_MODE_PWM_TEST_UL` | `PA7 / UL` |
| `AD_PARAM_ID_MODE_PWM_TEST_VH` | `PA9 / VH` |
| `AD_PARAM_ID_MODE_PWM_TEST_VL` | `PB0 / VL` |
| `AD_PARAM_ID_MODE_PWM_TEST_WH` | `PA10 / WH` |
| `AD_PARAM_ID_MODE_PWM_TEST_WL` | `PB1 / WL` |
| `AD_PARAM_ID_MODE_PWM_TEST_ALL` | все три плеча, duty фаз `0.5` |
Пример:
```c
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_PWM_TEST_UH, 0.08f);
```
Одиночный PWM-тест удерживает уже запущенный канал и не делает `Stop/Start` на каждом slow-step.
## Алгоритм измерения
Сейчас в `AD_ParamID_HardwareStep()` реализованы:
| Режим | Результат |
|---|---|
| `AD_PARAM_ID_MODE_STATOR_RESISTANCE` | измеряет `Rs_ohm`, ставит `AD_MOTOR_PARAM_VALID_RS` |
| `AD_PARAM_ID_MODE_LOCKED_ROTOR_LEAKAGE` | измеряет `Ll_H` и `Rr_ohm`; после `Lm_H` пересчитывает `Ls_H/Lr_H` |
| `AD_PARAM_ID_MODE_NO_LOAD_MAGNETIZING` | измеряет `Lm_H` вращающимся полем без нагрузки |
| `AD_PARAM_ID_MODE_INERTIA_FRICTION` | измеряет `J_kg_m2` и `B_Nm_s` по разгону/выбегу при наличии `speed_rpm` |
| `AD_PARAM_ID_MODE_AUTO_IDENTIFICATION` | последовательно выполняет `Rs`, `Ll`, `Rr`, `Lm`, затем `J/B` |
| `AD_PARAM_ID_MODE_ROTATION_3HZ` | выдаёт 6-step вращение до 3 Гц для проверки работы двигателя |
Для расчёта `J` и `B` нужен реальный сигнал скорости в `AD_Measurements_t.speed_rpm`; при нулевой скорости механические параметры не помечаются действительными.
## Что ещё нужно проверить на железе
- полярность `UH/UL/VH/VL/WH/WL` на входе реального драйвера;
- что `BKIN` реально выключает TIM1;
- dead-time на затворах или входах драйвера;
- масштаб и ноль токовых датчиков;
- предел тока и напряжения перед первым запуском с двигателем;
- наличие отдельного `EN_GATE`, если он есть на конкретной плате.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
# Распиновка STM32G474
## Источники
| Объект | Статус | Примечание |
|---|---|---|
| Проект `STM32G474CEU6` | не найден | Подтверждённого `.uvprojx/.ioc` для CEU6 нет |
| Кандидатный проект | найден | `IHM08M`, цель `STM32G474RETx` |
| CubeMX `.ioc` | найден | `AD_Keil_Project/IHM08M.ioc` |
| Схема `X-NUCLEO-IHM08M1` | найдена | источник для ADC-сигналов `PhA`, `PhB`, `PhC`, `BUSV`, `Temp. Sens.`: `https://www.st.com/resource/en/schematic_pack/x-nucleo-ihm08m1_schematic.pdf` |
| Плата | кандидат | `NUCLEO-G474RE`, не целевая плата CEU6 |
| Старый embedded-код | найден | C2000/TMS320 в `Inu_im_1wnd_3lvl/Inu` |
## Подтверждённые пины STM32G474CEU6
Подтверждённой распиновки для `STM32G474CEU6` в проекте нет. Ниже приведена только кандидатная распиновка из `STM32G474RETx / NUCLEO-G474RE`.
## Кандидатная распиновка из `IHM08M.ioc` и схемы IHM08M1
| Пин | Сигнал | Направление | Назначение | Источник |
|---|---|---|---|---|
| `PC13` | `B1` | вход EXTI | кнопка пользователя | `.ioc`, `main.h` |
| `PC14` | `RCC_OSC32_IN` | вход | LSE | `.ioc`, `main.h` |
| `PC15` | `RCC_OSC32_OUT` | выход | LSE | `.ioc`, `main.h` |
| `PC0` | `ADC1_IN6` | аналоговый вход | IHM08M1 `PhC`, ток фазы C | схема IHM08M1, `ad_board.c` |
| `PC1` | `ADC1_IN7` | аналоговый вход | IHM08M1 `PhB`, ток фазы B | схема IHM08M1, `ad_board.c` |
| `PC2` | `ADC1_IN8` | аналоговый вход | IHM08M1 `Temp. Sens.`, пока не читается | схема IHM08M1 |
| `PF0` | `RCC_OSC_IN` | вход | HSE | `.ioc`, `main.h` |
| `PF1` | `RCC_OSC_OUT` | выход | HSE | `.ioc`, `main.h` |
| `PA0` | `ADC1_IN1` | аналоговый вход | IHM08M1 `PhA`, ток фазы A | схема IHM08M1, `ad_board.c` |
| `PA1` | `ADC1_IN2` | аналоговый вход | IHM08M1 `BUSV`, напряжение DC-звена | схема IHM08M1, `ad_board.c` |
| `PA5` | не используется | не инициализируется | IHM08M1 `PA5 - DAC`; конфликт с `LD2` убран | схема IHM08M1, `ad_board.c` |
| `PA6` | `TIM1_BKIN` | AF-вход | аппаратный `BKIN` | `.ioc`, `stm32g4xx_hal_msp.c`, схема IHM08M1 |
| `PA8` | `TIM1_CH1` | AF-выход | IHM08M1 `UH`, верхний ключ U | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PA7` | `TIM1_CH1N` | AF-выход | IHM08M1 `UL`, нижний ключ U | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PA9` | `TIM1_CH2` | AF-выход | IHM08M1 `VH`, верхний ключ V | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PB0` | `TIM1_CH2N` | AF-выход | IHM08M1 `VL`, нижний ключ V | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PA10` | `TIM1_CH3` | AF-выход | IHM08M1 `WH`, верхний ключ W | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PB1` | `TIM1_CH3N` | AF-выход | IHM08M1 `WL`, нижний ключ W | `.ioc`, `stm32g4xx_hal_msp.c`, `ad_inverter.c` |
| `PA13` | `T_SWDIO` | отладка | SWDIO | `.ioc`, `main.h` |
| `PA14` | `T_SWCLK` | отладка | SWCLK | `.ioc`, `main.h` |
| `PB3` | `GPIO_Input` | вход | IHM08M1 `Enc. B/H2`; SWO убран из `.ioc` | `.ioc`, схема IHM08M1 |
## Периферия кандидатного проекта
| Периферия | Статус |
|---|---|
| ADC | вручную включен `ADC1` в `ad_board.c`; `.ioc` еще не синхронизирован |
| TIM/PWM | шесть линий IHM08M1 через `TIM1`: `UH/UL`, `VH/VL`, `WH/WL`; `PA6/TIM1_BKIN` включен |
| GPIO | кнопка `B1`; `PA5/LD2` не используется по умолчанию |
| DMA | HAL-файлы есть, CubeMX init нет |
| UART/USART | не включён |
| CAN/FDCAN | не включён |
| SPI/I2C | не включён |
| OPAMP/COMP | не включён |
| Watchdog | не настроен |
| TIM break | `PA6 / TIM1_BKIN`, `TIM_BREAKPOLARITY_HIGH`; полярность надо проверить на железе |
## Сигналы старого C2000-кода
Эти строки не являются STM32-распиновкой. Они нужны только как чек-лист миграции:
| Сигнал C2000 | Назначение |
|---|---|
| `EPWM1A/B` .. `EPWM6A/B` | PWM-выходы инвертора |
| `ADCRESULT0` | DC-звено `udc1` |
| `ADCRESULT2` | ток `ic1` |
| `ADCRESULT4` | ток `ia1` |
| `ADCRESULT6` | ток `ib1` |
Текущее STM32-сопоставление-кандидат:
| Сигнал | STM32 |
|---|---|
| `ia_A` | `PA0 / ADC1_IN1` |
| `ib_A` | `PC1 / ADC1_IN7` |
| `ic_A` | `PC0 / ADC1_IN6` |
| `vdc_V` | `PA1 / ADC1_IN2`, кусочная калибровка: `0.618 В ADC -> 10 В`, выше `14.5 В` масштаб `12.1770 В/В` |
| `EQEP2A/B/I` | датчик скорости/положения |
| `DI_24V_SOURCE_FAULT` | авария +24 В |
| `SPIA` + `CS` | EEPROM или внешний SPI |
| `LED_GREEN1/2`, `LED_RED` | индикация |
## Что нужно для настоящей платы CEU6
- Добавить настоящий `.ioc` и `.uvprojx` для `STM32G474CEU6`.
- Заполнить ШИМ-пины, комплементарные выходы, мёртвое время и break.
- Заполнить ADC-каналы токов, DC-звена и температуры.
- Добавить входы аварии и аварийного стопа.
- Перегенерировать этот документ по реальному CubeMX-проекту.

View File

@@ -0,0 +1,144 @@
# Распиновка, измерения и управление тестами
## Проект
Keil-проект:
```text
F:\set\workspace\setcorp\set506\AD\AD_Keil_Project\MDK-ARM\IHM08M.uvprojx
```
Распиновка силовой части приведена к `X-NUCLEO-IHM08M1`. ADC-каналы и PWM-линии не выбраны случайно: они сверены с IHM08M1.
## Распиновка IHM08M1
Разъёмы указаны для платы `NUCLEO-G474RE`: ST morpho `CN7/CN10`, Arduino `CN5/CN8/CN9` и ST-LINK VCP. Для `PC1` и `PC0` на ST morpho в документации ST есть альтернативы через solder bridges, поэтому их нужно проверить на конкретной плате.
| Пин STM32 | Функция | Сигнал IHM08M1 | Разъём NUCLEO | Пин разъёма | Использование в проекте |
|---|---|---|---|---:|---|
| `PA0` | `ADC1_IN1` | `PhA` | `CN8` / `CN7` | `1` / `28` | ток фазы A |
| `PC1` | `ADC1_IN7` | `PhB` | `CN8` / `CN7` | `5` / `36` | ток фазы B; на `CN7-36` проверить вариант `PC1/PB9` |
| `PC0` | `ADC1_IN6` | `PhC` | `CN8` / `CN7` | `6` / `38` | ток фазы C; на `CN7-38` проверить вариант `PC0/PA15` |
| `PA1` | `ADC1_IN2` | `BUSV` | `CN8` / `CN7` | `2` / `30` | напряжение DC-звена |
| `PC2` | `ADC1_IN8` | `Temp. Sens.` | `CN7` | `35` | пин учтён, чтение температуры ещё не подключено |
| `PA8` | `TIM1_CH1` | `UH` | `CN9` / `CN10` | `8` / `23` | верхний ключ U |
| `PA7` | `TIM1_CH1N` | `UL` | `CN5` / `CN10` | `4` / `15` | нижний ключ U |
| `PA9` | `TIM1_CH2` | `VH` | `CN5` / `CN10` | `1` / `21` | верхний ключ V |
| `PB0` | `TIM1_CH2N` | `VL` | `CN8` / `CN7` | `4` / `34` | нижний ключ V |
| `PA10` | `TIM1_CH3` | `WH` | `CN9` / `CN10` | `3` / `33` | верхний ключ W |
| `PB1` | `TIM1_CH3N` | `WL` | `CN10` | `24` | нижний ключ W |
| `PA6` | `TIM1_BKIN` | `BKIN` | `CN5` / `CN10` | `5` / `13` | аппаратное аварийное отключение TIM1 |
| `PA2` | `USART2_TX` | ST-LINK VCP TX | `CN10` | `35` | Modbus RTU; также ST-LINK VCP при нужных solder bridges |
| `PA3` | `USART2_RX` | ST-LINK VCP RX | `CN10` | `37` | Modbus RTU; также ST-LINK VCP при нужных solder bridges |
| `PC13` | GPIO/EXTI | `START/STOP` | `CN7` | `23` | кнопка `B1`, переключает логирование |
| `PA5` | не используется | `PA5 - DAC` | `CN5` / `CN10` | `6` / `11` | конфликт с `LD2` убран |
| `PB3` | GPIO input | `Enc. B/H2` | `CN9` / `CN10` | `4` / `31` | SWO убран из `.ioc` |
Подробная сверка всех пинов находится в `AD_docs/PIN_USAGE_IHM08M1_COMPARE.md`.
## Измеряемые параметры
`AD_Board_ReadMeasurements()` читает ADC и заполняет `AD_Measurements_t`.
| Величина | Поле | Источник | Примечание |
|---|---|---|---|
| ток фазы A | `ia_A` | `PA0 / ADC1_IN1` | масштаб тока пока задаётся макросами |
| ток фазы B | `ib_A` | `PC1 / ADC1_IN7` | масштаб тока пока задаётся макросами |
| ток фазы C | `ic_A` | `PC0 / ADC1_IN6` | масштаб тока пока задаётся макросами |
| DC-звено | `vdc_V` | `PA1 / ADC1_IN2` | учтено измерение `10.0 В -> 0.618 В` на ADC |
| время | `timestamp_us` | `HAL_GetTick() * 1000` | шаг 1 мс в текущей реализации |
DC-звено усредняется по `AD_BOARD_ADC_VDC_AVG_SAMPLES=16` выборкам и фильтруется IIR-фильтром `AD_BOARD_ADC_VDC_FILTER_ALPHA=0.125`. Коэффициент делителя:
```text
AD_BOARD_ADC_VDC_BUS_V_PER_ADC_V = 12.1770
AD_BOARD_ADC_VDC_LOW_CAL_ADC_V = 0.618
AD_BOARD_ADC_VDC_LOW_CAL_BUS_V = 10.0
AD_BOARD_ADC_VDC_HIGH_CAL_BUS_V = 14.5
```
## Команда теста
```c
typedef struct
{
uint8_t enable;
uint8_t test_mode;
uint8_t reset_faults;
uint16_t pwm_polarity_flags;
uint16_t pwm_timing_mode;
uint16_t motor_control_type;
uint16_t rotation_ramp_time_ms;
float pwm_duty_limit;
float rotation_frequency_Hz;
float rotation_modulation;
float current_limit_A;
float voltage_limit_V;
float undervoltage_limit_V;
float speed_limit_rpm;
float temperature_limit_C;
} AD_Command_t;
```
| Поле | Назначение |
|---|---|
| `enable` | `1` включает режим, `0` выключает |
| `test_mode` | один из `AD_PARAM_ID_MODE_*` |
| `reset_faults` | сброс защёлкнутых аварий |
| `pwm_polarity_flags` | bit0 инвертирует `UH/VH/WH`, bit1 инвертирует `UL/VL/WL` |
| `pwm_timing_mode` | `0` = up-counting PWM, `1` = center-aligned PWM |
| `motor_control_type` | `0` = AD/sine, `1` = BLDC/6-step |
| `rotation_ramp_time_ms` | время выхода `rotation_frequency_Hz`/`rotation_modulation` на уставку, по умолчанию `3000 ms` |
| `pwm_duty_limit` | лимит duty для PWM-тестов и измерительных импульсов |
| `rotation_frequency_Hz` | частота режима `AD_PARAM_ID_MODE_ROTATION_3HZ`, по умолчанию `3.0 Hz` |
| `rotation_modulation` | коэффициент модуляции режима `AD_PARAM_ID_MODE_ROTATION_3HZ`, по умолчанию `0.35` |
| `current_limit_A` | программный предел тока |
| `voltage_limit_V` | предел перенапряжения DC-звена |
| `undervoltage_limit_V` | предел недонапряжения, `0` отключает проверку |
| `speed_limit_rpm` | предел скорости |
| `temperature_limit_C` | предел температуры |
## Режимы
| Режим | Код | Назначение |
|---|---:|---|
| `AD_PARAM_ID_MODE_IDLE` | 0 | выключено |
| `AD_PARAM_ID_MODE_STATOR_RESISTANCE` | 1 | измерение `Rs_ohm` |
| `AD_PARAM_ID_MODE_NO_LOAD_MAGNETIZING` | 2 | измерение `Lm_H` |
| `AD_PARAM_ID_MODE_LOCKED_ROTOR_LEAKAGE` | 3 | измерение `Ll_H` и `Rr_ohm` |
| `AD_PARAM_ID_MODE_INERTIA_FRICTION` | 4 | измерение `J/B` при наличии `speed_rpm` |
| `AD_PARAM_ID_MODE_DATA_LOGGING` | 5 | только сбор данных |
| `AD_PARAM_ID_MODE_PWM_TEST_UH` | 6 | PWM на `PA8 / UH` |
| `AD_PARAM_ID_MODE_PWM_TEST_UL` | 7 | PWM на `PA7 / UL` |
| `AD_PARAM_ID_MODE_PWM_TEST_VH` | 8 | PWM на `PA9 / VH` |
| `AD_PARAM_ID_MODE_PWM_TEST_VL` | 9 | PWM на `PB0 / VL` |
| `AD_PARAM_ID_MODE_PWM_TEST_WH` | 10 | PWM на `PA10 / WH` |
| `AD_PARAM_ID_MODE_PWM_TEST_WL` | 11 | PWM на `PB1 / WL` |
| `AD_PARAM_ID_MODE_PWM_TEST_ALL` | 12 | все три плеча, duty фаз `0.5` |
| `AD_PARAM_ID_MODE_AUTO_IDENTIFICATION` | 13 | `Rs`, `Ll`, `Rr`, `Lm`, затем `J/B` |
| `AD_PARAM_ID_MODE_ROTATION_3HZ` | 14 | 6-step вращение с настраиваемой частотой и модуляцией |
## Управление из кода
```c
AD_Board_StartDataLogging();
AD_Board_StartParamTest(AD_PARAM_ID_MODE_STATOR_RESISTANCE);
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_PWM_TEST_UH, 0.08f);
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_AUTO_IDENTIFICATION, 0.08f);
AD_Board_StartParamTestWithDuty(AD_PARAM_ID_MODE_ROTATION_3HZ, 0.35f);
AD_Board_StopParamTest();
```
## Условия для реального PWM
Нужны все условия:
- сборка с `AD_PROJECT_POWER_TEST_ENABLE=1` или с двумя макросами `AD_PARAM_ID_ENABLE_POWER_TESTS=1` и `AD_INVERTER_ENABLE_OUTPUTS=1`;
- `AD_Command_t.enable = 1`;
- `current_limit_A > 0`;
- `voltage_limit_V > 0`;
- нет защёлкнутой аварии;
- `BKIN` не активен;
- для режима `AD_PARAM_ID_MODE_LOCKED_ROTOR_LEAKAGE` вызван `AD_ParamID_SetLockedRotorAllowed(1)`.
Если любое условие не выполнено, проект выключает TIM1 и уходит в `DATA_LOGGING`.

View File

@@ -0,0 +1,75 @@
# Сравнение использованных пинов с X-NUCLEO-IHM08M1
## Источник
Основной источник для сверки: официальная схема ST `x-nucleo-ihm08m1_schematic.pdf`.
```text
https://www.st.com/resource/en/schematic_pack/x-nucleo-ihm08m1_schematic.pdf
```
Сверка сделана по текущему проекту `AD_Keil_Project`, файлам:
- `Core/Inc/main.h`
- `Core/Src/main.c`
- `Core/Src/stm32g4xx_hal_msp.c`
- `Core/Src/ad_board.c`
- `Core/Src/ad_inverter.c`
- `IHM08M.ioc`
## Итог коротко
Проект приведен к распиновке `X-NUCLEO-IHM08M1` для измерений и шести управляющих линий ключей.
- ADC токов и DC-звена совпадает со схемой IHM08M1.
- ШИМ ключей переведен на `TIM1`: `UH/UL`, `VH/VL`, `WH/WL`.
- `PA5` больше не используется как `LD2` по умолчанию, чтобы не конфликтовать с линией `PA5 - DAC` на IHM08M1.
- `PB3` в `.ioc` больше не `SWO`; он помечен как `ENC_B_H2`.
- `PA6 / TIM1_BKIN` добавлен как аппаратный вход аварийного отключения.
Физические силовые выходы по-прежнему запрещены макросами `AD_INVERTER_ENABLE_OUTPUTS=0` и `AD_PARAM_ID_ENABLE_POWER_TESTS=0`, пока не проверены полярности, dead-time и аварийная цепь на железе.
## Таблица всех использованных пинов
| Пин | Где используется в проекте | Текущий режим проекта | Назначение по IHM08M1 | Статус |
|---|---|---|---|---|
| `PA0` | `ad_board.c`, `.ioc` | `ADC1_IN1` | `PhA`, ток фазы A | Совпадает |
| `PC1` | `ad_board.c`, `.ioc` | `ADC1_IN7` | `PhB`, ток фазы B | Совпадает |
| `PC0` | `ad_board.c`, `.ioc` | `ADC1_IN6` | `PhC`, ток фазы C | Совпадает |
| `PA1` | `ad_board.c`, `.ioc` | `ADC1_IN2` | `BUSV`, напряжение DC-звена | Совпадает, кусочная калибровка: `0.618 В ADC -> 10 В`, выше `14.5 В` масштаб `12.1770 В/В` |
| `PC2` | `.ioc`, документация | `ADC1_IN8` | `Temp. Sens.` | Пин задан, чтение температуры еще не реализовано |
| `PA8` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH1` | `UH`, верхний ключ фазы U | Совпадает |
| `PA7` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH1N` | `UL`, нижний ключ фазы U | Совпадает |
| `PA9` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH2` | `VH`, верхний ключ фазы V | Совпадает |
| `PB0` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH2N` | `VL`, нижний ключ фазы V | Совпадает |
| `PA10` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH3` | `WH`, верхний ключ фазы W | Совпадает |
| `PB1` | `stm32g4xx_hal_msp.c`, `ad_inverter.c`, `.ioc` | `TIM1_CH3N` | `WL`, нижний ключ фазы W | Совпадает |
| `PA6` | `stm32g4xx_hal_msp.c`, `.ioc` | `TIM1_BKIN` | `BKIN`, аппаратная защита | Добавлено, полярность проверить на железе |
| `PC13` | `main.c`, `ad_board.c`, `stm32g4xx_it.c`, `.ioc` | `EXTI`, кнопка `B1` | `START/STOP (B1 BUTTON)` | Совпадает |
| `PA5` | не инициализируется в `main.c`; LED отключен макросом | не используется по умолчанию | `PA5/PB13`, `PA5 - DAC` | Конфликт с LED убран |
| `PB3` | `.ioc` | `GPIO_Input`, метка `ENC_B_H2` | `Enc. B/H2` | Конфликт с SWO убран в `.ioc` |
| `PA13` | `main.h`, `.ioc` | `SWDIO` | не силовой сигнал IHM08M1 | Оставлено для отладки |
| `PA14` | `main.h`, `.ioc` | `SWCLK` | не силовой сигнал IHM08M1 | Оставлено для отладки |
| `PC14` | `main.h`, `.ioc` | `RCC_OSC32_IN` | не назначен как сигнал IHM08M1 | Не относится к шилду |
| `PC15` | `main.h`, `.ioc` | `RCC_OSC32_OUT` | не назначен как сигнал IHM08M1 | Не относится к шилду |
| `PF0` | `main.h`, `.ioc` | `RCC_OSC_IN` | не назначен как сигнал IHM08M1 | Не относится к шилду |
| `PF1` | `main.h`, `.ioc` | `RCC_OSC_OUT` | не назначен как сигнал IHM08M1 | Не относится к шилду |
## Что сейчас безопасно
- ADC-измерения `PA0`, `PC1`, `PC0`, `PA1` можно смотреть в `g_ad_debug.adc`.
- Для `PA1/BUSV` значение `vdc_adc_V = 0.618 В` пересчитывается примерно в `vdc_V = 10.0 В`.
- `PC13/B1` можно использовать для включения и выключения `DATA_LOGGING`.
- Силовые выходы физически не разрешены по умолчанию, потому что `AD_INVERTER_ENABLE_OUTPUTS=0` и `AD_PARAM_ID_ENABLE_POWER_TESTS=0`.
## Что еще проверить на железе
- Полярность верхних и нижних входов драйверов `UH/UL`, `VH/VL`, `WH/WL`.
- Dead-time: сейчас задано `AD_TIM1_DEADTIME_TICKS=100`.
- Полярность `BKIN`: сейчас `TIM_BREAKPOLARITY_HIGH`.
- Нужно ли использовать `PA5 - DAC` для задания порога защиты.
- Нужны ли `PA15/Enc. A/H1`, `PB3/Enc. B/H2`, `PB10/Enc. Z/H3` для скорости.
## Вывод
Распиновка проекта теперь соответствует IHM08M1 для измерений, шести ключей, кнопки и `BKIN`. Остались проверки физических полярностей и параметров защиты перед включением силовых макросов.

View File

@@ -0,0 +1,122 @@
# Последовательность инициализации проекта
Основная точка входа находится в:
```text
AD_Keil_Project/Core/Src/main.c
```
После сброса выполняется такая последовательность:
```c
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
AD_Project_Init();
```
В основном цикле выполняется:
```c
AD_Project_Loop();
```
## Что инициализируется
| Шаг | Функция | Что делает |
|---|---|---|
| 1 | `HAL_Init()` | Сбрасывает периферию HAL, запускает SysTick и базовое состояние HAL |
| 2 | `SystemClock_Config()` | Настраивает тактирование STM32G4 на 170 МГц от HSI/PLL |
| 3 | `MX_GPIO_Init()` | Включает тактирование GPIO, настраивает кнопку `B1` и прерывание `EXTI15_10`; `PA5/LD2` не используется по умолчанию |
| 4 | `MX_TIM1_Init()` | Настраивает `TIM1` для шести PWM-линий IHM08M1 `UH/UL`, `VH/VL`, `WH/WL`, dead-time и `PA6/TIM1_BKIN`; PWM не запускается автоматически |
| 5 | `AD_Project_Init()` | Общий старт прикладного слоя проекта |
## Что делает `AD_Project_Init()`
`AD_Project_Init()` находится в:
```text
AD_Keil_Project/Core/Src/ad_project.c
```
Внутри вызывается `AD_Board_Init()`, а она поднимает прикладные подсистемы:
| Подсистема | Функция | Состояние после запуска |
|---|---|---|
| Плата | `AD_Board_Init()` | Сбрасывает флаги кнопки, режим логирования и индикацию |
| ADC измерений | внутренний `board_adc_init()` | Настраивает `PA0`, `PA1`, `PC0`, `PC1` как аналоговые входы по схеме IHM08M1 и запускает `ADC1` |
| Ключи инвертора | `AD_Inverter_Init()` | Ставит duty в ноль, останавливает PWM, помечает слой как инициализированный |
| Интерфейс модели | `SimulinkInterface_Init()` | Сбрасывает входные и выходные шины, телеметрию и команду |
| Идентификация | `AD_ParamID_Init()` | Сбрасывает параметры, измерения, защиты, режимы и ошибки |
| Команда по умолчанию | внутренний вызов `board_apply_command(0, AD_PARAM_ID_MODE_IDLE)` | Проект стартует в выключенном режиме |
## Инициализация ключей
Слой ключей находится в:
```text
AD_Keil_Project/Core/Inc/ad_inverter.h
AD_Keil_Project/Core/Src/ad_inverter.c
```
Доступные функции:
| Функция | Назначение |
|---|---|
| `AD_Inverter_Init()` | Инициализирует программное состояние инвертора и гарантированно выключает PWM |
| `AD_Inverter_Disable()` | Ставит duty в ноль и останавливает основной и комплементарный PWM на подтвержденном канале |
| `AD_Inverter_SetDuty()` | Записывает duty A/B/C в состояние; сейчас в регистр TIM1 попадает только подтвержденный канал A |
| `AD_Inverter_Enable()` | Пытается запустить PWM, но только если разрешены все программные защиты |
| `AD_Inverter_ApplyCommand()` | Применяет команду включения и duty |
| `AD_Inverter_GetState()` | Возвращает состояние слоя инвертора для отладки |
Сейчас для IHM08M1 заведены:
```text
PA8 / TIM1_CH1 -> UH
PA7 / TIM1_CH1N -> UL
PA9 / TIM1_CH2 -> VH
PB0 / TIM1_CH2N -> VL
PA10 / TIM1_CH3 -> WH
PB1 / TIM1_CH3N -> WL
PA6 / TIM1_BKIN -> BKIN
```
Полный трехфазный драйвер требует аппаратной проверки полярностей, dead-time, `BKIN` и каналов измерения перед разрешением силовых макросов.
## Защитные условия включения PWM
В стандартной сборке ключи не включатся, даже если вызвать `AD_Inverter_Enable()`.
Нужно одновременно выполнить условия:
- в настройках компиляции задан `AD_PARAM_ID_ENABLE_POWER_TESTS=1`;
- в настройках компиляции задан `AD_INVERTER_ENABLE_OUTPUTS=1`;
- нет защелкнутой аварии;
- заданы пределы тока и напряжения;
- текущий режим действительно требует силового теста;
- для режима locked-rotor отдельно вызван `AD_ParamID_SetLockedRotorAllowed(1)`;
- подтверждена аппаратная распиновка и проверены защиты.
Если хотя бы одно условие не выполнено, `AD_Inverter_Enable()` выключает PWM и возвращает `0`.
## Состояние проекта для отладки
Для отладки добавлен:
```c
const AD_ProjectState_t* AD_Project_GetState(void);
```
Структура показывает:
| Поле | Смысл |
|---|---|
| `initialized` | Общий прикладной слой запущен |
| `board_initialized` | Инициализация платы выполнена |
| `inverter_initialized` | Слой инвертора прошел init |
| `simulink_initialized` | Интерфейс модели прошел init |
| `outputs_allowed_by_build` | Макрос `AD_INVERTER_ENABLE_OUTPUTS` разрешает физические выходы |
| `param_id_status` | Текущий статус идентификации |
| `param_id_faults` | Текущие ошибки идентификации |

View File

@@ -0,0 +1,128 @@
# Интеграция с Simulink
## Где модель
Основная модель:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl\inu_im_1wnd_3lvl.slx
```
Версия для R2021b:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl\inu_im_1wnd_3lvl_r2021b.slx
```
Инструкция запуска модели находится в:
```text
AD_Keil_Project\AD_docs\SIMULINK_MODEL_RUN_HELP.md
```
## Что найдено
| Артефакт | Статус |
|---|---|
| `Inu_im_1wnd_3lvl/inu_im_1wnd_3lvl.slx` | найден |
| `Inu_im_1wnd_3lvl/inu_im_1wnd_3lvl_r2021b.slx` | найден |
| `Inu_im_1wnd_3lvl/Inu/wrapper_inu.c` | найден |
| `Inu_im_1wnd_3lvl/Inu/controller.c` | найден |
| сгенерированный ERT-код `model.c/.h` | не найден |
Сейчас Keil-проект не является прямой автогенерацией из Simulink. В нём сделан C-интерфейс, через который можно связать модель, хост или ручной код с измерениями и командами.
## C-интерфейс
Файлы:
- `Core/Inc/simulink_interface.h`;
- `Core/Src/simulink_interface.c`;
- `Core/Inc/ad_parameter_identification.h`;
- `Core/Src/ad_parameter_identification.c`.
Основные функции:
```c
void SimulinkInterface_Init(void);
void SimulinkInterface_SetMeasurements(const AD_Measurements_t *meas);
void SimulinkInterface_SetCommand(const AD_Command_t *command);
void SimulinkInterface_StepFast(void);
void SimulinkInterface_StepSlow(void);
void SimulinkInterface_PackTelemetry(void);
```
## Команда от модели к MCU
Использовать `SimulinkInterface_OutputBus_t.command`:
```c
typedef struct
{
uint8_t enable;
uint8_t test_mode;
uint8_t reset_faults;
uint16_t pwm_polarity_flags;
uint16_t pwm_timing_mode;
uint16_t motor_control_type;
uint16_t rotation_ramp_time_ms;
float pwm_duty_limit;
float rotation_frequency_Hz;
float rotation_modulation;
float current_limit_A;
float voltage_limit_V;
float undervoltage_limit_V;
float speed_limit_rpm;
float temperature_limit_C;
} AD_Command_t;
```
Минимальные условия для силового режима:
- `enable = 1`;
- `test_mode` равен одному из режимов `1`, `3`, `6..14`;
- `current_limit_A > 0`;
- `voltage_limit_V > 0`;
- сборка с `AD_PROJECT_POWER_TEST_ENABLE=1`;
- нет активной аварии.
## Режимы для модели
| Код | Режим |
|---:|---|
| 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` |
## Данные от MCU к модели
Использовать `SimulinkInterface_InputBus_t`:
- `measurements` - токи, DC-звено, скорость, температура, время;
- `motor_parameters` - оценённые параметры двигателя;
- `param_id_status` - флаги состояния;
- `param_id_faults` - флаги ошибок;
- `param_id_mode` - фактический режим.
Результаты измерений считаются действительными только по `motor_parameters.valid_mask`.
## Частоты вызова
В текущем Keil-проекте:
- `SimulinkInterface_StepFast()` вызывается на каждой итерации `AD_Project_Loop()`;
- `SimulinkInterface_StepSlow()` вызывается раз в 10 мс.
Для настоящего привода быстрый шаг лучше переносить в прерывание ADC/PWM, но текущая реализация уже позволяет проверить каналы, телеметрию и начальные измерительные импульсы.

View File

@@ -0,0 +1,144 @@
# Помощь по запуску Simulink-модели
## Где находится модель
Simulink-модель находится не в каталоге Keil-проекта, а в исходной папке рабочей области:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl
```
Полный путь к основной модели:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl\inu_im_1wnd_3lvl.slx
```
Относительный путь от рабочей папки `AD`:
```text
Inu_im_1wnd_3lvl/inu_im_1wnd_3lvl.slx
```
Основные файлы:
| Файл | Назначение |
|---|---|
| `inu_im_1wnd_3lvl.slx` | основная Simulink-модель |
| `inu_im_1wnd_3lvl_r2021b.slx` | версия модели для MATLAB/Simulink R2021b |
| `init.m` | задание параметров модели |
| `wrapper_inu.mexw64` | скомпилированная S-function для Windows 64-bit |
| `allmex.m` | скрипт пересборки S-function |
| `Inu/*.c`, `Inu/*.h` | исходники S-function и C2000-логики |
Короткое описание из `ReadMe.txt`: один трёхуровневый инвертор питает однообмоточный асинхронный двигатель.
## Быстрый запуск
В MATLAB выполнить:
```matlab
cd('F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl')
run('init.m')
open_system('inu_im_1wnd_3lvl_r2021b.slx')
```
Если используется более новая версия MATLAB/Simulink, можно открыть:
```matlab
open_system('inu_im_1wnd_3lvl.slx')
```
После открытия модели запустить расчёт кнопкой запуска в Simulink или командой:
```matlab
sim('inu_im_1wnd_3lvl_r2021b')
```
## Что должно быть перед запуском
- Рабочая папка MATLAB должна быть `Inu_im_1wnd_3lvl`.
- Нужно выполнить `init.m`, иначе в workspace не будет параметров двигателя, инвертора и шага моделирования.
- В папке должен быть доступен `wrapper_inu.mexw64`.
- Разрядность MATLAB должна быть 64-bit Windows, потому что готовый файл имеет расширение `.mexw64`.
## Если модель ругается на S-function
Проверьте наличие файла:
```text
F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl\wrapper_inu.mexw64
```
Если MATLAB сообщает, что S-function не найдена:
```matlab
cd('F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl')
which wrapper_inu
```
Если `which` ничего не показывает, добавьте папку модели в путь:
```matlab
addpath('F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl')
```
## Пересборка S-function
Скрипт `allmex.m` есть, но сейчас он содержит старые абсолютные пути вида:
```text
F:\Work\Projects\MATLAB\Drives_lib_new\...
```
Поэтому пересборка через `allmex.m` без правки путей, скорее всего, не сработает. Для обычного запуска пересборка не нужна, потому что готовый `wrapper_inu.mexw64` уже лежит рядом с моделью.
Если понадобится пересобрать S-function, нужно:
1. Исправить include/source пути в `allmex.m` на актуальные.
2. Убедиться, что в MATLAB настроен компилятор:
```matlab
mex -setup C
```
3. Запустить:
```matlab
run('allmex.m')
```
## Связь с Keil-проектом
Keil-проект находится здесь:
```text
F:\set\workspace\setcorp\set506\AD\AD_Keil_Project\MDK-ARM\IHM08M.uvprojx
```
Simulink-модель не является сгенерированным Keil-проектом. Сейчас это отдельная модель/симулятор, а Keil-проект содержит безопасный STM32-каркас и интерфейсные структуры:
- `AD_Measurements_t`;
- `AD_MotorParameters_t`;
- `AD_Command_t`;
- `SimulinkInterface_InputBus_t`;
- `SimulinkInterface_OutputBus_t`.
Для настоящей связки Simulink -> STM32 нужно либо генерировать код из модели, либо вручную связать модельные сигналы с этими структурами.
## Минимальная проверка модели
В MATLAB:
```matlab
cd('F:\set\workspace\setcorp\set506\AD\Inu_im_1wnd_3lvl')
run('init.m')
exist('wrapper_inu', 'file')
open_system('inu_im_1wnd_3lvl_r2021b.slx')
```
Ожидаемо:
- `exist('wrapper_inu', 'file')` возвращает `3` для MEX-файла;
- модель открывается без ошибки отсутствующей S-function;
- параметры `Ts`, `Rs`, `Rr`, `Lls`, `Llr`, `Lm`, `UdcNom`, `Inom` появились в workspace.

View File

@@ -0,0 +1,455 @@
# Протокол телеметрии
Документ описывает текущий бинарный пакет телеметрии из прошивки
`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 |
Перед использованием результата идентификации обязательно проверять
соответствующий бит `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` |
| `9` | `0x00000200` | `AD_PARAM_ID_STATUS_PARTIAL_COMPLETE` |
| `10` | `0x00000400` | `AD_PARAM_ID_STATUS_STEP_FAILED` |
| `11` | `0x00000800` | `AD_PARAM_ID_STATUS_LOCKED_ROTOR_SKIPPED` |
`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` |
| Скорость | `512000` |
| Формат | `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
```