# Modbus RTU регистры

## Порт

Modbus RTU slave запущен на ST-LINK VCP:

| Параметр | Значение |
|---|---|
| UART | `USART2` |
| TX | `PA2 / USART2_TX` |
| RX | `PA3 / USART2_RX` |
| Скорость | `512000` |
| Формат | `8N1` |
| Slave address | `1` |

Приём `USART2` сделан через `USART2_IRQHandler()` и `HAL_UARTEx_ReceiveToIdle_IT()`. Готовый кадр фиксируется в `HAL_UARTEx_RxEventCallback()` по событию IDLE на линии UART, после этого основной цикл проверяет CRC и отправляет ответ.

Настройки задаются в `Core/Inc/ad_project_config.h`:

```c
AD_MODBUS_ENABLE
AD_MODBUS_SLAVE_ADDRESS
AD_MODBUS_UART_BAUDRATE
```

Поддержанные функции:

| Функция | Назначение |
|---:|---|
| `0x03` | чтение holding registers |
| `0x06` | запись одного holding register |
| `0x10` | запись нескольких holding registers |

## Структура holding-регистров в прошивке

Текущие значения holding-регистров зеркалируются в структуру:

```c
MB_REGS->holding.H000_device_id
MB_REGS->holding.H035_input_volt_0p1_v
MB_REGS->holding.H048_param_valid_mask_lo
```

Для доступа по номеру регистра можно использовать сырой массив:

```c
MB_REGS->holding_raw[35]
```

Та же карта копируется в debug-view:

```c
g_ad_debug.modbus_regs.holding.H035_input_volt_0p1_v
```

Значения в структуре уже в формате Modbus-регистров `uint16_t`, то есть с теми же масштабами, что указаны в таблицах ниже.
Если карту нужно обновить вручную из другого модуля, вызови `AD_Modbus_RefreshRegisters()`.

## Быстрая проверка связи

После прошивки сначала проверь чтение первых двух holding-регистров:

```text
Slave ID: 1
Function: 03 Read Holding Registers
Address: 0
Quantity: 2
Ожидаемый ответ: 0xAD01, 0x0008
```

Если программа на ПК работает в формате `4xxxx`, используй адрес `40001` вместо `0`. В прошивке поддержаны оба варианта: `0x0000` / `0` и `40001` читают один и тот же регистр `device id`.

Если ответа нет, проверь:

| Что смотреть | Норма |
|---|---|
| COM-порт ST-LINK VCP | выбран правильный порт платы |
| Скорость | `512000` |
| Формат | `8N1`, без flow control |
| Slave ID | `1` |
| Функция | `03`, не `04` |
| `g_ad_debug.modbus.initialized` | `1` |
| `g_ad_debug.modbus.status_flags` | есть бит `AD_MODBUS_STATUS_UART_RX_ACTIVE` |
| `g_ad_debug.modbus.rx_frames` | растёт при правильных запросах |
| `g_ad_debug.modbus.crc_errors` | не должен расти при верной скорости/CRC |
| `g_ad_debug.modbus.uart_errors` | не должен расти; если растёт, смотри скорость, порт, шум, overrun |

Расшифровка `g_ad_debug.modbus.status_flags`:

| Бит | Маска | Флаг | Значение |
|---:|---:|---|---|
| 0 | `0x00000001` | `AD_MODBUS_STATUS_INITIALIZED` | Modbus инициализирован |
| 1 | `0x00000002` | `AD_MODBUS_STATUS_FRAME_READY` | принят кадр UART, основной цикл ещё не обработал его |
| 2 | `0x00000004` | `AD_MODBUS_STATUS_LAST_FRAME_OK` | последний обработанный кадр прошёл CRC и был принят |
| 3 | `0x00000008` | `AD_MODBUS_STATUS_LAST_FRAME_ERROR` | в последнем кадре была ошибка CRC/UART/overflow |
| 4 | `0x00000010` | `AD_MODBUS_STATUS_UART_RX_ACTIVE` | приём UART активен, `HAL_UARTEx_ReceiveToIdle_IT()` запущен |
| 5 | `0x00000020` | `AD_MODBUS_STATUS_RX_OVERFLOW` | переполнение RX или новый кадр пришёл до обработки предыдущего |

Нормальное состояние после старта без запросов: есть `INITIALIZED` и `UART_RX_ACTIVE`. После корректного запроса дополнительно появляется `LAST_FRAME_OK`, а счётчик `g_ad_debug.modbus.rx_frames` растёт.

## Управление

| Адрес hex / dec | Поле структуры | Доступ | Масштаб | Назначение |
|---:|---|---|---:|---|
| `0x0000` / `0` | `H000_device_id` | R | 1 | device id `0xAD01` |
| `0x0001` / `1` | `H001_protocol_version` | R | 1 | версия карты регистров |
| `0x0002` / `2` | `H002_control_enable` | R/W | 1 | `enable`: `0` стоп, `1` разрешить команду |
| `0x0003` / `3` | `H003_control_mode` | R/W | 1 | режим `AD_PARAM_ID_MODE_*` |
| `0x0004` / `4` | `H004_control_reset_faults` | W | 1 | квитирование ошибок: записать `1` |
| `0x0005` / `5` | `H005_control_stop` | W | 1 | стоп: записать `1` |
| `0x0006` / `6` | `H006_control_locked_rotor_allowed` | R/W | 1 | разрешение locked-rotor теста |
| `0x0007` / `7` | `H007_control_pwm_duty_0p0001` | R/W | `0.0001` | лимит duty PWM |
| `0x0008` / `8` | `H008_limit_current_0p01_a` | R/W | `0.01 А` | предел тока |
| `0x0009` / `9` | `H009_limit_overvoltage_0p1_v` | R/W | `0.1 В` | предел перенапряжения DC-звена |
| `0x000A` / `10` | `H010_limit_undervoltage_0p1_v` | R/W | `0.1 В` | предел недонапряжения, `0` отключает проверку |
| `0x000B` / `11` | `H011_limit_speed_rpm` | R/W | `1 об/мин` | предел скорости |
| `0x000C` / `12` | `H012_limit_temperature_0p1_c` | R/W | `0.1 град C` | предел температуры |
| `0x000D` / `13` | `H013_control_rotation_freq_0p1_hz` | R/W | `0.1 Hz` | частота режима `ROTATION_3HZ`, firmware clamp `0.1..200.0 Hz` |
| `0x000E` / `14` | `H014_control_rotation_mod_0p0001` | R/W | `0.0001` | коэффициент модуляции режима `ROTATION_3HZ` |
| `0x000F` / `15` | `H015_control_pwm_polarity_flags` | R/W | bitmask | полярность PWM: bit0=`UH/VH/WH` inverted, bit1=`UL/VL/WL` inverted |

Пример: duty `0.08` записать как `800` в `0x0007` / `7`; для режима `ROTATION_3HZ` частоту `3.00 Hz` записать как `30`, `20.0 Hz` как `200`, `50.0 Hz` как `500` в `0x000D` / `13`, модуляцию `0.35` как `3500` в `0x000E` / `14`. Полярность PWM: `0` - штатно, `1` - инвертировать `UH/VH/WH`, `2` - инвертировать `UL/VL/WL`, `3` - инвертировать все.

## Статусы и ошибки

| Адрес hex / dec | Поле структуры | Доступ | Назначение |
|---:|---|---|---|
| `0x0010` / `16` | `H016_status_mode` | R | фактический режим идентификации |
| `0x0011` / `17` | `H017_status_flags_lo` | R | `AD_PARAM_ID_STATUS_*`, младшее слово |
| `0x0012` / `18` | `H018_status_flags_hi` | R | `AD_PARAM_ID_STATUS_*`, старшее слово |
| `0x0013` / `19` | `H019_fault_flags_lo` | R | `AD_PARAM_ID_FAULT_*`, младшее слово |
| `0x0014` / `20` | `H020_fault_flags_hi` | R | `AD_PARAM_ID_FAULT_*`, старшее слово |
| `0x0015` / `21` | `H021_power_stage_allowed` | R | `AD_ParamID_IsPowerStageAllowed()` |
| `0x0016` / `22` | `H022_test_running` | R | тест активен |
| `0x0017` / `23` | `H023_inverter_pwm_running` | R | PWM TIM1 запущен |
| `0x0018` / `24` | `H024_inverter_service_output` | R | сервисный выход `UH..WL/ALL` |
| `0x0019` / `25` | `H025_inverter_id_stage` | R | стадия алгоритма измерения |
| `0x001A` / `26` | `H026_control_pwm_timing_mode` | R/W | `0`=`up`, `1`=`center` |
| `0x001B` / `27` | `H027_control_motor_control_type` | R/W | `0`=`AD/sine`, `1`=`BLDC/6-step` |
| `0x001E` / `30` | `H030_control_rotation_ramp_time_ms` | R/W | ramp to `rotation_frequency_Hz`/`rotation_modulation`, `0` = default `3000 ms` |
| `0x001F` / `31` | `H031_control_reset_phase_current_peaks` | W | сброс накопленных пиков фазных токов: записать `1` |

Флаги статуса и ошибок оформлены enum в `ad_parameter_identification.h`: `AD_ParamID_StatusFlag_t`, `AD_ParamID_FaultFlag_t`, `AD_MeasurementStatusFlag_t`, `AD_MotorParamValidFlag_t`.

Расшифровка `status_flags_lo` (`0x0011` / `17`):

| Бит | Маска | Флаг | Значение |
|---:|---:|---|---|
| 0 | `0x0001` | `AD_PARAM_ID_STATUS_ACTIVE` | режим активен |
| 1 | `0x0002` | `AD_PARAM_ID_STATUS_POWER_TEST_BLOCKED` | силовой тест заблокирован |
| 2 | `0x0004` | `AD_PARAM_ID_STATUS_POWER_STAGE_ARMED` | силовая часть программно разрешена |
| 3 | `0x0008` | `AD_PARAM_ID_STATUS_FAULT_LATCHED` | авария защёлкнута |
| 4 | `0x0010` | `AD_PARAM_ID_STATUS_TIMEOUT` | истекло время теста |
| 5 | `0x0020` | `AD_PARAM_ID_STATUS_LOCKED_ROTOR_BLOCKED` | отдельный locked-rotor режим не разрешён |
| 6 | `0x0040` | `AD_PARAM_ID_STATUS_SAFETY_LIMITS_UNKNOWN` | не заданы пределы тока или напряжения |
| 7 | `0x0080` | `AD_PARAM_ID_STATUS_DATA_VALID` | есть актуальные измерения/данные |
| 8 | `0x0100` | `AD_PARAM_ID_STATUS_COMPLETE` | алгоритм завершён |
| 9 | `0x0200` | `AD_PARAM_ID_STATUS_PARTIAL_COMPLETE` | результат частичный, смотри `valid_mask` |
| 10 | `0x0400` | `AD_PARAM_ID_STATUS_STEP_FAILED` | один из этапов не получил достаточных условий |
| 11 | `0x0800` | `AD_PARAM_ID_STATUS_LOCKED_ROTOR_SKIPPED` | locked-rotor этап автоидентификации пропущен |

`fault_flags` в прошивке имеет тип `uint32_t` и отдается двумя holding-регистрами:

| Регистр | Поле структуры | Биты |
|---:|---|---|
| `0x0013` / `19` | `H019_fault_flags_lo` | биты `0..15` |
| `0x0014` / `20` | `H020_fault_flags_hi` | биты `16..31` |

Расшифровка `fault_flags_lo` (`0x0013` / `19`):

| Бит | Маска | Флаг | Значение |
|---:|---:|---|---|
| 0 | `0x0001` | `AD_PARAM_ID_FAULT_OVERCURRENT` | превышен ток |
| 1 | `0x0002` | `AD_PARAM_ID_FAULT_OVERVOLTAGE` | превышено напряжение DC-звена |
| 2 | `0x0004` | `AD_PARAM_ID_FAULT_UNDERVOLTAGE` | напряжение DC-звена ниже лимита |
| 3 | `0x0008` | `AD_PARAM_ID_FAULT_OVERTEMPERATURE` | превышена температура |
| 4 | `0x0010` | `AD_PARAM_ID_FAULT_DRIVER` | ошибка драйвера силового каскада |
| 5 | `0x0020` | `AD_PARAM_ID_FAULT_EMERGENCY_STOP` | аварийный останов |
| 6 | `0x0040` | `AD_PARAM_ID_FAULT_TIMEOUT` | таймаут теста |
| 7 | `0x0080` | `AD_PARAM_ID_FAULT_NULL_INPUT` | нет входных измерений |
| 8..15 | `0xFF00` | reserved | сейчас не используются |

`fault_flags_hi` (`0x0014` / `20`) сейчас зарезервирован и в норме равен `0`. Если позже появятся флаги с битами `16..31`, они будут отображаться здесь.

## Измерения

| Адрес hex / dec | Поле структуры | Доступ | Масштаб | Назначение |
|---:|---|---|---:|---|
| `0x0020` / `32` | `H032_meas_ia_0p001_a` | R | `0.001 А` | ток фазы A, int16 |
| `0x0021` / `33` | `H033_meas_ib_0p001_a` | R | `0.001 А` | ток фазы B, int16 |
| `0x0022` / `34` | `H034_meas_ic_0p001_a` | R | `0.001 А` | ток фазы C, int16 |
| `0x0023` / `35` | `H035_input_volt_0p1_v` | R | `0.1 В` | DC-звено |
| `0x0024` / `36` | `H036_meas_temp_0p1_c` | R | `0.1 град C` | STM32 internal temperature |
| `0x0025` / `37` | `H037_meas_status_lo` | R | 1 | `AD_MEAS_STATUS_*`, младшее слово |
| `0x0026` / `38` | `H038_meas_status_hi` | R | 1 | `AD_MEAS_STATUS_*`, старшее слово |
| `0x0027` / `39` | `H039_meas_speed_rpm` | R | `1 rpm` | `speed_rpm`, int16 |
| `0x0028` / `40` | `H040_meas_ia_rms_0p001_A` | R | `0.001 A` | phase A RMS current |
| `0x0029` / `41` | `H041_meas_ib_rms_0p001_B` | R | `0.001 A` | phase B RMS current |
| `0x002A` / `42` | `H042_meas_ic_rms_0p001_C` | R | `0.001 A` | phase C RMS current |
| `0x002B` / `43` | `H043_meas_torque_0p001_nm` | R | `0.001 Nm` | estimated torque, int16 |
| `0x002C` / `44` | `H044_meas_ia_peak_0p001_a` | R | `0.001 A` | phase A peak absolute current |
| `0x002D` / `45` | `H045_meas_ib_peak_0p001_a` | R | `0.001 A` | phase B peak absolute current |
| `0x002E` / `46` | `H046_meas_ic_peak_0p001_a` | R | `0.001 A` | phase C peak absolute current |
| `0x002F` / `47` | `H047_meas_slip_0p01_percent` | R | `0.01 %` | induction motor slip, int16: `(n_sync - speed_rpm) / n_sync * 100` |

Токи передаются как 16-битное значение в дополнительном коде. Например `-1200` означает `-1.2 А`.

## Результаты идентификации

| Адрес hex / dec | Поле структуры | Доступ | Масштаб | Назначение |
|---:|---|---|---:|---|
| `0x0030` / `48` | `H048_param_valid_mask_lo` | R | 1 | `valid_mask`, младшее слово |
| `0x0031` / `49` | `H049_param_valid_mask_hi` | R | 1 | `valid_mask`, старшее слово |
| `0x0032` / `50` | `H050_param_rs_mohm` | R | `1 мОм` | `Rs_ohm` |
| `0x0033` / `51` | `H051_param_ls_uh_lo` | R | `1 мкГн` | `Ls_H`, младшее слово uint32 |
| `0x0034` / `52` | `H052_param_ls_uh_hi` | R | `1 мкГн` | `Ls_H`, старшее слово uint32 |
| `0x0035` / `53` | `H053_param_ll_uh_lo` | R | `1 мкГн` | `Ll_H`, младшее слово uint32 |
| `0x0036` / `54` | `H054_param_ll_uh_hi` | R | `1 мкГн` | `Ll_H`, старшее слово uint32 |
| `0x0037` / `55` | `H055_param_rr_mohm` | R | `1 мОм` | `Rr_ohm` |
| `0x0038` / `56` | `H056_param_lr_uh_lo` | R | `1 мкГн` | `Lr_H`, младшее слово uint32 |
| `0x0039` / `57` | `H057_param_lr_uh_hi` | R | `1 мкГн` | `Lr_H`, старшее слово uint32 |
| `0x003A` / `58` | `H058_param_lm_uh_lo` | R | `1 мкГн` | `Lm_H`, младшее слово uint32 |
| `0x003B` / `59` | `H059_param_lm_uh_hi` | R | `1 мкГн` | `Lm_H`, старшее слово uint32 |
| `0x003C` / `60` | `H060_param_j_nkgm2_lo` | R | `1 nkg*m2` | `J_kg_m2`, младшее слово uint32 |
| `0x003D` / `61` | `H061_param_j_nkgm2_hi` | R | `1 nkg*m2` | `J_kg_m2`, старшее слово uint32 |
| `0x003E` / `62` | `H062_param_b_nnms_lo` | R | `1 nNm*s` | `B_Nm_s`, младшее слово uint32 |
| `0x003F` / `63` | `H063_param_b_nnms_hi` | R | `1 nNm*s` | `B_Nm_s`, старшее слово uint32 |
| `0x0040` / `64` | `H064_param_pole_pairs` | R/W | `1 pair` | motor pole pairs for synchronous speed, slip, and torque calculations |
| `0x0041` / `65` | `H065_param_phase_shunt_mohm` | R/W | `1 мОм` | сопротивление фазных шунтов; default `0.1 Ом` = `100` |

Перед использованием результата обязательно проверить `valid_mask`.

## Запуск теста через Modbus

Для запуска используй holding-регистры управления через функцию `0x06 Write Single Register` или `0x10 Write Multiple Registers`. Проверку состояния делай функцией `0x03 Read Holding Registers`.
Если мастер работает в формате `4xxxx`, прибавляй базу `40001`: например `0x0002` / `2` это `40003`, а `0x0007` / `7` это `40008`.

Пиши `enable` (`0x0002` / `2`) последним. До этого нужно задать duty, лимиты и режим, иначе можно запустить предыдущий режим со старыми настройками.

Базовая последовательность:

```text
1. Проверить связь:
   read 0x0000 / 0, quantity 2
   ожидаемо: 0xAD01, 0x0008

2. Остановить предыдущий тест:
   write 0x0005 / 5 = 1

3. Сбросить latched fault, если был:
   write 0x0004 / 4 = 1

4. Задать ограничения и duty:
   write 0x0007 / 7  = 800    ; duty 0.08
   write 0x0008 / 8  = 1000   ; ток 10.00 А
   write 0x0009 / 9  = 600    ; перенапряжение 60.0 В
   write 0x000A / 10 = 0      ; недонапряжение отключено
   write 0x000B / 11 = 1000   ; скорость 1000 rpm
   write 0x000C / 12 = 850    ; температура 85.0 град C
   write 0x000D / 13 = 30     ; ROTATION_3HZ: частота 3.00 Hz
   write 0x000E / 14 = 3500   ; ROTATION_3HZ: модуляция 0.35
   write 0x000F / 15 = 1      ; PWM polarity flags
   write 0x001A / 26 = 1      ; PWM timing: 0=up, 1=center
   write 0x001B / 27 = 0      ; motor control: 0=AD/sine, 1=BLDC/6-step
   write 0x001E / 30 = 3000   ; rotation ramp time, ms
   write 0x0041 / 65 = 100    ; phase shunts 0.1 Ohm

5. Выбрать режим теста:
   write 0x0003 / 3 = <mode>

6. Запустить:
   write 0x0002 / 2 = 1
```

Ту же подготовку можно отправить одним `0x10 Write Multiple Registers`, если пишешь непрерывный диапазон `0x0007..0x000F` для duty/лимитов/параметров вращения/полярности. `0x001A..0x001B` для `up/center` и `AD/BLDC` пишутся отдельным диапазоном, `0x001E` для времени рампы - отдельным регистром. Режим и `enable` удобнее оставить отдельными командами: сначала `0x0003 / 3`, затем `0x0002 / 2`.

`0x10 Write Multiple Registers` validates the whole writable range first, stages all values, then applies the command once. If any value is invalid, none of the packet values are committed.

Режимы `mode`:

| Значение | Режим |
|---:|---|
| `0` | `IDLE`, останов |
| `1` | `STATOR_RESISTANCE` |
| `2` | `NO_LOAD_MAGNETIZING` |
| `3` | `LOCKED_ROTOR_LEAKAGE` |
| `4` | `INERTIA_FRICTION` |
| `5` | `DATA_LOGGING` |
| `6` | `PWM_TEST_UH` |
| `7` | `PWM_TEST_UL` |
| `8` | `PWM_TEST_VH` |
| `9` | `PWM_TEST_VL` |
| `10` | `PWM_TEST_WH` |
| `11` | `PWM_TEST_WL` |
| `12` | `PWM_TEST_ALL` |
| `13` | `AUTO_IDENTIFICATION` |
| `14` | `ROTATION_3HZ` |

Режимы `1`, `2`, `3`, `4`, `6`..`14` требуют силовой части. `DATA_LOGGING` (`5`) можно использовать как безопасный режим наблюдения без включения PWM.

Пример запуска PWM-теста верхнего ключа фазы U (`UH`) с duty `0.08`:

```text
write 0x0005 / 5 = 1
write 0x0004 / 4 = 1
write 0x0007 / 7 = 800
write 0x0003 / 3 = 6
write 0x0002 / 2 = 1
```

Пример запуска автоидентификации:

```text
write 0x0005 / 5 = 1
write 0x0004 / 4 = 1
write 0x0007 / 7 = 800
write 0x0003 / 3 = 13
write 0x0002 / 2 = 1
```

Для locked-rotor теста дополнительно нужно разрешение:

```text
write 0x0005 / 5 = 1
write 0x0004 / 4 = 1
write 0x0006 / 6 = 1
write 0x0007 / 7 = 800
write 0x0003 / 3 = 3
write 0x0002 / 2 = 1
```

Контроль после запуска:

```text
read 0x0010 / 16, quantity 10
```

Смотри основные регистры:

| Адрес hex / dec | Что должно быть |
|---:|---|
| `0x0010` / `16` | фактический режим, должен совпасть с выбранным `mode` |
| `0x0011` / `17` | младшее слово `AD_PARAM_ID_STATUS_*` |
| `0x0013` / `19` | младшее слово `AD_PARAM_ID_FAULT_*`, норма `0` |
| `0x0015` / `21` | `1`, если силовой каскад разрешён |
| `0x0016` / `22` | `1`, если тест активен |
| `0x0017` / `23` | `1`, если PWM TIM1 реально запущен |
| `0x0019` / `25` | стадия алгоритма измерения |

Если читаешь через структуру в отладчике, это те же значения:

```c
MB_REGS->holding.H016_status_mode
MB_REGS->holding.H017_status_flags_lo
MB_REGS->holding.H019_fault_flags_lo
MB_REGS->holding.H022_test_running
```

Полезные биты статуса `0x0011`:

| Бит | Маска | Значение |
|---:|---:|---|
| 0 | `0x0001` | тест активен |
| 1 | `0x0002` | силовой тест заблокирован, прошивка ушла в `DATA_LOGGING` |
| 2 | `0x0004` | power stage armed |
| 3 | `0x0008` | ошибка защёлкнута, нужен сброс через `0x0004 / 4` |
| 4 | `0x0010` | timeout |
| 5 | `0x0020` | locked-rotor тест заблокирован |
| 6 | `0x0040` | не заданы безопасные лимиты |
| 8 | `0x0100` | тест завершён |

Расшифровка `fault_flags_lo` и `fault_flags_hi` приведена выше в разделе “Статусы и ошибки”.

При любой ошибке сначала останови тест, затем квитируй fault:

```text
write 0x0005 / 5 = 1
write 0x0004 / 4 = 1
```

Останов:

```text
write 0x0005 / 5 = 1
```

Или:

```text
write 0x0002 / 2 = 0
```

Для реального PWM нужен build-макрос `AD_PROJECT_POWER_TEST_ENABLE=1`. Если он равен `0`, команды силовых тестов не включат ключи: прошивка выставит `AD_PARAM_ID_STATUS_POWER_TEST_BLOCKED` и перейдёт в безопасный режим `DATA_LOGGING`.

## Быстрые сценарии

Квитировать ошибку:

```text
write 0x0004 / 4 = 1
```

Запустить PWM-тест `UH` с duty `0.08`:

```text
write 0x0007 / 7 = 800
write 0x0003 / 3 = 6
write 0x0002 / 2 = 1
```

Запустить автоидентификацию текущего уровня:

```text
write 0x0007 / 7 = 800
write 0x0003 / 3 = 13
write 0x0002 / 2 = 1
```

Остановить:

```text
write 0x0005 / 5 = 1
```

Для физического PWM всё равно нужен build-макрос `AD_PROJECT_POWER_TEST_ENABLE=1`, иначе команда будет заблокирована защитой проекта.
