# 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 = 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`, иначе команда будет заблокирована защитой проекта.