# Чтение датчиков токов через 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 и пересчитанные значения.
