Update CCS12 peripheral firmware and documentation

This commit is contained in:
2026-09-04 19:34:10 +03:00
parent 55bee25b4f
commit 8cd81736cf
56 changed files with 3808 additions and 504 deletions

67
Doc/Doxyfile Normal file
View File

@@ -0,0 +1,67 @@
# Doxygen configuration for the Balsam 167 peripheral controller firmware.
# Run from this directory with build.bat or: doxygen Doxyfile
PROJECT_NAME = "Balsam 167 Peripheral"
PROJECT_NUMBER = "1.0"
PROJECT_BRIEF = "TMS320F28335 peripheral controller firmware"
OUTPUT_DIRECTORY = build/api
OUTPUT_LANGUAGE = Russian
INPUT_ENCODING = UTF-8
INPUT_FILE_ENCODING = *.c=UTF-8 \
*.h=UTF-8
TAB_SIZE = 4
INPUT = mainpage.md \
api.dox \
files.dox \
README_CCS12.md \
PHASE_BREAK_ERROR_FLOW.md \
PHASE_COMPARISON_BALSAM_165_167.md \
PHASE_VOLTAGE_CALCULATION.md \
PHASE_VOLTAGE_WEAK_POINTS.md \
../Source/Internal
FILE_PATTERNS = *.c *.h *.md *.dox
RECURSIVE = YES
USE_MDFILE_AS_MAINPAGE = mainpage.md
OPTIMIZE_OUTPUT_FOR_C = YES
JAVADOC_AUTOBRIEF = YES
MARKDOWN_SUPPORT = YES
AUTOLINK_SUPPORT = YES
EXTRACT_ALL = YES
EXTRACT_STATIC = YES
EXTRACT_PRIVATE = YES
EXTRACT_LOCAL_CLASSES = YES
TYPEDEF_HIDES_STRUCT = YES
SORT_MEMBER_DOCS = NO
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
SKIP_FUNCTION_MACROS = YES
PREDEFINED = interrupt= \
__interrupt= \
EALLOW= \
EDIS=
ALIASES = precondition="\pre"
QUIET = YES
WARNINGS = YES
WARN_IF_UNDOCUMENTED = NO
WARN_IF_DOC_ERROR = NO
WARN_NO_PARAMDOC = NO
WARN_LOGFILE = build/doxygen-warnings.log
GENERATE_HTML = YES
HTML_OUTPUT = html
GENERATE_TREEVIEW = YES
FULL_SIDEBAR = NO
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
ALPHABETICAL_INDEX = YES
SEARCHENGINE = YES
GENERATE_LATEX = NO
HAVE_DOT = NO

View File

@@ -0,0 +1,337 @@
@page phase_break_error_flow Детектирование и передача ошибки обрыва фаз
# Детектирование и передача ошибки обрыва фаз
## 1. Назначение
Документ описывает полную связь от входов АЦП платы УМП до выставления
признаков обрыва фаз в `modbus_table_mpu_out[125]` основного контроллера
`BALZAM_167`.
В текущей реализации обрыв определяется как сильный перекос действующих
значений фаз. Отдельных независимых флагов для физических фаз A, B и C нет.
Передаётся общий флаг `Wry` с маской `0x0004`.
## 2. Общая цепочка сигнала
```text
Фазные напряжения
|
v
АЦП peripheral-платы
CONV02 -> channel 0 -> измеряемая фаза A
CONV03 -> channel 1 -> измеряемая фаза C
|
v
Current_count(0), Current_count(1)
|
+--> RMS A = lev_count[0]
+--> RMS C = lev_count[1]
+--> RMS B = lev_count[4], расчёт B = -(A + C)
|
v
Выбор Max = max(RMS A, RMS B, RMS C)
|
v
(Max - RMS проверяемой фазы) / Max > 0.30
AND Max > Curr_Edge
|
v
Счётчик выдержки er_anal()
|
v
error.bit.Wry = 1 (бит 2, маска 0x0004)
|
v
sens_error[0] / sens_error[1]
|
v
modbus[0] / modbus[1]
|
v
Быстрый CAN-цикл peripheral-платы
|
v
Unites[UMP1_CAN_DEVICE][0/1]
или Unites[UMP2_CAN_DEVICE][0/1]
|
v
BALZAM_167, update_abnormal.c
|
+--> mpu_out[125].bit6 — УМП1
+--> mpu_out[125].bit10 — УМП2
```
## 3. Связь входов АЦП с фазами
Для режима УМП (`Desk == dsk_LOAD`) преобразования настроены в
`Source/Internal/ADC.c`.
| АЦП | Физический вход | Внутренний канал | Назначение |
|---|---:|---:|---|
| `CONV02` | `ADCINA5` | `0` | измеряемое напряжение фазы A |
| `CONV03` | `ADCINA4` | `1` | измеряемое напряжение фазы C |
| `CONV04` | `ADCINA7` | `2` | ток первого канала |
| `CONV05` | `ADCINA2` | `3` | ток второго канала |
В обработчике АЦП результаты `ADCRESULT2...ADCRESULT5` записываются в
`adc_table_lem[0...3]`, после чего для каждого канала вызывается
`Current_count(i)`.
## 4. Расчёт действующих значений
Из отсчёта АЦП вычитается сохранённый нулевой уровень и применяется
калибровочный коэффициент:
```c
Current = (Numb - Zero_lev[chan]) * powK[chan];
```
Действующее значение измеряемой фазы рассчитывается через экспоненциальное
усреднение квадрата:
```c
lev_quadr[chan] +=
((Current * Current) - lev_quadr[chan]) / (1.0 * ADC_FREQ);
lev_count[chan] = sqrt(lev_quadr[chan]);
```
Третья фаза непосредственно не измеряется. Она восстанавливается из условия
нулевой суммы фаз:
```text
A + B + C = 0
B = -(A + C)
```
В коде:
```c
Numb = -Current - aCurrent;
lev_quadr[thrd] +=
(Numb * Numb - lev_quadr[thrd]) / (1.0 * ADC_FREQ);
lev_count[thrd] = sqrt(lev_quadr[thrd]);
```
Диагностические значения публикуются в следующих регистрах:
| Регистр peripheral | Значение |
|---:|---|
| `0x68` | RMS фазы A |
| `0x69` | RMS фазы C |
| `0x6A` | расчётный RMS фазы B |
| `0x6B` | RMS первого токового канала |
| `0x6C` | RMS второго токового канала |
| `0x6D` | расчётный RMS третьего тока |
## 5. Условие выставления `Wry`
Для каждой пары выбирается максимальный уровень:
```c
Max = max(lev_count[chan], lev_count[pair], lev_count[thrd]);
```
Ошибка набирается при выполнении обоих условий:
```text
(Max - PhaseRms) / Max > 0.30
Max > Curr_Edge
```
Для УМП устанавливается:
```c
Curr_Edge = 300;
```
Следовательно, флаг формируется, когда:
1. максимальная из трёх фаз больше `300`;
2. проверяемая фаза ниже максимальной более чем на 30%;
3. счётчик `er_anal()` достиг выдержки.
Пример:
```text
RMS A = 400
RMS B = 390
RMS C = 100
Max = 400
(400 - 100) / 400 = 0.75
0.75 > 0.30 -> условие обрыва/перекоса фазы C активно
```
Если все три уровня одновременно ниже `Curr_Edge`, `Wry` не выставляется.
Полное пропадание напряжения должно определяться диагностикой низкого уровня
`Out`, а не фазовым перекосом `Wry`.
## 6. Выдержка времени
Частота обработки:
```c
#define ADC_FREQ 3750
```
Порог счётчика:
```c
time_3sec = 3 * ADC_FREQ; /* 11250 */
```
Функция `er_anal()` увеличивает счётчик на каждом ошибочном отсчёте и уменьшает
его на каждом нормальном отсчёте:
```c
if (term) {
if (*count >= edge)
return 1;
(*count)++;
return 0;
}
if (*count == 0)
return 0;
(*count)--;
return 0;
```
Для измеряемых фаз A и C номинальная выдержка составляет около 3 секунд.
Расчётная фаза B проверяется при обработке обоих измеряемых каналов, поэтому
её общий счётчик увеличивается дважды за цикл АЦП. Фактическая выдержка для B
может составлять около 1,5 секунды.
После запуска УМП диагностика блокируется на время `WAKE`:
```c
WAKE_TIME = 10L * ADC_FREQ;
```
То есть первые 10 секунд после запуска `Wry` не формируется.
## 7. Формирование регистра ошибки peripheral-платы
Структура регистра ошибки определена в `Source/Internal/Include/measure.h`:
| Бит | Маска | Поле | Назначение |
|---:|---:|---|---|
| 0 | `0x0001` | `Tear` | крайнее значение АЦП/обрыв датчика |
| 2 | `0x0004` | `Wry` | перекос или предполагаемый обрыв фазы |
| 3 | `0x0008` | `Out` | низкий общий уровень |
| 4 | `0x0010` | `Over` | превышение тока |
| 5 | `0x0020` | `Hyper` | превышение напряжения |
| 8 | `0x0100` | `Stop` | требование аварийного останова |
| 9 | `0x0200` | `Ready` | готовность канала |
| 14 | `0x4000` | `Ignor` | запрет локального останова |
| 15 | `0x8000` | `Bypas` | полное исключение канала |
После достижения выдержки выполняется:
```c
error.bit.Wry = 1;
if (!ignor)
error.bit.Stop = 1;
```
Поведение управляющих признаков:
| Состояние | `Wry` | `Stop` | Передача по CAN |
|---|---:|---:|---:|
| обычный канал | 1 | 1 | да |
| `Ignor = 1` | 1 | 0 | да |
| `Bypas = 1` | 0 | 0 | передаётся регистр без ошибки |
| `WAKE != 0` | 0 | 0 | передаётся регистр без ошибки |
Для УМП признаки напряжений находятся в регистрах ошибок:
| Регистр | Содержит `Wry` при проблеме |
|---:|---|
| `modbus[0]` | измеряемая фаза A или расчётная фаза B |
| `modbus[1]` | измеряемая фаза C или расчётная фаза B |
В основном контроллере регистры `0` и `1` объединяются логическим ИЛИ, поэтому
для индикации достаточно наличия маски `0x0004` в любом из них.
## 8. Передача по CAN
`Init_packMask()` включает регистры ошибок УМП `0...3` в быстрый CAN-цикл.
Главный цикл peripheral-платы вызывает:
```c
CAN_send(0, modbus, address);
```
Один CAN-пакет содержит начальный адрес и три 16-битных регистра. Основной
контроллер принимает пакет и записывает данные в массив:
```text
Unites[номер устройства][адрес регистра]
```
Соответствие устройств:
| Устройство | CAN-индекс основного контроллера | Регистры флага |
|---|---:|---:|
| УМП1 | `UMP1_CAN_DEVICE = 5` | `Unites[5][0]`, `Unites[5][1]` |
| УМП2 | `UMP2_CAN_DEVICE = 6` | `Unites[6][0]`, `Unites[6][1]` |
## 9. Выставление `mpu_out[125]`
Основной контроллер выполняет проверку в
`BALZAM_167/Src/balzam_7/update_abnormal.c`.
### УМП1
```c
modbus_table_mpu_out[125].bit.bit6 =
(Unites[UMP1_CAN_DEVICE][0] & 0x4) ||
(Unites[UMP1_CAN_DEVICE][1] & 0x4)
? 1 : 0;
```
### УМП2
```c
modbus_table_mpu_out[125].bit.bit10 =
(Unites[UMP2_CAN_DEVICE][0] & 0x4) ||
(Unites[UMP2_CAN_DEVICE][1] & 0x4)
? 1 : 0;
```
Оба признака разрешены только при следующем условии:
```c
f.FittingScheme == 1
```
Если выбрана другая схема, основной контроллер принудительно записывает ноль
в `mpu_out[125].bit6` и `mpu_out[125].bit10` независимо от данных УМП.
## 10. Итоговая таблица связей
| Физическое событие | Peripheral-регистр | Передаваемая маска | Приёмник | Выход MPU |
|---|---:|---:|---|---|
| перекос/обрыв A на УМП1 | `modbus[0]` | `0x0004` | `Unites[5][0]` | `mpu_out[125].bit6` |
| расчётный перекос B на УМП1 | `modbus[0]` или `[1]` | `0x0004` | `Unites[5][0/1]` | `mpu_out[125].bit6` |
| перекос/обрыв C на УМП1 | `modbus[1]` | `0x0004` | `Unites[5][1]` | `mpu_out[125].bit6` |
| перекос/обрыв A на УМП2 | `modbus[0]` | `0x0004` | `Unites[6][0]` | `mpu_out[125].bit10` |
| расчётный перекос B на УМП2 | `modbus[0]` или `[1]` | `0x0004` | `Unites[6][0/1]` | `mpu_out[125].bit10` |
| перекос/обрыв C на УМП2 | `modbus[1]` | `0x0004` | `Unites[6][1]` | `mpu_out[125].bit10` |
## 11. Ограничения алгоритма
1. `Wry` означает как обрыв, так и сильный перекос более 30%; эти события не
различаются.
2. Фаза B не измеряется непосредственно. Если при физическом обрыве B фазы A
и C остаются нормальными, расчётная B также может выглядеть нормальной и
ошибка не будет обнаружена.
3. Одновременное исчезновение всех фаз не создаёт `Wry`, потому что не
выполняется условие `Max > 300`.
4. Из-за двойной проверки расчётной фазы B её выдержка короче выдержки A и C.
5. Выходные биты `mpu_out[125].bit6` и `bit10` не показывают конкретную фазу,
а только общий факт фазового перекоса соответствующего УМП.

View File

@@ -0,0 +1,600 @@
@page phase_comparison_balsam Сравнение фазовой части Balsam 165 и Balsam 167
# Сравнение фазовой части Balsam 165 и Balsam 167
## Область сравнения
Сравнивались:
- захват фазных сигналов АЦП;
- удаление нулевого смещения;
- масштабирование в физические единицы;
- расчёт общего действующего значения;
- расчёт уровней отдельных фаз A, B и C;
- диагностика перекоса и обрыва фаз;
- диагностические биты и регистры Modbus;
- пороги напряжения и выдержки времени.
Исходные проекты:
- `K:/git_project/Balsam_165_periph`;
- `K:/git_project/Balsam_167_periph`.
Основные файлы:
| Проект | Файл |
|---|---|
| Balsam 165 | `K:/git_project/Balsam_165_periph/Source/Internal/measure.c` |
| Balsam 165 | `K:/git_project/Balsam_165_periph/Source/Internal/ADC.c` |
| Balsam 165 | `K:/git_project/Balsam_165_periph/Source/Internal/Include/measure.h` |
| Balsam 165 | `K:/git_project/Balsam_165_periph/Doc/Balsam 165 data stru.xls` |
| Balsam 167 | `Source/Internal/measure.c` |
| Balsam 167 | `Source/Internal/ADC.c` |
| Balsam 167 | `Source/Internal/Include/measure.h` |
| Balsam 167 | `Doc/Balsam 167 data stru.xls` |
## Вывод
Balsam 167 использует более правильный способ расчёта действующих значений
отдельных фаз: усреднение квадрата с последующим извлечением корня. Balsam 165
сглаживает модуль сигнала, что не является настоящим RMS.
При этом в Balsam 167 появились две ошибки индексации:
1. Парный канал записывается по абсолютному индексу, но читается по
относительному.
2. Бит `Ignor` устанавливается у абсолютного номера датчика, но проверяется у
относительного `chan`.
Для УМП, где `pow_addr == 0`, первая ошибка не проявляется. Для силового блока
она может приводить к дублированию фазных уровней и ложному `Wry + Stop`.
Рекомендуемый вариант: сохранить RMS-алгоритм Balsam 167, исправить индексацию
и выполнять диагностику трёх фаз один раз после обработки всей пары каналов.
## Сводная таблица
| Параметр | Balsam 165 | Balsam 167 | Оценка |
|---|---|---|---|
| Частота АЦП | 5000 Гц | 3750 Гц | Изменена |
| Выбор фазных входов АЦП | `CONV02…CONV05` | `CONV02…CONV05` | Одинаковый |
| Формула `im_calc()` | Одинаковая | Одинаковая | Без изменений |
| Фаза B | `-A-C`, для `dsk_COMM` эквивалентная разность | `-A-C` после смены знака A | По модулю эквивалентно |
| Уровень отдельной фазы | EMA от `abs(x)` | `sqrt(EMA(x²))` | 167 корректнее |
| Частота фильтра результата | 5 Гц | 3 Гц | 167 медленнее |
| Тип `lev_count[]` | `float` | `int` | В 167 раньше теряется дробная часть |
| Порог перекоса | 20% | 30% | 167 менее чувствителен |
| Выдержка измеряемой фазы | около 1,5 с | около 3 с | 167 медленнее |
| Выдержка восстановленной фазы | около 0,75 с | около 1,5 с | В обеих версиях вдвое короче |
| `Curr_Edge`, УМП | 100 | 300 | Порог существенно повышен |
| `Curr_Edge`, силовой блок | 40 | 200 | Порог существенно повышен |
| Низкое `Utr` | без выдержки | счётчик около 3 с | Улучшено, но блокируется `Ignor` |
| Верхнее `Utr` | без выдержки | без выдержки | Без улучшения |
| Калибровка входов | жёсткий `curK[]` | Modbus/EEPROM `Caliber[]` | 167 гибче |
| Пары каналов силового блока | корректные относительные индексы | несогласованные индексы | Регрессия 167 |
| Обработка `Ignor` | правильный абсолютный датчик | проверяется `chan` | Регрессия 167 |
| Отдельные биты фаз A/B/C | нет | нет | Общий недостаток |
| Контроль пределов АЦП U/I | нет | нет | Общий недостаток |
## 1. Захват сигналов АЦП
Для УМП обе версии используют одинаковые входы:
```c
CONV02 = 0x5; // напряжение 1
CONV03 = 0x4; // напряжение 2
CONV04 = 0x7; // ток 1
CONV05 = 0x2; // ток 2
```
Данные читаются из результатов `ADCRESULT2…ADCRESULT5`, после чего вызывается
`Current_count(i)`.
Различается частота обработки:
```text
Balsam 165: ADC_FREQ = 5000 Гц
Balsam 167: ADC_FREQ = 3750 Гц
```
Назначение входов УМП:
| `chan` | Сигнал |
|---:|---|
| 0 | `Utr A` |
| 1 | `Utr C` |
| 2 | `Itr A` |
| 3 | `Itr C` |
Фаза B физически не измеряется.
## 2. Нулевое смещение
В обеих версиях используется медленное усреднение:
```text
zero[n] = zero[n-1] + (ADC-zero[n-1]) / (5 * ADC_FREQ)
```
### Balsam 165
Ноль записывается постоянно:
```c
Zero_lev[chan] = filterbat(&out_filter[chan], zer_count[chan]);
```
Для подстройки используются те же `out_filter[]`, которые участвуют в другой
обработке сигналов.
### Balsam 167
Добавлены отдельные фильтры `zer_filter[]`:
```c
Zer = filterbat(&zer_filter[chan], zer_count[chan]);
if (WAKE < (WAKE_TIME / 2)) Zero_lev[chan] = Zer;
```
Разделение фильтров является улучшением. Однако после того как `WAKE` достиг
нуля, условие остаётся истинным. Подстройка нуля фактически продолжается во
время всей работы.
Если подстройка нужна только при запуске, должно проверяться также `WAKE > 0`.
## 3. Масштабирование
### Balsam 165
Используются жёстко заданные коэффициенты `curK[]`:
| Устройство | Напряжение | Ток |
|---|---:|---:|
| УМП 1/2 | `0.386` | `4.82` |
| Силовой блок | `0.402` | — |
Коэффициенты одинаковы для однотипных каналов и не берутся из EEPROM.
### Balsam 167
Коэффициенты вычисляются из `Caliber[0…3]`:
```text
напряжение: powK = Caliber / 10000
ток: powK = Caliber / 1000
```
Преимущества:
- индивидуальная калибровка каждого входа;
- отдельные значения для экземпляров устройств;
- сохранение и загрузка через EEPROM.
Значения тока заметно отличаются от Balsam 165. Например, для УМП 1 в
Balsam 167 используются `3.505` и `3.380` вместо `4.82`. Это может быть связано
с изменением аппаратной части или новой калибровкой и требует стендовой сверки.
## 4. Общий уровень трёхфазного сигнала
Функция `im_calc()` в проектах совпадает:
```text
isA = -1.5 * (a + b)
isB = cos(π/6) * (a - b)
Amplitude = 2/3 * sqrt(isA² + isB²)
Level = Amplitude / sqrt(2)
```
Эквивалентная формула амплитуды:
```text
Amplitude = 2/sqrt(3) * sqrt(a² + a*b + b²)
```
### Фильтрация общего уровня
Balsam 165 сначала делит амплитуду на `sqrt(2)`, затем фильтрует `Level`
фильтром 5 Гц. Для тока и всех каналов `dsk_COMM` используется дополнительная
статическая переменная `ffLev`.
`ffLev` одна на все вызовы функции. На силовом блоке результаты разных пар
могут влиять друг на друга.
Balsam 167 фильтрует амплитуду отдельным фильтром текущего датчика:
```c
Amplitude = filterbat(&out_filter[sens], im_calc(Current, aCurrent));
Level = Amplitude / RADIX2;
```
Это устраняет общий `ffLev` и разделяет состояния фильтров разных пар.
### Зона нечувствительности
Balsam 165:
```text
Level < Curr_Edge -> Level = 0
```
Для УМП порог равен 100 единицам RMS.
Balsam 167:
```text
напряжение: Amplitude < 20 -> 0
ток: Amplitude < 75 -> 0
```
В пересчёте на RMS это примерно 14,1 для напряжения и 53,0 для тока. Поэтому
Balsam 167 публикует слабые сигналы, которые Balsam 165 обнулял.
## 5. Уровни отдельных фаз
### Balsam 165: среднее модуля
```c
lev_count[chan] +=
(fabs(Current) - lev_count[chan]) / (ADC_FREQ / 5);
```
Для синусоиды установившееся значение соответствует среднему модулю:
```text
MeanAbs = 2/π * Amplitude ≈ 0.6366 * Amplitude
```
### Balsam 167: настоящий RMS
```c
lev_quadr[chan] +=
(Current * Current - lev_quadr[chan]) / ADC_FREQ;
lev_count[chan] = sqrt(lev_quadr[chan]);
```
Для синусоиды:
```text
RMS = 1/sqrt(2) * Amplitude ≈ 0.7071 * Amplitude
```
При одинаковом входном масштабе Balsam 167 показывает примерно в 1,1107 раза,
или на 11,1%, больше Balsam 165.
Это изменение влияет на абсолютные фазные уровни, но почти не влияет на
отношение минимальной фазы к максимальной, используемое для `Wry`.
### Скорость сглаживания
Balsam 165 использует коэффициент `5/ADC_FREQ`, что соответствует постоянной
времени около 0,2 с.
Balsam 167 использует коэффициент `1/ADC_FREQ`, что соответствует постоянной
времени около 1 с. Фазные уровни Balsam 167 изменяются медленнее и стабильнее.
## 6. Восстановление фазы B
Для УМП в обеих версиях:
```text
B = -A - C
```
Для силового блока в Balsam 165 используется:
```text
third = current_channel - first_channel
```
В Balsam 167 сначала меняется знак сохранённого первого канала, затем
применяется общая формула `-Current-aCurrent`. Результат отличается знаком, но
для RMS знак не имеет значения.
Основной недостаток общий: фаза B не измеряется. Неисправность B определяется
только через математическую связь с A и C.
## 7. Карта фазных регистров
В Balsam 165 код записывает:
```text
0x68 + n*3 + 0 = текущий нечётный канал, для УМП фаза C
0x68 + n*3 + 1 = парный канал, для УМП фаза A
0x68 + n*3 + 2 = восстановленная фаза B
```
То есть фактический порядок для УМП:
```text
C, A, B
```
В Balsam 167 первые две записи переставлены:
```text
0x68 + n*3 + 0 = парный канал A
0x68 + n*3 + 1 = текущий канал C
0x68 + n*3 + 2 = восстановленная фаза B
```
Порядок соответствует карте Balsam 167:
| Регистр | УМП |
|---:|---|
| `0x68` | `Utr A` |
| `0x69` | `Utr C` |
| `0x6A` | `Utr B` |
| `0x6B` | `Itr A` |
| `0x6C` | `Itr C` |
| `0x6D` | `Itr B` |
В карте Balsam 165 эти регистры не описаны как фазные. Адрес `0x68` обозначен
как яркость ламп, хотя исходный код записывает туда фазный уровень. Это риск
несовместимости прошивки с внешним ПО и документацией.
## 8. Диагностика `Wry`
В обеих версиях используется отношение:
```text
Max = max(phase_A, phase_C, phase_B)
deviation = (Max - phase) / Max
```
### Balsam 165
```text
deviation > 20%
Max > Curr_Edge
выдержка измеряемой фазы ≈ 1,5 с
```
`Curr_Edge`:
| Режим | Значение |
|---|---:|
| УМП | 100 |
| Силовой блок | 40 |
### Balsam 167
```text
deviation > 30%
Max > Curr_Edge
выдержка измеряемой фазы ≈ 3 с
```
`Curr_Edge`:
| Режим | Значение |
|---|---:|
| УМП | 300 |
| Силовой блок | 200 |
Balsam 167 значительно менее чувствителен к перекосу и реагирует медленнее.
### Ошибка выдержки восстановленной фазы
Один счётчик `err_count[thrd]` вызывается при обработке обоих каналов пары.
Поэтому он увеличивается дважды за цикл АЦП.
Фактическая выдержка постоянного дефекта фазы B:
| Проект | Заданная | Фактическая для B |
|---|---:|---:|
| Balsam 165 | 1,5 с | около 0,75 с |
| Balsam 167 | 3 с | около 1,5 с |
Проблема присутствует в обеих версиях.
### Деление на ноль
Обе версии сначала вычисляют отношение с делением на `Max`, а затем проверяют
нижний порог:
```c
((Max - phase) / Max > limit) && (Max > Curr_Edge)
```
При `Max == 0` выполняется `0/0`. Безопасный порядок:
```c
(Max > Curr_Edge) && (((Max - phase) / Max) > limit)
```
## 9. Различия обработки `Ignor`
### Balsam 165
Используется правильный абсолютный номер датчика:
```c
sens = tpl_cans + chan;
ignor = sens_error[sens].bit.Ignor;
```
Ошибки `Wry`, `Out` и `Hyper` продолжают вычисляться и передаваться, но
`Stop` не устанавливается для игнорируемого канала.
### Balsam 167
Проверяется относительный индекс:
```c
sens = pow_addr + chan;
if (WAKE || sens_error[chan].bit.Ignor) goto fin;
```
Последствия различаются по типу устройства.
#### УМП
Для УМП `pow_addr == 0`, поэтому индекс формально совпадает. Каналам напряжения
`0x00` и `0x01` по умолчанию устанавливается `Ignor`. В результате Balsam 167
полностью пропускает для напряжения:
- `Wry`;
- `Out` при `Utr < 300 В`;
- `Hyper` при `Utr > 420 В`;
- `Stop`.
В Balsam 165 эти информационные биты вычислялись, но `Stop` блокировался.
#### Силовой блок
На силовом блоке `pow_addr != 0`. `Ignor` устанавливается в
`sens_error[pow_addr+chan]`, но проверяется `sens_error[chan]`.
Из-за этого игнорирование может не сработать, а `Wry + Stop` сформироваться для
канала, который должен быть исключён из диагностики.
## 10. Регрессия `sens_pair` в Balsam 167
В Balsam 165 пары хранятся относительно `chan`:
```c
sens_pair[0] = 1;
sens_pair[1] = 0;
sens_pair[2] = 3;
sens_pair[3] = 2;
pair = sens_pair[chan];
```
В Balsam 167 пары записываются по абсолютному индексу:
```c
sens_pair[pow_addr+i] = pow_addr+(i^1);
```
Но чтение осталось старым:
```c
pair = sens_pair[chan];
```
Для силового блока элементы `sens_pair[0…3]` после общей инициализации равны
`0, 1, 2, 3`. Следовательно, `pair` указывает на текущий канал, а не на
соседний.
Последствия:
- один фазный уровень может записываться дважды;
- максимальная фаза выбирается из неполного набора;
- `Wry` может быть пропущен или сформирован ошибочно;
- карта `0x68…0x6D` для силового блока не соответствует ожидаемой.
Вероятное исправление:
```c
pair = sens_pair[sens] - pow_addr;
```
Окончательный вариант следует подтвердить стендовым тестом и ожидаемым
порядком фаз в регистрах.
## 11. Пороговые уровни УМП
Заводские уставки в проектах совпадают:
| Сигнал | Нижний порог | Верхний порог |
|---|---:|---:|
| `Utr` | 300 В | 420 В |
| `Itr` | 800 А, предупреждение | 900 А, авария |
Различается обработка нижнего напряжения:
| Проект | Реакция `Utr < 300 В` |
|---|---|
| Balsam 165 | `Out` сразу; `Stop` блокируется при штатном `Ignor` |
| Balsam 167 | счётчик около 3 с, но при штатном `Ignor` проверка не выполняется |
Верхний порог `Utr > 420 В` в обеих версиях не имеет отдельной выдержки и
гистерезиса.
## 12. Диагностические биты
Расположение основных битов не изменилось:
| Бит | Маска | Имя | Назначение |
|---:|---:|---|---|
| 0 | `0x0001` | `Tear` | обрыв датчика/предельное значение АЦП |
| 2 | `0x0004` | `Wry` | перекос фаз |
| 3 | `0x0008` | `Out` | ниже нижнего порога |
| 4 | `0x0010` | `Over` | выше предупредительного порога |
| 5 | `0x0020` | `Hyper` | выше аварийного порога |
| 8 | `0x0100` | `Stop` | авария |
| 9 | `0x0200` | `Ready` | готовность |
| 14 | `0x4000` | `Ignor` | игнорировать аварию |
| 15 | `0x8000` | `Bypas` | отключить канал |
В обеих версиях `Current_count()` не формирует `Tear` для фазных входов U/I.
Поэтому неисправность измерительного тракта нельзя надёжно отличить от
пропадания напряжения или тока.
## 13. Что следует перенести из Balsam 165
В Balsam 167 полезно вернуть следующие свойства реализации 165:
1. Согласованную систему индексов `sens_pair`.
2. Проверку `Ignor` через абсолютный индекс `sens_error[sens]`.
3. Использование `float` для внутренних фазных уровней до момента публикации.
Не следует возвращать:
- усреднение `fabs()` вместо RMS;
- общий статический фильтр `ffLev`;
- немедленное срабатывание нижнего напряжения;
- жёсткие коэффициенты `curK[]`.
## 14. Рекомендуемая итоговая реализация
1. Считать все абсолютные номера датчиков один раз:
```c
sens = pow_addr + chan;
```
2. Хранить `sens_pair[]` либо полностью в относительных, либо полностью в
абсолютных индексах. Не смешивать системы адресации.
3. Сохранить RMS Balsam 167, но хранить `lev_count[]` как `float`.
4. После поступления обоих отсчётов пары вычислять A, C и B в одном месте.
5. Один раз за цикл обновлять три RMS и три диагностических счётчика.
6. Проверять `Max > Curr_Edge` до деления.
7. Проверять `Ignor` через `sens_error[sens]`.
8. Разделить понятия:
- информационная ошибка присутствует;
- ошибка участвует в формировании `Stop`.
9. Добавить отдельную маску фаз A/B/C.
10. Добавить `Tear` по сырому значению АЦП.
11. Добавить выдержку и гистерезис для 300/420 В.
12. Явно определить, должна ли подстройка нуля работать после запуска.
## 15. Минимальные сравнительные тесты
| Тест | Что проверить |
|---|---|
| Одинаковая синусоида на обеих версиях | Разницу MeanAbs и RMS около 11,1% |
| Номинальные A и C | Правильный порядок A/C/B в `0x68…0x6D` |
| Обрыв A | RMS A, `Wry`, выдержку |
| Обрыв C | RMS C, `Wry`, выдержку |
| Нарушение вычисленной B | Отсутствие двойной скорости счётчика |
| Перекос 20%, 25%, 30%, 35% | Различия порогов 165 и 167 |
| `Max == 0` | Отсутствие деления на ноль |
| `Utr = 299 В` | `Out` и выдержку |
| `Utr = 421 В` | `Hyper` и отсутствие дребезга |
| `Ignor = 1` | Ошибка видна, но не создаёт `Stop`, если это требуемая семантика |
| Силовой блок, две пары | Отсутствие дублирования фазных RMS |
| АЦП около 0 и 4095 | Формирование `Tear` |
## Заключение
Фазовый расчёт Balsam 167 является хорошей основой благодаря настоящему RMS,
раздельным фильтрам и калибровке из регистров. Главный риск версии 167 находится
не в математике RMS, а в адресации состояния каналов.
До исправления `sens_pair` и `Ignor` фазные регистры и `Wry` силового блока
нельзя считать надёжными. Для УМП необходимо отдельно решить, должны ли
напряжения с установленным `Ignor` продолжать формировать информационные
`Wry`, `Out` и `Hyper`, как это происходило в Balsam 165.

View File

@@ -0,0 +1,344 @@
@page phase_voltage_calculation Расчёт входных фаз, диагностика обрыва и уровни напряжения
# Расчёт входных фаз, диагностика обрыва и уровни напряжения
Документ составлен по исходникам прошивки Balsam 167 и карте регистров
`Doc/Balsam 167 data stru.xls`.
## Где находится код
Основная обработка входных напряжений и токов находится в
`Source/Internal/measure.c`:
- `im_calc()` — восстановление амплитуды трёхфазного сигнала, строки 82–89;
- `Current_count()` — смещение АЦП, масштабирование, RMS фаз, общий уровень и
диагностика перекоса/пропадания, строки 444–614;
- `Power_count()` — контроль отдельных источников питания шкафа ВЭП,
строки 769–841;
- `er_anal()` — выдержка времени для диагностических признаков,
строки 423–432.
Частота расчёта задаётся как `ADC_FREQ = 3750 Гц` в
`Source/Internal/Include/measure.h:73`. Выбор каналов АЦП выполняется в
`Source/Internal/ADC.c`, а пороги по умолчанию — в
`Source/Internal/message.c:24–135`.
## 1. Получение входных отсчётов
Для плат с измерением напряжения/тока обрабатываются четыре входа:
| `chan` | Назначение для УМП (`dsk_LOAD`) | Сырой регистр |
|---:|---|---:|
| 0 | напряжение фазы A (`Utr A`) | `0x64` |
| 1 | напряжение фазы C (`Utr C`) | `0x65` |
| 2 | ток фазы A (`Itr A`) | `0x66` |
| 3 | ток фазы C (`Itr C`) | `0x67` |
В `ADC.c:228–233` результат АЦП читается так:
```c
adc_table_lem[i] = *((&AdcRegs.ADCRESULT0)+i+2) >> 4;
Current_count(i);
```
То есть используются 12 старших значащих бит результата АЦП.
### Удаление нулевого смещения и перевод в физические единицы
В `Current_count()` выполняется:
```text
N = adc_table_lem[chan]
X = (N - Zero_lev[chan]) * powK[chan]
```
`Zero_lev[0..3]` хранится в регистрах `0x70…0x73`; значение по умолчанию —
`0x800 = 2048`. Ноль дополнительно подстраивается медленным фильтром:
```text
zero[n] = zero[n-1] + (N - zero[n-1]) / (5 * 3750)
```
Коэффициент `powK` формируется из калибровки `Caliber[0..3]`:
```text
для напряжения: powK = Caliber / 10000
для тока: powK = Caliber / 1000
```
Калибровочные значения находятся в Modbus-регистрах `0x74…0x77`, могут быть
загружены из EEPROM и поэтому не обязаны совпадать со значениями по умолчанию.
Для Balsam 167 значения по умолчанию такие:
| Устройство | Каналы | `Caliber` | `powK` |
|---|---|---|---|
| Силовой 1, адрес 3 | `UA1, UB1, UA2, UB2` | `4936, 4877, 4972, 4857` | `0.4936, 0.4877, 0.4972, 0.4857` |
| Силовой 2, адрес 4 | `UA1, UB1, UA2, UB2` | `4876, 5005, 4876, 4847` | `0.4876, 0.5005, 0.4876, 0.4847` |
| УМП 1, адрес 5 | `Utr A, Utr C, Itr A, Itr C` | `3820, 3790, 3505, 3380` | `0.3820, 0.3790, 3.505, 3.380` |
| УМП 2, адрес 6 | `Utr A, Utr C, Itr A, Itr C` | `3835, 3785, 3522, 3332` | `0.3835, 0.3785, 3.522, 3.332` |
Источник значений: `Source/Internal/Include/caliber.h`, секция
`#if BALSAM == 167`.
## 2. Расчёт амплитуды и действующего значения
Функция `im_calc(a, b)` использует два измеренных фазных сигнала:
```text
isA = -1.5 * (a + b)
isB = cos(π/6) * (a - b)
Amplitude = 2/3 * sqrt(isA² + isB²)
```
Эквивалентная свёрнутая формула:
```text
Amplitude = 2/sqrt(3) * sqrt(a² + a*b + b²)
```
После цифрового фильтра Баттерворта 3 Гц получается амплитуда сигнала.
Действующее значение вычисляется как:
```text
Level = Amplitude / sqrt(2)
```
Для каналов напряжения амплитуда принудительно обнуляется при
`Amplitude < 20`; для каналов тока — при `Amplitude < 75`.
Результат пары записывается в два регистра данных:
```text
sens_data[нечётный канал] = Amplitude
sens_data[чётный канал] = Level
```
Для УМП это даёт:
| Регистр | Значение |
|---:|---|
| `0x18` | действующее `Utr` |
| `0x19` | амплитудное `Utr` |
| `0x1A` | действующее `Itr` |
| `0x1B` | амплитудное `Itr` |
Для силового блока адреса данных сдвинуты из-за температурных каналов:
`0x28/0x29` — действующее/амплитудное `Uвх1`, `0x2A/0x2B` —
действующее/амплитудное `Uвх2`.
## 3. Действующие значения отдельных фаз
Для каждой пары измеряются две фазы, третья восстанавливается по условию
трёхпроводной системы:
```text
x3 = -x1 - x2
```
Для каждого из трёх сигналов считается скользящее среднеквадратичное значение:
```text
Qphase[n] = Qphase[n-1] + (xphase² - Qphase[n-1]) / 3750
RMSphase = sqrt(Qphase)
```
`lev_count[]` имеет тип `int`, поэтому сохранённый RMS усекается до целого.
Карта регистров фаз для УМП:
| Регистр | Пара входов | Значение |
|---:|---|---|
| `0x68` | напряжение | RMS фазы A (`Utr A`) |
| `0x69` | напряжение | RMS фазы C (`Utr C`) |
| `0x6A` | напряжение | RMS восстановленной фазы B (`Utr B`) |
| `0x6B` | ток | RMS фазы A (`Itr A`) |
| `0x6C` | ток | RMS фазы C (`Itr C`) |
| `0x6D` | ток | RMS восстановленной фазы B (`Itr B`) |
В коде назначение пары определяется как `feed = chan >> 1`, а начальный адрес
фазных уровней — как `0x68 + feed * 3`.
## 4. Диагностика обрыва или сильного перекоса фазы
Отдельных битов «обрыв фазы A», «обрыв фазы B» и «обрыв фазы C» в прошивке
нет. Есть общий признак сильного отличия одной из трёх фаз — `Wry`.
Конкретную пропавшую фазу надо определять по минимальному значению среди
регистров `0x68…0x6A` или `0x6B…0x6D`.
Алгоритм в `Current_count()`:
```text
Max = max(RMS_A, RMS_B, RMS_C)
перекос фазы = ((Max - RMS_phase) / Max > 0.30) AND (Max > Curr_Edge)
```
Условие проходит через реверсивный счётчик `er_anal()`: при наличии ошибки он
увеличивается, а при исчезновении уменьшается, но не сбрасывается сразу. Порог
для измеряемой фазы равен примерно 3 секундам
(`time_3sec = 3 * ADC_FREQ = 11250` отсчётов). Для восстановленной фазы один и
тот же счётчик вызывается из обоих каналов пары, поэтому при постоянной ошибке
порог фактически достигается примерно вдвое быстрее — около 1,5 секунды.
После достижения порога устанавливаются:
| Бит диагностического слова | Маска | Имя | Значение |
|---:|---:|---|---|
| 2 | `0x0004` | `Wry` | перекос/пропадание одной из фаз более чем на 30% от максимальной |
| 8 | `0x0100` | `Stop` | авария, устанавливается вместе с `Wry` |
Для УМП `Curr_Edge = 300`. Поэтому фазовая диагностика не работает при
`Max <= 300` и не реагирует на слабый сигнал/шум около нуля.
Диагностические слова УМП:
| Регистр | Канал |
|---:|---|
| `0x00` | `Utr A` |
| `0x01` | `Utr C` |
| `0x02` | `Itr A` |
| `0x03` | `Itr C` |
При одиночном пропадании фаз ожидается следующая комбинация:
| Пропавшая фаза | Напряжение | Ток |
|---|---|---|
| A | `0x00.Wry` | `0x02.Wry` |
| C | `0x01.Wry` | `0x03.Wry` |
| B, восстановленная | `0x00.Wry` и `0x01.Wry` | `0x02.Wry` и `0x03.Wry` |
Вместе с каждым `Wry` ставится `Stop`. Эта комбинация выведена из порядка
проверок в коде, но при сложном перекосе она неоднозначна. Надёжнее определять
проблемную фазу по тройке RMS-регистров.
В месте чтения Modbus признаки можно проверять масками:
```c
// Напряжение УМП
bool phase_a_fault = (modbus[0x00] & 0x0004) != 0;
bool phase_c_fault = (modbus[0x01] & 0x0004) != 0;
bool voltage_stop = ((modbus[0x00] | modbus[0x01]) & 0x0100) != 0;
// Ток УМП
bool current_a_fault = (modbus[0x02] & 0x0004) != 0;
bool current_c_fault = (modbus[0x03] & 0x0004) != 0;
bool current_stop = ((modbus[0x02] | modbus[0x03]) & 0x0100) != 0;
```
Названия `phase_a_fault` и `phase_c_fault` здесь условные: каждый `Wry` также
участвует в контроле восстановленной фазы B. Для точной индикации следует
сравнить сами уровни `0x68…0x6D`.
### Другие связанные биты диагностики
Структура диагностического слова задана в
`Source/Internal/Include/measure.h:19–46`:
| Бит | Маска | Имя | Назначение |
|---:|---:|---|---|
| 0 | `0x0001` | `Tear` | обрыв датчика/крайнее значение АЦП; в фазовом расчёте не формируется |
| 2 | `0x0004` | `Wry` | перекос парных датчиков/фаз |
| 3 | `0x0008` | `Out` | уровень ниже нижней уставки |
| 4 | `0x0010` | `Over` | уровень выше предупредительной уставки |
| 5 | `0x0020` | `Hyper` | уровень выше аварийной верхней уставки |
| 6 | `0x0040` | `Contr` | нет сигнала на одном из дискретных входов контроля |
| 8 | `0x0100` | `Stop` | аварийный останов |
| 9 | `0x0200` | `Ready` | готовность, равна `!Stop` |
| 10–13 | `0x0400…0x2000` | `Discr1…Discr4` | отдельные дискретные входы контроля |
| 14 | `0x4000` | `Ignor` | игнорировать диагностику |
| 15 | `0x8000` | `Bypas` | отключить канал |
`reset_errs()` сохраняет управляющие биты 13–15 маской `NOER = 0xE000`,
добавляет вычисленные ошибки и каждый цикл формирует `Ready = !Stop`.
## 5. Уровни напряжения УМП
Проверка уровня выполняется только после расчёта общей амплитуды/RMS пары и
только для нечётного канала пары (`Current_count():569–591`).
### Входное напряжение `Utr`
| Условие | Выдержка | Устанавливаемые биты |
|---|---:|---|
| `Level < 300 В` | около 3 с | `Out` (бит 3) + `Stop` (бит 8) |
| `Level > 420 В` | без отдельной выдержки | `Hyper` (бит 5) + `Stop` (бит 8) |
Уставки находятся в `sens_lo_edge[0/1]` (Modbus `0x48/0x49`) и
`sens_hi_edge[0/1]` (Modbus `0x30/0x31`). Значения выше — заводские; записью
по Modbus или загрузкой EEPROM они могут быть изменены.
### Ток `Itr`
Для полноты связанной логики:
| Условие | Биты |
|---|---|
| `Level > 800 А` | `Over` (бит 4), предупреждение |
| `Level > 900 А` | `Hyper` (бит 5) + `Stop` (бит 8) |
## 6. Контроль напряжений шкафа ВЭП (`Power_count`)
На плате шкафа каждый канал масштабируется напрямую:
```text
Power = ADC_table[chan] * powK[sens_type[chan]]
```
Коэффициенты заданы в `measure.c:52–65`: `0.127` для каналов 380 В и
`0.0076` для низковольтных каналов.
Нижние уровни по умолчанию:
| Каналы | Контролируемое напряжение | Нижний уровень |
|---:|---|---:|
| 0, 1 | 380 В, фидеры 1 и 2 | 300 В |
| 2, 3 | 31 В, фидеры 1 и 2 | 15 В |
| 4, 5 | 31 В UC, фидеры 1 и 2 | 15 В |
| 6 | 24 В ПУ | 20 В |
| 7 | 27 В ФА | 22 В |
| 8 | 24 В ПК | 20 В |
| 9 | 15 В ДР | 10 В |
| 10 | +24 В ДТ | 20 В |
| 11 | −24 В ДТ | 20 В по модулю измеряемого значения |
| 12 | 24 В ПМУ | 20 В |
Если `Power < sens_lo_edge[chan]`, ставится `Out` (бит 3). `Stop` (бит 8)
ставится, когда `Out` уже установлен и у парного канала. Пары заданы как
`0↔1`, `2↔3`, `4↔5`; одиночные каналы спарены сами с собой. Поэтому для
двухфидерных источников аварийный останов означает пропадание обоих фидеров,
а пропадание одного фидера даёт только `Out` в его диагностическом слове.
Проверка превышения верхней уставки в `Power_count():829–835` закомментирована
и фактически не выполняется.
## 7. Особенности текущей реализации
1. На силовых блоках (`dsk_COMM`) четырём каналам назначается `Ignor` по
абсолютным индексам `pow_addr…pow_addr+3` (`measure.c:359–365`), но проверка
в `Current_count()` обращается к `sens_error[chan].Ignor`. Из-за разных
индексов назначенный `Ignor` может не отключать фазовую диагностику.
2. В `Current_count()` используется `pair = sens_pair[chan]`, тогда как для
силового блока пары инициализируются по абсолютному индексу
`sens_pair[pow_addr + chan]`. Для УМП, где `pow_addr == 0`, это работает как
задумано. На силовом блоке индекс `pair` остаётся равен текущему `chan`, что
может дублировать один фазовый RMS вместо чтения парного канала.
3. Следствие двух ошибок индексации: на силовом блоке `Wry` может формироваться,
хотя для канала выставлен `Ignor`, и опираться на неверно сопоставленные
фазовые уровни. Эту часть логики нельзя считать надёжной без исправления или
стендовой проверки.
4. В выражении перекоса деление на `Max` записано раньше проверки
`Max > Curr_Edge`. При `Max == 0` сначала вычисляется деление `0/0`.
Безопаснее сначала проверять порог, затем выполнять деление.
5. Все публикуемые значения и пороги хранятся в 16-битных регистрах как целые числа;
дробная часть после вычислений отбрасывается.
## Краткий итог
- Фазы: два сигнала измеряются, третий восстанавливается как `-x1-x2`.
- RMS каждой фазы: корень из экспоненциально усреднённого квадрата.
- Общий RMS входа: амплитуда `im_calc()` после фильтра, делённая на `sqrt(2)`.
- Обрыв/перекос одной фазы: `Wry`, бит 2; авария: `Stop`, бит 8.
- Определение конкретной фазы: по минимальному из трёх RMS-регистров, отдельного
фазового бита нет.
- Низкое `Utr`: ниже 300 В около 3 секунд — `Out + Stop`.
- Высокое `Utr`: выше 420 В — `Hyper + Stop`.

View File

@@ -0,0 +1,289 @@
@page phase_voltage_weak_points Слабые места расчёта фаз и контроля напряжения
# Слабые места расчёта фаз и контроля напряжения
Документ относится к алгоритмам из `Source/Internal/measure.c`, структуре
диагностики из `Source/Internal/Include/measure.h` и карте регистров
`Doc/Balsam 167 data stru.xls`.
## Критические проблемы
### 1. Неверная индексация парного канала на силовом блоке
В `Current_count()` используется:
```c
pair = sens_pair[chan];
```
При этом пары каналов инициализируются по абсолютному адресу:
```c
sens_pair[pow_addr+i] = pow_addr+(i^1);
```
Для УМП `pow_addr == 0`, поэтому ошибка не проявляется. На силовом блоке
`pow_addr != 0`, и `sens_pair[chan]` остаётся равным самому `chan`.
Последствия:
- вместо парного фазового RMS может использоваться текущий канал;
- регистры `0x68…0x6D` могут содержать дублирующиеся значения;
- расчёт максимальной фазы и диагностика `Wry` могут быть неверными.
Рекомендуемое исправление требует определить, в какой системе индексов должен
работать `lev_count[]`. Вероятный вариант:
```c
pair = sens_pair[sens] - pow_addr;
```
Перед изменением необходимо проверить ожидаемую карту каналов на силовом блоке.
### 2. Бит `Ignor` проверяется по неверному индексу
В `Init_sensors_more()` бит устанавливается так:
```c
Modbus[pow_addr+i].bit.bitE = 1;
```
Но в `Current_count()` проверяется:
```c
if (WAKE || sens_error[chan].bit.Ignor) goto fin;
```
Следует проверять диагностическое слово текущего датчика:
```c
if (WAKE || sens_error[sens].bit.Ignor) goto fin;
```
Последствие текущего кода: `Wry` может формироваться для канала, который должен
быть исключён из диагностики.
### 3. Деление на ноль в расчёте перекоса
Текущее условие:
```c
((Numb - lev_count[chan]) / Numb > 0.3) && (Numb > Curr_Edge)
```
Левая часть вычисляется раньше проверки `Numb > Curr_Edge`. При `Numb == 0`
сначала выполняется деление `0/0`.
Безопасный порядок:
```c
(Numb > Curr_Edge) &&
(((Numb - lev_count[chan]) / Numb) > 0.3)
```
Такое же изменение требуется для проверки `lev_count[thrd]`.
## Высокий приоритет
### 4. Нет независимых признаков обрыва фаз A, B и C
Прошивка формирует общий бит:
| Бит | Маска | Имя |
|---:|---:|---|
| 2 | `0x0004` | `Wry` |
Отдельных битов `PhaseA`, `PhaseB`, `PhaseC` нет. Конкретная фаза определяется
косвенно по RMS-регистрам `0x68…0x6D`.
Фаза B физически не измеряется и восстанавливается:
```text
B = -A - C
```
Из-за этого повреждение фазы B не всегда можно однозначно отличить от
изменения измеряемых фаз A и C.
Рекомендуется формировать отдельную маску состояния фаз, например:
| Бит | Назначение |
|---:|---|
| 0 | низкий RMS фазы A |
| 1 | низкий RMS фазы B |
| 2 | низкий RMS фазы C |
| 3 | общий перекос фаз |
### 5. Выдержка восстановленной фазы примерно вдвое короче
Для восстановленной фазы используется `err_count[thrd]`. Одна и та же ячейка
счётчика проверяется при обработке обоих каналов пары. Поэтому она может
увеличиваться два раза за один цикл АЦП.
Ожидаемое время:
```text
11250 / 3750 = 3 секунды
```
Фактическое время для восстановленной фазы при двух вызовах за цикл:
```text
11250 / (2 * 3750) ≈ 1,5 секунды
```
Рекомендуется выполнять проверку всех трёх фаз один раз после обновления всей
пары каналов.
### 6. Для входов U/I не формируется `Tear`
Бит `Tear` объявлен как бит 0 диагностического слова, но `Current_count()` его
не устанавливает. Нет отдельной проверки АЦП на значения около 0 или 4095.
Последствия:
- обрыв или короткое замыкание измерительного тракта может выглядеть как
обычное отсутствие напряжения;
- невозможно отличить пропадание сети от неисправности самого датчика.
Рекомендуется добавить контроль сырого значения АЦП с выдержкой времени и
отдельными порогами около нижней и верхней границ диапазона.
### 7. Подстройка нуля продолжается после запуска
Ноль обновляется при условии:
```c
if (WAKE < (WAKE_TIME / 2)) Zero_lev[chan] = Zer;
```
`WAKE` уменьшается до нуля. После запуска выражение `0 < WAKE_TIME/2` остаётся
истинным, поэтому `Zero_lev` продолжает изменяться во время работы.
Риск: медленная постоянная составляющая или неисправность входа может частично
компенсироваться алгоритмом нуля.
Если подстройка нужна только при запуске, условие должно ограничивать окно:
```c
if ((WAKE > 0) && (WAKE < (WAKE_TIME / 2))) {
Zero_lev[chan] = Zer;
}
```
Если непрерывная подстройка задумана, её следует явно документировать и
ограничить допустимым диапазоном изменения.
## Средний приоритет
### 8. Жёсткая зона нечувствительности
Для напряжения применяется:
```c
if (Amplitude < 20) Amplitude = 0;
```
Для тока применяется порог 75. Реальный слабый сигнал ниже порога становится
неотличим от полного отсутствия сигнала.
Рекомендуется использовать отдельные пороги включения и выключения
(гистерезис), а также сохранить необрезанное диагностическое значение.
### 9. Несимметричная запись ошибок пары
Проверки общего уровня `Level` выполняются только при `!ist`, то есть в
нечётном канале пары. Поэтому `Out` и `Hyper` записываются только в одно из двух
диагностических слов.
Например, для напряжения УМП низкий общий уровень записывается в `0x01`, хотя
само измерение относится ко всей трёхфазной системе.
Рекомендуется выделить отдельное диагностическое слово системы или явно
дублировать общий признак в оба слова пары.
### 10. Контроль перенапряжения шкафа ВЭП отключён
В `Power_count()` проверка:
```c
if (Power > sens_hi_edge[chan])
```
закомментирована. В рабочем коде контролируется только падение напряжения.
Неисправность с повышением напряжения не создаёт `Hyper` и `Stop`.
Перед включением проверки необходимо задать и проверить верхние уставки для
всех типов источников питания.
### 11. Недостаточный гистерезис порогов
Верхний порог `Utr > 420 В` проверяется непосредственно, без отдельного
счётчика и без порога возврата. При шуме около 420 В диагностический бит может
часто переключаться.
Для нижнего порога используется реверсивный счётчик, но состояние также не
имеет явно заданных разных порогов срабатывания и возврата.
Рекомендуется добавить:
- выдержку времени для перенапряжения;
- разные пороги срабатывания и возврата;
- насыщение и явный сброс диагностических счётчиков.
### 12. Потеря дробной части RMS
`lev_count[]` и Modbus-регистры имеют целочисленный тип. Результат `sqrt()`
сохраняется с отбрасыванием дробной части.
Для текущих порогов в сотнях вольт это не критично, но ухудшает точность
индикации и поведение около границы. При необходимости можно передавать
значения в десятых долях единицы.
## Дополнительные риски
### Разная задержка аварии у парных источников
В `Power_count()` бит `Stop` устанавливается, если `Out` уже присутствует у
парного канала. Из-за последовательной обработки второй канал пары может
получить `Stop` раньше первого. Первый получит его только при следующем опросе.
### Одиночный источник спарен сам с собой
Для непарных источников `sens_pair[chan] == chan`. После первого обнаружения
`Out` бит `Stop` появится при следующем вызове, когда будет прочитано собственное
предыдущее состояние `Out`. Это неочевидная зависимость от предыдущего цикла.
### Уставки доступны для записи без локальной проверки диапазона
Команда Modbus записывает переданное значение непосредственно в массив
регистров. В расчёте не видно проверки допустимого диапазона порогов. Ошибочная
уставка может полностью отключить контроль или вызвать постоянную аварию.
## Рекомендуемый порядок исправлений
1. Исправить индексацию `sens_pair` и `sens_error[].Ignor`.
2. Переставить проверку `Numb > Curr_Edge` перед делением.
3. Перенести расчёт и диагностику трёх фаз в один вызов на пару каналов.
4. Добавить отдельные признаки состояния фаз A/B/C.
5. Добавить диагностику предельных значений АЦП (`Tear`).
6. Определить требуемый режим подстройки нуля и ограничить его.
7. Добавить выдержку и гистерезис верхнего/нижнего напряжения.
8. Проверить все изменения стендовыми тестами: нормальная сеть, обрыв каждой
фазы, перекос, пропадание обоих фидеров, пониженное и повышенное напряжение.
## Минимальный набор тестов
| Тест | Ожидаемый результат |
|---|---|
| Номинальное трёхфазное напряжение | `Wry=0`, `Out=0`, `Hyper=0`, `Stop=0` |
| Обрыв измеряемой фазы A | снижение RMS A, отдельный признак A, затем `Stop` |
| Обрыв измеряемой фазы C | снижение RMS C, отдельный признак C, затем `Stop` |
| Нарушение восстановленной фазы B | снижение вычисленного RMS B без двойной скорости счётчика |
| Перекос ровно 30% | проверка граничного условия `>` |
| `Max = 0` | отсутствие деления на ноль |
| `Utr < 300 В` | `Out`, после выдержки `Stop` |
| `Utr > 420 В` | `Hyper`, после заданной выдержки `Stop` |
| АЦП около 0 или 4095 | `Tear`, отличие от пропадания сети |
| Канал с `Ignor=1` | отсутствие новых автоматических ошибок этого канала |
| Отключённый канал `Bypas=1` | нулевые данные, сохранён только `Bypas` |

16
Doc/README.md Normal file
View File

@@ -0,0 +1,16 @@
# Документация Balsam 167
Запустите `build.bat` из этого каталога. Скрипт всегда собирает обзорную
страницу в `build/index.html`. Скрипт использует переносимый Doxygen из
`.tools/doxygen-1.18.0` либо `doxygen.exe` из `PATH` и создаёт API-справочник
в `build/api/html/index.html`. Без Doxygen по
этому адресу создаётся поясняющая страница, поэтому ссылка из обзора не
остаётся битой.
Исходные файлы старого проекта сохранены в CP1251. `Doxyfile` явно задаёт эту
кодировку для `*.c` и `*.h`, а новые файлы документации хранятся в UTF-8.
```bat
cd Doc
build.bat
```

53
Doc/README_CCS12.md Normal file
View File

@@ -0,0 +1,53 @@
# Сборка проекта в Code Composer Studio 12
Проект поддерживает обе IDE параллельно:
- CCS 3 использует корневой файл `UKSSTMS320F28335.pjt`;
- CCS 12 использует проект из каталога `UKSSTMS320F28335`.
Файлы CCS 3 не изменялись. Объектные файлы и итоговые образы двух систем
сборки разнесены по разным каталогам и не перезаписывают друг друга.
## Импорт
1. Откройте **File → Import → Code Composer Studio → CCS Projects**.
2. В **Select search-directory** укажите каталог репозитория.
3. Выберите проект **UKSSTMS320F28335** и нажмите **Finish**.
4. Выберите конфигурацию **Debug** или **Release** и выполните **Build Project**.
Проект настроен для `TMS320F28335`, COFF ABI и C2000 Compiler
`22.6.0.LTS`, входящего в CCS 12.3.
## Результаты сборки
- CCS 3 продолжает использовать прежние каталоги `Debug`, `Release` и `Bin`.
- CCS 12 Debug создаёт `.out`, `.hex`, `.bin` и `.map` в `Bin/CCS12`.
- CCS 12 Release создаёт `UKSSTMS320F28335_Release.out` и
`UKSSTMS320F28335_Release.map` в `Bin/CCS12`.
## Публикация прошивки
`publish_firmware.bat` создаёт Gitea Release, загружает образ, вычисляет
SHA-256 и обновляет `firmware.releases` общего `update.json`. Перед запуском
сохраните вход Gitea в SETGUI. Альтернативно задайте `GITEA_TOKEN` либо пару
`GITEA_USER`/`GITEA_PASSWORD` в окружении CCS.
Пример отдельного шага после сборки CCS:
```bat
"${SRC_ROOT}\publish_firmware.bat" "${SRC_ROOT}\Bin\CCS12\UKSSTMS320F28335.bin" "1.0.0" "Описание изменений" "tms"
```
Путь к образу зависит от конфигурации CCS; первым параметром должен быть
фактический полный путь созданного `.bin` или `.hex`. Версия задаётся в формате
`MAJOR.MINOR.PATCH`. Повторный запуск той же версии заменяет asset и запись
каталога, не создавая дубликат.
Для проверки файла и доступа к Gitea без публикации добавьте пятый параметр:
```bat
"${SRC_ROOT}\publish_firmware.bat" "${SRC_ROOT}\Bin\CCS12\UKSSTMS320F28335.bin" "1.0.0" "Проверка" "tms" "check"
```
Все пути к исходникам заданы относительно каталога проекта через `SRC_ROOT`;
перенос репозитория в другой каталог не требует изменения настроек.

10
Doc/api-unavailable.html Normal file

File diff suppressed because one or more lines are too long

723
Doc/api.dox Normal file
View File

@@ -0,0 +1,723 @@
/**
@file api.dox
@brief Русскоязычные Doxygen-описания интерфейсов Balsam 167.
@defgroup measurement Измерения и диагностика
@brief АЦП, фильтрация, пересчёт каналов и формирование аварий.
@{
*/
/**
@fn void setup_adc(void)
@brief Настраивает ADC и быстрый обработчик преобразований.
@details Конфигурирует последовательность каналов и параметры накопления,
используемые измерительным контуром. Вызывается один раз при старте для всех
ролей, кроме пульта EPLT.
*/
/**
@fn void adc_isr(void)
@brief Обрабатывает очередную последовательность преобразований ADC.
@details Обновляет сырые выборки и фильтры; выполняется в контексте прерывания.
*/
/**
@fn void Init_sensors(void)
@brief Выполняет первичную инициализацию измерительных каналов.
*/
/**
@fn void Init_sensors_more(void)
@brief Применяет параметры изделия после загрузки сохранённых настроек.
*/
/**
@fn void Init_packMask(void)
@brief Формирует маски публикуемых CAN-регистров для каждой шины.
*/
/**
@fn void Temper_count(int chan, int own)
@brief Пересчитывает температурный канал и обновляет его диагностику.
@param chan Индекс базового канала в общей модели измерений.
@param own Признак локального либо внешнего источника температуры.
*/
/**
@fn void Current_count(int chan)
@brief Вычисляет ток по паре измерительных каналов и формирует ошибки.
@param chan Индекс канала тока.
*/
/**
@fn void Power_count(int chan)
@brief Вычисляет напряжение/мощность для заданного канала.
@param chan Индекс обрабатываемого канала.
*/
/**
@fn void calc_sensor_koef(void)
@brief Пересчитывает коэффициенты датчиков по калибровочным точкам.
*/
/**
@fn void calc_volta_edge(void)
@brief Пересчитывает пороги контроля напряжения из текущих настроек.
*/
/**
@fn void Is_Voltage_Hi(void)
@brief Проверяет превышение напряжения и обновляет общий признак аварии.
*/
/**
@fn void cpu_timer1_isr_SENS(void)
@brief Периодический обработчик измерительной платы.
@details Планирует выбор каналов, пересчёт величин, диагностику, мигание и
циклическую передачу. Выполняется в контексте прерывания CPU Timer1.
*/
/**
@fn int er_anal(int term, unsigned int *count, long edge, int pre)
@brief Реализует выдержку времени для появления и снятия диагностического условия.
@param term Текущее логическое состояние диагностического условия.
@param count Счётчик длительности условия; изменяется функцией.
@param edge Порог счётчика, после которого условие подтверждается.
@param pre Состояние ошибки на предыдущем шаге.
@return Ненулевое значение, когда ошибка считается активной.
*/
/**
@struct FILTERBAT
@brief Состояние рекурсивного фильтра второго порядка.
@details Содержит три коэффициента, три входные и три выходные выборки.
*/
/**
@fn float filterbat(FILTERBAT *b, float InpVarCurr)
@brief Пропускает одну выборку через фильтр Баттерворта.
@param b Экземпляр фильтра с коэффициентами и историей.
@param InpVarCurr Новая входная выборка.
@return Отфильтрованное значение.
*/
/**
@struct ERROR
@brief Шестнадцатибитное слово состояния измерительного канала.
@details Объединяет признаки обрыва, неправильного сигнала, выхода за пределы,
перегрева, блокировки, готовности, дискретных входов, игнорирования и bypass.
*/
/**
@struct FLAG
@brief Общие признаки ошибки, аварии, нагрева и теста ламп.
*/
/**
@}
*/
/**
@defgroup communication Последовательный обмен и сообщения
@brief Два канала SCI, физический режим RS-485 и прикладные команды.
@{
*/
/**
@struct RS_DATA
@brief Контекст одного SCI/RS-485 канала.
@details Хранит регистры SCI, указатели RX/TX, буферы, длины сообщений,
тайм-ауты, адреса и параметры линии. Глобальные экземпляры: `rs_a` и `rs_b`.
*/
/**
@fn void create_uart_vars(char size_cmd15_set)
@brief Инициализирует контексты двух UART и таблицу длин команд.
@param size_cmd15_set Размер payload команды 15 для текущего приложения.
*/
/**
@fn void setup_uart(char commnumber, unsigned long speed_baud)
@brief Настраивает SCI-A или SCI-B и соответствующие обработчики PIE.
@param commnumber Номер порта: `COM_1` либо `COM_2`.
@param speed_baud Скорость линии, бит/с.
*/
/**
@fn void RS_SetLineMode(RS_DATA *rs_arr, int bit, char parity, int stop)
@brief Задаёт длину слова, чётность и число стоп-битов.
@param rs_arr Контекст канала.
@param bit Число информационных битов.
@param parity Режим контроля чётности.
@param stop Режим стоп-битов.
*/
/**
@fn void RS_SetLineSpeed(RS_DATA *rs_arr, unsigned long speed)
@brief Изменяет скорость выбранного последовательного канала.
@param rs_arr Контекст канала.
@param speed Новая скорость, бит/с.
*/
/**
@fn void RS_SetBitMode(RS_DATA *rs_arr, int n)
@brief Выбирает байтовый либо упакованный режим данных SCI.
@param rs_arr Контекст канала.
@param n Требуемый режим представления элементов.
*/
/**
@fn int RS_Send(RS_DATA *rs_arr, unsigned int *pBuf, unsigned long len)
@brief Запускает неблокирующую передачу массива слов.
@param rs_arr Контекст канала.
@param pBuf Буфер передаваемых слов.
@param len Число слов.
@return Признак успешного запуска передачи.
*/
/**
@fn int RS_BSend(RS_DATA *rs_arr, unsigned int *pBuf, unsigned long len)
@brief Запускает передачу байтов, хранящихся в 16-битных ячейках C28x.
@param rs_arr Контекст канала.
@param pBuf Буфер данных.
@param len Число восьмибитных элементов.
@return Признак успешного запуска передачи.
*/
/**
@fn int get_command(RS_DATA *rs_arr)
@brief Проверяет принятый кадр и извлекает код команды.
@param rs_arr Контекст канала с принятым пакетом.
@return Код команды либо `-1`, если полного корректного кадра нет.
*/
/**
@fn void RSA_RX_Handler(void)
@brief Обработчик приёма SCI-A, делегирующий работу общему RX-автомату.
*/
/**
@fn void RSA_TX_Handler(void)
@brief Обработчик передачи SCI-A.
*/
/**
@fn void RSB_RX_Handler(void)
@brief Обработчик приёма SCI-B, делегирующий работу общему RX-автомату.
*/
/**
@fn void RSB_TX_Handler(void)
@brief Обработчик передачи SCI-B.
*/
/**
@fn void clear_timer_rs_live(RS_DATA *rs_arr)
@brief Сбрасывает счётчик контроля активности последовательного канала.
@param rs_arr Контекст канала.
*/
/**
@fn void test_rs_live(RS_DATA *rs_arr)
@brief Проверяет тайм-аут активности последовательного канала.
@param rs_arr Контекст канала.
*/
/**
@fn void ReceiveCommandModbus3(RS_DATA *rs_arr)
@brief Обрабатывает Modbus function 3 — чтение регистров.
@param rs_arr Канал, на котором принята команда.
*/
/**
@fn void ReceiveCommandModbus6(RS_DATA *rs_arr)
@brief Обрабатывает Modbus function 6 — запись одного регистра.
@param rs_arr Канал, на котором принята команда.
*/
/**
@fn void SendCommandModbus4(RS_DATA *rs_arr)
@brief Формирует запрос function 4 к внешнему устройству OWEN.
@param rs_arr Канал связи с устройством.
*/
/**
@fn void ReceiveAnswerModbus4(RS_DATA *rs_arr)
@brief Разбирает ответ OWEN на запрос входных регистров.
@param rs_arr Канал, на котором принят ответ.
*/
/**
@struct CMD_TO_TMS
@brief Формат короткой команды контроллеру.
@details Содержит адрес, номер команды, восемь байтов данных, CRC и добавочный байт.
*/
/**
@}
*/
/**
@defgroup can_bus CAN
@brief Инициализация eCAN-A, приём команд и публикация регистров.
@{
*/
/**
@fn void InitCan(int Port, int DevNum)
@brief Настраивает CAN-контроллер и почтовые ящики.
@param Port Зарезервированный номер CAN-порта; текущая плата использует порт 0.
@param DevNum Адрес/режим устройства, участвующий в конфигурации идентификаторов.
*/
/**
@fn void CAN_send(int Port, int data[], int Addr)
@brief Передаёт группу из трёх регистров общей модели.
@param Port Номер CAN-порта.
@param data Начало массива регистров.
@param Addr Адрес первого публикуемого регистра.
*/
/**
@fn void CANa_handler(void)
@brief Обработчик приёма eCAN-A; переносит данные mailbox в прикладную модель.
*/
/**
@fn void CANa_reset_err(void)
@brief Обрабатывает состояние ошибки eCAN-A и восстанавливает обмен.
*/
/**
@}
*/
/**
@defgroup storage Параметры, EEPROM и журнал
@brief Долговременное хранение конфигурации и диагностических выборок.
@{
*/
/**
@struct SE2P_DATA
@brief Описание операции с последовательной EEPROM.
@details Содержит указатель данных, размер операции и адрес EEPROM.
*/
/**
@struct SPISE2P_DRV
@brief Состояние и виртуальные методы драйвера SPI EEPROM.
*/
/**
@fn void InitSeeprom(void)
@brief Настраивает SPI-A, GPIO chip-select и Timer2 для EEPROM.
*/
/**
@fn void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size)
@brief Синхронно записывает блок 16-битных слов во внешнюю EEPROM.
@param adres Адрес слова в EEPROM.
@param buf Буфер исходных слов.
@param size Количество записываемых слов.
*/
/**
@fn void Seeprom_read(unsigned int adres, unsigned int buf[], unsigned int size)
@brief Синхронно читает блок 16-битных слов из внешней EEPROM.
@param adres Адрес слова в EEPROM.
@param buf Буфер результата.
@param size Количество читаемых слов.
*/
/**
@fn void Default_params(void)
@brief Загружает заводские уставки и калибровочные значения в RAM.
*/
/**
@fn void Load_params(void)
@brief Читает настройки из EEPROM и проверяет их целостность.
*/
/**
@fn void Save_params(void)
@brief Записывает текущие настройки и контрольную сумму в EEPROM.
*/
/**
@struct LOG
@brief Границы и текущий указатель циклического журнала во внешней памяти.
*/
/**
@fn void clear_mem(void)
@brief Инициализирует область журнала и очищает её рабочее состояние.
*/
/**
@}
*/
/**
@defgroup hardware Аппаратные интерфейсы
@brief GPIO, режим платы, ЦАП и последовательная индикация.
@{
*/
/**
@fn void get_Mode(void)
@brief Читает конфигурационные входы и определяет `Mode` и `Desk`.
*/
/**
@fn void get_Buttons(void)
@brief Опросивает дискретные входы и обновляет биты кнопок/команд.
*/
/**
@fn void setup_leds_line(void)
@brief Временно настраивает линии двух диагностических светодиодов.
*/
/**
@fn void unsetup_leds_line(void)
@brief Возвращает мультиплексируемые линии из режима стартовой индикации.
*/
/**
@fn void select_tpl_canal(int n_tpl)
@brief Выбирает один канал мультиплексора термопар.
@param n_tpl Номер канала термопары.
*/
/**
@fn void select_tpl_255(void)
@brief Переводит адресные линии мультиплексора в неактивное состояние.
*/
/**
@fn void Setup_DAC_time(void)
@brief Вычисляет временные параметры программного обслуживания ЦАП.
*/
/**
@fn void Init_DAC(void)
@brief Инициализирует GPIO последовательного интерфейса ЦАП.
*/
/**
@fn void Anal_output(long vrot, long maxx)
@brief Выдаёт нормированное значение на аналоговый выход.
@param vrot Требуемое значение.
@param maxx Верхняя граница шкалы входного значения.
*/
/**
@fn void Load_runner(void)
@brief Обновляет выход нагрузки и формирует старт/стоп импульсы.
*/
/**
@fn void kanal_Send(int adr, long dat, int dot)
@brief Передаёт число на внешний семисегментный индикатор.
@param adr Адрес индикаторного канала.
@param dat Отображаемое целое значение.
@param dot Позиция десятичной точки.
*/
/**
@fn void cpu_timer1_isr_PULT(void)
@brief Периодический обработчик пульта и внешней индикации.
*/
/**
@}
*/
/**
@defgroup boot_protocol BIOS-протокол
@brief Команды удалённого чтения, записи, запуска и прошивки контроллера.
@{
*/
/**
@fn void Answer(RS_DATA *rs_arr, int n)
@brief Формирует и отправляет ответ BIOS-протокола.
@param rs_arr Канал ответа.
@param n Число элементов данных ответа.
*/
/**
@fn void init(RS_DATA *rs_arr)
@brief Возвращает идентификацию работающего приложения.
@param rs_arr Канал запроса.
*/
/**
@fn void initload(RS_DATA *rs_arr)
@brief Инициализирует сеанс загрузки программы.
@param rs_arr Канал запроса.
*/
/**
@fn void load(RS_DATA *rs_arr)
@brief Принимает очередной блок загружаемой программы.
@param rs_arr Канал запроса.
*/
/**
@fn void run(RS_DATA *rs_arr)
@brief Завершает загрузку и передаёт управление программе.
@param rs_arr Канал запроса.
*/
/**
@fn void peek(RS_DATA *rs_arr)
@brief Читает участок адресного пространства контроллера.
@param rs_arr Канал запроса и ответа.
*/
/**
@fn void poke(RS_DATA *rs_arr)
@brief Записывает данные в адресное пространство контроллера.
@param rs_arr Канал запроса.
*/
/**
@fn void upload(RS_DATA *rs_arr)
@brief Передаёт блок памяти контроллера ведущему устройству.
@param rs_arr Канал запроса и ответа.
*/
/**
@fn void tflash(RS_DATA *rs_arr)
@brief Выполняет команду обслуживания Flash TMS320F28335.
@param rs_arr Канал запроса и ответа.
*/
/**
@fn void xflash(RS_DATA *rs_arr)
@brief Обрабатывает совместимую команду доступа к внешней Flash/памяти.
@param rs_arr Канал запроса и ответа.
*/
/**
@fn void extendbios(RS_DATA *rs_arr)
@brief Обрабатывает расширенную команду BIOS-протокола.
@param rs_arr Канал запроса и ответа.
*/
/**
@fn unsigned int read_memory(unsigned long addr)
@brief Читает 16-битное слово по физическому адресу.
@param addr Адрес в пространстве данных C28x.
@return Прочитанное слово.
*/
/**
@fn void write_memory(unsigned long addr, unsigned int data)
@brief Записывает 16-битное слово по физическому адресу.
@param addr Адрес в пространстве данных C28x.
@param data Записываемое слово.
*/
/**
@}
*/
/**
@defgroup utilities Служебные функции
@brief CRC, внешняя зона памяти и точные задержки.
@{
*/
/**
@fn unsigned int get_crc_ccitt(unsigned int crc, unsigned int *buf, unsigned long size)
@brief Вычисляет CRC-CCITT для массива восьмибитных значений в словах C28x.
@param crc Начальное значение CRC.
@param buf Буфер входных значений.
@param size Число элементов.
@return Итоговое значение CRC.
*/
/**
@fn unsigned int get_crc_16(unsigned int crc, unsigned int *buf, unsigned long size)
@brief Вычисляет основной вариант CRC-16 проекта.
@param crc Начальное значение CRC.
@param buf Буфер входных значений.
@param size Число элементов.
@return Итоговое значение CRC.
*/
/**
@fn unsigned int get_crc_16b(unsigned int crc, unsigned int *buf, unsigned long size)
@brief Вычисляет CRC-16 с альтернативным порядком байтов.
@param crc Начальное значение CRC.
@param buf Буфер входных значений.
@param size Число элементов.
@return Итоговое значение CRC.
*/
/**
@fn int get_crc16(unsigned int *buf, int size)
@brief Вычисляет контрольное слово пакета прикладного протокола.
@param buf Буфер пакета.
@param size Число обрабатываемых элементов.
@return Шестнадцатибитное контрольное значение.
*/
/**
@fn void init_zone7(void)
@brief Настраивает зону XINTF7 для внешней памяти и периферии.
*/
/**
@fn void pause_us(unsigned long t)
@brief Выполняет программную задержку.
@param t Длительность в микросекундах согласно частоте проекта.
*/
/**
@fn void set_cntrl_addr(int cntrl_addr, int cntrl_addr_for_all)
@brief Устанавливает индивидуальный и групповой адреса контроллера.
@param cntrl_addr Индивидуальный адрес устройства.
@param cntrl_addr_for_all Групповой широковещательный адрес.
*/
/**
@fn void SPISE2P_DRV_init(SPISE2P_DRV *eeprom)
@brief Инициализирует низкоуровневый автомат SPI EEPROM.
@param eeprom Экземпляр драйвера.
*/
/**
@fn void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
@brief Выполняет один шаг неблокирующего автомата EEPROM.
@param eeprom Экземпляр драйвера.
*/
/**
@fn void SPISE2P_DRV_csset(void)
@brief Деактивирует линию chip-select EEPROM.
*/
/**
@fn void SPISE2P_DRV_csclr(void)
@brief Активирует линию chip-select EEPROM.
*/
/**
@fn unsigned int spiSe2pFree(SPISE2P_DRV *se2p)
@brief Проверяет готовность автомата EEPROM к новой операции.
@param se2p Экземпляр драйвера.
@return Ненулевое значение, когда драйвер свободен.
*/
/**
@fn void spiSe2pWrite(SPISE2P_DRV *se2p, SE2P_DATA *data)
@brief Передаёт автомату описание операции записи.
@param se2p Экземпляр драйвера.
@param data Описание адреса, буфера и длины.
*/
/**
@fn void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *data)
@brief Передаёт автомату описание операции чтения.
@param se2p Экземпляр драйвера.
@param data Описание адреса, буфера и длины.
*/
/**
@}
*/

64
Doc/build.bat Normal file
View File

@@ -0,0 +1,64 @@
@echo off
rem Builds the local HTML overview and Doxygen API reference in Doc\build.
rem Prefers the pinned Doc\.tools Doxygen, then falls back to a system install.
rem If Doxygen is unavailable, installs the explanatory API placeholder instead.
setlocal EnableExtensions
set "DOC_DIR=%~dp0"
set "SOURCE=%DOC_DIR%index.html"
set "BUILD_DIR=%DOC_DIR%build"
set "OVERVIEW=%BUILD_DIR%\index.html"
set "DOXYGEN_EXE=%DOC_DIR%.tools\doxygen-1.18.0\doxygen.exe"
echo [BALSAM 167] Building HTML documentation...
if not exist "%SOURCE%" (
echo [ERROR] Source HTML not found:
echo %SOURCE%
exit /b 1
)
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
if errorlevel 1 (
echo [ERROR] Cannot create build directory:
echo %BUILD_DIR%
exit /b 2
)
copy /Y "%SOURCE%" "%OVERVIEW%" >nul
if errorlevel 1 (
echo [ERROR] Cannot build overview HTML.
exit /b 3
)
if not exist "%DOXYGEN_EXE%" (
where doxygen >nul 2>nul
if not errorlevel 1 set "DOXYGEN_EXE=doxygen"
)
if not exist "%DOXYGEN_EXE%" if /I not "%DOXYGEN_EXE%"=="doxygen" (
if not exist "%BUILD_DIR%\api\html" mkdir "%BUILD_DIR%\api\html"
copy /Y "%DOC_DIR%api-unavailable.html" "%BUILD_DIR%\api\html\index.html" >nul
echo [WARN] Doxygen is not installed; API reference was not regenerated.
echo [OK] Overview: %OVERVIEW%
exit /b 0
)
pushd "%DOC_DIR%"
"%DOXYGEN_EXE%" Doxyfile
set "DOXYGEN_RESULT=%ERRORLEVEL%"
popd
if not "%DOXYGEN_RESULT%"=="0" (
echo [ERROR] Doxygen failed with code %DOXYGEN_RESULT%.
exit /b %DOXYGEN_RESULT%
)
if not exist "%BUILD_DIR%\api\html\index.html" (
echo [ERROR] Doxygen did not create the expected API index.
exit /b 4
)
echo [OK] Overview: %OVERVIEW%
echo [OK] API: %BUILD_DIR%\api\html\index.html
exit /b 0

69
Doc/files.dox Normal file
View File

@@ -0,0 +1,69 @@
/**
@file main.c
@brief Точка входа, запуск периферии и главный цикл приложения.
@details Связывает все внутренние модули, выбирает ISR Timer1 по роли платы и
обслуживает CAN, команды конфигурации, кнопки и оба последовательных канала.
*/
/** @file ADC.c
@brief Настройка ADC TMS320F28335 и обработка потока сырых измерений. */
/** @file measure.c
@brief Пересчёт измерений Balsam 167, калибровка и диагностика каналов.
@details Содержит периодический измерительный ISR и общие массивы фильтров,
счётчиков, внешних температур, масок CAN и слов ошибок. */
/** @file filter_bat2.c
@brief Реализация рекурсивного фильтра Баттерворта второго порядка. */
/** @file RS485.c
@brief Драйвер SCI-A/SCI-B и автоматы приёма/передачи RS-485. */
/** @file bios.c
@brief Сервисные и загрузочные команды BIOS-протокола TMS. */
/** @file message.c
@brief Modbus-команды, обмен с OWEN и сохранение параметров изделия. */
/** @file ecan.c
@brief Настройка eCAN-A, почтовые ящики, приём и отправка телеметрии. */
/** @file spise2p.c
@brief Доступ к последовательной EEPROM через SPI-A. */
/** @file peripher.c
@brief Определение аппаратной роли, таблицы GPIO и дискретные входы/выходы. */
/** @file DAC.c
@brief Программный последовательный интерфейс аналогового выхода нагрузки. */
/** @file pulto.c
@brief Периодическая логика панели EPLT. */
/** @file kanal.c
@brief Низкоуровневая передача данных внешним семисегментным каналам. */
/** @file crc16.c
@brief Варианты CRC-16, используемые транспортными и сервисными протоколами. */
/** @file cntrl_adr.c
@brief Хранение и проверка индивидуального и группового адресов контроллера. */
/** @file log_to_mem.c
@brief Инициализация циклического журнала во внешней памяти XINTF. */
/** @file tools.c
@brief Настройка XINTF zone 7 и программные задержки. */
/** @file package.h
@brief Конфигурация варианта Balsam и логическая карта массива Modbus.
@warning Изменение смещений влияет на CAN, RS-485, EEPROM и внешнее ПО. */
/** @file GPIO_table.h
@brief Направления, уровни и мультиплексирование GPIO для всех ролей платы. */
/** @file caliber.h
@brief Заводские калибровочные таблицы вариантов Balsam.
@note Заголовок содержит определения данных и намеренно исключён из разбора
Doxygen, чтобы препроцессор не выбирал несовместимые варианты. */

117
Doc/index.html Normal file

File diff suppressed because one or more lines are too long

70
Doc/mainpage.md Normal file
View File

@@ -0,0 +1,70 @@
# Balsam 167 Peripheral {#mainpage}
Прошивка периферийного контроллера **Balsam 167** для цифрового сигнального
контроллера Texas Instruments TMS320F28335. Контроллер измеряет токи,
напряжения и температуры, анализирует аварийные состояния, управляет
дискретными выходами и обменивается данными по CAN и двум каналам SCI/RS-485.
@tableofcontents
## Назначение
Один исполняемый образ поддерживает несколько аппаратных ролей. Роль платы
определяется входами режима при старте и хранится в `Desk`:
| Значение | Роль | Основная задача |
|---|---|---|
| `dsk_COMM` | COMM | связь и термокалибровка |
| `dsk_BKSD` | BKSD | дискретные сигналы |
| `dsk_BKST` | BKST | температурные каналы |
| `dsk_EPLT` | EPLT | пульт/индикация |
| `dsk_SHKF` | SHKF | шкафной контроллер |
| `dsk_LOAD` | LOAD | нагрузка и аналоговый выход |
Конфигурация изделия выбирается макросом `BALSAM` в `package.h`. Для этого
репозитория задано значение `167`.
## Выполнение программы
1. `main()` настраивает системную тактовую частоту, PIE, внешнюю память,
GPIO, CAN, SCI, EEPROM и измерительные каналы.
2. `timer_Init()` выбирает обработчик Timer1: `cpu_timer1_isr_SENS()` для
измерительных плат или `cpu_timer1_isr_PULT()` для пульта.
3. Быстрый контур АЦП выполняется в `adc_isr()`.
4. Главный цикл передаёт телеметрию, обслуживает команды настройки,
дискретные входы и протокол BIOS/Modbus.
## Карта модулей
- @ref measurement — сбор АЦП, фильтрация, пересчёт физических величин и ошибки.
- @ref communication — SCI/RS-485, разбор команд и Modbus-пакеты.
- @ref can_bus — CAN eCAN-A и циклическая телеметрия.
- @ref storage — SPI EEPROM, параметры и журнал во внешней памяти.
- @ref hardware — GPIO, дискретные выходы, ЦАП и семисегментный канал.
- @ref boot_protocol — команды чтения, записи и запуска ПО через BIOS-протокол.
- @ref utilities — CRC, фильтр Баттерворта, задержки и служебные функции.
## Общая память данных
Массив `modbus` — центральная модель состояния. Макросы из `package.h`
проецируют его участки на флаги ошибок, измерения, уставки, коэффициенты
калибровки и команды. Измерительный контур обновляет модель, а CAN/RS-485
публикуют или изменяют её. При изменении разметки следует одновременно
проверять адреса протокола, `ANSWER_LEN` и таблицу данных изделия в `Doc`.
## Сборка прошивки
Инструкции для Code Composer Studio 12 находятся в
[README_CCS12.md](README_CCS12.md). Старый проект CCS 3 использует
`UKSSTMS320F28335.pjt`; проект CCS 12 расположен в каталоге
`UKSSTMS320F28335`.
## Сборка документации
Запустите `Doc\\build.bat`. Обзор будет помещён в `Doc\\build\\index.html`,
а при наличии Doxygen API-справочник появится в
`Doc\\build\\api\\html\\index.html`.
@warning Обработчики прерываний и функции, меняющие GPIO или регистры
периферии, нельзя вызывать как обычные функции без понимания контекста PIE,
частоты тактирования и выбранной роли платы.