diff --git a/.gitignore b/.gitignore
index 0479bf4..158eab9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
/Debug/
/Release/
/UKSSTMS320F28335.CS_/
+/Doc/build/
+/Doc/.tools/
diff --git a/CAN_ADDRESS_MAP.md b/CAN_ADDRESS_MAP.md
new file mode 100644
index 0000000..b625198
--- /dev/null
+++ b/CAN_ADDRESS_MAP.md
@@ -0,0 +1,167 @@
+# Карта CAN-адресов `Balsam_167_periph` и `BALZAM_167`
+
+## Формирование адреса peripheral
+
+В `Source/Internal/main.c` выполняется:
+
+```c
+get_Mode();
+InitCan(0, Mode);
+```
+
+В `Source/Internal/ecan.c` настроены два extended CAN ID:
+
+```c
+long id = 0x80BA0000;
+
+MBOX0.MSGID.all = id + 0x10 + DevNum; /* передача */
+MBOX1.MSGID.all = id + DevNum; /* приём */
+```
+
+`0x80000000` задаёт extended-формат и не является частью отображаемого
+29-битного идентификатора. Фактические адреса на шине:
+
+```text
+TX peripheral = 0x00BA0010 + Mode
+RX peripheral = 0x00BA0000 + Mode
+```
+
+## Адреса всех режимов peripheral
+
+| `Mode` | Назначение | TX peripheral -> BALZAM | RX BALZAM -> peripheral |
+|---:|---|---:|---:|
+| 1 | `adr_TRN1` | `0x00BA0011` | `0x00BA0001` |
+| 2 | `adr_TRN2` | `0x00BA0012` | `0x00BA0002` |
+| 3 | `adr_POW1` | `0x00BA0013` | `0x00BA0003` |
+| 4 | `adr_POW2` | `0x00BA0014` | `0x00BA0004` |
+| 5 | `adr_LOA1`, УМП1 | `0x00BA0015` | `0x00BA0005` |
+| 6 | `adr_LOA2`, УМП2 | `0x00BA0016` | `0x00BA0006` |
+| 7 | `adr_ENG1` | `0x00BA0017` | `0x00BA0007` |
+| 8 | `adr_PULT` | `0x00BA0018` | `0x00BA0008` |
+| 9 | `adr_SHKF` | `0x00BA0019` | `0x00BA0009` |
+
+## Приёмные адреса основного контроллера
+
+В `BALZAM_167/Src/balzam_7/CanSetupBalzam7.c` настроено:
+
+| RX mailbox | CAN ID | Запись обработчиком |
+|---:|---:|---|
+| 0 | `0x00BA0010` | `Unites[1]` |
+| 1 | `0x00BA0011` | `Unites[2]` |
+| 2 | `0x00BA0012` | `Unites[3]` |
+| 3 | `0x00BA0013` | `Unites[4]` |
+| 4 | `0x00BA0014` | `Unites[5]` |
+| 5 | `0x00BA0015` | `Unites[6]` |
+| 6 | `0x00BA0016` | `Unites[7]` |
+| 7 | `0x00BA0017` | `Unites[8]` |
+| 8 | `0x00BA0018` | `Unites[9]` |
+| 9 | `0x00745019` | специальная обработка MPU |
+| 10 | `0x00BA001A` | `Unites[11]` |
+| 11 | `0x00BA001B` | `Unites[12]` |
+
+Причина такого соответствия — запись в обработчике:
+
+```c
+Unites[box + 1][register_address] = received_data;
+```
+
+## Имена индексов `Unites`
+
+Текущие определения в `CanSetupBalzam7.h`:
+
+| Индекс | Имя устройства |
+|---:|---|
+| 1 | `UKSS1_CAN_DEVICE` |
+| 2 | `UKSS4_CAN_DEVICE` |
+| 3 | `UKSS2_CAN_DEVICE` |
+| 4 | `UKSS3_CAN_DEVICE` |
+| 5 | `UMP1_CAN_DEVICE` |
+| 6 | `UMP2_CAN_DEVICE` |
+| 7 | `UKSS8_CAN_DEVICE` |
+| 8 | `VPU1_CAN_DEVICE` |
+| 9 | `UKSS5_CAN_DEVICE` |
+| 10 | `MPU_CAN_DEVICE` |
+| 11 | `UKSS6_CAN_DEVICE` |
+| 12 | `UKSS7_CAN_DEVICE` |
+
+## Фактическое попадание пакетов
+
+При текущем коде пакет peripheral с `Mode=N` принимается mailbox `N`, после
+чего записывается в `Unites[N+1]`.
+
+| Peripheral | TX CAN ID | Фактический `Unites` | Имя по текущим константам |
+|---|---:|---:|---|
+| `adr_TRN1`, Mode 1 | `0x00BA0011` | `Unites[2]` | `UKSS4_CAN_DEVICE` |
+| `adr_TRN2`, Mode 2 | `0x00BA0012` | `Unites[3]` | `UKSS2_CAN_DEVICE` |
+| `adr_POW1`, Mode 3 | `0x00BA0013` | `Unites[4]` | `UKSS3_CAN_DEVICE` |
+| `adr_POW2`, Mode 4 | `0x00BA0014` | `Unites[5]` | `UMP1_CAN_DEVICE` |
+| `adr_LOA1`, Mode 5 | `0x00BA0015` | `Unites[6]` | `UMP2_CAN_DEVICE` |
+| `adr_LOA2`, Mode 6 | `0x00BA0016` | `Unites[7]` | `UKSS8_CAN_DEVICE` |
+| `adr_ENG1`, Mode 7 | `0x00BA0017` | `Unites[8]` | `VPU1_CAN_DEVICE` |
+| `adr_PULT`, Mode 8 | `0x00BA0018` | `Unites[9]` | `UKSS5_CAN_DEVICE` |
+| `adr_SHKF`, Mode 9 | `0x00BA0019` | нет приёма | — |
+
+## Связь с ошибкой обрыва фаз
+
+Peripheral передаёт регистры ошибок `modbus[0]`, `modbus[1]` и `modbus[2]`
+в CAN-пакете с начальным адресом данных `0`. Признак обрыва/перекоса `Wry`
+занимает бит 2:
+
+```text
+Wry = 0x0004
+```
+
+Основной контроллер выставляет:
+
+```c
+mpu_out[125].bit6 =
+ (Unites[UMP1_CAN_DEVICE][0] & 0x0004) ||
+ (Unites[UMP1_CAN_DEVICE][1] & 0x0004);
+
+mpu_out[125].bit10 =
+ (Unites[UMP2_CAN_DEVICE][0] & 0x0004) ||
+ (Unites[UMP2_CAN_DEVICE][1] & 0x0004);
+```
+
+С учётом `UMP1_CAN_DEVICE=5` и `UMP2_CAN_DEVICE=6` получаем:
+
+| Выход MPU | Читаемые данные | CAN ID, заполняющий эти данные |
+|---|---|---:|
+| `mpu_out[125].bit6` | `Unites[5][0/1]` | `0x00BA0014` |
+| `mpu_out[125].bit10` | `Unites[6][0/1]` | `0x00BA0015` |
+
+Но УМП отправляют:
+
+| Плата | Фактический CAN ID | Куда пакет попадает сейчас |
+|---|---:|---|
+| УМП1, `Mode=5` | `0x00BA0015` | `Unites[6]`, область УМП2 |
+| УМП2, `Mode=6` | `0x00BA0016` | `Unites[7]`, область `UKSS8` |
+
+Таким образом, сейчас присутствует смещение адреса на единицу. УМП1 может
+влиять на `mpu_out[125].bit10` вместо `bit6`, а УМП2 не попадает ни в один из
+двух ожидаемых массивов УМП.
+
+## Обратное направление
+
+Основной контроллер отправляет устройству с индексом `N` через mailbox
+`15 + N`, которому назначен CAN ID с номером `N - 1`.
+
+| Получатель | TX CAN ID BALZAM | RX CAN ID peripheral |
+|---|---:|---:|
+| УМП1 | `0x00BA0004` | `0x00BA0005` |
+| УМП2 | `0x00BA0005` | `0x00BA0006` |
+
+В обратном направлении также имеется смещение на единицу.
+
+## Вывод
+
+Перед изменением кода необходимо сверить CAN-трассу и реальные положения
+адресных перемычек. После подтверждения адресной схемы возможны два варианта:
+
+1. использовать при настройке CAN peripheral значение `Mode - 1`;
+2. перенастроить RX/TX mailbox основного контроллера под текущие значения
+ `Mode` peripheral.
+
+Комментарии возле mailbox основного проекта частично не совпадают с текущими
+константами устройств, поэтому менять адреса только по комментариям нельзя.
+
diff --git a/Doc/Doxyfile b/Doc/Doxyfile
new file mode 100644
index 0000000..d6fd869
--- /dev/null
+++ b/Doc/Doxyfile
@@ -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
diff --git a/Doc/PHASE_BREAK_ERROR_FLOW.md b/Doc/PHASE_BREAK_ERROR_FLOW.md
new file mode 100644
index 0000000..67aed90
--- /dev/null
+++ b/Doc/PHASE_BREAK_ERROR_FLOW.md
@@ -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` не показывают конкретную фазу,
+ а только общий факт фазового перекоса соответствующего УМП.
diff --git a/PHASE_COMPARISON_BALSAM_165_167.md b/Doc/PHASE_COMPARISON_BALSAM_165_167.md
similarity index 99%
rename from PHASE_COMPARISON_BALSAM_165_167.md
rename to Doc/PHASE_COMPARISON_BALSAM_165_167.md
index 22c617f..05f47a3 100644
--- a/PHASE_COMPARISON_BALSAM_165_167.md
+++ b/Doc/PHASE_COMPARISON_BALSAM_165_167.md
@@ -1,3 +1,5 @@
+@page phase_comparison_balsam Сравнение фазовой части Balsam 165 и Balsam 167
+
# Сравнение фазовой части Balsam 165 и Balsam 167
## Область сравнения
diff --git a/PHASE_VOLTAGE_CALCULATION.md b/Doc/PHASE_VOLTAGE_CALCULATION.md
similarity index 99%
rename from PHASE_VOLTAGE_CALCULATION.md
rename to Doc/PHASE_VOLTAGE_CALCULATION.md
index 1437234..f480ca0 100644
--- a/PHASE_VOLTAGE_CALCULATION.md
+++ b/Doc/PHASE_VOLTAGE_CALCULATION.md
@@ -1,3 +1,5 @@
+@page phase_voltage_calculation Расчёт входных фаз, диагностика обрыва и уровни напряжения
+
# Расчёт входных фаз, диагностика обрыва и уровни напряжения
Документ составлен по исходникам прошивки Balsam 167 и карте регистров
diff --git a/PHASE_VOLTAGE_WEAK_POINTS.md b/Doc/PHASE_VOLTAGE_WEAK_POINTS.md
similarity index 99%
rename from PHASE_VOLTAGE_WEAK_POINTS.md
rename to Doc/PHASE_VOLTAGE_WEAK_POINTS.md
index 0bd81e3..ba8f17b 100644
--- a/PHASE_VOLTAGE_WEAK_POINTS.md
+++ b/Doc/PHASE_VOLTAGE_WEAK_POINTS.md
@@ -1,3 +1,5 @@
+@page phase_voltage_weak_points Слабые места расчёта фаз и контроля напряжения
+
# Слабые места расчёта фаз и контроля напряжения
Документ относится к алгоритмам из `Source/Internal/measure.c`, структуре
diff --git a/Doc/README.md b/Doc/README.md
new file mode 100644
index 0000000..2734a15
--- /dev/null
+++ b/Doc/README.md
@@ -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
+```
diff --git a/README_CCS12.md b/Doc/README_CCS12.md
similarity index 55%
rename from README_CCS12.md
rename to Doc/README_CCS12.md
index 84c5a65..6d637ca 100644
--- a/README_CCS12.md
+++ b/Doc/README_CCS12.md
@@ -25,5 +25,29 @@
- 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`;
перенос репозитория в другой каталог не требует изменения настроек.
diff --git a/Doc/api-unavailable.html b/Doc/api-unavailable.html
new file mode 100644
index 0000000..d6b6148
--- /dev/null
+++ b/Doc/api-unavailable.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+ Balsam 167 · Doxygen не найден
+
+
+Doxygen API ещё не собран Установите Doxygen, добавьте doxygen.exe в PATH и повторно запустите Doc\build.bat.
Конфигурация и русскоязычные API-комментарии уже находятся в Doc\Doxyfile, Doc\mainpage.md и Doc\api.dox.
Вернуться к обзору
+
diff --git a/Doc/api.dox b/Doc/api.dox
new file mode 100644
index 0000000..a064baa
--- /dev/null
+++ b/Doc/api.dox
@@ -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 Описание адреса, буфера и длины.
+*/
+
+/**
+@}
+*/
diff --git a/Doc/build.bat b/Doc/build.bat
new file mode 100644
index 0000000..95496e7
--- /dev/null
+++ b/Doc/build.bat
@@ -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
diff --git a/Doc/files.dox b/Doc/files.dox
new file mode 100644
index 0000000..08cbabd
--- /dev/null
+++ b/Doc/files.dox
@@ -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, чтобы препроцессор не выбирал несовместимые варианты. */
+
diff --git a/Doc/index.html b/Doc/index.html
new file mode 100644
index 0000000..5b54c3a
--- /dev/null
+++ b/Doc/index.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+ Balsam 167 · Документация прошивки
+
+
+
+
+
+ TMS320F28335 · C2000 · Balsam 167
+ Периферийный контроллер карта прошивки
+ Архитектура измерений, диагностики и обмена для многоролевого контроллера Balsam 167. Страница служит точкой входа, а Doxygen содержит навигацию по исходникам и полное API.
+ 17 внутренних C-модулей6 ролей платы2× SCI + eCAN-ACCS 3 / 12
+
+
+
+ Обзор
+ Архитектура
+ Модули
+ Данные
+ Анализ кода
+ Сборка
+
+
+
+ Что делает проект Прошивка объединяет измерительный тракт, диагностику, управление выходами и два транспортных интерфейса. Аппаратная роль определяется при старте, поэтому один код работает на нескольких платах комплекса.
+
+
Поток запуска От сброса до рабочего цикла InitSysCtrl → PIE / XINTF → get_Mode
+ ↓
+eCAN-A + SCI-A/SCI-B + SPI EEPROM
+ ↓
+GPIO / ADC / Timer1 / параметры / маски CAN
+ ↓
+главный цикл: CAN → команды → кнопки → RS-485
+
Платформа Texas Instruments C2000 TMS320F28335, COFF ABI, large memory model и аппаратные модули ADC, eCAN, SCI, SPI, CPU Timer и XINTF.
+
Конфигурация BALSAM = 167 package.h выбирает направление, RS-калибровку, OWEN и раскладку общих регистров.
+
Главная модель 128 Modbus-регистров Измерения, уставки, ошибки, калибровка и команды представлены через массив modbus и макросы доступа.
+
Реальное время Три контура ADC ISR собирает выборки, Timer1 ведёт измерение или пульт, главный цикл выполняет связь и команды.
+
+
+
+
+ Как связаны подсистемы Быстрые обработчики обновляют состояние, а главный цикл публикует его и принимает управляющие команды. Общая модель связывает подсистемы без динамического выделения памяти.
+ 01 · Входы ADC, GPIO, SCI, eCAN
02 · Обработка фильтры, физические величины
03 · Диагностика таймеры, пороги, ERROR
04 · Модель modbus и команды
05 · Выходы CAN, RS-485, DAC, GPIO
+
+
Измерительная плата ADC ISR + Timer1 SENS ADC ISR накапливает и фильтрует выборки. cpu_timer1_isr_SENS выбирает канал и планирует пересчёт.Temper_count, Current_count и Power_count обновляют значения.er_anal подтверждает ошибки с выдержкой времени.
+
Пульт EPLT Timer1 PULT Для роли dsk_EPLT Timer1 направляется в cpu_timer1_isr_PULT. Обработчик ведёт внешнюю индикацию и дискретный интерфейс, не запуская обычный измерительный контур.
+
Конкурентный доступ Главный цикл и прерывания используют общие переменные При изменениях учитывайте атомарность доступа C28x, порядок обновления modbus, флаги CanGO/READY и время выполнения ISR. В прерываниях нельзя добавлять блокирующий обмен с EEPROM или UART.
+
+
+
+
+ Внутренние модули Таблица показывает назначение каждого слоя и его основные точки входа.
+
+
+
+
+ Данные и конфигурация Система построена вокруг статических массивов и битовых слов. Это подходит для C28x, но требует строго сохранять индексы и размеры.
+
+
modbus[] Карта регистров 0x00 — слова ошибок каналов.0x18 — текущие измерения.0x30 — верхние уставки.0x48 — нижние уставки.0x60+ — периоды, яркость и служебные параметры.0x7F — команды.
+
ERROR Слово диагностики Биты описывают обрыв, неправильный сигнал, выход за диапазон, перегрев, стоп, готовность, дискретные входы, игнорирование и bypass. Поле all позволяет передавать слово целиком.
+
caliber.h Заводские таблицы Калибровки зависят от значения BALSAM. Файл содержит определения данных и должен включаться только там, где формируются значения по умолчанию.
+
GPIO_table.h Роли платы Направления и начальные уровни GPIO заданы отдельными наборами для COMM, BKSD, BKST, EPLT, SHKF и LOAD.
+
CP1251 Кодировка legacy-кода Старые комментарии исходников записаны в Windows-1251. Doxygen настроен декодировать *.c и *.h как CP1251; новые документы — UTF-8.
+
+
+
+
+ Анализ кода Markdown-отчёты из каталога Doc включаются в Doxygen при каждой сборке. Выберите документ — его HTML откроется справа без ухода с этой вкладки.
+
+
+
+
+ Как собрать документацию Скрипт повторяет схему эталонных HTML-документов: формирует автономную точку входа и дополнительно запускает Doxygen, если он установлен.
+
+
Команда Запуск из корня репозитория Doc\build.bat Готовый обзор: Doc\build\index.html API: Doc\build\api\html\index.html Предупреждения: Doc\build\doxygen-warnings.log
+
Зависимость Doxygen в PATH Без Doxygen обзор всё равно собирается, а скрипт печатает предупреждение. После установки повторный запуск добавит API-справочник.
+
Проверка изменений Перед публикацией Открывается build/index.html. Ссылка «Открыть Doxygen API» ведёт на созданный индекс. В журнале нет неизвестных команд и неверных ссылок. Новые публичные функции описаны в заголовке или api.dox. Разметка modbus согласована с внешним описанием данных.
+
+
+
+
+
+
+
diff --git a/Doc/mainpage.md b/Doc/mainpage.md
new file mode 100644
index 0000000..d6f0808
--- /dev/null
+++ b/Doc/mainpage.md
@@ -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,
+частоты тактирования и выбранной роли платы.
diff --git a/Source/Internal/ADC.c b/Source/Internal/ADC.c
index 119d8a4..2bece25 100644
--- a/Source/Internal/ADC.c
+++ b/Source/Internal/ADC.c
@@ -1,3 +1,13 @@
+/**
+ * @file ADC.c
+ * @brief Сбор аналоговых отсчётов и первичная обработка измерительных каналов.
+ *
+ * Модуль настраивает АЦП TMS320F28335 и обслуживает прерывание окончания
+ * преобразования. Отсчёты накапливаются в ADC_table и adc_table_lem, после
+ * чего передаются измерительному контуру. Обработчик работает в контексте
+ * прерывания, поэтому его время выполнения ограничено частотой дискретизации,
+ * а разделяемые таблицы должны читаться основным циклом согласованно.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP281x Examples Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -14,20 +24,37 @@
#include "peripher.h"
+/** @brief Сырые отсчёты четырёх силовых каналов, передаваемые Current_count(). */
Uint16 adc_table_lem[4];
-Uint16 ADC_table[28]; // +4
+/** @brief Фильтрованные значения всех рабочих и калибровочных каналов. */
+Uint16 ADC_table[28]; // Потому что +4 калибр
+/** @brief Число допустимых отсчётов прошлого окна для контроля выбросов. */
Uint16 prev_ok[28];
+/** @brief Параметры временного мультиплексирования термоканалов в тиках АЦП. */
unsigned int COUNT_ONE_CANAL;
unsigned int COUNT_DISCHARGE;
unsigned int COUNT_TRANSICIA;
unsigned int FILTER_CLIP;
+/** @brief Счётчик прогрева измерений и его начальная длительность. */
long WAKE, WAKE_TIME;
// Prototype statements for functions found within this file.
+/** @brief ISR конца последовательности ADC SEQ1. */
interrupt void adc_isr(void);
+/**
+ * @brief Полностью настраивает АЦП, ePWM-триггер и расписание каналов платы.
+ *
+ * @details По Desk выбирается физическая последовательность входов ADCINA/B.
+ * ePWM1 затем запускает SEQ1 с частотой ADC_FREQ. Для мультиплексированных
+ * термоканалов рассчитываются интервалы разряда, переходного процесса и окна
+ * накопления; WAKE подавляет использование неустановившихся фильтров при старте.
+ *
+ * @pre Desk и признаки TermoAD/TermoSW определены функцией get_Mode().
+ * @post ADCINT подключён к adc_isr(), SEQ1 и ePWM1 запущены.
+ */
void setup_adc()
{
long CLKdiv,HSPCLKdiv,Rate;
@@ -59,21 +86,23 @@ void setup_adc()
// EINT; // Enable Global interrupt INTM
// ERTM; // Enable Global realtime interrupt DBGM
+ /* Исключаем использование остаточных данных до первого полного цикла АЦП. */
for(i=0;i<28;i++)
ADC_table[i]=
prev_ok[i] = 0;
+ /* Каждая роль платы имеет собственную разводку входов и длину sequencer. */
// Configure ADC
if(Desk==dsk_LOAD)
{
AdcRegs.ADCMAXCONV.bit.MAX_CONV1 = 0x0005; // Setup 2 conv's on SEQ1
- AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x3; // ( )
- AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x1; //
- AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x5; // 1
- AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0x4; // 2
- AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 0x7; // 1
- AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 0x2; // 2
+ AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x3; // температура (которой нет)
+ AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x1; // омега
+ AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x5; // напруга 1
+ AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0x4; // напруга 2
+ AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 0x7; // ток 1
+ AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 0x2; // ток 2
}
if(Desk==dsk_BKST)
@@ -87,7 +116,7 @@ void setup_adc()
if(Desk==dsk_COMM)
{
AdcRegs.ADCMAXCONV.bit.MAX_CONV1 = 0x0006; // Setup 2 conv's on SEQ1
- AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x5; //
+ AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x5; // сначала тоже будут температуры
AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x4; // Setup ADCINA2 as 2nd SEQ1 conv.
AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x7; // Setup ADCINA2 as 2nd SEQ1 conv.
AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0x2; // Setup ADCINA2 as 2nd SEQ1 conv.
@@ -105,23 +134,23 @@ void setup_adc()
if(Desk==dsk_SHKF)
{
AdcRegs.ADCMAXCONV.bit.MAX_CONV1 = 0x000F; // Setup 2 conv's on SEQ1
- AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x2; // 380 1
- AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x3; // 380 2
- AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x6; // 31 1
- AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0xC; // 31 2 ?
- AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 0xA; // 31 UC1
- AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 0xB; // 31 UC2
- AdcRegs.ADCCHSELSEQ2.bit.CONV06 = 0x7; // 24
- AdcRegs.ADCCHSELSEQ2.bit.CONV07 = 0x4; // 24
+ AdcRegs.ADCCHSELSEQ1.bit.CONV00 = 0x2; // 380В Ф1
+ AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 0x3; // 380В Ф2
+ AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 0x6; // 31В Ф1
+ AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 0xC; // 31В Ф2 ?
+ AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 0xA; // 31В UC1
+ AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 0xB; // 31В UC2
+ AdcRegs.ADCCHSELSEQ2.bit.CONV06 = 0x7; // 24В ПУ
+ AdcRegs.ADCCHSELSEQ2.bit.CONV07 = 0x4; // 24В ПУ
- AdcRegs.ADCCHSELSEQ3.bit.CONV08 = 0x5; // 24 5
- AdcRegs.ADCCHSELSEQ3.bit.CONV09 = 0x1; // 15
- AdcRegs.ADCCHSELSEQ3.bit.CONV10 = 0xE; // +24
- AdcRegs.ADCCHSELSEQ3.bit.CONV11 = 0x0; // +24
- AdcRegs.ADCCHSELSEQ4.bit.CONV12 = 0x8; // -24 8
+ AdcRegs.ADCCHSELSEQ3.bit.CONV08 = 0x5; // 24В ПК было 5
+ AdcRegs.ADCCHSELSEQ3.bit.CONV09 = 0x1; // 15В Др
+ AdcRegs.ADCCHSELSEQ3.bit.CONV10 = 0xE; // +24В Дт
+ AdcRegs.ADCCHSELSEQ3.bit.CONV11 = 0x0; // +24В Дт
+ AdcRegs.ADCCHSELSEQ4.bit.CONV12 = 0x8; // -24В Дт было 8
- AdcRegs.ADCCHSELSEQ4.bit.CONV13 = 0xF;//0xF; // Ұ 1
- AdcRegs.ADCCHSELSEQ4.bit.CONV14 = 0xD;//0xD; // Ұ 2
+ AdcRegs.ADCCHSELSEQ4.bit.CONV13 = 0xF;//0xF; // ДТ° 1
+ AdcRegs.ADCCHSELSEQ4.bit.CONV14 = 0xD;//0xD; // ДТ° 2
AdcRegs.ADCCHSELSEQ4.bit.CONV15 = 0x9;//0x9;
}
@@ -152,6 +181,7 @@ void setup_adc()
if(EPwm1Regs.TBCTL.bit.HSPCLKDIV) HSPCLKdiv = 2*EPwm1Regs.TBCTL.bit.HSPCLKDIV;
else HSPCLKdiv = 1;
+ /* Период ePWM получается из реальной частоты TBCLK после обоих делителей. */
Rate = (SYSCLKOUT/(HSPCLKdiv*CLKdiv))/ADC_FREQ;
EPwm1Regs.TBPRD = Rate; // Set period for ePWM1
@@ -195,6 +225,18 @@ void setup_adc()
WAKE = WAKE_TIME;
}
+/**
+ * @brief Забирает результаты SEQ1, фильтрует их и запускает расчёт величин.
+ *
+ * @details Сначала разрешает вложенные прерывания согласно программному
+ * приоритету PIE. В зависимости от аппаратной роли обрабатывает силовые каналы,
+ * нагрузочный аналоговый вход, шкафные каналы либо мультиплексированные
+ * термодатчики. Выброс принимается только при прохождении FILTER_CLIP; во время
+ * WAKE фильтр обучается, но наружу выдаётся непосредственный отсчёт.
+ *
+ * @note На общем выходе fin всегда сбрасывает SEQ1, подтверждает PIE и
+ * восстанавливает сохранённую маску PIEIER1, включая путь раннего выхода.
+ */
interrupt void adc_isr(void)
{
static int cownt_one_canal=0;
@@ -214,6 +256,7 @@ interrupt void adc_isr(void)
PieCtrlRegs.PIEACK.all = 0xFFFF; // Enable PIE interrupts
EINT;
+ /* До READY обслуживается только обязательное завершение аппаратного цикла. */
if(WAKE) WAKE--;
if(!READY) goto fin;
@@ -246,7 +289,7 @@ interrupt void adc_isr(void)
{
if (++cownt_cans>=tpl_cans) cownt_cans=0;
-//
+// Именно здесь нет дыр
// if(sens_type[cownt_cans])
{
i++;
@@ -256,6 +299,7 @@ interrupt void adc_isr(void)
else Power_count(cownt_cans);
} } }
+ /* Программный мультиплексор: переключение, выдержка, накопление и расчёт. */
if(TermoSW)
{
if(cownt_one_canal==COUNT_DISCHARGE)
@@ -265,11 +309,12 @@ interrupt void adc_isr(void)
if(TermoAD)
{
if(cownt_cans == TPL_CANS ) code_tpl_canal = TERMOPAIR-1;
- if(cownt_cans == TPL_CANS+1) code_tpl_canal = TERMOPAIR-2; // 300 400
+ if(cownt_cans == TPL_CANS+1) code_tpl_canal = TERMOPAIR-2; // потому что 300 и 400 наоборот
}
select_tpl_canal(code_tpl_canal);
}
+ /* Отсчёты до окончания переходного процесса намеренно отбрасываются. */
if(cownt_one_canal > COUNT_TRANSICIA)
for(i=0;i>4;
+ /* Ограничитель защищает рекурсивный фильтр от одиночного выброса. */
ok = abs(ADC_table[n]-Temper) < FILTER_CLIP;
if(ok) cwnt_ok[i]++;
@@ -325,6 +371,7 @@ for(i=0;i<1;i++)
fin:
+ /* Единая эпилоговая секция обязательна и после goto при READY == 0. */
// Reinitialize for next ADC sequence
AdcRegs.ADCTRL2.bit.RST_SEQ1 = 1; // Reset SEQ1
AdcRegs.ADCST.bit.INT_SEQ1_CLR = 1; // Clear INT SEQ1 bit
diff --git a/Source/Internal/DAC.c b/Source/Internal/DAC.c
index 37600e2..828209e 100644
--- a/Source/Internal/DAC.c
+++ b/Source/Internal/DAC.c
@@ -1,3 +1,13 @@
+/**
+ * @file DAC.c
+ * @brief Управление внешним ЦАП и формирование аналогового задания нагрузки.
+ *
+ * Формирует последовательный поток через GPIO, рассчитывает временные интервалы
+ * относительно READY_FREQ и выдаёт нормированное значение Anal_output().
+ * Линии CLK/CS/DATA управляются непосредственно, поэтому порядок переключений
+ * и программные задержки являются частью аппаратного протокола и не должны
+ * переставляться без проверки осциллографом.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
#include "filter_bat2.h"
@@ -21,6 +31,10 @@ unsigned long WAKEpowse;
unsigned long IMPowse;
unsigned int period_dac, time_dac;
+/**
+ * @brief Переводит физические интервалы профиля нагрузки в тики READY_FREQ.
+ * @post Обновлены WAKEpowse, period_dac и time_dac.
+ */
void Setup_DAC_time(void)
{
WAKEpowse = 3L * DAC_FREQ +1;
@@ -29,12 +43,14 @@ void Setup_DAC_time(void)
time_dac = LOAD_TIME * DAC_FREQ;
}
+/** @brief Устанавливает логический уровень последовательной линии DATA ЦАП. */
void dOUT(int x)
{
if(x) GpioDataRegs.GPBSET.bit.GPIO60=1;
else GpioDataRegs.GPBCLEAR.bit.GPIO60=1;
}
+/** @brief Формирует короткую аппаратно-зависимую задержку между фронтами GPIO. */
void wast()
{
int i;
@@ -42,6 +58,11 @@ void wast()
for(i=0;i<25;i++);
}
+/**
+ * @brief Последовательно передаёт одно управляющее слово во внешний ЦАП.
+ * @param word Слово протокола, выдаваемое по DATA синхронно с CLK.
+ * @note CS и CLK переключаются программно; порядок фронтов менять нельзя.
+ */
void dSEND(unsigned int word)
{
int i;
@@ -57,12 +78,20 @@ void dSEND(unsigned int word)
dCS_1();
}
+/** @brief Устанавливает CS, CLK и DATA в безопасные уровни ожидания. */
void Init_DAC(void)
{
dSEND(0x9002|0x0000);
toggle_RES_OUT_1();
}
+/**
+ * @brief Масштабирует прикладное задание и отправляет код во внешний ЦАП.
+ * @param vrot Требуемое значение в прикладных единицах.
+ * @param maxx Значение полной шкалы; должно быть больше нуля.
+ * @details Результат ограничивается аппаратным диапазоном перед формированием
+ * управляющего слова, поэтому выход не переполняется при завышенном задании.
+ */
void Anal_output(long vrot, long maxx)
{
long out;
@@ -70,7 +99,7 @@ void Anal_output(long vrot, long maxx)
out = DAC_min() + vrot * DAC_max() / maxx - vrot * DAC_min() / maxx ;
- x=0;// !x;
+ x=0;// !x; тщетно бытие
if(cCalibrDac) out=DAC_cal();
@@ -99,6 +128,11 @@ Log_to_mem(modbus[0x1D]);
}
+/**
+ * @brief Выполняет очередной шаг временного профиля аналоговой нагрузки.
+ * @details Использует IMPowse как фазу профиля и на границах интервалов
+ * выбирает новое задание, передаваемое через Anal_output().
+ */
void Load_runner()
{
static unsigned int count_dac=0, count_load=0, x=1;
diff --git a/Source/Internal/Include/ADC.h b/Source/Internal/Include/ADC.h
index ff176ab..df8a164 100644
--- a/Source/Internal/Include/ADC.h
+++ b/Source/Internal/Include/ADC.h
@@ -1,8 +1,18 @@
+/**
+ * @file ADC.h
+ * @brief Публичный интерфейс подсистемы АЦП.
+ *
+ * Экспортирует таблицы последних/накопленных отсчётов и временные признаки,
+ * обновляемые ADC.c. Размеры массивов определены реализацией и аппаратной
+ * разводкой; потребители должны использовать только известные индексы каналов.
+ */
extern Uint16 adc_table_lem[];
extern Uint16 ADC_table[];
+/** Инициализирует тактирование, последовательность каналов и прерывание АЦП. */
void setup_adc(void);
+/** Счётчик активности и его порог, используемые для контроля измерительного цикла. */
extern long WAKE,WAKE_TIME;
diff --git a/Source/Internal/Include/DAC.h b/Source/Internal/Include/DAC.h
index 31e0786..9300b6d 100644
--- a/Source/Internal/Include/DAC.h
+++ b/Source/Internal/Include/DAC.h
@@ -1,7 +1,23 @@
+/**
+ * @file DAC.h
+ * @brief Интерфейс внешнего ЦАП и генератора задания нагрузки.
+ *
+ * Перед первым выводом требуется Init_DAC() и Setup_DAC_time(). Значение
+ * Anal_output() масштабируется относительно maxx; maxx должен быть положительным.
+ */
+/** Выполняет очередной шаг временного профиля аналогового выхода нагрузки. */
void Load_runner(void);
+/**
+ * Выдаёт нормированное значение на ЦАП.
+ * @param vrot Требуемая величина выхода.
+ * @param maxx Полная шкала; должна быть больше нуля.
+ */
void Anal_output(long vrot, long maxx);
+/** Переводит линии внешнего ЦАП в исходное состояние. */
void Init_DAC(void);
+/** Пересчитывает интервалы ЦАП из READY_FREQ, DAC_FREQ и LOAD_TIME. */
void Setup_DAC_time(void);
+/** Внутренние счётчики профиля, обновляемые DAC.c. */
extern unsigned long IMPowse;
extern unsigned int period_dac, time_dac;
diff --git a/Source/Internal/Include/GPIO_table.h b/Source/Internal/Include/GPIO_table.h
index 1c8079b..91ee1a7 100644
--- a/Source/Internal/Include/GPIO_table.h
+++ b/Source/Internal/Include/GPIO_table.h
@@ -1,3 +1,12 @@
+/**
+ * @file GPIO_table.h
+ * @brief Таблицы направления и мультиплексирования GPIO для вариантов плат.
+ *
+ * Макросы сгруппированы по аппаратным исполнениям и напрямую соответствуют
+ * выводам разъёмов. Значения 0/1 кодируют вход/выход либо выбранную функцию MUX.
+ * Любое изменение требует сверки со схемой конкретной платы и package.h;
+ * этот заголовок содержит конфигурацию, а не переносимую логику GPIO.
+ */
#define COMM_gpio00_dir 0UL
#define COMM_gpio01_dir 0UL
#define COMM_gpio02_dir 0UL
@@ -11,7 +20,7 @@
#define COMM_gpio10_dir 0UL
#define COMM_gpio11_dir 0UL
-#define COMM_gpio19_dir 1UL // 63 SPI
+#define COMM_gpio19_dir 1UL // 63 — SPI
#define COMM_gpio20_dir 0UL // 64 2:9B mode 2
#define COMM_gpio21_dir 0UL // 65 2:9A mode 4
#define COMM_gpio22_dir 0UL // 66 2:12C mode 1
@@ -23,7 +32,7 @@
#define COMM_gpio32_dir 1UL // 74 2:10B DIOD green
#define COMM_gpio33_dir 0UL
-#define COMM_gpio34_dir 1UL // 142 SCI
+#define COMM_gpio34_dir 1UL // 142 — SCI
#define COMM_gpio48_dir 1UL // 88 2:14C DIOD red
#define COMM_gpio49_dir 1UL // 89 2:14B select
@@ -54,7 +63,7 @@
#define BKSD_gpio10_dir 0UL // 19 2:6C oil
#define BKSD_gpio11_dir 1UL // 20 2:3C select
-#define BKSD_gpio19_dir 1UL // 63 SPI
+#define BKSD_gpio19_dir 1UL // 63 — SPI
#define BKSD_gpio20_dir 0UL // 64 2:9B mode 2
#define BKSD_gpio21_dir 0UL // 65 2:9A mode 4
#define BKSD_gpio22_dir 0UL // 66 2:12C mode 1
@@ -66,7 +75,7 @@
#define BKSD_gpio32_dir 1UL // 74 2:10B DIOD green
#define BKSD_gpio33_dir 0UL
-#define BKSD_gpio34_dir 1UL // 142 SCI
+#define BKSD_gpio34_dir 1UL // 142 — SCI
#define BKSD_gpio48_dir 1UL // 88 2:14C DIOD red
#define BKSD_gpio49_dir 0UL // 89 2:14B input 2
@@ -97,7 +106,7 @@
#define BKST_gpio10_dir 0UL
#define BKST_gpio11_dir 1UL // 20 2:3C select
-#define BKST_gpio19_dir 1UL // 63 SPI
+#define BKST_gpio19_dir 1UL // 63 — SPI
#define BKST_gpio20_dir 0UL // 64 2:9B mode 2
#define BKST_gpio21_dir 0UL // 65 2:9A mode 4
#define BKST_gpio22_dir 0UL // 66 2:12C mode 1
@@ -109,7 +118,7 @@
#define BKST_gpio32_dir 1UL // 74 2:10B DIOD green
#define BKST_gpio33_dir 0UL
-#define BKST_gpio34_dir 1UL // 142 SCI
+#define BKST_gpio34_dir 1UL // 142 — SCI
#define BKST_gpio48_dir 1UL // 88 2:14C DIOD red
#define BKST_gpio49_dir 0UL
@@ -140,7 +149,7 @@
#define PULT_gpio10_dir 0UL
#define PULT_gpio11_dir 0UL
-#define PULT_gpio19_dir 1UL // 63 SPI
+#define PULT_gpio19_dir 1UL // 63 — SPI
#define PULT_gpio20_dir 0UL // 64 2:9B mode 2
#define PULT_gpio21_dir 0UL // 65 2:9A mode 4
#define PULT_gpio22_dir 0UL // 66 2:12C mode 1
@@ -152,7 +161,7 @@
#define PULT_gpio32_dir 1UL // 74 2:10B DIOD green
#define PULT_gpio33_dir 0UL
-#define PULT_gpio34_dir 1UL // 142 SCI
+#define PULT_gpio34_dir 1UL // 142 — SCI
#define PULT_gpio48_dir 1UL // 88 2:14C DIOD red
#define PULT_gpio49_dir 0UL // 89 2:14B button
@@ -183,7 +192,7 @@
#define PLT2_gpio10_dir 0UL
#define PLT2_gpio11_dir 0UL
-#define PLT2_gpio19_dir 1UL // 63 SPI
+#define PLT2_gpio19_dir 1UL // 63 Ч SPI
#define PLT2_gpio20_dir 0UL // 64 2:9B mode 2
#define PLT2_gpio21_dir 0UL // 65 2:9A mode 4
#define PLT2_gpio22_dir 0UL // 66 2:12C mode 1
@@ -195,7 +204,7 @@
#define PLT2_gpio32_dir 1UL // 74 2:10B DIOD green
#define PLT2_gpio33_dir 0UL // 75 2:10C button
-#define PLT2_gpio34_dir 1UL // 142 SCI
+#define PLT2_gpio34_dir 1UL // 142 Ч SCI
#define PLT2_gpio48_dir 0UL // 88 2:14C button
#define PLT2_gpio49_dir 0UL // 89 2:14B button
@@ -226,7 +235,7 @@
#define SHKF_gpio10_dir 0UL // 19 2:6C input
#define SHKF_gpio11_dir 0UL // 20 2:3C input
-#define SHKF_gpio19_dir 1UL // 63 SPI
+#define SHKF_gpio19_dir 1UL // 63 — SPI
#define SHKF_gpio20_dir 0UL // 64 2:9B mode 2
#define SHKF_gpio21_dir 0UL // 65 2:9A mode 4
#define SHKF_gpio22_dir 0UL // 66 2:12C mode 1
@@ -238,7 +247,7 @@
#define SHKF_gpio32_dir 0UL // 74 2:10B input
#define SHKF_gpio33_dir 0UL // 75 2:10C input
-#define SHKF_gpio34_dir 1UL // 142 SCI
+#define SHKF_gpio34_dir 1UL // 142 — SCI
#define SHKF_gpio48_dir 1UL // 88 2:14C DIOD red
#define SHKF_gpio49_dir 0UL // 89 2:14B input
@@ -269,7 +278,7 @@
#define LOAD_gpio10_dir 0UL
#define LOAD_gpio11_dir 0UL // 20 2:3C res_in 0
-#define LOAD_gpio19_dir 1UL // 63 SPI
+#define LOAD_gpio19_dir 1UL // 63 — SPI
#define LOAD_gpio20_dir 0UL // 64 2:9B mode 2
#define LOAD_gpio21_dir 0UL // 65 2:9A mode 4
#define LOAD_gpio22_dir 0UL // 66 2:12C mode 1
@@ -281,7 +290,7 @@
#define LOAD_gpio32_dir 1UL // 74 2:10B DIOD green
#define LOAD_gpio33_dir 0UL
-#define LOAD_gpio34_dir 1UL // 142 SCI
+#define LOAD_gpio34_dir 1UL // 142 — SCI
#define LOAD_gpio48_dir 1UL // 88 2:14C DIOD red
#define LOAD_gpio49_dir 0UL
diff --git a/Source/Internal/Include/RS485.h b/Source/Internal/Include/RS485.h
index 3e4ca38..016bba5 100644
--- a/Source/Internal/Include/RS485.h
+++ b/Source/Internal/Include/RS485.h
@@ -1,11 +1,20 @@
+/**
+ * @file RS485.h
+ * @brief Состояние портов SCI/RS-485, ограничения буферов и API обмена.
+ *
+ * RS_DATA объединяет регистры SCI, буферы, счётчики и состояние конечного
+ * автомата. Память буферов принадлежит вызывающему коду и должна оставаться
+ * доступной до завершения операции. MAX_RECEIVE_LENGTH/MAX_SEND_LENGTH задают
+ * жёсткую границу, которую необходимо проверить до помещения данных в буфер.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************
RS485.h
****************************************************************
- * UART *
+ * Процедуры работы с UART *
****************************************************************/
#ifndef _RS485
#define _RS485
@@ -38,30 +47,30 @@ typedef struct
{
volatile struct SCI_REGS *SciRegs;
- unsigned int commnumber; //
- unsigned long RS_Length; //
+ unsigned int commnumber; // Номер порта
+ unsigned long RS_Length; // Длина пакета
- unsigned int *pRS_RecvPtr; //
- unsigned int *pRS_SendPtr; //
+ unsigned int *pRS_RecvPtr; // Буфер приема
+ unsigned int *pRS_SendPtr; // Буфер посылки
unsigned int *pRecvPtr;
- unsigned int RS_PrevCmd; //
- unsigned int RS_Cmd; //
- unsigned int RS_Header[MAX_RECEIVE_LENGTH]; //
- unsigned int flag_TIMEOUT_to_Send; //
- unsigned int flag_TIMEOUT_to_Receive; //
- unsigned int RS_DataReady; // RS
- unsigned int buffer[MAX_SEND_LENGTH]; // RS
+ unsigned int RS_PrevCmd; // Предыдущаа комманда
+ unsigned int RS_Cmd; // Текущаа комманда
+ unsigned int RS_Header[MAX_RECEIVE_LENGTH]; // Заголовок
+ unsigned int flag_TIMEOUT_to_Send; // Флаг ожиданиа таймаута на отсылку
+ unsigned int flag_TIMEOUT_to_Receive; // Флаг ожиданиа таймаута на прием
+ unsigned int RS_DataReady; // Флаг готовности RS данных
+ unsigned int buffer[MAX_SEND_LENGTH]; // Буфер дла отсылки по RS
- unsigned int addr_answer; //
- unsigned int addr_recive; //
- unsigned int flag_LEADING; // ( )
+ unsigned int addr_answer; // адрес куда отвечать в режиме ведущего
+ unsigned int addr_recive; // адрес по которому нас запросили
+ unsigned int flag_LEADING; // Флаг режима контроллера (по умолчанию ведомый)
unsigned long RS_RecvLen;
- unsigned long RS_SLength; //
- unsigned long RS_SendLen; //
- char RS_SendBlockMode; //
- char RS_Flag9bit; // RS485????????
- int BS_LoadOK; //
+ unsigned long RS_SLength; // Длина пакета дла посылки
+ unsigned long RS_SendLen; // Количество байт уже передали
+ char RS_SendBlockMode; // Режим передачи
+ char RS_Flag9bit; // дла RS485????????
+ int BS_LoadOK; // Флаг успешности приема блока
int RS_FlagBegin;
int RS_HeaderCnt;
int RS_FlagSkiping;
@@ -73,61 +82,72 @@ typedef struct
extern RS_DATA rs_a,rs_b;
extern unsigned int
- RS_Len[70]; /* () + 1 */
+ RS_Len[70]; /* Действительнаа длина команды (отладочной) + 1 */
+/** ISR приёма и передачи SCI-A/SCI-B; вызывают общий автомат соответствующего порта. */
interrupt void RSA_RX_Handler(void);
interrupt void RSA_TX_Handler(void);
interrupt void RSB_RX_Handler(void);
interrupt void RSB_TX_Handler(void);
-/* rs_a,rs_b*/
+/* иницилизациа переменных rs_a,rs_b*/
+/** Создаёт исходное состояние обоих RS_DATA и задаёт размер команды 15. */
void create_uart_vars(char size_cmd15);
-/** , */
-/** / */
+/** Повторнаа инициализациа последовательного порта, используетса после подвиса */
+/** Настройка режима приема/передачи */
void RS_SetBitMode(RS_DATA *rs_arr, int n);
-/** .
- 32- 0.
- @precondition - RS_TRANSMIT_INTR
- @param buf
- @param len
+/** Посылка блока байтов.
+ Посылает массива 32-битных целых чисел старшие биты должны быть 0.
+ @precondition Работа ф-ции зависит от макро RS_TRANSMIT_INTR
+ @param rs_arr Состояние выбранного порта.
+ @param pBuf Адрес массива слов, каждое из которых содержит один байт.
+ @param len количество байт
+ @return Ненулевое значение, если передача принята драйвером.
@see RS_BSend, RS_TRANSMIT_INTR
*/
int RS_Send(RS_DATA *rs_arr,unsigned int *pBuf, unsigned long len);
-/** .
- @precondition - RS_TRANSMIT_INTR
- @param buf
- @param len 8-
+/** Посылка блока упакованных байтов.
+ @precondition Работа ф-ции зависит от макро RS_TRANSMIT_INTR
+ @param rs_arr Состояние выбранного порта.
+ @param pBuf Адрес массива с упакованными байтами.
+ @param len количество 8-битных байт
+ @return Ненулевое значение, если передача принята драйвером.
@see RS_Send, RS_TRANSMIT_INTR
*/
int RS_BSend(RS_DATA *rs_arr,unsigned int *pBuf, unsigned long len);
-/** */
-void setup_uart(char commnumber,unsigned long speed_baud); /* speed_baud - */
+/** Инициализациа последовательного порта */
+/** @param commnumber COM_1 или COM_2. @param speed_baud Скорость линии в бодах. */
+void setup_uart(char commnumber,unsigned long speed_baud); /* speed_baud - скорость линии в бодах */
+/** Настраивает число бит данных, чётность и число стоп-битов выбранного SCI. */
void RS_SetLineMode(RS_DATA *rs_arr, int bit, char parity, int stop);
+/** Меняет скорость уже созданного порта и обновляет curr_baud. */
void RS_SetLineSpeed(RS_DATA *rs_arr, unsigned long speed);
// Transmit a character from the SCI'
#define SCI_send(x,y) x->SciRegs->SCITXBUF=(unsigned char)(y)
-// UART
+// Ожидание завершениа передачи UART
// wait for TRDY =1 for empty state
#define RS_Wait4OK(x) while(!(x->SciRegs->SCICTL2.bit.TXEMPTY))
-/** */
+/** Переключение линии на прием */
#define RS_Line_to_receive(x) if(x->commnumber==COM_2) GpioDataRegs.GPBDAT.bit.GPIO34 = 1;
-/** */
+/** Переключение линии на передачу */
#define RS_Line_to_send(x) if(x->commnumber==COM_2) GpioDataRegs.GPBDAT.bit.GPIO34 = 0;
-/** UART */
+/** Разрешение прерываний по получению символа и ошибкам от UART */
#define enableUARTInt(x) x->SciRegs->SCICTL2.all=2
#define enableUARTIntW(x) x->SciRegs->SCICTL2.all=1
+/** Сбрасывает таймер контроля активности после корректного обмена. */
void clear_timer_rs_live(RS_DATA *rs_arr);
+/** Обновляет признак потери связи по тайм-ауту RS_TIME_OUT. */
void test_rs_live(RS_DATA *rs_arr);
#ifdef __cplusplus
diff --git a/Source/Internal/Include/bios_dsp.h b/Source/Internal/Include/bios_dsp.h
index 2575cfa..a291e65 100644
--- a/Source/Internal/Include/bios_dsp.h
+++ b/Source/Internal/Include/bios_dsp.h
@@ -1,11 +1,19 @@
+/**
+ * @file bios_dsp.h
+ * @brief Команды сервисного BIOS и API удалённой работы с памятью.
+ *
+ * Объявляет номера команд загрузчика и обработчики, работающие с RS_DATA.
+ * Интерфейс предоставляет прямые операции с адресным пространством DSP и
+ * предназначен только для доверенных, уже проверенных протокольных запросов.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************/
/* Bios_dsp.h */
/****************************************************************/
- /* BIOS */
+ /* Основные комманды BIOS */
/****************************************************************/
#ifndef _BIOS_DSP
#define _BIOS_DSP
@@ -42,71 +50,85 @@ enum {
CMD_VECTOR=61,
CMD_IMPULSE,
- /* */
+ /* стандартные команды */
CMD_STD=65, CMD_STD_ANS
};
enum {false=0, true};
-/** , -1 */
+/** Возвращает номер комманды, если есть или -1 если транзакций не было */
+/** Проверяет кадр и выполняет содержащуюся в нём сервисную команду. */
int get_command(RS_DATA *rs_arr);
-/** , */
+/** Стандартный ответ, без параметров */
+/** Формирует и отправляет ответ длиной n слов через заданный порт. */
void Answer(RS_DATA *rs_arr,int n);
-/* ( )*/
+/* начальные установки (не работает)*/
+/** Сбрасывает состояние текущей сессии загрузчика. */
void init(RS_DATA *rs_arr);
-/**@name
-* , */
+/**@name Комманды
+* Комманды, вызываемые через последовательный канал*/
//@{
-/** .
- */
+/** Инициировать загрузку блока.
+ Настраивает прием блока данных */
+/** Подготавливает диапазон памяти к приёму нового образа. */
void initload(RS_DATA *rs_arr);
-/** .
- RS */
+/** Загрузка блока.
+ Вызываетса после загрузки блока через RS */
+/** Записывает очередной проверенный блок образа в память. */
void load(RS_DATA *rs_arr);
-/** Serial Boot.
- @precondition
-
- RecvPtr, - load
+/** Выполнить программу в формате Serial Boot.
+ @precondition Должна быть произведена загрузка блока
+ Адрес программы беретса из заголовка и
+ сравниваетса с переменной RecvPtr, заполнаемой в ф-ции load
@see load */
+/** Передаёт управление по адресу, заданному сервисной командой. */
void run (RS_DATA *rs_arr);
-/** */
+/** Прочитать ачейку памати */
+/** Читает запрошенный диапазон памяти и возвращает его ведущему узлу. */
void peek(RS_DATA *rs_arr);
-/** */
+/** Записать в ачейку памати */
+/** Записывает одно или несколько слов по адресу из команды. */
void poke(RS_DATA *rs_arr);
-/** */
+/** Передать блок памати */
+/** Передаёт ведущему узлу очередной блок содержимого памяти. */
void upload(RS_DATA *rs_arr);
-/** XILINX.
- @precondition
-
- RecvPtr Length, - load,
-
+/** Прошить XILINX.
+ @precondition Должна быть произведена загрузка блока
+ Адрес и длина прошивки беретса из заголовка и
+ сравниваетса с переменными RecvPtr и Length, заполнаемыми в ф-ции load,
+ так же смотрит магическое слово в начале прошивки
@see load */
+/** Обрабатывает команду доступа к внешней Flash-памяти. */
void xflash(RS_DATA *rs_arr);
-/** TMS.
- @precondition
-
- RecvPtr Length, - load
+/** Прошить TMS.
+ @precondition Должна быть произведена загрузка блока
+ Адрес и длина прошивки беретса из заголовка и
+ сравниваетса с переменными RecvPtr и Length, заполнаемыми в ф-ции load
@see load */
+/** Обрабатывает команду доступа к целевой Flash-памяти. */
void tflash(RS_DATA *rs_arr);
-/* */
+/* расширенные команды дла биоса */
+/** Обрабатывает расширенную сервисную команду BIOS. */
void extendbios(RS_DATA *rs_arr);
+/** Записывает слово по физическому адресу; адрес обязан быть допустимым. */
void write_memory(unsigned long addr, unsigned int data);
+/** Читает слово по физическому адресу; адрес обязан быть допустимым. */
unsigned int read_memory(unsigned long addr);
diff --git a/Source/Internal/Include/caliber.h b/Source/Internal/Include/caliber.h
index 5da8d7b..39746fb 100644
--- a/Source/Internal/Include/caliber.h
+++ b/Source/Internal/Include/caliber.h
@@ -1,22 +1,31 @@
+/**
+ * @file caliber.h
+ * @brief Заводские калибровочные таблицы для вариантов семейства Balsam.
+ *
+ * Ровно одна таблица def_cal включается по значению BALSAM. Строки и столбцы
+ * имеют фиксированный смысл для измерительных каналов и диапазонов; их порядок
+ * согласован с message.c и внешней методикой калибровки. Файл определяет данные,
+ * поэтому должен подключаться только одним модулем реализации.
+ */
#if BALSAM == 167 //-------------------------------------------------------------
int def_cal[][8] = {
-// 100 1 100 2 150 1 150 2
- 0, 0, 0, 0, 455, 441, 2459, 2392, // 0
- 0, 0, 0, 0, 462, 439, 2442, 2400, // 1
+// БКСС транс 100 1 100 2 150 1 150 2
+ 0, 0, 0, 0, 455, 441, 2459, 2392, // Борт 0
+ 0, 0, 0, 0, 462, 439, 2442, 2400, // Борт 1
-// UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
- 4936, 4877, 4972, 4857, 1886, 1898, 2517, 2535, // 0
- 4876, 5005, 4876, 4847, 1898, 1873, 2535, 2502, // 1
+// Силовой UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
+ 4936, 4877, 4972, 4857, 1886, 1898, 2517, 2535, // Борт 0
+ 4876, 5005, 4876, 4847, 1898, 1873, 2535, 2502, // Борт 1
-// UA UC IA IC 20mA O 4mA O 20mA I 4mA I
- 3820, 3790, 3505, 3380, 1736, 683, 854, 14, // 0 1062 1980
- 3835, 3785, 3522, 3332, 1745, 689, 860, 17, // 1 1720 2405
+// УМП UA UC IA IC 20mA O 4mA O 20mA I 4mA I
+ 3820, 3790, 3505, 3380, 1736, 683, 854, 14, // Борт 0 1062 1980
+ 3835, 3785, 3522, 3332, 1745, 689, 860, 17, // Борт 1 1720 2405
-// 100 Ohm 150 Ohm
+// БКССД 100 Ohm 150 Ohm
1281, 1272, 0, 0, 622, 2263, 0, 0,
-// 3801 3802 300 1 300 2 400 1 400 2
+// ВЭП 380Ф1 380Ф2 300 1 300 2 400 1 400 2
1290, 1285, 0, 0, 2045, 2045, 2725, 2725 };
#endif //---------------------------------------------------------------------
@@ -24,45 +33,45 @@
#if BALSAM == 166 //-------------------------------------------------------------
int def_cal[][8] = {
-// UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
- 4936, 4877, 4972, 4857, 1960, 1920, 2610, 2570, // 0
- 4876, 5005, 4876, 4847, 1950, 1965, 2600, 2615, // 1
+// Силовой UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
+ 4936, 4877, 4972, 4857, 1960, 1920, 2610, 2570, // Борт 0
+ 4876, 5005, 4876, 4847, 1950, 1965, 2600, 2615, // Борт 1
-// UA UC IA IC 20mA O 4mA O 20mA I 4mA I
- 3820, 3790, 3505, 3380, 1718, 673, 854, 14, // 0
- 3835, 3785, 3522, 3332, 1697, 653, 860, 17, // 1
+// УМП UA UC IA IC 20mA O 4mA O 20mA I 4mA I
+ 3820, 3790, 3505, 3380, 1718, 673, 854, 14, // Борт 0
+ 3835, 3785, 3522, 3332, 1697, 653, 860, 17, // Борт 1
-// 3801 3802 300 1 300 2 400 1 400 2
+// ВЭП 380Ф1 380Ф2 300 1 300 2 400 1 400 2
1281, 1272, 0, 0, 2049, 2050, 2749, 2750 };
#endif //---------------------------------------------------------------------
#if BALSAM == 165 //-------------------------------------------------------------
int def_cal[][8] = {
-// UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
- 5130, 4960, 4925, 5070, 1881, 1881, 2506, 2516, // 0
- 5020, 5040, 4950, 5030, 1871, 1879, 2507, 2514, // 1
+// Силовой UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
+ 5130, 4960, 4925, 5070, 1881, 1881, 2506, 2516, // Борт 0
+ 5020, 5040, 4950, 5030, 1871, 1879, 2507, 2514, // Борт 1
-// UA UC IA IC 20mA O 4mA O 20mA I 4mA I
- 3890, 3865, 5100, 4850, 1730, 675, 854, 14, // 0
- 3845, 3800, 3250, 3100, 1044, 0, 860, 17, // 1
+// УМП UA UC IA IC 20mA O 4mA O 20mA I 4mA I
+ 3890, 3865, 5100, 4850, 1730, 675, 854, 14, // Борт 0
+ 3845, 3800, 3250, 3100, 1044, 0, 860, 17, // Борт 1
-// 3801 3802 300 1 300 2 400 1 400 2
+// ВЭП 380Ф1 380Ф2 300 1 300 2 400 1 400 2
1289, 1280, 0, 0, 2073, 2071, 2773, 2771 };
#endif //---------------------------------------------------------------------
#if BALSAM == 164 //-------------------------------------------------------------
int def_cal[][8] = {
-// UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
- 7573, 7573, 7573, 7573, 1979, 1888, 2592, 2513, // 0
- 7573, 7573, 7573, 7573, 1970, 1907, 2584, 2558, // 1
+// Силовой UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
+ 7573, 7573, 7573, 7573, 1979, 1888, 2592, 2513, // Борт 0
+ 7573, 7573, 7573, 7573, 1970, 1907, 2584, 2558, // Борт 1
-// UA UC IA IC 20mA O 4mA O 20mA I 4mA I
- 3920, 3905, 3300, 3220, 1015, 0, 854, 14, // 0
- 3835, 3785, 5100, 4600, 1750, 680, 860, 17, // 1
+// УМП UA UC IA IC 20mA O 4mA O 20mA I 4mA I
+ 3920, 3905, 3300, 3220, 1015, 0, 854, 14, // Борт 0
+ 3835, 3785, 5100, 4600, 1750, 680, 860, 17, // Борт 1
-// 3801 3802 300 1 300 2 400 1 400 2
+// ВЭП 380Ф1 380Ф2 300 1 300 2 400 1 400 2
1266, 1267, 0, 0, 2063, 2040, 2678, 2655 };
#endif //---------------------------------------------------------------------
@@ -71,15 +80,15 @@
#if PXXXXX == 1 //-------------------------------------------------------------
int def_cal[][6] = {
-// UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
- 0400, 0400, 0400, 0400, 1950, 1950, 2600, 2600, // 0
- 0400, 0400, 0400, 0400, 1950, 1950, 2600, 2600, // 1
+// Силовой UA1 UB1 UA2 UB2 300 1 300 2 400 1 400 2
+ 0400, 0400, 0400, 0400, 1950, 1950, 2600, 2600, // Борт 0
+ 0400, 0400, 0400, 0400, 1950, 1950, 2600, 2600, // Борт 1
-// UA UC IA IC 20mA 4mA
- 0400, 0400, 4800, 4800, 0, 0, 0, 0, // 0
- 0400, 0400, 4800, 4800, 0, 0, 0, 0, // 1
+// УМП UA UC IA IC 20mA 4mA
+ 0400, 0400, 4800, 4800, 0, 0, 0, 0, // Борт 0
+ 0400, 0400, 4800, 4800, 0, 0, 0, 0, // Борт 1
-// 3801 3802 300 1 300 2 400 1 400 2
+// ВЭП 380Ф1 380Ф2 300 1 300 2 400 1 400 2
1270, 1270, 0, 0, 1950, 1950, 2600, 2600 };
#endif //---------------------------------------------------------------------
*/
diff --git a/Source/Internal/Include/cntrl_adr.h b/Source/Internal/Include/cntrl_adr.h
index 100021f..78fe2ef 100644
--- a/Source/Internal/Include/cntrl_adr.h
+++ b/Source/Internal/Include/cntrl_adr.h
@@ -1,11 +1,18 @@
+/**
+ * @file cntrl_adr.h
+ * @brief Адреса узла в сервисном протоколе и функция их установки.
+ *
+ * Константные адреса задают специальные роли, CNTRL_ADDR и ADDR_FOR_ALL могут
+ * меняться во время настройки. Заголовок не выполняет валидацию диапазонов.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************
cntrl_adr.h
****************************************************************
- * *
+ * Адрес контроллера *
****************************************************************/
#ifndef _CNTRL_ADR
@@ -15,22 +22,26 @@
extern "C" {
#endif
-/** */
+/** адрес контроллера дла посылки всем АИНам */
extern int ADDR_FOR_ALL;
-/** */
+/** адрес контроллера дла посылки ответа */
extern const int ADDR_ANSWER;
-/** */
+/** адреса терминала дла посылки ответа */
extern const int ADDR_TERMINAL;
-/* */
+/* Адрес контроллера */
extern int CNTRL_ADDR;
-/* */
+/* Универсальный адрес контроллера */
extern const int CNTRL_ADDR_UNIVERSAL;
-/** */
+/** Установка адреса контроллера дла прошивки */
+/**
+ * @param cntrl_addr Новый индивидуальный адрес узла.
+ * @param cntrl_addr_for_all Новый адрес групповых команд.
+ */
void set_cntrl_addr (int cntrl_addr,int cntrl_addr_for_all);
diff --git a/Source/Internal/Include/crc16.h b/Source/Internal/Include/crc16.h
index a1787cc..6f3bb66 100644
--- a/Source/Internal/Include/crc16.h
+++ b/Source/Internal/Include/crc16.h
@@ -1,8 +1,20 @@
+/**
+ * @file crc16.h
+ * @brief Интерфейс алгоритмов CRC, используемых коммуникационными модулями.
+ *
+ * Буфер представлен словами целевого DSP; параметр size трактуется реализацией
+ * как число обрабатываемых элементов. Вызывающий код выбирает вариант CRC и
+ * передаёт требуемое протоколом начальное значение.
+ */
typedef unsigned short WORD;
typedef unsigned char byte;
+/** Вычисляет CRC-CCITT для size слов, продолжая переданное значение crc. */
unsigned int get_crc_ccitt(unsigned int crc, unsigned int *buf, unsigned long size );
+/** Вычисляет табличный CRC-16 (полином 0xA001) для size слов. */
unsigned int get_crc_16(unsigned int crc,unsigned int *buf,unsigned long size );
+/** Вычисляет побитовый вариант CRC-16 для проверки табличной реализации. */
unsigned int get_crc_16b(unsigned int crc,unsigned int *buf,unsigned long size );
+/** Возвращает CRC-16 кадра с принятым в проекте начальным значением. */
int get_crc16(unsigned int *buf, int size );
diff --git a/Source/Internal/Include/ecan.h b/Source/Internal/Include/ecan.h
index d252d5d..9be1332 100644
--- a/Source/Internal/Include/ecan.h
+++ b/Source/Internal/Include/ecan.h
@@ -1,3 +1,22 @@
+/**
+ * @file ecan.h
+ * @brief Минимальный публичный интерфейс обмена по CAN.
+ *
+ * Port выбирает аппаратный контроллер, DevNum/Addr определяют логический узел.
+ * Массив данных должен содержать формат кадра, ожидаемый реализацией ecan.c.
+ */
+/**
+ * Настраивает CAN-порт и почтовые ящики логического устройства.
+ * @param Port Номер аппаратного CAN-контроллера.
+ * @param DevNum Номер узла; реализация ограничивает его диапазоном 1..16.
+ */
void InitCan(int Port, int DevNum);
+/**
+ * Ставит прикладные данные в передающий mailbox.
+ * @param Port Номер аппаратного CAN-контроллера.
+ * @param data Массив слов кадра в формате проекта.
+ * @param Addr Адрес получателя.
+ */
void CAN_send(int Port, int data[], int Addr);
+/** Последние прикладные слова, принятые обработчиком CAN. */
extern int CAN_input_data[];
diff --git a/Source/Internal/Include/filter_bat2.h b/Source/Internal/Include/filter_bat2.h
index a002e9b..2f96d23 100644
--- a/Source/Internal/Include/filter_bat2.h
+++ b/Source/Internal/Include/filter_bat2.h
@@ -1,3 +1,11 @@
+/**
+ * @file filter_bat2.h
+ * @brief Коэффициенты и состояние рекурсивного НЧ-фильтра второго порядка.
+ *
+ * Наборы K*_FILTER_BATTER2_* рассчитаны для заданных частот среза при штатной
+ * частоте дискретизации. DEF_FILTERBAT создаёт нулевое состояние для 3 Гц;
+ * один экземпляр FILTERBAT нельзя одновременно использовать для разных каналов.
+ */
#ifndef _FILTER_BAT2
#define _FILTER_BAT2
@@ -38,6 +46,12 @@ typedef struct { float k_0;
K3_FILTER_BATTER2_3HZ, \
0,0,0,0,0,0}
+/**
+ * Выполняет один шаг фильтра и обновляет его историю на месте.
+ * @param b Уникальное состояние фильтра конкретного канала.
+ * @param InpVarCurr Текущий входной отсчёт.
+ * @return Новый фильтрованный отсчёт.
+ */
float filterbat(FILTERBAT *b, float InpVarCurr);
diff --git a/Source/Internal/Include/kanal.h b/Source/Internal/Include/kanal.h
index fce5473..f10b6cb 100644
--- a/Source/Internal/Include/kanal.h
+++ b/Source/Internal/Include/kanal.h
@@ -1 +1,13 @@
+/**
+ * @file kanal.h
+ * @brief Интерфейс вывода значения на адресуемый канальный индикатор.
+ *
+ * adr выбирает индикатор, dat содержит отображаемое число, dot — позицию точки.
+ */
+/**
+ * Передаёт число адресуемому индикатору.
+ * @param adr Аппаратный адрес канала.
+ * @param dat Отображаемое целое значение.
+ * @param dot Позиция десятичной точки в протокольном формате индикатора.
+ */
void kanal_Send(int adr, long dat, int dot);
diff --git a/Source/Internal/Include/log_to_mem.h b/Source/Internal/Include/log_to_mem.h
index f6a9e82..0c55b11 100644
--- a/Source/Internal/Include/log_to_mem.h
+++ b/Source/Internal/Include/log_to_mem.h
@@ -1,11 +1,19 @@
+/**
+ * @file log_to_mem.h
+ * @brief Разметка внешней памяти и состояние кольцевого журнала.
+ *
+ * LOG_PAGE_START и LOG_PAGE_LEN обязаны совпадать с картой линковщика. Флаги
+ * no_write/never_write запрещают запись на разных уровнях, а clear_mem()
+ * полностью обнуляет область и потому является длительной операцией.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2001. */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2001г. */
/****************************************************************/
/* log_to_mem.h
****************************************************************
- * y *
+ * Запись логов в памyть *
****************************************************************/
#ifndef _LOG_TO_MEM
@@ -15,11 +23,11 @@
extern "C" {
#endif
-/* a a */
+/* Определениa длa работы логгера */
#define LOG_PAGE_START 0x0200000
#define LOG_PAGE_LEN 0xFA00
-extern int no_write, never_write; // , ( )
+extern int no_write, never_write; // Флаги, чтобы не писать (если что)
typedef struct
{
@@ -32,14 +40,15 @@ typedef struct
extern LOG Log;
-/* a , */
+/* Запись словa в памать, где логи лежат */
#define Log_to_mem(x) *(int *)(Log.Adres++) = x
-/* */
+/* Проверка границы памати дла логов */
//#define Test_mem_limit(x) if(Log.Adres > (Log.Finis - (x))) Log.Adres = Log.Start
#define Test_mem_limit(x) if(Log.Adres > (Log.Finis - (x))) Log.Adres -=(x)
-/* () */
+/* Очистка памати (обнуление) */
+/** Полностью обнуляет область журнала и возвращает указатель записи в начало. */
void clear_mem();
#ifdef __cplusplus
diff --git a/Source/Internal/Include/measure.h b/Source/Internal/Include/measure.h
index 357ca3c..eac9c2e 100644
--- a/Source/Internal/Include/measure.h
+++ b/Source/Internal/Include/measure.h
@@ -1,19 +1,41 @@
-//
+// вгв
#ifndef _MEASURE
#define _MEASURE
+/**
+ * @file measure.h
+ * @brief Общие типы, состояния и API измерительно-диагностической подсистемы.
+ *
+ * Объявляет этапы обработки каналов, битовые флаги ошибок и разделяемые данные,
+ * используемые ISR, протоколом и главным циклом. Номера каналов и позиции битов
+ * являются частью внешнего формата сообщений; менять их независимо нельзя.
+ */
+/** ISR Timer1 для периодического запуска измерительных и диагностических этапов. */
interrupt void cpu_timer1_isr_SENS(void);
+/** Инициализирует основные структуры, фильтры и состояния датчиков. */
void Init_sensors(void);
+/** Выполняет дополнительную инициализацию, зависящую от типа платы. */
void Init_sensors_more(void);
+/** Строит маски упаковки каналов для внешних сообщений. */
void Init_packMask(void);
+/** Рассчитывает температуру канала; ovn выбирает источник/режим измерения. */
void Temper_count(int chan, int ovn);
+/** Рассчитывает ток и обновляет диагностику заданного канала. */
void Current_count(int chan);
+/** Рассчитывает электрическую мощность заданного канала. */
void Power_count(int chan);
+/** Пересчитывает масштабные коэффициенты из калибровочных параметров. */
void calc_sensor_koef(void);
+/** Обновляет признаки превышения допустимого напряжения. */
void Is_Voltage_Hi(void);
+/** Пересчитывает пороги напряжения для текущего режима изделия. */
void calc_volta_edge(void);
+/**
+ * Выполняет временную проверку порога ошибки.
+ * @return Ненулевое значение после требуемого числа последовательных нарушений.
+ */
int er_anal(int term, unsigned int * count, long edge, int pre);
typedef union
@@ -64,14 +86,14 @@ typedef union
#define NOER 0xE000//C
#define EROR 0x01FF
-#define READY_FREQ (500.0 * 2)//
-#define BLINK_FREQ 2 //
+#define READY_FREQ (500.0 * 2)// Гц
+#define BLINK_FREQ 2 // Гц
#define BLINK_TIME (READY_FREQ / BLINK_FREQ)
#define CANPOWSE 20
-#define ADC_FREQ 3750 //3885.0//777//2000//20000 //777 //3885 // (777*5)
-#define DAC_FREQ 200//5 //
+#define ADC_FREQ 3750 //3885.0//777//2000//20000 //777 //3885 // Гц (777*5)
+#define DAC_FREQ 200//5 // Гц
#define LOAD_TIME 30//15 // sec
@@ -86,23 +108,23 @@ extern unsigned int Caliber_time;
#define ZERO 27
-#define Cooling 5 // ()
+#define Cooling 5 // (°С) Гистерезис по снатию перегрева
#define COSPi6 0.86602540378443864676372317075294
#define RADIX2 1.4142135623730950488016887242097
-#define POWER_380 0 // 380
-#define POWER_38O 1 // 380
-#define CURRENT 2 //
-#define VOLTAGE 3 //
-#define POWER_31 4 // 31
-#define POWER_27 5 // 24
-#define POWER_24 6 // 24
-#define POWER_15 7 // 15
-#define TERMO_AD 8 //
-#define TERMO_RS 9 //
-#define TRM_OBEH 10 //
+#define POWER_380 0 // питание 380В
+#define POWER_38O 1 // питание 380В
+#define CURRENT 2 // ток
+#define VOLTAGE 3 // напражение
+#define POWER_31 4 // питание 31В
+#define POWER_27 5 // питание 24В
+#define POWER_24 6 // питание 24В
+#define POWER_15 7 // питание 15В
+#define TERMO_AD 8 // термодатчик мелкосхема
+#define TERMO_RS 9 // терморезистор
+#define TRM_OBEH 10 // термодатчик овен
extern int TPL_CANS,tpl_cans;
diff --git a/Source/Internal/Include/message.h b/Source/Internal/Include/message.h
index 350e495..475c643 100644
--- a/Source/Internal/Include/message.h
+++ b/Source/Internal/Include/message.h
@@ -1,3 +1,11 @@
+/**
+ * @file message.h
+ * @brief Форматы команд, размеры ответов и API обработки Modbus-сообщений.
+ *
+ * CMD_TO_TMS описывает точную раскладку принятого кадра, включая CRC. Поля и
+ * ANSWER_LEN/REPLY_LEN согласованы с удалённым мастером; структура использует
+ * целевые типы DSP, поэтому не должна сериализоваться простым memcpy на PC.
+ */
#ifndef MESSAGE_H
#define MESSAGE_H
@@ -11,8 +19,8 @@ typedef unsigned char CHAR;
typedef struct
{
- unsigned char Address; //
- unsigned char Number; //
+ unsigned char Address; // Адрес контроллера
+ unsigned char Number; // Номер команды
BAITE byte0;
BAITE byte1;
@@ -30,13 +38,20 @@ typedef struct
extern int modbus[];
+/** Принимает запрос чтения регистров и подготавливает соответствующий ответ. */
void ReceiveCommandModbus3(RS_DATA *rs_arr);
+/** Принимает запрос записи одного регистра и применяет разрешённый параметр. */
void ReceiveCommandModbus6(RS_DATA *rs_arr);
+/** Отправляет текущий блок телеметрии ответом прикладной функции 4. */
void SendCommandModbus4(RS_DATA *rs_arr);
+/** Разбирает ответ функции 4, полученный от ведомого устройства. */
void ReceiveAnswerModbus4(RS_DATA *rs_arr);
+/** Сохраняет текущий массив параметров в EEPROM. */
void Save_params(void);
+/** Загружает параметры из EEPROM и заменяет некорректные значения безопасными. */
void Load_params(void);
+/** Заполняет параметры значениями для выбранного в package.h варианта платы. */
void Default_params(void);
#endif //MESSAGE_H
diff --git a/Source/Internal/Include/package.h b/Source/Internal/Include/package.h
index 1a9d3d3..aaab063 100644
--- a/Source/Internal/Include/package.h
+++ b/Source/Internal/Include/package.h
@@ -1,3 +1,12 @@
+/**
+ * @file package.h
+ * @brief Центральная конфигурация изделия Balsam и адресов его подсистем.
+ *
+ * Значение BALSAM выбирает аппаратный вариант, направление вращения и набор
+ * возможностей. adr_* и последующие константы связывают физические модули с
+ * протоколом и массивами измерений. Изменение этого файла влияет на всю прошивку
+ * и должно сопровождаться проверкой схемы, карты CAN/Modbus и калибровки.
+ */
#ifndef PACKAGE
#define PACKAGE
@@ -91,15 +100,15 @@
#define bSecretBt Buttons.bit.bit1
#define bTermoCal Buttons.bit.bit2
-#define Cancount (modbus+0x60) // I CAN
-#define Bright (modbus+0x62) //
-#define Brightness modbus[0x62] //
+#define Cancount (modbus+0x60) // пауза между I посылками CAN
+#define Bright (modbus+0x62) // аркость сигнальных лампочек
+#define Brightness modbus[0x62] // аркость сигнальных лампочек
-#define Owncount modbus[0x63] // OWEN
+#define Owncount modbus[0x63] // пауза между опросами OWEN
-/* !
-#define DAC_go modbus[0x64] //
-#define DAC_stop modbus[0x65] //
+/* Не врема!
+#define DAC_go modbus[0x64] // начало зарада
+#define DAC_stop modbus[0x65] // конец зарада
*/
#define m_FAST 0
diff --git a/Source/Internal/Include/peripher.h b/Source/Internal/Include/peripher.h
index 3d1eecf..3ad261b 100644
--- a/Source/Internal/Include/peripher.h
+++ b/Source/Internal/Include/peripher.h
@@ -1,3 +1,12 @@
+/**
+ * @file peripher.h
+ * @brief GPIO-примитивы и интерфейс определения аппаратного режима.
+ *
+ * Inline-функции учитывают различия разводки плат через глобальный Desk.
+ * Большинство выходов активны низким уровнем, поэтому set/clear не всегда
+ * совпадают с физическими SET/CLEAR регистрами. Desk должен быть определён
+ * get_Mode() до первого управления линиями.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
extern int Mode,Desk,TermoAD,TermoRS,TermoSW,Kurrent,Kalibro,Owen;
diff --git a/Source/Internal/Include/pulto.h b/Source/Internal/Include/pulto.h
index fb41363..034cc73 100644
--- a/Source/Internal/Include/pulto.h
+++ b/Source/Internal/Include/pulto.h
@@ -1,3 +1,10 @@
+/**
+ * @file pulto.h
+ * @brief Обработчик периодического таймера для режима пульта.
+ *
+ * Функция устанавливается в вектор Timer1 только для соответствующего Desk.
+ */
//void what_is(void);
+/** ISR таймера CPU1: обновляет индикацию и контроль связи в режиме пульта. */
interrupt void cpu_timer1_isr_PULT(void);
diff --git a/Source/Internal/Include/spise2p.h b/Source/Internal/Include/spise2p.h
index 1e41c26..a2aefcc 100644
--- a/Source/Internal/Include/spise2p.h
+++ b/Source/Internal/Include/spise2p.h
@@ -1,3 +1,12 @@
+/**
+ * @file spise2p.h
+ * @brief Конфигурация SPI EEPROM и структуры её конечного автомата.
+ *
+ * Определяет команды микросхемы, ширину адреса/данных, скорость SPI и состояния
+ * транзакции. SPISE2P_DRV_DEFAULTS создаёт единственный драйвер с callback-функциями;
+ * перед чтением или записью требуется InitSeeprom(), а диапазон операции должен
+ * оставаться внутри SEEPROM_LEN.
+ */
/*=====================================================================
File name : SPISE2P.H
@@ -17,7 +26,7 @@ Date : 30/6/2003 (DD/MM/YYYY)
#define __SPISE2P_H__
-//
+// Ёмкость памати в байтах
#define SEEPROM_LEN 0x10000
#define NULL 0
@@ -90,13 +99,28 @@ typedef struct {
typedef SPISE2P_DRV *SPISE2P_DRV_handle;
+/** Сбрасывает поля конечного автомата и конфигурирует SPI. */
void SPISE2P_DRV_init(SPISE2P_DRV * );
+/** Продвигает конечный автомат EEPROM на один таймерный такт. */
void SPISE2P_DRV_tick(SPISE2P_DRV *);
+/** Деактивирует аппаратный chip-select EEPROM. */
void SPISE2P_DRV_csset(void);
+/** Активирует аппаратный chip-select EEPROM. */
void SPISE2P_DRV_csclr(void);
+/** @return Ненулевое значение, когда драйвер готов принять новую операцию. */
unsigned int spiSe2pFree(SPISE2P_DRV *se2p);
+/**
+ * Запускает асинхронную запись.
+ * @param se2p Экземпляр конечного автомата.
+ * @param msgPtr Описание адреса, буфера и длины блока.
+ */
void spiSe2pWrite(SPISE2P_DRV *se2p, SE2P_DATA *data);
+/**
+ * Запускает асинхронное чтение.
+ * @param se2p Экземпляр конечного автомата.
+ * @param msgPtr Описание адреса, буфера и длины блока.
+ */
void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *data);
#if(SPISE2P_DATA_WIDTH==SIXTEEN_BIT)
@@ -109,25 +133,27 @@ void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *data);
#define WORD_LEN 1
#endif
-/* EEPROM. **
-** SPI . . **
-** 2! */
+/* Установка драйвера сериальной EEPROM. **
+** Инициализациа SPI и проч. Также настройка таймера. **
+** Драйвер работает на прерываниах от таймера 2! */
void InitSeeprom(void);
-/* SEEPROM. : **
-** adres - , . **
-** adres = 0..0x8000, 8 **
-** adres = 0..0x4000, 16 **
-** buf - , . **
-** size - . - ! */
+/* Запись блока в SEEPROM. Параметры таковы: **
+** adres - адрес в епромке, куда писать. **
+** adres = 0..0x8000, если длина слова 8 бит **
+** adres = 0..0x4000, если длина слова 16 бит **
+** buf - указатель на памать, откуда писать. **
+** size - длина блока в байтах. По-любому в байтах! */
+/** @param size Размер блока в байтах, независимо от SPISE2P_DATA_WIDTH. */
void Seeprom_write(unsigned int adres, unsigned int buf[], unsigned int size);
-/* SEEPROM. : **
-** adres - , . **
-** adres = 0..0x8000, 8 **
-** adres = 0..0x4000, 16 **
-** buf - , . **
-** size - . - ! */
+/* Чтение блока из SEEPROM. Параметры таковы: **
+** adres - адрес в епромке, откуда читать. **
+** adres = 0..0x8000, если длина слова 8 бит **
+** adres = 0..0x4000, если длина слова 16 бит **
+** buf - указатель на памать, куда читать. **
+** size - длина блока в байтах. По-любому в байтах! */
+/** @param size Размер блока в байтах, независимо от SPISE2P_DATA_WIDTH. */
void Seeprom_read(unsigned int adres, unsigned int buf[], unsigned int size);
#endif
diff --git a/Source/Internal/Include/tools.h b/Source/Internal/Include/tools.h
index 27df89b..d330272 100644
--- a/Source/Internal/Include/tools.h
+++ b/Source/Internal/Include/tools.h
@@ -1,7 +1,18 @@
+/**
+ * @file tools.h
+ * @brief Низкоуровневые вспомогательные функции платформы TMS320F28335.
+ *
+ * init_zone7() настраивает внешнюю шину, pause_us() выполняет активную задержку.
+ */
#ifndef TOOLS_H
#define TOOLS_H
+/** Настраивает 16-битную внешнюю шину XINTF Zone 7. */
void init_zone7(void);
+/**
+ * Выполняет активную задержку.
+ * @param t Длительность в микросекундах при штатной частоте CPU.
+ */
void pause_us(unsigned long t);
#endif //TOOLS_H
diff --git a/Source/Internal/RS485.c b/Source/Internal/RS485.c
index 0b248c9..c96b31b 100644
--- a/Source/Internal/RS485.c
+++ b/Source/Internal/RS485.c
@@ -1,11 +1,21 @@
+/**
+ * @file RS485.c
+ * @brief Драйвер двух каналов SCI/RS-485 и автоматы приёма/передачи кадров.
+ *
+ * Настраивает SCI-A/SCI-B, обслуживает RX/TX-прерывания, контролирует длины
+ * команд и тайм-аут активности, а также переключает формат и скорость линии.
+ * RS_DATA содержит разделяемое состояние конечного автомата; буферы должны
+ * жить до завершения передачи и не превышать MAX_*_LENGTH. Обработчики обязаны
+ * подтверждать PIE и корректно возвращать приоритеты прерываний.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************
- RS485.
+ RS485.с
****************************************************************
- * UART *
+ * Процедуры работы с UART *
****************************************************************/
//#include "big_dsp_module.h"
@@ -32,7 +42,12 @@ static char size_cmd15=1;
void RS_RX_Handler(RS_DATA *rs_arr);
void RS_TX_Handler(RS_DATA *rs_arr);
-/** UART - */
+/** Обработчик прерываний UART - принато */
+/**
+ * @brief ISR приёма SCI-A.
+ * @details Временно ограничивает приоритеты группы PIE9, вызывает общий
+ * RS_RX_Handler() для rs_a, затем восстанавливает маску и подтверждает группу.
+ */
interrupt void RSA_RX_Handler(void)
{
// Set interrupt priority:
@@ -51,6 +66,7 @@ interrupt void RSA_RX_Handler(void)
PieCtrlRegs.PIEIER9.all = TempPIEIER;
}
+/** @brief ISR приёма SCI-B; адаптер общего автомата к состоянию rs_b. */
interrupt void RSB_RX_Handler(void)
{
// Set interrupt priority:
@@ -69,6 +85,7 @@ interrupt void RSB_RX_Handler(void)
PieCtrlRegs.PIEIER9.all = TempPIEIER;
}
+/** @brief ISR освобождения TX SCI-A; продолжает передачу буфера rs_a. */
interrupt void RSA_TX_Handler(void)
{
// Set interrupt priority:
@@ -87,6 +104,7 @@ interrupt void RSA_TX_Handler(void)
PieCtrlRegs.PIEIER9.all = TempPIEIER;
}
+/** @brief ISR освобождения TX SCI-B; продолжает передачу буфера rs_b. */
interrupt void RSB_TX_Handler(void)
{
// Set interrupt priority:
@@ -105,7 +123,15 @@ interrupt void RSB_TX_Handler(void)
PieCtrlRegs.PIEIER9.all = TempPIEIER;
}
-/** UART - */
+/** Обработчик прерываний UART - принато */
+/**
+ * @brief Принимает очередной символ и продвигает автомат сборки кадра.
+ * @param rs_arr Состояние порта, вызвавшего прерывание.
+ * @details Проверяет аппаратные ошибки, адресный девятый бит, ожидаемую длину
+ * команды и границы буфера. После полного кадра выставляет признак готовности
+ * для foreground-кода; при нарушении синхронизации возвращается к ожиданию адреса.
+ * @warning Выполняется из ISR и не должен вызывать блокирующие операции.
+ */
void RS_RX_Handler(RS_DATA *rs_arr)
{
char Rc;
@@ -113,16 +139,16 @@ void RS_RX_Handler(RS_DATA *rs_arr)
// led1_on();
- for(;;) // 'goto'
+ for(;;) // 'goto' это не оператор азыка С
{
if(!rs_arr->SciRegs->SCIRXST.bit.RXRDY) // Receiver ready flag
{
PieCtrlRegs.PIEACK.bit.ACK9 |= 1;
rs_arr->SciRegs->SCIFFRX.bit.RXFFINTCLR=1; // Clear INT flag
- return; //
+ return; // кстати это единственный выход из прерываниа
}
- Rc = rs_arr->SciRegs->SCIRXBUF.bit.RXDT; //
+ Rc = rs_arr->SciRegs->SCIRXBUF.bit.RXDT; // Читаем символ в любом случае
if(rs_arr->SciRegs->SCIRXST.bit.RXERROR) // Receiver error flag
{
@@ -132,42 +158,42 @@ void RS_RX_Handler(RS_DATA *rs_arr)
continue;
}
- if(rs_arr->RS_DataReady) continue; //
+ if(rs_arr->RS_DataReady) continue; // Не забрали данные
- if (rs_arr->RS_Flag9bit==1) // RS485????????
+ if (rs_arr->RS_Flag9bit==1) // дла RS485????????
{
- //
- rs_arr->RS_FlagBegin = true; //
+ // Инициализируем переменные и флаги
+ rs_arr->RS_FlagBegin = true; // Ждем заголовок
rs_arr->RS_RecvLen = 0;
rs_arr->RS_FlagSkiping = false;
rs_arr->RS_HeaderCnt = 0;
rs_arr->RS_Cmd = 0;
}
- if(rs_arr->RS_FlagSkiping) continue; //
+ if(rs_arr->RS_FlagSkiping) continue; // Не нам
- if (rs_arr->RS_FlagBegin) //
+ if (rs_arr->RS_FlagBegin) // Заголовок
{
- if (rs_arr->RS_HeaderCnt==0) //
+ if (rs_arr->RS_HeaderCnt==0) // Адрес контроллера или стандартнаа команда
{
if( (Rc == CNTRL_ADDR_UNIVERSAL) || (Rc == CNTRL_ADDR && CNTRL_ADDR!=0) || ((Rc == rs_arr->addr_answer) && rs_arr->flag_LEADING)
|| ((Rc == ADDR_FOR_ALL && ADDR_FOR_ALL!=0) && !rs_arr->flag_LEADING))
{
- rs_arr->addr_recive=Rc; //
- rs_arr->RS_Header[rs_arr->RS_HeaderCnt++] = Rc; //
- RS_SetBitMode(rs_arr,8); // 8-
+ rs_arr->addr_recive=Rc; // запомнили адрес по которому нас запросили
+ rs_arr->RS_Header[rs_arr->RS_HeaderCnt++] = Rc; // Первый байт
+ RS_SetBitMode(rs_arr,8); // перестроились в 8-бит режим
}
else
{
- rs_arr->RS_FlagSkiping = true; //
- rs_arr->RS_FlagBegin = false; // 9-
+ rs_arr->RS_FlagSkiping = true; // Не нашему контроллеру
+ rs_arr->RS_FlagBegin = false; // остались в 9-бит режиме
// led1_off();
}
}
else
{
- rs_arr->RS_Header[rs_arr->RS_HeaderCnt++] = Rc; // ..
+ rs_arr->RS_Header[rs_arr->RS_HeaderCnt++] = Rc; // Второй байт и т.д.
if (rs_arr->RS_HeaderCnt == 7 && rs_arr->RS_Cmd==CMD_MODBUS_16 && !rs_arr->flag_LEADING)
{
@@ -180,68 +206,75 @@ void RS_RX_Handler(RS_DATA *rs_arr)
RS_Len[rs_arr->RS_Cmd] = Rc + 5;
}
- // -
+ // если второй байт - это команда
if (rs_arr->RS_HeaderCnt == 2)
{
rs_arr->RS_Cmd = Rc;
- //
- // CMD_LOAD -
- // CMD_STD_ANS -
+ // Проверка длины посылки
+ // CMD_LOAD - младшаа на данный момент
+ // CMD_STD_ANS - старшаа на данный момент
if ((rs_arr->RS_Cmd < CMD_MODBUS_3) || (rs_arr->RS_Cmd > CMD_STD_ANS) || (RS_Len[rs_arr->RS_Cmd]<3)
|| ((rs_arr->RS_Cmd == CMD_LOAD)&&(rs_arr->RS_PrevCmd != CMD_INITLOAD))
)
{
- RS_SetBitMode(rs_arr,9); // 9- RS485?
- rs_arr->RS_HeaderCnt = 0; //
+ RS_SetBitMode(rs_arr,9); // Получили все перестроились в 9-бит дла RS485?
+ rs_arr->RS_HeaderCnt = 0; // Потому что команда не та
rs_arr->RS_FlagBegin = true;
rs_arr->RS_FlagSkiping = false;
rs_arr->RS_Cmd=0;
// led1_off();
continue;
}
- if (rs_arr->RS_Cmd == CMD_LOAD) //
- rs_arr->RS_FlagBegin = false;//
+ if (rs_arr->RS_Cmd == CMD_LOAD) // Дла этой команды заголовок очень короткий
+ rs_arr->RS_FlagBegin = false;// дальше идут данные
}
if( (rs_arr->RS_HeaderCnt >= RS_Len[rs_arr->RS_Cmd]) ||
(rs_arr->RS_HeaderCnt >= sizeof(rs_arr->RS_Header)))
- { //
- RS_SetBitMode(rs_arr,9); // 9- RS485?
+ { // Получили заголовок
+ RS_SetBitMode(rs_arr,9); // Получили все перестроились в 9-бит дла RS485?
rs_arr->RS_FlagBegin = false;
rs_arr->RS_FlagSkiping = true;
rs_arr->RS_DataReady = true;
rs_arr->RS_Cmd=0;
// led1_off();
} } }
- else //
+ else // Поток данных
{
if(rs_arr->pRS_RecvPtr<(unsigned int *)Rec_Bloc_Begin || rs_arr->pRS_RecvPtr>(unsigned int *)Rec_Bloc_End)
{
- rs_arr->pRS_RecvPtr = (unsigned int *)Rec_Bloc_Begin; // ,
- rs_arr->pRecvPtr = (unsigned int *)Rec_Bloc_Begin; // ,
+ rs_arr->pRS_RecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
+ rs_arr->pRecvPtr = (unsigned int *)Rec_Bloc_Begin; // На программу надейса, а сам не плошай
}
- if(rs_arr->RS_PrevCmd != CMD_INITLOAD) continue; // -
+ if(rs_arr->RS_PrevCmd != CMD_INITLOAD) continue; // Мы здесь оказались по какой-то чудовищной ошибке
- if(rs_arr->RS_DataReady) // ,
- { //
- rs_arr->RS_FlagSkiping = true; //
+ if(rs_arr->RS_DataReady) // Если данные в основном цикле не забраны,
+ { // то пропускаем следующую посылку
+ rs_arr->RS_FlagSkiping = true; // Игнорируем до следующего заголовка
// led1_off();
continue;
}
RS_BytePtr = rs_arr->RS_RecvLen++ % 2;
- if(RS_BytePtr) *rs_arr->pRS_RecvPtr++ |= Rc; //
+ if(RS_BytePtr) *rs_arr->pRS_RecvPtr++ |= Rc; // Получили слово
else *rs_arr->pRS_RecvPtr = Rc<<8;
- if(rs_arr->RS_Length <= rs_arr->RS_RecvLen) //
+ if(rs_arr->RS_Length <= rs_arr->RS_RecvLen) // Конец посылки
{
rs_arr->RS_PrevCmd = rs_arr->RS_Header[1] = CMD_LOAD;
- RS_SetBitMode(rs_arr,9); // 9- RS485?
- rs_arr->RS_FlagSkiping = true; //
- rs_arr->RS_DataReady = true; // -
+ RS_SetBitMode(rs_arr,9); // Получили все данные перестроились в 9-бит дла RS485?
+ rs_arr->RS_FlagSkiping = true; // Игнорируем до следующего заголовка
+ rs_arr->RS_DataReady = true; // Флаг в основной цикл - данные получены
// led1_off();
} } } }
-/** UART - */
+/** Обработчик прерываний UART - послано */
+/**
+ * @brief Передаёт следующий элемент активного RS-485 буфера.
+ * @param rs_arr Состояние обслуживаемого SCI.
+ * @details Пока счётчик не достиг RS_Length, пишет следующий байт в SCITXBUF.
+ * После последнего байта дожидается освобождения линии, возвращает трансивер
+ * в режим приёма и сбрасывает состояние передачи.
+ */
void RS_TX_Handler(RS_DATA *rs_arr)
{
char RS_BytePtr;
@@ -251,18 +284,18 @@ void RS_TX_Handler(RS_DATA *rs_arr)
{
if(++rs_arr->RS_SendLen >= rs_arr->RS_SLength)
{
- enableUARTInt(rs_arr); /* */
+ enableUARTInt(rs_arr); /* Запрещаем прерываниа по передаче */
}
SCI_send(rs_arr,*(rs_arr->pRS_SendPtr++));
if(rs_arr->RS_SendLen >= rs_arr->RS_SLength)
{
RS_Wait4OK(rs_arr);
-// for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* PC */
- RS_SetBitMode(rs_arr,9); /* 9- RS485?*/
- RS_Line_to_receive(rs_arr); /* RS485 */
+// for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* Пауза дла PC */
+ RS_SetBitMode(rs_arr,9); /* Передали все перестроились в 9-бит дла RS485?*/
+ RS_Line_to_receive(rs_arr); /* режим приема RS485 */
- rs_arr->flag_TIMEOUT_to_Send=false; /* */
+ rs_arr->flag_TIMEOUT_to_Send=false; /* сбросили флаг ожиданиа таймаута */
}
}
else /* BM_PACKED */
@@ -271,7 +304,7 @@ void RS_TX_Handler(RS_DATA *rs_arr)
RS_BytePtr = (rs_arr->RS_SendLen++) % 2;
if(rs_arr->RS_SendLen >= rs_arr->RS_SLength)
{
- enableUARTInt(rs_arr); /* */
+ enableUARTInt(rs_arr); /* Запрещаем прерываниа по передаче */
}
if(RS_BytePtr) SCI_send(rs_arr, LOBYTE( *(rs_arr->pRS_SendPtr++) ));
else SCI_send(rs_arr, HIBYTE( *rs_arr->pRS_SendPtr ));
@@ -279,9 +312,9 @@ void RS_TX_Handler(RS_DATA *rs_arr)
if(rs_arr->RS_SendLen >= rs_arr->RS_SLength)
{
RS_Wait4OK(rs_arr);
-// for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* PC */
-// RS_SetBitMode(rs_arr,9); /* 9- RS485?*/
-// RS_Line_to_receive(); /* RS485 */
+// for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* Пауза дла PC */
+// RS_SetBitMode(rs_arr,9); /* Передали все перестроились в 9-бит дла RS485?*/
+// RS_Line_to_receive(); /* режим приема RS485 */
}
}
@@ -290,7 +323,11 @@ void RS_TX_Handler(RS_DATA *rs_arr)
// rs_arr->SciRegs->SCIFFTX.bit.TXINTCLR=1; // Clear INT flag
}
-/** */
+/** Инициализациа массива длин команд */
+/**
+ * @brief Заполняет таблицу ожидаемых длин сервисных и Modbus-команд.
+ * @post RS_Len[код] содержит полную длину кадра для поддерживаемых команд.
+ */
void setup_arr_cmd_length()
{
int i;
@@ -317,86 +354,105 @@ void setup_arr_cmd_length()
RS_Len[CMD_EXTEND] = 18;
}
-/** / */
+/** Настройка режима приема/передачи */
+/**
+ * @brief Переключает SCI между 8- и 9-битным форматом символа.
+ * @param rs_arr Настраиваемый порт.
+ * @param n Требуемое число бит данных.
+ */
void RS_SetBitMode(RS_DATA *rs_arr,int n)
{
if(n == 8)
{
- RS_SetLineMode(rs_arr,8,'N',1); /* */
+ RS_SetLineMode(rs_arr,8,'N',1); /* режим линии */
rs_arr->RS_Flag9bit=0;
}
if(n == 9)
{
- RS_SetLineMode(rs_arr,8,'N',1); /* */
+ RS_SetLineMode(rs_arr,8,'N',1); /* режим линии */
rs_arr->RS_Flag9bit=1;
} }
-/** .
- 32- 0.
- @precondition - RS_TRANSMIT_INTR
- @param buf
- @param len
+/** Посылка блока байтов.
+ Посылает массива 32-битных целых чисел старшие биты должны быть 0.
+ @precondition Работа ф-ции зависит от макро RS_TRANSMIT_INTR
+ @param buf адрес массива
+ @param len количество байт
@see RS_BSend, RS_TRANSMIT_INTR */
+/**
+ * @brief Запускает передачу массива, где один элемент хранит один байт.
+ * @return 1 после успешного запуска, 0 если порт занят или длина недопустима.
+ * @note Первый адресный символ отправляется в 9-битном режиме, оставшиеся — в 8-битном.
+ */
int RS_Send(RS_DATA *rs_arr,unsigned int *pBuf,unsigned long len)
{
unsigned int i;
- for (i=0; i <= 30000; i++){} /* PC */
+ for (i=0; i <= 30000; i++){} /* Пауза дла PC */
- RS_Line_to_send(rs_arr); /* RS485 */
+ RS_Line_to_send(rs_arr); /* режим передачи RS485 */
- for (i=0; i <= 10000; i++){} /* PC */
+ for (i=0; i <= 10000; i++){} /* Пауза дла PC */
- rs_arr->RS_SLength = len; /* */
+ rs_arr->RS_SLength = len; /* Настраиваем переменные */
rs_arr->pRS_SendPtr = pBuf + 1;
rs_arr->RS_SendBlockMode = BM_CHAR32;
- RS_Wait4OK(rs_arr); /* */
- RS_SetBitMode(rs_arr,8); /* 8- */
+ RS_Wait4OK(rs_arr); /* Дожидаемса ухода */
+ RS_SetBitMode(rs_arr,8); /* Остальные в 8-бит режиме */
- rs_arr->RS_SendLen = 1; /* */
+ rs_arr->RS_SendLen = 1; /* Два байта уже передали */
if(len > 1)
{
- enableUARTIntW(rs_arr); /* */
- SCI_send(rs_arr, *pBuf); //
+ enableUARTIntW(rs_arr); /* Разрешаем прерываниа по передаче */
+ SCI_send(rs_arr, *pBuf); // Передаем второй байт по прерыванию
}
else
{
- SCI_send(rs_arr, *pBuf); //
- RS_Wait4OK(rs_arr); /* */
- for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* PC */
- RS_SetBitMode(rs_arr,9); /* 9- */
- RS_Line_to_receive(rs_arr); /* RS485 */
+ SCI_send(rs_arr, *pBuf); // Передаем второй байт по прерыванию
+ RS_Wait4OK(rs_arr); /* Дожидаемса ухода без прерываниа */
+ for (i=0; i <= TIME_WAIT_RS_BYTE_OUT; i++){} /* Пауза дла PC */
+ RS_SetBitMode(rs_arr,9); /* Обратно в 9-бит режим */
+ RS_Line_to_receive(rs_arr); /* режим приема RS485 */
}
return 0;
}
-//
+// Посылка блока упакованных байтов
+/**
+ * @brief Запускает передачу массива с байтами, упакованными в слова DSP.
+ * @return 1 после успешного запуска, 0 при занятом порте или ошибке длины.
+ */
int RS_BSend(RS_DATA *rs_arr,unsigned int *pBuf, unsigned long len)
{
- RS_Line_to_send(rs_arr); // RS485
+ RS_Line_to_send(rs_arr); // режим передачи RS485
- rs_arr->RS_SLength = len; //
+ rs_arr->RS_SLength = len; // Настраиваем переменные
rs_arr->pRS_SendPtr = pBuf;
rs_arr->RS_SendBlockMode = BM_PACKED;
- RS_Wait4OK(rs_arr); //
- RS_SetBitMode(rs_arr,8); /* 8- */
+ RS_Wait4OK(rs_arr); // Ожидаем очистки и ухода последнего байта
+ RS_SetBitMode(rs_arr,8); /* Остальные в 8-бит режиме */
- rs_arr->RS_SendLen = 1; //
+ rs_arr->RS_SendLen = 1; // Один байт уже передали
- enableUARTIntW(rs_arr); /* */
+ enableUARTIntW(rs_arr); /* Разрешаем прерываниа по передаче */
- SCI_send(rs_arr,HIBYTE(*pBuf));//
+ SCI_send(rs_arr,HIBYTE(*pBuf));// Передаем первый байт
return 0;
}
-/** .
- @param speed RS */
-/** .
- @param speed RS */
+/** Устанавливает скорость обмена.
+ @param speed скорость RS в бод */
+/** Устанавливает скорость обмена.
+ @param speed скорость RS в бод */
+/**
+ * @brief Пересчитывает делитель baud-rate выбранного SCI.
+ * @param speed Требуемая скорость в бодах.
+ * @pre speed больше нуля и достижима от LSPCLK.
+ */
void RS_SetLineSpeed(RS_DATA *rs_arr,unsigned long speed)
{
long SciBaud;
@@ -410,7 +466,11 @@ void RS_SetLineSpeed(RS_DATA *rs_arr,unsigned long speed)
}
-/** */
+/** Инициализациа последовательного порта */
+/**
+ * @brief Обнуляет состояния обоих портов и создаёт таблицу длин команд.
+ * @param size_cmd15_set Прикладная длина команды функции 15.
+ */
void create_uart_vars(char size_cmd15_set)
{
size_cmd15=size_cmd15_set;
@@ -420,8 +480,13 @@ void create_uart_vars(char size_cmd15_set)
-/** */
+/** Инициализациа последовательного порта */
+/**
+ * @brief Назначает GPIO/векторы и запускает один из двух SCI-портов.
+ * @param commnumber COM_1 для SCI-A либо COM_2 для SCI-B.
+ * @param speed_baud Начальная скорость линии.
+ */
void setup_uart(char commnumber, unsigned long speed_baud)
{
volatile struct SCI_REGS *SciRegs;
@@ -485,14 +550,14 @@ void setup_uart(char commnumber, unsigned long speed_baud)
SciRegs->SCICTL1.bit.RXENA=1;
SciRegs->SCIFFTX.bit.SCIFFENA=0; // fifo off
- SciRegs->SCIFFRX.bit.RXFFIL=1; //
+ SciRegs->SCIFFRX.bit.RXFFIL=1; // Длина наименьшей команды
setup_arr_cmd_length();
- RS_SetLineSpeed(rs_arr,speed_baud); //
- RS_Line_to_receive(rs_arr); // RS485
- enableUARTInt(rs_arr); // UART
+ RS_SetLineSpeed(rs_arr,speed_baud); // скорость линии
+ RS_Line_to_receive(rs_arr); // режим приема RS485
+ enableUARTInt(rs_arr); // разрешение прерываний UART
RS_SetBitMode(rs_arr,9);
- rs_arr->RS_PrevCmd = 0; //
+ rs_arr->RS_PrevCmd = 0; // не было никаких команд
rs_arr->flag_TIMEOUT_to_Send = 0;
rs_arr->flag_LEADING = 0;
@@ -500,10 +565,11 @@ void setup_uart(char commnumber, unsigned long speed_baud)
SciRegs->SCICTL1.bit.SWRESET=1; // Relinquish SCI from Reset
}
-/** .
- @param bit
- @param parity (N,O,E,M,S)
- @param stop */
+/** Настройка режима линии.
+ @param bit количество бит данных
+ @param parity режим четности (N,O,E,M,S)
+ @param stop количество стоповых бит */
+/** @brief Программирует длину слова, чётность и число стоп-битов SCI. */
void RS_SetLineMode(RS_DATA *rs_arr, int bit, char parity, int stop)
{
volatile struct SCI_REGS *SciRegs;
@@ -541,12 +607,14 @@ Bit Bit Name Designation Functions
SciRegs->SCICCR.bit.ADDRIDLE_MODE = 0;
}
+/** @brief Отмечает успешную активность порта и сбрасывает его watchdog связи. */
void clear_timer_rs_live(RS_DATA *rs_arr)
{
rs_arr->time_wait_rs_out=0;
}
-/* RS */
+/* проверка на живучесть RS */
+/** @brief Увеличивает watchdog связи и фиксирует тайм-аут удалённого узла. */
void test_rs_live(RS_DATA *rs_arr)
{
/* if (rs_arr->time_wait_rs_out < RS_TIME_OUT)
@@ -554,6 +622,6 @@ void test_rs_live(RS_DATA *rs_arr)
else
{
rs_arr->time_wait_rs_out=0;
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
}*/ }
diff --git a/Source/Internal/bios.c b/Source/Internal/bios.c
index e03ac19..b7b4641 100644
--- a/Source/Internal/bios.c
+++ b/Source/Internal/bios.c
@@ -1,12 +1,22 @@
+/**
+ * @file bios.c
+ * @brief Сервисные команды удалённого доступа к памяти и загрузки программы.
+ *
+ * Реализует разбор BIOS-команд, формирование ответов, чтение/запись памяти,
+ * загрузку блоков и запуск кода через транспорт RS-485. Адреса и размеры
+ * приходят извне, поэтому корректность пакета и CRC должна быть подтверждена
+ * до вызова обработчиков. Прямой доступ к памяти преднамеренно использует
+ * volatile и требует адресов из допустимой карты памяти контроллера.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************
Bios.c
**************************************************************
- BIOS *
- RS232
+ Основные комманды BIOS *
+ дла работы с RS232
****************************************************************/
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "RS485.h"
@@ -18,82 +28,95 @@
//#include "spartan_tools.h"
//#include "big_dsp_module.h"
-int flag_DEBUG = false; /* */
+int flag_DEBUG = false; /* Флаг отладочного режима */
//static unsigned int *RecvPtr;
-//static int BS_LoadOK = false; /** */
+//static int BS_LoadOK = false; /** Флаг успешности приема блока */
/**********************************************************/
-/* , */
+/* Прототипы функций, используемых и определенных в файле */
/**********************************************************/
//static int _getbyte(int *addr, int offs);
+/** @brief Читает одно volatile-слово из физического адресного пространства DSP. */
unsigned int read_memory(unsigned long addr)
{
return (*(volatile int *)(addr));
}
+/** @brief Записывает одно слово data по физическому адресу addr. */
void write_memory(unsigned long addr, unsigned int data)
{
(*(volatile int *)( addr )) = data;
}
-/** , -1 */
+/** Возвращает номер комманды, если есть или -1 если транзакций не было */
+/**
+ * @brief Проверяет и диспетчеризует полностью принятый сервисный кадр.
+ * @param rs_arr Порт и буфер принятой команды.
+ * @return Код распознанной команды либо признак ошибки кадра.
+ * @details Проверяет адрес, длину и CRC, затем вызывает обработчик CMD_*.
+ */
int get_command(RS_DATA *rs_arr)
{
int cmd;
unsigned int crc, rcrc;
- if(rs_arr->RS_DataReady) // RS
+ if(rs_arr->RS_DataReady) // Данные по RS пришли
{
rs_arr->RS_DataReady = false;
- cmd = rs_arr->RS_Header[1]; //
+ cmd = rs_arr->RS_Header[1]; // Прочитали номер команды
- // CRC
+ // Провераем длину команды дла считываниа CRC
if((RS_Len[cmd]<3) || (RS_Len[cmd]>MAX_RECEIVE_LENGTH))
{
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
return -1;
}
- if(cmd == CMD_LOAD) //
+ if(cmd == CMD_LOAD) // Если команда загрузки
{
rs_arr->RS_PrevCmd = cmd;
- return cmd; // crc
+ return cmd; // Нет проверки crc
}
- else //
+ else // Все остальные команды
{
- // crc
+ // Считываем crc из посылки
crc = (rs_arr->RS_Header[RS_Len[cmd]-1] << 8) |
(rs_arr->RS_Header[RS_Len[cmd]-2]) ;
}
- // crc
+ // Рассчитываем crc из посылки
rcrc = 0xffff;
rcrc = get_crc_16( rcrc, rs_arr->RS_Header, (RS_Len[cmd]-2) );
- if(crc == rcrc) // crc
+ if(crc == rcrc) // Провераем crc
{
rs_arr->RS_PrevCmd = cmd;
return cmd;
}
else
{
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
} }
return -1;
}
-/** , */
+/** Стандартный ответ, без параметров */
+/**
+ * @brief Добавляет CRC и отправляет n слов ответа через исходный порт.
+ * @param rs_arr Порт, через который пришёл запрос.
+ * @param n Число информационных слов до CRC.
+ */
void Answer(RS_DATA *rs_arr,int n)
{
int crc;
- flag_DEBUG = true; //
+ flag_DEBUG = true; // Флаг отладочного режима
rs_arr->buffer[0] = rs_arr->addr_recive; //CNTRL_ADDR;
rs_arr->buffer[1] = n;
@@ -109,7 +132,7 @@ void Answer(RS_DATA *rs_arr,int n)
RS_Send(rs_arr,rs_arr->buffer, 6);
}
-/* - */
+/* Внутреннаа ф-циа */
static char _getbyte(unsigned int *addr, int32 offs)
{
unsigned int *address;
@@ -122,7 +145,8 @@ static char _getbyte(unsigned int *addr, int32 offs)
}
-/* ( )*/
+/* начальные установки (не работает)*/
+/** @brief Сбрасывает параметры текущей загрузочной сессии и подтверждает команду. */
void init(RS_DATA *rs_arr)
{
/*
@@ -145,13 +169,18 @@ void init(RS_DATA *rs_arr)
*/
}
-/**@name
-* ,
+/**@name Комманды
+* Комманды, вызываемые через последовательный канал
*/
//@{
-/** .
- */
+/** Инициировать загрузку блока.
+ Настраивает прием блока данных */
+/**
+ * @brief Принимает заголовок нового образа и подготавливает диапазон загрузки.
+ * @details Из кадра извлекаются начальный адрес, длина и контрольные параметры;
+ * запись данных разрешается только после успешной проверки заголовка.
+ */
void initload(RS_DATA *rs_arr)
{
unsigned long Address;
@@ -175,8 +204,12 @@ void initload(RS_DATA *rs_arr)
Answer(rs_arr,CMD_INITLOAD);
}
-/** .
- RS */
+/** Загрузка блока.
+ Вызываетса после загрузки блока через RS */
+/**
+ * @brief Записывает очередную порцию образа по текущему адресу загрузки.
+ * @post Рабочий адрес и остаток длины продвинуты на принятый блок.
+ */
void load(RS_DATA *rs_arr)
{
unsigned int rcrc, crc;
@@ -201,27 +234,32 @@ void load(RS_DATA *rs_arr)
else
{
rs_arr->BS_LoadOK = false;
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
} }
-/** Serial Boot.
- @precondition
-
- RecvPtr, - load
+/** Выполнить программу в формате Serial Boot.
+ @precondition Должна быть произведена загрузка блока
+ Адрес программы беретса из заголовка и
+ сравниваетса с переменной RecvPtr, заполнаемой в ф-ции load
@see load */
+/** @brief Подтверждает команду и передаёт управление загруженной программе. */
void run (RS_DATA *rs_arr)
{
return;
}
-/** */
+/** Прочитать ачейку памати */
+/**
+ * @brief Возвращает содержимое заданного диапазона памяти.
+ * @warning Диапазон должен быть предварительно разрешён протокольным уровнем.
+ */
void peek(RS_DATA *rs_arr)
{
unsigned long Address;
unsigned int Data, crc;
- flag_DEBUG = true; //
+ flag_DEBUG = true; // Флаг отладочного режима
Address = rs_arr->RS_Header[5] & 0xFF;
Address = (Address<<8) | (rs_arr->RS_Header[4] & 0xFF);
@@ -254,7 +292,8 @@ void peek(RS_DATA *rs_arr)
}
-/** */
+/** Записать в ачейку памати */
+/** @brief Записывает данные команды в заданную область памяти и отвечает статусом. */
void poke(RS_DATA *rs_arr)
{
unsigned long Address;
@@ -276,12 +315,13 @@ void poke(RS_DATA *rs_arr)
Answer(rs_arr,CMD_POKE);
}
-/** */
+/** Передать блок памати */
+/** @brief Передаёт ведущему очередной блок ранее выбранной области памяти. */
void upload(RS_DATA *rs_arr)
{
int32 Address, Length, crc;
- no_write = 1; //
+ no_write = 1; // Флаг отладочного режима
// stopp=1;
Address = rs_arr->RS_Header[5] & 0xFF;
@@ -301,10 +341,10 @@ void upload(RS_DATA *rs_arr)
crc = get_crc_16( crc, rs_arr->buffer, 2);
crc = get_crc_16b( crc, (unsigned int *)Address, Length);
- RS_Send(rs_arr,rs_arr->buffer, 1); // <=2
+ RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
rs_arr->buffer[0] = CMD_UPLOAD;
- RS_Send(rs_arr,rs_arr->buffer, 1); // <=2
+ RS_Send(rs_arr,rs_arr->buffer, 1); // <=2 байт по флагу
RS_Wait4OK(rs_arr);
RS_BSend(rs_arr,(unsigned int*)Address, Length);
@@ -318,22 +358,24 @@ void upload(RS_DATA *rs_arr)
}
-/** XILINX.
- @precondition
-
- RecvPtr Length, - load,
-
+/** Прошить XILINX.
+ @precondition Должна быть произведена загрузка блока
+ Адрес и длина прошивки беретса из заголовка и
+ сравниваетса с переменными RecvPtr и Length, заполнаемыми в ф-ции load,
+ так же смотрит магическое слово в начале прошивки
@see load */
+/** @brief Обрабатывает совместимую команду программирования внешней логики. */
void xflash(RS_DATA *rs_arr)
{
return;
}
-/** TMS.
- @precondition
-
- RecvPtr Length, - load
+/** Прошить TMS.
+ @precondition Должна быть произведена загрузка блока
+ Адрес и длина прошивки беретса из заголовка и
+ сравниваетса с переменными RecvPtr и Length, заполнаемыми в ф-ции load
@see load */
+/** @brief Обрабатывает команду программирования памяти целевого контроллера. */
void tflash(RS_DATA *rs_arr)
{
// volatile unsigned long Address1,Address2;
@@ -341,7 +383,7 @@ void tflash(RS_DATA *rs_arr)
/*
if(!rs_arr->BS_LoadOK)
{
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
return;
}
@@ -366,7 +408,7 @@ void tflash(RS_DATA *rs_arr)
if( (Address2 < 0x100000) || (Address2 > 0x180000) || ((Address2+LengthW) > 0x180000) )
{
- RS_Line_to_receive(rs_arr); // RS485
+ RS_Line_to_receive(rs_arr); // режим приема RS485
RS_SetBitMode(rs_arr,9);
return;
}
@@ -378,11 +420,15 @@ void tflash(RS_DATA *rs_arr)
return;
}
-/** TMS.
- @precondition
-
- RecvPtr Length, - load
+/** Прошить TMS.
+ @precondition Должна быть произведена загрузка блока
+ Адрес и длина прошивки беретса из заголовка и
+ сравниваетса с переменными RecvPtr и Length, заполнаемыми в ф-ции load
@see load */
+/**
+ * @brief Выполняет расширенную BIOS-команду, не входящую в базовый набор.
+ * @details Подкоманда и её аргументы извлекаются из принятого сервисного кадра.
+ */
void extendbios(RS_DATA *rs_arr)
{
volatile unsigned long Address1,Address2,Length;
@@ -407,10 +453,10 @@ void extendbios(RS_DATA *rs_arr)
switch ( code )
{
- // EPROM RAM
+ // Прошиваем EPROM Из RAM
case 4: Seeprom_write(Address1,(unsigned int*)Address2,Length);
break;
- // EPROM RAM
+ // Читаем из EPROM в RAM
case 5: Seeprom_read(Address1,(unsigned int*)Address2,Length);
break;
diff --git a/Source/Internal/cntrl_adr.c b/Source/Internal/cntrl_adr.c
index 2f29cdb..16921e8 100644
--- a/Source/Internal/cntrl_adr.c
+++ b/Source/Internal/cntrl_adr.c
@@ -1,11 +1,19 @@
+/**
+ * @file cntrl_adr.c
+ * @brief Хранение и изменение сетевых адресов контроллера.
+ *
+ * Содержит рабочий, широковещательный, ответный и терминальный адреса узла.
+ * set_cntrl_addr() меняет только изменяемые адреса; вызывающий код отвечает
+ * за проверку диапазона и за отсутствие обмена во время перенастройки.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2000 . */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2000 г. */
/****************************************************************
cntrl_adr.c
****************************************************************
- * *
+ * Адрес контроллера *
****************************************************************/
#include "cntrl_adr.h"
@@ -16,27 +24,34 @@
#define ADDR_UNIVERSAL_DEF 10
-/** */
+/** Установка адреса контроллера дла посылки всем АИНам */
int ADDR_FOR_ALL = ADDR_FOR_ALL_DEF;
-/** */
+/** Установка адреса контроллера дла посылки ответа */
const int ADDR_ANSWER = ADDR_ANSWER_DEF;
-/** */
+/** Установка адреса терминала дла посылки ответа */
const int ADDR_TERMINAL = ADDR_TERMINAL_DEF;
-/* */
+/* Универсальный адрес контроллера */
const int CNTRL_ADDR_UNIVERSAL=ADDR_UNIVERSAL_DEF;
-/* */
+/* Адрес контроллера */
int CNTRL_ADDR=1;
int cntr_addr_c;
int cntr_addr_c_all;
-/** */
+/** Установка адреса контроллера дла прошивки */
+/**
+ * @brief Одновременно обновляет индивидуальный и групповой адреса узла.
+ * @param cntrl_addr Новый адрес данного контроллера.
+ * @param cntrl_addr_for_all Адрес, по которому принимаются групповые команды.
+ * @note Проверка допустимого диапазона выполняется уровнем конфигурации.
+ */
void set_cntrl_addr (int cntrl_addr,int cntrl_addr_for_all)
{
+ /* Пара адресов меняется в одном месте, исключая смешанную конфигурацию. */
CNTRL_ADDR = cntrl_addr;
ADDR_FOR_ALL = cntrl_addr_for_all;
}
diff --git a/Source/Internal/crc16.c b/Source/Internal/crc16.c
index 756fa01..72b00f2 100644
--- a/Source/Internal/crc16.c
+++ b/Source/Internal/crc16.c
@@ -1,3 +1,12 @@
+/**
+ * @file crc16.c
+ * @brief Расчёт контрольных сумм CRC-CCITT и CRC-16 для протокольных кадров.
+ *
+ * Поддерживает табличные и побитовые варианты алгоритмов. Текущая конфигурация
+ * FAST_CRC/ONLY_CRC16 оставляет в прошивке только требуемый быстрый CRC-16.
+ * Размер задаётся в элементах буфера целевой платформы, а не в байтах host-PC;
+ * начальное значение CRC определяет вызывающий протокол.
+ */
#include "crc16.h"
#define MAKE_TABS 0 /* Builds tables below */
#define FAST_CRC 1 /* If fast CRC should be used */
@@ -87,6 +96,13 @@ WORD crc_16_tab[] = {
/* CRC-CCITT is based on the polynomial x^16 + x^12 + x^5 + 1. Bits */
/* are sent MSB to LSB. */
+/**
+ * @brief Продолжает расчёт CRC-CCITT по массиву слов.
+ * @param crc Начальное значение либо CRC предыдущего блока.
+ * @param buf Буфер данных; значим младший байт каждого слова.
+ * @param size Число обрабатываемых элементов.
+ * @return Итоговое 16-битное значение CRC-CCITT.
+ */
unsigned int get_crc_ccitt(unsigned int crc,unsigned int *buf,unsigned long size )
{
#if !(FAST_CRC & !MAKE_TABS)
@@ -112,6 +128,13 @@ unsigned int get_crc_ccitt(unsigned int crc,unsigned int *buf,unsigned long size
/* CRC-16 is based on the polynomial x^16 + x^15 + x^2 + 1. Bits are */
/* sent LSB to MSB. */
+/**
+ * @brief Вычисляет быстрый табличный CRC-16 с отражённым полиномом 0xA001.
+ * @param crc Начальное значение или результат предыдущей порции.
+ * @param buf Входные слова протокольного кадра.
+ * @param size Количество слов.
+ * @return CRC после обработки всего блока.
+ */
unsigned int get_crc_16(unsigned int crc,unsigned int *buf,unsigned long size )
{
#if !(FAST_CRC & !MAKE_TABS)
@@ -139,6 +162,11 @@ unsigned int get_crc_16(unsigned int crc,unsigned int *buf,unsigned long size )
+/**
+ * @brief Побитовый эталон расчёта CRC-16 без таблицы.
+ * @details Используется как компактная реализация и для сверки табличного
+ * варианта; восемь итераций обрабатывают каждый входной байт.
+ */
unsigned int get_crc_16b(unsigned int crc,unsigned int *buf,unsigned long size )
{
@@ -175,6 +203,12 @@ unsigned long i;
return (crc & 0xffff);
}
+/**
+ * @brief Проектная обёртка CRC-16 с установленным протоколом начальным значением.
+ * @param buf Кадр без добавляемого поля CRC.
+ * @param size Число элементов кадра.
+ * @return Контрольная сумма, готовая для сравнения или передачи.
+ */
int get_crc16(unsigned int *buf, int size )
{
int crc16,i,j;
diff --git a/Source/Internal/ecan.c b/Source/Internal/ecan.c
index 82b901b..5dcbe9c 100644
--- a/Source/Internal/ecan.c
+++ b/Source/Internal/ecan.c
@@ -1,3 +1,12 @@
+/**
+ * @file ecan.c
+ * @brief Инициализация eCAN, передача кадров и обработка CAN-прерываний.
+ *
+ * Настраивает выбранный CAN-контроллер и mailbox, нормализует номер устройства,
+ * отправляет данные и ведёт счётчики приёма, передачи и ошибок. Регистры eCAN
+ * изменяются через shadow-копии согласно требованиям C2000; обработчики должны
+ * подтверждать источник прерывания и не задерживать остальные группы PIE.
+ */
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -30,6 +39,14 @@ int CanTimeOutErrorTR = 0;
int wait=0;
+/**
+ * @brief Настраивает eCAN-A и его почтовые ящики для выбранного номера узла.
+ * @param Port Номер интерфейса; текущая аппаратная реализация использует CAN-A.
+ * @param DevNum Номер устройства 1..16, насыщаемый реализацией на границах.
+ * @details Регистры управления изменяются через ECanShadow, чтобы исключить
+ * опасные read-modify-write операции над регистрами с особыми битами.
+ * @post Разрешены приёмные mailbox и прерывания CAN, счётчики ошибок сброшены.
+ */
void InitCan(int Port, int DevNum)
{
struct ECAN_REGS ECanShadow;
@@ -40,6 +57,7 @@ void InitCan(int Port, int DevNum)
long id = 0x80BA0000;
+ /* Протокольная нумерация начинается с 1, аппаратное поле — с 0. */
DevNum--;
if(DevNum<0)DevNum=0;
if(DevNum>15)DevNum=15;
@@ -66,16 +84,16 @@ void InitCan(int Port, int DevNum)
// Required before writing the MSGIDs
ECanRegs->CANME.all = 0;
-// 0 a
+// задаем адрес 0 ащикa на передачу
ECanMboxes->MBOX0.MSGID.all = id + 0x10 + DevNum;
-// 1 a
- ECanMboxes->MBOX1.MSGID.all = id + DevNum; //!!!! 1 0!!!
+// задаем адрес 1 ащикa на прием
+ ECanMboxes->MBOX1.MSGID.all = id + DevNum; //поменать!!!! 1 и 0!!!
-// a 0 ,
+// задаем режимы работы ащикa 0 на передачу, остальные на прием
ECanRegs->CANMD.all = 0xFFFFFFFE;
-// 2 a ,
+// выбираем только 2 ащикa дла работы, остальные запрещаем
ECanRegs->CANME.all = 0x00000003;
// Clear all TAn bits
@@ -100,7 +118,7 @@ void InitCan(int Port, int DevNum)
ECanRegs->CANMC.all = ECanShadow.CANMC.all;
while(!ECanRegs->CANES.bit.CCE); // Wait for CCE bit to be set..
-// CAN
+// настриваем скорость CAN
ECanShadow.CANBTC.all = ECanRegs->CANBTC.all;
ECanShadow.CANBTC.bit.SJWREG=1;
@@ -113,7 +131,7 @@ void InitCan(int Port, int DevNum)
ECanRegs->CANMC.all = ECanShadow.CANMC.all;
while(ECanRegs->CANES.bit.CCE); // Wait for CCE bit to be cleared..
-//
+// задаем таймауты дла ожиданиа отправки получениа посылки
ECanMOTORegs->MOTO0 = 550000;
ECanMOTORegs->MOTO1 = 550000;
@@ -140,7 +158,7 @@ void InitCan(int Port, int DevNum)
IER |= M_INT9; // Enable CPU INT
EDIS;
-// CAN
+// завершили настройку CAN ащиков
MessageReceivedCount = 0;
ErrorCount = 0;
@@ -148,6 +166,14 @@ void InitCan(int Port, int DevNum)
MessageTransivedCount=0;
}
+/**
+ * @brief Формирует и запускает передачу одного прикладного CAN-кадра.
+ * @param Port Номер CAN-интерфейса.
+ * @param data Слова полезной нагрузки в формате проекта.
+ * @param Addr Идентификатор/адрес получателя.
+ * @note Перед записью mailbox проверяется завершение предыдущей передачи;
+ * тайм-аут отражается в CanTimeOutErrorTR.
+ */
void CAN_send(int Port, int data[], int Addr)
{
unsigned long hiword,loword;
@@ -177,13 +203,19 @@ void CAN_send(int Port, int data[], int Addr)
ECanRegs->CANTSC = 0; // clear time-out counter
EDIS;
- ECanRegs->CANTRS.all = 1; //
+ ECanRegs->CANTRS.all = 1; // запустить передачу
wait=1;
led1_toggle();
}
+/**
+ * @brief Разбирает данные сработавшего приёмного mailbox.
+ * @param ECanMboxes Набор mailbox активного CAN-контроллера.
+ * @details Копирует полезную нагрузку в CAN_input_data и обновляет счётчик
+ * принятых сообщений; подтверждение аппаратного флага выполняет вызывающий ISR.
+ */
void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
{
unsigned int adr;
@@ -215,6 +247,11 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
led2_toggle();
}
+/**
+ * @brief Основной ISR eCAN-A для приёма кадров и завершения передачи.
+ * @details Определяет источник по CANRMP/CANTA, вызывает Handlai() для входного
+ * mailbox, очищает обслуженные флаги и подтверждает группу PIE9.
+ */
interrupt void CANa_handler(void)
{
// Set interrupt priority:
@@ -234,6 +271,11 @@ interrupt void CANa_handler(void)
PieCtrlRegs.PIEIER9.all = TempPIEIER;
}
+/**
+ * @brief ISR линии ошибок eCAN-A.
+ * @details Сохраняет диагностические счётчики, очищает причину ошибки и
+ * возвращает контроллер к приёму, не меняя прикладную адресацию mailbox.
+ */
interrupt void CANa_reset_err(void)
{
// Set interrupt priority:
diff --git a/Source/Internal/filter_bat2.c b/Source/Internal/filter_bat2.c
index a577836..0035094 100644
--- a/Source/Internal/filter_bat2.c
+++ b/Source/Internal/filter_bat2.c
@@ -1,12 +1,32 @@
+/**
+ * @file filter_bat2.c
+ * @brief Один шаг рекурсивного фильтра второго порядка.
+ *
+ * filterbat() вычисляет новый выход по текущему входу и сохранённой истории,
+ * затем сдвигает состояние FILTERBAT. Каждый независимый сигнал обязан иметь
+ * собственный экземпляр структуры; коэффициенты задаются в filter_bat2.h и
+ * предполагают фиксированную частоту вызова, использованную при их расчёте.
+ */
#include "filter_bat2.h"
+/**
+ * @brief Вычисляет очередной отсчёт IIR-фильтра второго порядка.
+ * @param b Коэффициенты и история конкретного фильтруемого канала.
+ * @param InpVarCurr Текущий нефильтрованный отсчёт.
+ * @return Отфильтрованное значение.
+ * @pre b указывает на инициализированный объект FILTERBAT.
+ * @post История двух предыдущих входов и выходов сдвинута на один отсчёт.
+ */
float filterbat(FILTERBAT *b, float InpVarCurr)
{
float y;
+ /* Прямая ветвь использует текущий и два предыдущих входных отсчёта. */
y = (b->k_0 * (InpVarCurr + (b->i_0*2) + b->i_1)) +
+ /* Рекурсивная ветвь возвращает два предыдущих выхода фильтра. */
(b->k_1 * b->u_0) + (b->k_2 * b->u_1);
+ /* Сначала сдвигается старая история, затем сохраняются новые значения. */
b->u_1=b->u_0;
b->u_0=y;
b->i_1=b->i_0;
diff --git a/Source/Internal/kanal.c b/Source/Internal/kanal.c
index c719f08..bf36823 100644
--- a/Source/Internal/kanal.c
+++ b/Source/Internal/kanal.c
@@ -1,3 +1,12 @@
+/**
+ * @file kanal.c
+ * @brief Последовательный вывод числовых данных на канальный индикатор.
+ *
+ * Управляет линиями DCLK/DOUT, формирует импульс сброса и передаёт разряды
+ * индикатора с учётом адреса, десятичной точки и множителя тактовой частоты.
+ * Задержки и таблица сегментов привязаны к внешней схеме; изменение GPIO или
+ * CLKMULT требует повторной проверки временной диаграммы.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
#include "filter_bat2.h"
@@ -15,12 +24,14 @@
int digits[16] = {63,6,91,79,102,109,125,7,127,111,64,0,0,0,121,0};
int readr[5] = {0x08,0x0C,0x10,0x00,0x04};
+/** @brief Устанавливает тактовую линию канального последовательного интерфейса. */
void DCLK(int x)
{
if(x) GpioDataRegs.GPASET.bit.GPIO6=1;
else GpioDataRegs.GPACLEAR.bit.GPIO6=1;
}
+/** @brief Устанавливает линию данных канального последовательного интерфейса. */
void DOUT(int x)
{
if(x) GpioDataRegs.GPASET.bit.GPIO8=1;
@@ -48,6 +59,10 @@ void DOUT(int x)
#define POWS1 80L
#endif
+/**
+ * @brief Формирует последовательность сброса внешних регистров индикатора.
+ * @note Число холостых тактов является частью аппаратного протокола.
+ */
void RESET()
{
DCLK(0);
@@ -64,6 +79,7 @@ void RESET()
#endif
}
+/** @brief Выдаёт один бит данных и сопровождающий его импульс DCLK. */
void SENDBIT(int x)
{
DOUT(x); DCLK(1);
@@ -78,12 +94,20 @@ void SENDBIT(int x)
#endif
}
+/**
+ * @brief Кодирует адрес и число и передаёт их семисегментному индикатору.
+ * @param adr Адрес физического канала.
+ * @param dat Значение для отображения.
+ * @param dot Позиция десятичной точки.
+ * @details Таблица digits преобразует цифры в сегменты; знак и ведущие позиции
+ * обрабатываются до побитовой отправки каждого разряда.
+ */
void kanal_Send(int adr, long dat, int dot)
{
long Word,data,aliq_part,dg[4];
int i,j,bit,byt,addr,sgn=0,punkt=0,aliq_len=0,full_len, isdot=0;
- if(adr>1) //
+ if(adr>1) // Лампочки
{
Word =dat;
}
@@ -134,7 +158,7 @@ void kanal_Send(int adr, long dat, int dot)
for(i=3;i>0;i--)
{
if((dg[i]==0)&&(i!=dot))
- dg[i]=0xF; //
+ dg[i]=0xF; // Это значит пусто
else break;
}
@@ -143,7 +167,7 @@ void kanal_Send(int adr, long dat, int dot)
{
if( (dg[i]==0xF)||(i==3))
{
- dg[i]=0xA; //
+ dg[i]=0xA; // Это значит минус
break;
} }
diff --git a/Source/Internal/log_to_mem.c b/Source/Internal/log_to_mem.c
index 1b0a255..87fb62e 100644
--- a/Source/Internal/log_to_mem.c
+++ b/Source/Internal/log_to_mem.c
@@ -1,17 +1,26 @@
+/**
+ * @file log_to_mem.c
+ * @brief Кольцевой журнал событий во внешней области памяти.
+ *
+ * Резервирует секцию .logg, хранит границы и текущий адрес журнала и очищает
+ * всю отведённую область по запросу. LOG_PAGE_START/LOG_PAGE_LEN должны
+ * соответствовать linker command file; clear_mem() выполняет длительную
+ * блокирующую запись и не предназначена для вызова из обработчика прерывания.
+ */
/****************************************************************/
/* TMS320C32 */
-/* ====== BIOS, , ====== */
-/* () 1998-2001. */
+/* ====== BIOS, КЛАИН, КЛВСП ====== */
+/* ЦНИИ СЭТ (с) 1998-2001г. */
/****************************************************************/
/* log_to_mem.c
****************************************************************
- * y *
+ * Запись логов в памyть *
****************************************************************/
#include "log_to_mem.h"
int no_write = 0,
- never_write = 0; // , ( )
+ never_write = 0; // Флаги, чтобы не писать (если что)
#pragma DATA_SECTION(logs_block,".logg");
unsigned int logs_block[0xFA00];
@@ -19,7 +28,13 @@ unsigned int logs_block[0xFA00];
LOG Log;
unsigned int flog=0;
-// ,
+// Очищение памати, где логи лежат
+/**
+ * @brief Сбрасывает метаданные и физически очищает всю область журнала.
+ * @post Log.Adres установлен в LOG_PAGE_START, Log.Circl сброшен, а каждое
+ * слово выделенной области равно нулю.
+ * @warning Функция блокируется на LOG_PAGE_LEN операций внешней памяти.
+ */
void clear_mem()
{
unsigned long i;
@@ -29,6 +44,7 @@ void clear_mem()
Log.Adres = Log.Start;
Log.Circl = 0;
+ /* Полуинтервал [Start, Finis) исключает запись за последним словом секции. */
for (i=Log.Start; i // ! sqrt !!!
+#include // Это чтобы мерить амплитуду! sqrt без этого будет крив!!!
+/**
+ * @brief Состояние периодического запуска CAN-обмена.
+ *
+ * `CanPowse` увеличивается обработчиком таймера до значения `CANPOWSE`.
+ * При достижении порога счётчик сбрасывается, а `CanGO` устанавливается в 1.
+ */
unsigned int CanPowse=CANPOWSE,CanGO=0;
+/**
+ * @brief Битовые маски состава быстрых и медленных пакетов.
+ *
+ * Первый индекс выбирает режим передачи (`m_FAST` или `m_SLOW`), второй —
+ * 16-битное слово общей таблицы данных.
+ */
unsigned int Maska[2][8];
-int READY=0; //
+/** @brief Флаг завершения начальной загрузки и готовности модуля. */
+int READY=0; // Все загрузилос
-int TPL_CANS=0; //
-int tpl_cans=0; //
-int cal_addr=0; //
-int pow_addr=0; // \
+/** @brief Настроенное количество температурных групп TPL для текущего режима. */
+int TPL_CANS=0; // Количество температурных каналов
+/** @brief Фактическое количество логических температурных каналов. */
+int tpl_cans=0; // Количество температурных датчиков
+/** @brief Индекс начала калибровочных каналов в общей таблице датчиков. */
+int cal_addr=0; // Начало калибровочных датчиков
+/** @brief Индекс начала каналов измерения напряжения и тока. */
+int pow_addr=0; // Начало датчиков напражениа\\тока
+/**
+ * @brief Периоды, используемые логикой готовности и мигания индикации.
+ */
int period_ready, period_blink;
+/**
+ * @brief Накопленные и зафиксированные сводные признаки состояния.
+ *
+ * `chk` накапливает признаки в текущем цикле обработки каналов, а `sig`
+ * получает снимок накопленного состояния при начале следующего цикла.
+ */
FLAG chk,sig;
+/**
+ * @brief Временные интервалы, выраженные в периодах обработки АЦП.
+ *
+ * Значения рассчитываются функцией Init_sensors() из частоты `ADC_FREQ`.
+ */
unsigned int time_1_5sec, time_5msec, time_3sec, time_5sec;
+/** @brief Счётчики выдержки пониженного уровня для двух силовых каналов. */
unsigned int low_count[2] = {0,0};
+/** @brief Интегрирующие счётчики ошибок по фазным значениям. */
unsigned int err_count[6];
+/** @brief Действующие значения измеряемых фаз, полученные из lev_quadr[]. */
int lev_count[6];
+/** @brief Состояния фильтра квадратов мгновенных фазных значений. */
float lev_quadr[6];
+/** @brief Оценки среднего, то есть нулевого, уровня четырёх каналов АЦП. */
float zer_count[4];
+/** @brief Тип каждого логического измерительного канала. */
int sens_type[24];
+/** @brief Индекс парного канала для совместной диагностики и вычислений. */
int sens_pair[24];
+/** @brief Счётчики фильтрации диагностик дискретных входов. */
unsigned int din_count[32];
+/** @brief Температуры, полученные от внешних измерительных устройств. */
int ext_temp[8];
+/** @brief Диагностические состояния внешних температурных каналов. */
int ext_diag[8];
+/** @brief Рабочий счётчик времени процедуры калибровки. */
unsigned int Caliber_time = 0;
+/** @brief Минимальный уровень тока, при котором контролируется перекос фаз. */
int Curr_Edge;
+/**
+ * @brief Маска состояний, исключающих канал из выбора опорных данных имитации.
+ */
ERROR okay;
+/**
+ * @brief Коэффициенты пересчёта кодов АЦП в физические величины.
+ *
+ * Начальные значения соответствуют типам питающих каналов. Первые четыре
+ * коэффициента могут быть переопределены функцией calc_sensor_koef() по
+ * калибровочным значениям `Caliber[]`.
+ */
float powK[] =
{
- 0.127, // 380
- 0.127, // 380
+ 0.127, // питание 380В
+ 0.127, // питание 380В
- 1.000, //
- 1.000, //
+ 1.000, // ток
+ 1.000, // напражение
- 0.0076, // 31
- 0.0076, // 24
- 0.0076, // 24
- 0.0076, // 15
- 1.0000, //
+ 0.0076, // питание 31В
+ 0.0076, // питание 24В
+ 0.0076, // питание 24В
+ 0.0076, // питание 15В
+ 1.0000, // термодатчик мелкосхема
};
+/** @brief Шаблон начального состояния цифрового фильтра. */
FILTERBAT def_FILTERBAT = DEF_FILTERBAT;
-FILTERBAT adc_filter[20+4]; // .TPL*2 + 4
-FILTERBAT out_filter[20+4]; // .TPL*2 + 4
-FILTERBAT zer_filter[4]; // 4
+/** @brief Фильтры входных значений АЦП и калибровочных каналов. */
+FILTERBAT adc_filter[20+4]; // макс.TPL*2 + 4 калибр
+/** @brief Вторичные фильтры выходных и калибровочных значений. */
+FILTERBAT out_filter[20+4]; // макс.TPL*2 + 4 напраж
+/** @brief Фильтры оценок нулевого уровня четырёх силовых каналов. */
+FILTERBAT zer_filter[4]; // 4 напраж
+/** @brief Рабочие счётчики состояний измерительных каналов. */
long sens_count[24+4];
+/** @brief Коэффициенты пересчёта температурных каналов групп 100 и 50. */
float K100,K_50;
+/** @brief Коэффициенты пересчёта первой и второй температурных ветвей. */
float K_T1,K_T2;
+/**
+ * @brief Обработчик прерывания CPU Timer1 для обслуживания датчиков и индикации.
+ *
+ * Обработчик обслуживает сторожевой таймер, формирует периодический флаг
+ * `CanGO`, управляет индикацией готовности, аварии и нагрева, а в режиме
+ * `dsk_LOAD` передаёт выполнение функции Load_runner().
+ *
+ * @note Функция использует статические счётчики ШИМ яркости и мигания ламп.
+ */
interrupt void cpu_timer1_isr_SENS(void);
-/********************************************************************/
-/* */
-/********************************************************************/
+/**
+ * @brief Рассчитывает модуль трёхфазного вектора по двум измеренным фазам.
+ *
+ * @param ia Мгновенное значение первой измеренной фазы.
+ * @param ib Мгновенное значение второй измеренной фазы.
+ *
+ * @return Модуль результирующего вектора в тех же единицах, что и входы.
+ *
+ * @note Расчёт предполагает, что третья фаза может быть восстановлена из
+ * условия `ia + ib + ic = 0`.
+ */
float im_calc(float ia,float ib)
{
float isa,isb;
-
+
+ /*
+ * Из двух измеренных фаз формируются две взаимно перпендикулярные
+ * составляющие результирующего трёхфазного вектора. Третья фаза
+ * подразумевается равной -(ia + ib).
+ */
isa = - 1.5 * (ia + ib);
+
+ /* COSPi6 = cos(pi/6) = sqrt(3)/2; эта составляющая зависит от разности фаз. */
isb = COSPi6 * (ia - ib);
+
+ /* Нормировка 2/3 возвращает модуль в масштабе исходных фазных величин. */
return (2*sqrt(isa*isa+isb*isb)/3);
}
+/**
+ * @brief Пересчитывает коэффициенты преобразования измерительных каналов.
+ *
+ * Для температурных каналов коэффициенты выбираются по активному варианту
+ * `TermoRS` или `TermoAD`. Коэффициенты первых четырёх силовых каналов
+ * рассчитываются из массива `Caliber[]` с учётом типа канала.
+ *
+ * @pre Разности калибровочных точек, используемые как делители, не равны нулю.
+ * @post Обновлены `K100`, `K_50`, `K_T1`, `K_T2` и `powK[0..3]` в зависимости
+ * от выбранной конфигурации.
+ */
void calc_sensor_koef()
{
int i;
float K;
+ /*
+ * Вариант резистивных температурных датчиков. Коэффициент равен
+ * заданному температурному диапазону, делённому на разность двух
+ * сохранённых калибровочных кодов АЦП.
+ */
if(TermoRS)
{
K100 = 129.0/(K150_D - K100_D);
@@ -101,6 +227,10 @@ void calc_sensor_koef()
K_T2 = 129.0/(K150_2 - K100_2);
}
+ /*
+ * Вариант аналоговых термодатчиков: для каждой ветви используется
+ * собственная пара калибровочных точек K300/K400.
+ */
if(TermoAD)
{
K_T1 = 100.0/(K400_1 - K300_1);
@@ -108,66 +238,101 @@ void calc_sensor_koef()
}
// if(Kurrent)
+ /* Перевод пользовательских калибровок первых четырёх каналов в float-масштаб. */
for(i=0;i<4;i++)
{
K = Caliber[i];
+
+ /* Для тока и остальных величин калибровка хранится с разным десятичным масштабом. */
if(sens_type[i]==CURRENT) K/=1000.0;
else K/=10000.0;
+
powK[i] = K;
} }
+/** @copydoc cpu_timer1_isr_SENS */
interrupt void cpu_timer1_isr_SENS(void)
{
+ /*
+ * Статические переменные сохраняют фазу программного ШИМ яркости,
+ * фазу мигания и требуемые логические состояния ламп между прерываниями.
+ */
static unsigned int
count_blink=0, count_bright=0, count_mode,
blink_over, blink_alarm, work_lamp, heat_lamp, power_lamp;
+
+ /* Предыдущее состояние теста нужно для обнаружения фронта команды тестирования. */
static int preTest;
+ /* Объединённый запрос теста ламп от двух источников управления. */
int TST;
-#define err_lamp work_lamp // ,
-#define alm_lamp heat_lamp //
+#define err_lamp work_lamp // Для исполнения шкафа первый светодиод трактуется как «Ошибка».
+#define alm_lamp heat_lamp // Второй светодиод трактуется как «Авария».
+ /*
+ * Разрешается запись в защищённые регистры, обновляется статистика таймера
+ * и настраивается маска прерываний. EINT разрешает вложенные прерывания
+ * с приоритетом, оставленным маской MINT13.
+ */
EALLOW;
CpuTimer1.InterruptCount++;
IER |= M_INT13; // Set "global" priority
IER &= MINT13; // Set "global" priority
EINT;
- EDIS; // This is needed to disable write to EALLOW protected registers
+ EDIS; // Запрет дальнейшей записи в защищённые EALLOW-регистры.
+ /* Во время активного сброса сторожевой таймер намеренно не обслуживается. */
if(!cReset) ServiceDog();
+ /* Делитель частоты: один раз в CANPOWSE прерываний разрешается очередной CAN-цикл. */
if(++CanPowse >= CANPOWSE)
{
CanPowse = 0;
CanGO = 1;
}
+ /* Тест включён, если установлен хотя бы один из двух командных флагов. */
TST = cTestLamp|bTestLamp;
+ /*
+ * Индикатор READY мигает при отсутствии ошибки; тест ламп принудительно
+ * разрешает его переключение. Здесь применено побитовое OR для флагов 0/1.
+ */
if(!sig.bit.Error|TST) toggle_READY();
else clear_READY();
+ /* В исполнении нагрузки дальнейшей индикацией занимается отдельный автомат. */
if(Desk==dsk_LOAD)
{
Load_runner();
return;
}
+ /*
+ * Начало нового периода программного ШИМ яркости. Нужные выходы сначала
+ * включаются согласно логическим состояниям ламп, а ниже будут выключены
+ * при достижении значения Brightness.
+ */
if(++count_bright == maximum_bright)
{
count_bright = 0 ;
+ /* Назначение дискретного RES_OUT_1 зависит от исполнения устройства. */
if( ((Desk==dsk_SHKF) && power_lamp) ||
((Desk==dsk_COMM) && heat_lamp) ) set_RES_OUT_1();
else clear_RES_OUT_1();
- if(work_lamp) set_LED_OUT_1(); // err_lamp
+ if(work_lamp) set_LED_OUT_1(); // Она же err_lamp для шкафа.
else clear_LED_OUT_1();
- if(heat_lamp) set_LED_OUT_2(); // alm_lamp
+ if(heat_lamp) set_LED_OUT_2(); // Она же alm_lamp для шкафа.
else clear_LED_OUT_2();
}
+ /*
+ * Конец активной части ШИМ. В режиме теста выходы не гасятся по Brightness,
+ * поэтому лампы проверяются с полной заданной логической активностью.
+ */
if(!TST)
if(count_bright == Brightness)
{
@@ -176,6 +341,7 @@ interrupt void cpu_timer1_isr_SENS(void)
clear_RES_OUT_1();
}
+ /* На фронте теста немедленно запускается новый цикл мигания с известной фазы. */
if(TST & !preTest)
{
count_blink = BLINK_TIME;
@@ -183,13 +349,19 @@ interrupt void cpu_timer1_isr_SENS(void)
}
preTest = TST;
+ /* Обновление медленной фазы индикации выполняется один раз за BLINK_TIME. */
if(++count_blink >= BLINK_TIME)
{
count_blink=0;
count_mode++;
+
+ /* Быстрый шаблон меняет состояние на каждом шаге. */
blink_over = (count_mode & 1)?1:0;
+
+ /* Медленный шаблон равен нулю только на каждом восьмом шаге. */
blink_alarm = (count_mode & 7)?1:0;
+ /* При тесте все лампы получают один и тот же мигающий шаблон. */
if(TST)
{
heat_lamp = blink_over;
@@ -198,13 +370,18 @@ interrupt void cpu_timer1_isr_SENS(void)
}
else
{
+ /* Отдельная схема индикации для шкафа. */
if(Mode==adr_SHKF)
{
+ /* Наличие питания показывается постоянно, пока нет общей ошибки. */
power_lamp= 1;
+
+ /* Общая авария мигает на выходе alm_lamp. */
if(sig.bit.Alarm){// power_lamp= blink_alarm;
alm_lamp = blink_over; }
else alm_lamp = 0;
+ /* Ошибка останова заставляет одновременно мигать питание и err_lamp. */
if(sig.bit.Error){ power_lamp= blink_over;
err_lamp = blink_over; }
else err_lamp = 0;
@@ -214,22 +391,45 @@ interrupt void cpu_timer1_isr_SENS(void)
// if(sig.bit.Error) work_lamp = blink_over;
// else if(sig.bit.Alarm) work_lamp = blink_alarm;
// else
+ /* В штатном режиме лампа работы горит непрерывно. */
work_lamp = 1;
+ /* Секретная команда заменяет постоянное свечение медленным шаблоном. */
if(bSecretBt|cSecretBt) work_lamp = blink_alarm;
+ /*
+ * Температурная индикация имеет приоритет: перегрев — постоянно,
+ * предупреждение — быстрое мигание, обрыв — короткий импульс
+ * на каждом восьмом шаге.
+ */
if(sig.bit.OverHeat) heat_lamp = 1;
else if(sig.bit.SubHeat) heat_lamp = blink_over;
else if(sig.bit.OutHeat) heat_lamp = !blink_alarm;
else heat_lamp = 0;
} } } }
+/**
+ * @brief Выполняет базовую инициализацию измерительных каналов.
+ *
+ * Функция определяет число и размещение каналов по `Mode`, назначает типы и
+ * пары датчиков, сбрасывает фильтры и счётчики, настраивает специальные каналы
+ * для выбранного исполнения `Desk`, а также переводит временные выдержки из
+ * секунд в число периодов `ADC_FREQ`.
+ *
+ * @post Инициализированы таблицы `sens_type[]`, `sens_pair[]`, фильтры,
+ * диагностические счётчики и маска `okay`.
+ */
void Init_sensors()
{
int i;
-
+
+ /* Сначала формируется пустая карта каналов; далее её заполняет выбранный Mode. */
TPL_CANS = tpl_cans = cal_addr = 0;
+ /*
+ * Определение числа температурных каналов и позиции калибровочных входов.
+ * В режимах TRN/POW на одну группу TPL приходится два логических датчика.
+ */
switch(Mode)
{
case adr_TRN1:
@@ -246,17 +446,24 @@ void Init_sensors()
case adr_SHKF: tpl_cans = TPL_SHK; cal_addr = tpl_cans-2; break;
}
+ /* Силовые каналы размещаются после локальных и внешних температурных каналов. */
pow_addr = tpl_cans + Owen;
+ /* Общие значения по умолчанию для всей статической таблицы датчиков. */
for(i=0;i<24;i++)
{
+ /* Тип 0 означает, что специальная обработка каналу пока не назначена. */
sens_type[i]=0;
+
+ /* До явной настройки парным считается сам канал. */
sens_pair[i]=i;
+ /* Каждый канал получает независимое начальное состояние обоих фильтров. */
adc_filter[i] = def_FILTERBAT;
out_filter[i] = def_FILTERBAT;
}
+ /* Назначение типа всем локальным температурным каналам. */
if(TermoSW)
for(i=0;i1 и 2<->3. */
sens_pair[pow_addr+i]=pow_addr+(i^1);
+ /* Старт оценки нуля с последнего сохранённого значения. */
zer_count[i] = Zero_lev[i];
zer_filter[i] = def_FILTERBAT;
} }
+ /* Для стенда нагрузки вторая пара силовых входов измеряет ток. */
if(Desk==dsk_LOAD)
{
+ /* Для этого исполнения контроль перекоса начинается с более высокого уровня. */
Curr_Edge = 300;
sens_type[2]=
sens_type[3]=CURRENT;
}
+ /*
+ * В шкафном исполнении карта каналов фиксирована аппаратной схемой:
+ * первые шесть входов образуют три пары, остальные обрабатываются отдельно.
+ */
if(Mode==adr_SHKF)
{
sens_type[0] = POWER_380; sens_pair[0]=1;
@@ -310,45 +530,72 @@ void Init_sensors()
sens_type[14] = TERMO_AD;
}
+ /* Сброс принятой карты дискретных неисправностей. */
DigErr.all=0;
+ /* Все выдержки ниже хранятся не в секундах, а в количестве циклов ADC_FREQ. */
time_1_5sec = (3 * ADC_FREQ) / 2;
time_5msec = (5 * ADC_FREQ) / 1000;
time_3sec = (3 * ADC_FREQ);
time_5sec = (5 * ADC_FREQ);
+ /*
+ * Канал с любым из этих признаков не используется как исправный источник
+ * при имитации температурного датчика в Temper_count().
+ */
okay.all=0;
okay.bit.Tear=1;
okay.bit.Bypas=1;
okay.bit.Ignor=1;
okay.bit.Imit=1;
+ /* Сброс накопителей RMS и выдержек фазных неисправностей. */
for(i=0;i<6; i++)
{
err_count[i] = 0;
lev_count[i] = 0;
lev_quadr[i] = 0;
}
+
+ /* Сброс остальных счётчиков и буферов внешних температур. */
for(i=0;i<28;i++) sens_count[i] = 0;
for(i=0;i<32;i++) din_count[i] = 0;
for(i=0;i<8; i++) ext_temp[i] = ext_diag[i] = 0;
}
+/**
+ * @brief Завершает инициализацию после загрузки рабочей конфигурации.
+ *
+ * Очищает оперативные измерения и признаки ошибок, восстанавливает нулевые
+ * уровни, задаёт начальные значения для режима нагрузки, включает игнорирование
+ * отдельных каналов в специальных исполнениях и пересчитывает коэффициенты.
+ *
+ * @pre Ранее вызвана Init_sensors(), а калибровочные параметры уже доступны.
+ */
void Init_sensors_more()
{
int i;
+ /*
+ * Удаляются динамические биты состояния, но сохраняются служебные биты,
+ * разрешённые маской NOER. Параллельно очищаются измеренные значения.
+ */
for(i=0;i<24;i++)
{
modbus[i] &= NOER;
sens_data[i]=0;
}
+ /* Восстановление сохранённых нулей силовых АЦП. */
for(i=0;i<4;i++)
{
zer_count[i] = Zero_lev[i];
+ /*
+ * Для LOAD до появления реального измерения задаются стартовые значения
+ * напряжения, различающиеся для чётного и нечётного входа пары.
+ */
if(Desk==dsk_LOAD)
if(sens_type[i]==VOLTAGE)
{
@@ -356,6 +603,7 @@ void Init_sensors_more()
else sens_data[i]=390;
} }
+ /* В COMM все четыре силовых канала запускаются с установленным признаком Ignore. */
if(Desk==dsk_COMM)
{
Modbus[pow_addr+0].bit.bitE = 1; // Ignore
@@ -364,91 +612,186 @@ void Init_sensors_more()
Modbus[pow_addr+3].bit.bitE = 1; // Ignore
}
+ /* В LOAD игнорируются первые два канала общей таблицы. */
if(Desk==dsk_LOAD)
{
Modbus[0].bit.bitE = 1; // Ignore
Modbus[1].bit.bitE = 1; // Ignore
}
+ /* Коэффициенты рассчитываются после того, как типы каналов окончательно назначены. */
calc_sensor_koef();
}
+/**
+ * @brief Формирует маски данных быстрых и медленных пакетов обмена.
+ *
+ * Состав масок зависит от исполнения `Desk`, количества температурных каналов,
+ * наличия измерения тока (`Kurrent`) и внешних каналов (`Owen`).
+ *
+ * @post Массив `Maska[m_FAST][]` и `Maska[m_SLOW][]` содержит актуальный набор
+ * передаваемых диагностик, показаний, уставок и команд.
+ */
void Init_packMask()
{
int i,j,can=0;
+ /* Количество каналов, для которых надо включить диагностику и показания. */
can = pow_addr;
+ /* При наличии силового измерителя к базовой карте добавляются четыре канала. */
if(Kurrent) can += 4;
+ /* Полная очистка двух наборов по восемь 16-битных слов. */
for(i=0;i<2;i++)
for(j=0;j<8;j++) { Maska[i][j]=0; }
switch(Desk)
{
case dsk_EPLT:
- Maska[m_SLOW][0]|= 0x000F; //
- Maska[m_SLOW][6]|= 0x01FC; //
+ /* Для EPLT используется фиксированный сокращённый набор медленных данных. */
+ Maska[m_SLOW][0]|= 0x000F; // Полученные данные.
+ Maska[m_SLOW][6]|= 0x01FC; // Яркость ламп и служебные признаки.
break;
default:
+ /* Для каждого канала включается один бит диагностики и один бит показания. */
for(i=0;i=edge) return 1;
+
+ /* До порога сохраняется предыдущее логическое состояние. */
(*count)++; return pre;
- }
+ }
+
+ /* При снятии условия подтверждение также отпускается постепенно. */
if( (*count) == 0 ) return 0;
(*count)--; return pre;
}
+/**
+ * @brief Обновляет состояние ошибок выбранного датчика.
+ *
+ * Сохраняет служебные признаки, заданные маской `NOER`, добавляет вычисленные
+ * ошибки, обновляет сводный признак останова `chk.bit.Error` и формирует признак
+ * готовности канала как инверсию `Stop`.
+ *
+ * @param sens Индекс датчика в массиве `sens_error[]`.
+ * @param err Новое вычисленное состояние ошибок канала.
+ */
void reset_errs(int sens, ERROR err)
{
ERROR set;
+ /* Служебные флаги канала сохраняются, вычисляемые ошибки заменяются новыми. */
set.all = (sens_error[sens].all & NOER) | err.all;
+
+ /* Общий Stop накапливается по всем каналам текущего цикла обработки. */
chk.bit.Error|= set.bit.Stop;
+
+ /* Канал готов только тогда, когда его ошибки не требуют останова. */
set.bit.Ready = !set.bit.Stop;
+
sens_error[sens] = set;
}
+/**
+ * @brief Обрабатывает один канал измерения тока или напряжения.
+ *
+ * Функция отслеживает нулевой уровень АЦП, вычисляет мгновенное и действующее
+ * значение фазы, а для второго канала пары — амплитуду результирующего вектора
+ * и восстановленное действующее значение третьей фазы. Результаты записываются
+ * в `sens_data[]` и диагностическую область `modbus[]`. Дополнительно выполняется
+ * контроль перекоса фаз, верхнего и нижнего уровней.
+ *
+ * @param chan Относительный номер силового канала; допустимый диапазон задаётся
+ * массивами четырёхканального измерительного тракта.
+ *
+ * @pre Выполнены Init_sensors() и Init_sensors_more(); индекс `chan` допустим
+ * для `adc_table_lem[]`, `zer_count[]`, `powK[]` и связанных массивов.
+ */
void Current_count(int chan)
{
+ /* Numb — рабочее значение; Zer — оценка нуля; Current — мгновенная фаза; Level — RMS. */
float Numb,Zer,Current,Level;
+
+ /*
+ * aCurrent хранит первую измеренную фазу до обработки второго канала пары.
+ * Поэтому каналы каждой пары должны вызываться последовательно: чётный, затем нечётный.
+ */
static float aCurrent,Amplitude;
- int sens, pair, ist, thrd, fazz, feed;
+
+ /* Индексы глобального датчика, пары, группы и восстановленной третьей фазы. */
+ int ignor, sens, pair, ist, thrd, fazz, feed;
ERROR error;
+ /* В начале нового четырёхканального цикла фиксируется сводка прошлого цикла. */
if(Desk == dsk_LOAD)
if(!chan)
{
@@ -456,13 +799,25 @@ void Current_count(int chan)
chk.all = 0;
}
+ /* Переход от относительного номера силового входа к общей таблице датчиков. */
sens = pow_addr + chan;
+
+ /* Парный канал нужен для сравнения фазных RMS-значений. */
pair = sens_pair[chan];
+
+ /* Чётный канал — первая измеренная фаза пары, нечётный — вторая. */
ist = !(chan & 1);
+
+ /* Номер двухканальной измерительной группы: 0 для каналов 0/1, 1 для 2/3. */
feed = (chan >>1);
+
+ /* Начальная позиция трёх фаз этой группы в диагностической области Modbus. */
fazz = feed*3;
+
+ /* Индекс накопителя RMS восстановленной третьей фазы: 4 либо 5. */
thrd = feed+4;
+ /* Bypass полностью исключает канал из измерения, сохраняя сам флаг Bypass. */
if(sens_error[sens].bit.Bypas)
{
sens_error[sens].all = 0;
@@ -471,13 +826,23 @@ void Current_count(int chan)
return;
}
+ /* Сырой код специализированного АЦП/датчика LEM. */
Numb = adc_table_lem[chan];
+ /* Сохранение сырого кода в отдельной диагностической области. */
modbus[0x64+chan] = Numb;
-// ( )
+// Средний (он же нулевой) уровень показаний АЦП.
+ /*
+ * Первый IIR-слой медленно ведёт оценку к текущему коду. При вызове
+ * ADC_FREQ раз в секунду коэффициент соответствует примерно 5-секундной базе.
+ */
zer_count[chan] += (Numb-zer_count[chan])/(5.0 * ADC_FREQ);
+
+ /* Второй фильтр подавляет остаточную пульсацию оценки нулевого уровня. */
Zer = (int)(filterbat(&zer_filter[chan],zer_count[chan]));
+
+ /* Сохранённый рабочий ноль обновляется только в разрешённой части WAKE-цикла. */
if(WAKE < (WAKE_TIME /2)) Zero_lev[chan] = Zer;
/*
Test_mem_limit(12);
@@ -485,40 +850,53 @@ Log_to_mem((int)adc_table_lem[chan]);
Log_to_mem((int)zer_count[chan]);
Log_to_mem((int)Zero_lev[chan]);
*/
-//
+// Мгновенное значение фазы.
+ /* Удаление смещения АЦП и масштабирование в физическую величину. */
Current = (Numb - Zero_lev[chan]) * powK[chan];
-// ()
+// Действующее (среднеквадратичное) значение фазы.
+ /* Низкочастотная фильтрация квадрата мгновенного значения. */
lev_quadr[chan] += ((Current*Current)-lev_quadr[chan])/(1.0 * ADC_FREQ);
+
+ /* Корень из среднего квадрата даёт RMS текущей измеренной фазы. */
lev_count[chan] = sqrt(lev_quadr[chan]);
-//
+// Первая фаза пары запоминается до прихода второй.
if(ist)
{
+ /* В COMM полярность первой фазы аппаратно/логически развёрнута. */
if(Desk==dsk_COMM) aCurrent =-Current;
- else aCurrent = Current; // -
+ else aCurrent = Current; // Мгновенное значение первой фазы для расчёта вектора.
}
else
{
-//
+// Вычисление амплитуды по двум измеренным фазам.
+ /* im_calc() строит модуль трёхфазного вектора, затем результат фильтруется. */
Amplitude = filterbat(&out_filter[sens], im_calc(Current,aCurrent));
+ /* Небольшие значения подавляются как шум; порог зависит от типа канала. */
if(sens_type[sens]==VOLTAGE)
if(Amplitude<20) Amplitude = 0;
if(sens_type[sens]==CURRENT)
if(Amplitude<75) Amplitude = 0;
+ /* Для синусоиды действующее значение равно амплитуде, делённой на sqrt(2). */
Level = Amplitude / RADIX2;
+ /* Во время WAKE вычисления ведутся, но рабочие выходные данные не обновляются. */
if(!WAKE)
{
+ /* Нечётный элемент пары хранит амплитуду, предыдущий — RMS. */
sens_data[sens ] = Amplitude;
sens_data[sens-1] = Level;
}
-// ()
+// Действующее (среднеквадратичное) значение третьей фазы.
+ /* Для симметричной трёхпроводной системы сумма мгновенных фаз равна нулю. */
Numb =-Current-aCurrent;
+
+ /* Та же RMS-фильтрация применяется к восстановленной фазе. */
lev_quadr[thrd] += (Numb*Numb-lev_quadr[thrd])/(1.0 * ADC_FREQ);
lev_count[thrd] = sqrt(lev_quadr[thrd]);
/*
@@ -538,43 +916,60 @@ Log_to_mem(Amplitude);
Log_to_mem(Level);
}
*/
+/* Публикация трёх RMS-фаз группы в последовательных Modbus-регистрах. */
modbus[0x68+fazz ] = lev_count[pair];
modbus[0x68+fazz+1] = lev_count[chan];
modbus[0x68+fazz+2] = lev_count[thrd];
}
-//
+// Выбор максимального RMS из трёх фаз — опорного уровня для оценки перекоса.
Numb = lev_count[chan];
if(Numb 0.3) && (Numb>Curr_Edge),
&err_count[chan],time_3sec,0))
{
error.bit.Wry = 1;
- error.bit.Stop = 1;
+ if(!ignor) error.bit.Stop = 1;
}
+ /* Аналогичный контроль для вычисленной третьей фазы. */
if(er_anal( ((Numb-lev_count[thrd])/Numb > 0.3) && (Numb>Curr_Edge),
&err_count[thrd],time_3sec,0))
{
error.bit.Wry = 1;
- error.bit.Stop = 1;
+ if(!ignor) error.bit.Stop = 1;
}
+ /* Полное игнорирование канала прекращает дальнейший контроль уровней. */
+ if(ignor) goto fin;
+
+ /* Общий уровень пары проверяется один раз — при обработке её второго канала. */
if(Desk == dsk_LOAD)
if(!ist)
{
+ /* Верхняя уставка всегда считается аварийной и формирует Stop. */
if(Level > sens_hi_edge[sens])
{
error.bit.Hyper = 1;
error.bit.Stop = 1;
}
+ /* Для напряжения пониженный уровень подтверждается трёхсекундной выдержкой. */
if(sens_type[sens]==VOLTAGE)
if(er_anal( (Level < sens_lo_edge[sens]),
@@ -584,6 +979,7 @@ modbus[0x68+fazz+2] = lev_count[thrd];
error.bit.Stop = 1;
}
+ /* Для тока та же sens_lo_edge используется как порог предупреждения Over. */
if(sens_type[sens]==CURRENT)
if(Level > sens_lo_edge[sens])
{
@@ -610,24 +1006,49 @@ Log_to_mem(WAKE);
}
*/
fin:
+ /* Объединение вычисленных ошибок со служебными флагами выбранного канала. */
reset_errs(sens,error);
}
+/**
+ * @brief Обрабатывает один температурный канал.
+ *
+ * Поддерживаются локальные значения АЦП, внешние температуры типа `TRM_OBEH`,
+ * режим выдачи сырого кода, имитация по исправным однотипным датчикам и режим
+ * калибровки. После пересчёта температура ограничивается диапазоном от -20 до
+ * 200 градусов, записывается в `sens_data[]` с масштабом 0,1 градуса и
+ * проверяется на обрыв, перегрев и предупредительный уровень.
+ *
+ * @param chan Индекс температурного канала в общих массивах датчиков.
+ * @param own Ненулевое значение разрешает получение данных внешнего канала
+ * `TRM_OBEH` из массивов `ext_temp[]` и `ext_diag[]`.
+ *
+ * @pre Индекс `chan` допустим для выбранной конфигурации каналов.
+ */
void Temper_count(int chan, int own)
{
+ /* Рабочее значение: сначала код/внешние данные, затем температура в градусах. */
float Numb;
+
+ /* Целая температура используется при сравнении с уставками диагностики. */
static int Temper;
+
+ /* j — число доноров имитации; kun — индекс калибровочного канала. */
int i,j, kun;
long s;
ERROR error;
+
+ /* Индекс внешнего температурного канала относительно начала блока OWEN. */
int ovn;
+ /* Первый температурный канал открывает новый цикл накопления общих флагов. */
if(!chan)
{
sig.all = chk.all;
chk.all = 0;
}
+ /* Bypass применяется здесь только к локальным температурным каналам. */
if(sens_error[chan].bit.Bypas)
if(chan2200) kun|=2;
@@ -690,21 +1127,25 @@ void Temper_count(int chan, int own)
return;
} }
+ /* Калибровочные/служебные позиции за пределами tpl_cans здесь не измеряются. */
if(chan>=tpl_cans) return;
-// Kun
+// Нечётность kun выбирает одну из двух независимых аналоговых ветвей.
if(kun&1) Numb = (Numb-K300_2)*K_T2+ZERO;
else Numb = (Numb-K300_1)*K_T1+ZERO;
}
+ /* Резистивный вариант температурного тракта. */
if(TermoRS)
{
+ /* Каналы начиная с cal_addr используются как калибровочные, а не измерительные. */
if(kun>=0)
{
+ /* Во время WAKE вторичный фильтр синхронизируется с первичным. */
if(WAKE) out_filter[chan] = adc_filter[chan];
else
{
-//
+// Калибровочный код дополнительно фильтруется вторым слоем.
Numb = filterbat(&out_filter[chan],Numb);
TCaliber[kun] = Numb;
@@ -715,29 +1156,39 @@ void Temper_count(int chan, int own)
if(Desk==dsk_BKSD)
{
+ /* Для диагностики BKSD сырой код дополнительно публикуется со смещением +8. */
sens_data[chan+8] = ADC_table[chan];
+
+ /* Первые шесть и последующие каналы используют разные масштабы. */
if(chan<6) Numb = (Numb-K100_D)*K100;
else Numb = (Numb-K100_D)*K_50;
}
else
{
+ /* В остальных исполнениях чётные и нечётные каналы имеют свои калибровки. */
if(chan&1) Numb = (Numb-K100_2)*K_T2;
else Numb = (Numb-K100_1)*K_T1;
} }
+ /* Ограничение диапазона одновременно защищает формат данных и задаёт аварийные границы. */
if(Numb < -20) Numb = -20;
if(Numb > 200) Numb = 200;
+ /* Сервисная команда подменяет результат фиксированной температурой 40 °C. */
if(bSecretBt|cSecretBt) Numb = 40.0;
+ /* В таблице температура хранится в десятых долях градуса. */
sens_data[chan] = Numb*10;
+ /* Для пороговых сравнений дробная часть отбрасывается. */
Temper = Numb;
error.all = 0;
+
+ /* Во время запуска или при Ignore показание сохраняется, но диагностика не обновляется. */
if(WAKE || sens_error[chan].bit.Ignor) goto fin;
-//
+// Обрыв или неисправность: результат упёрся в одну из границ допустимого диапазона.
if(Temper<=-20 || Temper>=200)
{
error.bit.Tear = 1;
@@ -745,7 +1196,11 @@ sens_data[chan+8] = ADC_table[chan];
}
else
-//
+// Перегрев.
+ /*
+ * Если Hyper уже был активен, он удерживается до снижения температуры
+ * ниже верхней уставки на величину Cooling — это температурный гистерезис.
+ */
if(((Temper>sens_hi_edge[chan]-Cooling) && (sens_error[chan].bit.Hyper)) ||
(Temper>sens_hi_edge[chan]) )
{
@@ -755,7 +1210,7 @@ sens_data[chan+8] = ADC_table[chan];
}
else
-//
+// Предупреждение по температуре проверяется только при отсутствии обрыва и перегрева.
if(Temper>sens_lo_edge[chan])
{
error.bit.Over = 1;
@@ -763,21 +1218,39 @@ sens_data[chan+8] = ADC_table[chan];
}
fin:
+ /* Запись локальной диагностики и обновление общего признака Stop. */
reset_errs(chan,error);
}
+/**
+ * @brief Обрабатывает канал контроля питающего напряжения.
+ *
+ * Код АЦП масштабируется коэффициентом, выбранным по типу датчика. Функция
+ * фильтрует диагностические дискретные входы, контролирует пониженное напряжение
+ * и формирует общий признак аварии. Останов по низкому напряжению формируется,
+ * когда аналогичный признак присутствует и у парного канала.
+ *
+ * @param chan Индекс канала питания в таблицах `ADC_table[]`, `sens_type[]`,
+ * `sens_pair[]` и `sens_error[]`.
+ *
+ * @note Проверка повышенного напряжения в исходном коде отключена блочным
+ * комментарием и настоящей документацией не активируется.
+ */
void Power_count(int chan)
{
+ /* Numb — сырой код АЦП; Power — пересчитанное напряжение; bitt — позиция диагностики. */
float Numb;
int Power,bitt;
ERROR error;
+ /* Нулевой канал начинает новый цикл контроля питающих каналов. */
if(!chan)
{
sig.all = chk.all;
chk.all = 0;
}
+ /* Bypass исключает канал, обнуляя его показание и оставляя флаг Bypass. */
if(sens_error[chan].bit.Bypas)
{
sens_error[chan].all = 0;
@@ -786,56 +1259,77 @@ void Power_count(int chan)
return;
}
+ /* Получение последнего кода общего АЦП. */
Numb = ADC_table[chan];
+ /* В сервисном режиме наружу передаётся сырой код без проверок. */
if(cRawMeat)
{
sens_data[chan] = Numb;
return;
}
+ /* Тип канала используется как индекс таблицы коэффициентов powK[]. */
Power = Numb * powK[sens_type[chan]];
+ /* Рабочая таблица хранит уже пересчитанное физическое значение. */
sens_data[chan] = Power;
error.all = 0;
+ /* При запуске и Ignore аналоговое значение обновляется, а защиты пропускаются. */
if(WAKE || sens_error[chan].bit.Ignor) goto fin;
-//
+// Дискретные каналы.
+ /* Обычно каждому аналоговому каналу соответствуют два последовательных бита DigErr. */
bitt = chan*2;
if(chan!=7)
{
+ /* Каждый вход подтверждается интегрирующей выдержкой 1000 вызовов. */
error.bit.Discr1 = er_anal(((DigErr.all>>bitt)&1), &din_count[bitt], 1000, 0); bitt++;
error.bit.Discr2 = er_anal(((DigErr.all>>bitt)&1), &din_count[bitt], 1000, 0); bitt++;
}
+
+ /* У шестого канала предусмотрены ещё два дополнительных дискретных признака. */
if(chan==6)
{
error.bit.Discr3 = er_anal(((DigErr.all>>bitt)&1), &din_count[bitt], 1000, 0); bitt++;
error.bit.Discr4 = er_anal(((DigErr.all>>bitt)&1), &din_count[bitt], 1000, 0); bitt++;
}
+
+ /* Contr — сводный признак наличия хотя бы одной дискретной неисправности. */
error.bit.Contr = error.bit.Discr1 | error.bit.Discr2 | error.bit.Discr3 | error.bit.Discr4;
-//
+// Пониженное напряжение.
if(Power sens_hi_edge[chan])
{
error.bit.Hyper = 1;
error.bit.Stop = 1;
}
*/
+ /* Любая локальная неисправность питания поднимает общую ламповую аварию. */
if(error.all)
chk.bit.Alarm = 1;
fin:
+ /* Сохранение вычисленных ошибок и обновление общего Stop. */
reset_errs(chan,error);
}
+
+/** @} */
+
diff --git a/Source/Internal/message.c b/Source/Internal/message.c
index 8c15358..e8b0def 100644
--- a/Source/Internal/message.c
+++ b/Source/Internal/message.c
@@ -1,3 +1,12 @@
+/**
+ * @file message.c
+ * @brief Параметры устройства и прикладные сообщения протокола Modbus.
+ *
+ * Инициализирует значения по умолчанию, загружает и сохраняет параметры в
+ * EEPROM, принимает команды функций 3/6 и формирует ответы функции 4.
+ * Индексы param/Modbus согласованы с внешней картой регистров; менять порядок,
+ * ANSWER_LEN или представление слов можно только вместе с ведущей системой.
+ */
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "package.h"
#include "RS485.h"
@@ -18,9 +27,17 @@
#include "caliber.h"
+/** @brief Рабочий образ регистров Modbus, передаваемый внешнему мастеру. */
int modbus[ANSWER_LEN+1];
+/** @brief Сохраняемые параметры, индексно согласованные с картой регистров. */
unsigned int param[ANSWER_LEN+1];
+/**
+ * @brief Создаёт полный набор безопасных параметров для текущего варианта платы.
+ * @post Modbus/param, режимы, пороги, яркость и калибровки имеют штатные значения.
+ * @details Начальная очистка предотвращает сохранение мусора в зарезервированных
+ * регистрах; далее значения выбираются по Desk и compile-time конфигурации.
+ */
void Default_params()
{
unsigned int i,def;
@@ -46,19 +63,19 @@ void Default_params()
if(Desk == dsk_EPLT)
{
- Cancount[m_FAST] = 2; // CAN
+ Cancount[m_FAST] = 2; // пауза между посылками CAN
for(i=0;i<6;i++) Bright[i] = bright[i];
}
else
{
- Cancount[m_FAST] = 10; // CAN
+ Cancount[m_FAST] = 10; // пауза между посылками CAN
Brightness = 8;
}
- Cancount[m_SLOW] = 101; // CAN
+ Cancount[m_SLOW] = 101; // пауза между посылками CAN
- if(Owen) Owncount = 20; // OWEN
+ if(Owen) Owncount = 20; // пауза между опросами OWEN
if(Kalibro)
{
@@ -122,9 +139,9 @@ void Default_params()
DAC_20 = Caliber[6];
DAC_04 = Caliber[7];
-/* !
- DAC_go = 6; //
- DAC_stop= 15; //
+/* Не врема!
+ DAC_go = 6; // начало зарада
+ DAC_stop= 15; // конец зарада
*/
}
@@ -135,6 +152,11 @@ void Default_params()
Init_sensors_more();
}
+/**
+ * @brief Загружает параметрический блок из EEPROM и проверяет его целостность.
+ * @details При несовпадении сигнатуры/CRC вызывается Default_params(); после
+ * успешной загрузки значения переносятся в рабочие глобальные настройки.
+ */
void Load_params()
{
unsigned int i,crc;
@@ -159,6 +181,11 @@ void Load_params()
Caliber_time = 0xFFFF;
} } }
+/**
+ * @brief Формирует параметрический блок с CRC и записывает его в EEPROM.
+ * @warning Запись следует запускать только при свободном драйвере EEPROM;
+ * питание должно сохраняться до физического завершения транзакции.
+ */
void Save_params()
{
unsigned int i,dif=0;
@@ -177,24 +204,30 @@ void Save_params()
/***************************************************************/
/***************************************************************/
-/* ModBus - 3
- */
+/* Передача данных по протоколу ModBus - команда 3
+ Чтение ачеек данных */
/***************************************************************/
/***************************************************************/
+/**
+ * @brief Обрабатывает запрос Modbus чтения диапазона регистров.
+ * @param rs_arr Порт с проверенным входным кадром и буфером ответа.
+ * @details Проверяет начальный адрес и длину, копирует слова из modbus и
+ * формирует CRC непосредственно перед отправкой.
+ */
void ReceiveCommandModbus3(RS_DATA *rs_arr)
{
unsigned int crc, Address_MB, Length_MB, i;
-//
+// получили начальный адрес чтениа
Address_MB =/*(rs_arr->RS_Header[2] << 8) |*/ rs_arr->RS_Header[3];
-//
+// получили количество слов данных
Length_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
/////////////////////////////////////////////////
- //
- /* */
+ // Отсылка
+ /* Посчитали контрольную сумму перед самой посылкой */
rs_arr->buffer[0] = CNTRL_ADDR;
rs_arr->buffer[1] = CMD_MODBUS_3;
@@ -223,22 +256,27 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr)
return;
}
+/**
+ * @brief Применяет команду записи одного параметра Modbus.
+ * @details После проверки адреса обновляет разрешённое значение, выставляет
+ * события перенастройки/сохранения и возвращает эхо принятой команды.
+ */
void ReceiveCommandModbus6(RS_DATA *rs_arr)
{
unsigned int Address_MB, Data_MB, i;
/////////////////////////////////////////////////
- //
- /* */
+ // Отсылка
+ /* Отправлаем назад то же самое */
for (i=0;i<8;i++)
rs_arr->buffer[i] = rs_arr->RS_Header[i];
-//
+// получили начальный адрес записи
Address_MB = (/*(rs_arr->RS_Header[2] << 8) | */rs_arr->RS_Header[3]);
-//
+// получили слово данных
Data_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
Modbus[Address_MB].all = Data_MB;
@@ -247,12 +285,17 @@ void ReceiveCommandModbus6(RS_DATA *rs_arr)
RS_Send(rs_arr, rs_arr->buffer, 10);
}
+/**
+ * @brief Формирует периодический запрос/ответ функции 4 для внешнего устройства.
+ * @details Адрес, полезная нагрузка и CRC помещаются в буфер выбранного RS-порта,
+ * после чего передача запускается неблокирующим RS_Send().
+ */
void SendCommandModbus4(RS_DATA *rs_arr)
{
unsigned int crc;
/////////////////////////////////////////////////
- //
+ // Отсылка
rs_arr->buffer[0] = 16;
rs_arr->buffer[1] = 4;
@@ -275,12 +318,17 @@ void SendCommandModbus4(RS_DATA *rs_arr)
RS_Send(rs_arr, rs_arr->buffer, 8);
}
+/**
+ * @brief Разбирает ответ внешнего измерителя на функцию Modbus 4.
+ * @details Проверяет формат и переносит температуры/диагностики в ext_temp и
+ * ext_diag; некорректный ответ не должен заменять последний пригодный снимок.
+ */
void ReceiveAnswerModbus4(RS_DATA *rs_arr)
{
unsigned int i;
/////////////////////////////////////////////////
- //
+ // Отсылка
for (i=0;i<8;i++)
{
diff --git a/Source/Internal/peripher.c b/Source/Internal/peripher.c
index bba30e2..dfd84b7 100644
--- a/Source/Internal/peripher.c
+++ b/Source/Internal/peripher.c
@@ -1,3 +1,12 @@
+/**
+ * @file peripher.c
+ * @brief Определение типа платы и управление дискретной периферией.
+ *
+ * Читает конфигурационные входы, выбирает режим изделия, коммутирует каналы
+ * термопар и опрашивает кнопки. Временное переназначение LED-линий сохраняет
+ * исходные MUX/DIR и должно всегда завершаться unsetup_leds_line(). Таблицы GPIO
+ * зависят от варианта платы и описаны в GPIO_table.h.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
#include "filter_bat2.h"
#include "measure.h"
@@ -10,12 +19,19 @@
#include "peripher.h"
#include "GPIO_table.h"
+/** @brief Распознанный адрес/режим и признаки установленной периферии платы. */
int Mode,Desk,TermoAD=0,TermoRS=0,TermoSW=0,Kurrent=0,Kalibro=0,Owen=0;
+/** @brief Интегрирующие счётчики фильтрации дискретных входов. */
unsigned int INcount0=0,INcount1=0;
+/** @brief Снимок диагностических битов дискретной периферии. */
LONGE DigErr;
int MUX_GPIO32,MUX_GPIO48,DIR_GPIO32,DIR_GPIO48;
+/**
+ * @brief Временно переводит GPIO32/GPIO48 в режим обычных цифровых выходов.
+ * @post Предыдущие MUX/DIR сохранены для unsetup_leds_line().
+ */
void setup_leds_line()
{
MUX_GPIO32 = GpioCtrlRegs.GPBMUX1.bit.GPIO32;
@@ -31,6 +47,7 @@ void setup_leds_line()
EDIS;
}
+/** @brief Восстанавливает MUX/DIR LED-линий, сохранённые setup_leds_line(). */
void unsetup_leds_line()
{
EALLOW;
@@ -41,14 +58,21 @@ void unsetup_leds_line()
EDIS;
}
+/**
+ * @brief Считывает код исполнения платы и формирует её программную конфигурацию.
+ * @details По комбинации GPIO задаёт Mode/Desk, наличие температурных трактов,
+ * токовых входов, калибровки и внешнего OWEN. Затем применяет соответствующие
+ * таблицы направления, MUX и начальных уровней GPIO_table.h.
+ * @post Все inline-примитивы peripher.h можно безопасно использовать.
+ */
void get_Mode()
{
EALLOW;
- GpioCtrlRegs.GPAMUX1.all &= 0xFF000000; // 0011
- GpioCtrlRegs.GPAMUX2.all &= 0xFF00003F; // 1927
- GpioCtrlRegs.GPBMUX1.all &= 0xFFFFFFC0; // 3234
- GpioCtrlRegs.GPBMUX2.all &= 0x000FF000; // 4853, 5863
+ GpioCtrlRegs.GPAMUX1.all &= 0xFF000000; // 00—11
+ GpioCtrlRegs.GPAMUX2.all &= 0xFF00003F; // 19—27
+ GpioCtrlRegs.GPBMUX1.all &= 0xFFFFFFC0; // 32—34
+ GpioCtrlRegs.GPBMUX2.all &= 0x000FF000; // 48—53, 58—63
GpioCtrlRegs.GPADIR.bit.GPIO20 = 0;
GpioCtrlRegs.GPADIR.bit.GPIO21 = 0;
@@ -117,6 +141,10 @@ void get_Mode()
EDIS;
}
+/**
+ * @brief Переводит адресные линии мультиплексора термопар в невыбранное состояние.
+ * @note Используется между каналами, чтобы снизить влияние переходных процессов.
+ */
void select_tpl_255(void)
{
if(Desk==dsk_BKSD)
@@ -144,6 +172,11 @@ void select_tpl_255(void)
GpioDataRegs.GPBSET.all = BKST_B_tpl_zero;
} }
+/**
+ * @brief Выбирает один физический канал мультиплексора термопар.
+ * @param n_tpl Номер канала в логической нумерации измерительного модуля.
+ * @details Номер раскладывается на адресные GPIO с учётом разводки текущего Desk.
+ */
void select_tpl_canal(int n_tpl)
{
unsigned long GPIO_A_tpl_set = 0;
@@ -187,6 +220,11 @@ void select_tpl_canal(int n_tpl)
GpioDataRegs.GPBCLEAR.all = GPIO_B_tpl_set;
} }
+/**
+ * @brief Опрашивает кнопки и дискретные входы с программным антидребезгом.
+ * @details Счётчики INcount* требуют устойчивого уровня несколько циклов;
+ * подтверждённые состояния переносятся в команды и диагностические биты.
+ */
void get_Buttons()
{
unsigned long butt =0;
@@ -208,7 +246,7 @@ void get_Buttons()
Inputs.all = butt;
}
-/*
+/* Ничего такого в действительности нет
if(Desk==dsk_BKST)
{ bSecretBt = !GpioDataRegs.GPBDAT.bit.GPIO63;
InputRep1 = bSecretBt;
@@ -244,7 +282,7 @@ void get_Buttons()
if(GpioDataRegs.GPADAT.bit.GPIO5 ) butt +=0x0800;
if(GpioDataRegs.GPBDAT.bit.GPIO48) butt +=0x1000;
- if(GpioDataRegs.GPADAT.bit.GPIO10) butt +=0x2000; //
+ if(GpioDataRegs.GPADAT.bit.GPIO10) butt +=0x2000; // Секретный вход
Inputs.all = butt;
}
diff --git a/Source/Internal/pulto.c b/Source/Internal/pulto.c
index 2efd425..e24418e 100644
--- a/Source/Internal/pulto.c
+++ b/Source/Internal/pulto.c
@@ -1,3 +1,12 @@
+/**
+ * @file pulto.c
+ * @brief Периодическое обслуживание пульта индикации.
+ *
+ * Обработчик Timer1 обновляет яркость, маску ламп, числовые каналы и признак
+ * связи с ведущим устройством. Работа выполняется в прерывании, поэтому обмен
+ * с общими переменными должен оставаться коротким; частоты мигания вычисляются
+ * из READY_FREQ и изменятся при смене базовой частоты таймера.
+ */
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_SWPrioritizedIsrLevels.h"
@@ -10,6 +19,7 @@
#include "kanal.h"
#include "peripher.h"
+/** @brief Маска мигающих сегментов и тестовое число режима самопроверки. */
unsigned long isMask;
int isNumb;
@@ -17,6 +27,11 @@ int quaLamp = 6;
unsigned long Lonely=0;
+/**
+ * @brief Выполняет шаг визуального теста всех цифр и сегментов.
+ * @details Через BLINK_TIME изменяет цифру 0..9 и попеременно включает либо
+ * гасит полную маску, позволяя заметить неисправный сегмент индикатора.
+ */
void what_is()
{
static unsigned int count_blink;
@@ -33,6 +48,12 @@ void what_is()
else isMask = 0xFFFFFFFF;
} }
+/**
+ * @brief Периодический ISR управления панелью EPLT.
+ * @details Поддерживает программный PWM яркости, отображает актуальные каналы,
+ * управляет лампами и отслеживает тайм-аут связи через Lonely. Перед выходом
+ * восстанавливает PIEIER и подтверждает источник Timer1.
+ */
interrupt void cpu_timer1_isr_PULT(void)
{
static int cownt=0,count_bright;
@@ -53,10 +74,11 @@ interrupt void cpu_timer1_isr_PULT(void)
CanGO = 1;
}
- toggle_ONLINE(); //
- toggle_RES_OUT_1(); //
- toggle_RES_OUT_2(); //
+ toggle_ONLINE(); // строб дла ПЛИС
+ toggle_RES_OUT_1(); // типа это готовность
+ toggle_RES_OUT_2(); // на всакий
+ /* Насыщение предотвращает переполнение счётчика после длительной потери связи. */
if(++Lonely > Alone) Lonely = Alone;
if(++count_bright >= maximum_bright) count_bright = 0 ;
@@ -66,6 +88,7 @@ interrupt void cpu_timer1_isr_PULT(void)
if(count_bright < Bright[i]) light+=(1<2) cownt=0;
if(cTestLamp|bTestLamp)
diff --git a/Source/Internal/spise2p.c b/Source/Internal/spise2p.c
index 4c7cca8..618a6a8 100644
--- a/Source/Internal/spise2p.c
+++ b/Source/Internal/spise2p.c
@@ -1,3 +1,12 @@
+/**
+ * @file spise2p.c
+ * @brief Неблокирующий драйвер последовательной EEPROM поверх SPI.
+ *
+ * Высокоуровневые Seeprom_read/Seeprom_write запускают транзакцию, а конечный
+ * автомат SPISE2P_DRV_tick продвигается от Timer2. Состояние драйвера единично:
+ * новую операцию разрешено начинать только после spiSe2pFree(). Адрес и длина
+ * должны укладываться в SEEPROM_LEN и не пересекать ограничения микросхемы.
+ */
/*=================================================================
File name : SPISE2PD.C
@@ -32,6 +41,14 @@ int ccc=0;
/******* SPI bus Serial EEPROM driver Initialization routine ********/
/********************************************************************/
+/**
+ * @brief Синхронно записывает пользовательский блок через асинхронный драйвер.
+ * @param adres Начальный адрес EEPROM.
+ * @param buf Источник данных в памяти DSP.
+ * @param size Размер блока в байтах.
+ * @details Ожидает освобождения драйвера, запускает транзакцию и блокируется до
+ * её завершения, которое продвигается прерываниями Timer2.
+ */
void Seeprom_write( unsigned int adres,
unsigned int buf[],
unsigned int size)
@@ -63,6 +80,12 @@ void Seeprom_write( unsigned int adres,
CpuTimer2Regs.TCR.all = 0x4010; // Use write-only instruction to set TSS bit = 1
}
+/**
+ * @brief Синхронно читает блок EEPROM в пользовательский буфер.
+ * @param adres Начальный адрес EEPROM.
+ * @param buf Приёмный буфер DSP.
+ * @param size Размер в байтах.
+ */
void Seeprom_read( unsigned int adres,
unsigned int buf[],
unsigned int size)
@@ -94,6 +117,10 @@ void Seeprom_read( unsigned int adres,
CpuTimer2Regs.TCR.all = 0x4010; // Use write-only instruction to set TSS bit = 1
}
+/**
+ * @brief Инициализирует SPI, объект драйвера и периодический Timer2.
+ * @post se2p находится в свободном состоянии, Timer2 вызывает cpu_timer2_isr().
+ */
void InitSeeprom()
{
se2p.init(&se2p);
@@ -108,6 +135,7 @@ void InitSeeprom()
IER |= M_INT14;
}
+/** @brief Сбрасывает конечный автомат и программирует регистры SPI-A. */
void SPISE2P_DRV_init(SPISE2P_DRV *eeprom)
{
/* Configure SPI-A pins using GPIO regs*/
@@ -141,28 +169,33 @@ void SPISE2P_DRV_init(SPISE2P_DRV *eeprom)
SpiaRegs.SPICCR.bit.SPISWRESET=1; // Enable SCI
}
+/** @brief Деактивирует chip-select EEPROM. */
void SPISE2P_DRV_csset()
{
GpioDataRegs.GPADAT.bit.GPIO19 = 1;
}
+/** @brief Активирует chip-select EEPROM перед транзакцией. */
void SPISE2P_DRV_csclr()
{
GpioDataRegs.GPADAT.bit.GPIO19 = 0;
}
+/** @brief Возвращает ненулевое значение, если конечный автомат не занят. */
unsigned int spiSe2pFree(SPISE2P_DRV *se2p)
{
if(se2p->csr&0x3) return(0);
else return(1);
}
+/** @brief Привязывает описание блока и переводит автомат в начало записи. */
void spiSe2pWrite(SPISE2P_DRV *se2p, SE2P_DATA *msgPtr)
{
se2p->msgPtr=msgPtr;
se2p->csr|=0x1;
}
+/** @brief Привязывает описание блока и переводит автомат в начало чтения. */
void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *msgPtr)
{
se2p->msgPtr=msgPtr;
@@ -173,6 +206,10 @@ void spiSe2pRead(SPISE2P_DRV *se2p, SE2P_DATA *msgPtr)
/******* SPI bus Serial EEPROM driver Tick function *****************/
/********************************************************************/
+/**
+ * @brief ISR Timer2, задающий такт конечному автомату EEPROM.
+ * @note Один вызов выполняет только ограниченный шаг SPI-транзакции.
+ */
interrupt void cpu_timer2_isr(void)
{ EALLOW;
CpuTimer2.InterruptCount++;
@@ -183,6 +220,12 @@ interrupt void cpu_timer2_isr(void)
EDIS;
}
+/**
+ * @brief Выполняет один переход конечного автомата SPI EEPROM.
+ * @details Состояния последовательно выдают opcode, адрес и данные, ожидают
+ * флаги SPI, управляют CS и проверяют готовность микросхемы после записи.
+ * @param eeprom Активный экземпляр драйвера с описанием текущего блока.
+ */
void SPISE2P_DRV_tick(SPISE2P_DRV *eeprom)
{
static unsigned int step=0;
diff --git a/Source/Internal/tools.c b/Source/Internal/tools.c
index 382de8b..5bd3868 100644
--- a/Source/Internal/tools.c
+++ b/Source/Internal/tools.c
@@ -1,3 +1,12 @@
+/**
+ * @file tools.c
+ * @brief Низкоуровневая настройка XINTF Zone 7 и микросекундные задержки.
+ *
+ * init_zone7() программирует GPIO и временные параметры внешней 16-битной шины.
+ * Значения lead/active/trail привязаны к частоте SYSCLKOUT и характеристикам
+ * внешней памяти. pause_us() является активным ожиданием и блокирует процессор,
+ * поэтому подходит только для коротких аппаратных пауз вне критичных ISR.
+ */
#include "DSP2833x_Device.h" // DSP281x Headerfile Include File
// Configure the timing paramaters for Zone 7.
@@ -5,6 +14,12 @@
// This function should not be executed from XINTF
// Adjust the timing based on the data manual and
// external device requirements.
+/**
+ * @brief Включает XINTF и программирует временную диаграмму Zone 7.
+ * @pre Функция исполняется не из памяти, отображённой в настраиваемую Zone 7.
+ * @post Внешняя шина работает в 16-битном режиме с XTIMCLK=SYSCLKOUT.
+ * @warning Значения тактов рассчитаны для штатной частоты платы.
+ */
void init_zone7(void)
{
@@ -55,6 +70,11 @@ void init_zone7(void)
asm(" RPT #7 || NOP");
}
+/**
+ * @brief Выполняет блокирующую паузу приблизительно t микросекунд.
+ * @param t Требуемая длительность при текущей конфигурации SYSCLKOUT.
+ * @note Во время цикла процессор не выполняет foreground-работу.
+ */
void pause_us(unsigned long t)
{
unsigned long i;
diff --git a/UKSSTMS320F28335/postBuildStep_Debug.bat b/UKSSTMS320F28335/postBuildStep_Debug.bat
index bbbe0d4..e4e1ee7 100644
--- a/UKSSTMS320F28335/postBuildStep_Debug.bat
+++ b/UKSSTMS320F28335/postBuildStep_Debug.bat
@@ -1,4 +1,7 @@
@echo off
+rem CCS post-build step: converts the OUT image to bootable SCI8 HEX and BIN.
+rem %1 is hex2000, %2 is the linked OUT file, %3 is the Bin artifact directory.
+rem Propagates ERRORLEVEL after every critical conversion command.
setlocal
set "HEX_TOOL=%~1"
diff --git a/UKSSTMS320F28335/postBuildStep_Debug.old b/UKSSTMS320F28335/postBuildStep_Debug.old
index d7027dd..7795eec 100644
--- a/UKSSTMS320F28335/postBuildStep_Debug.old
+++ b/UKSSTMS320F28335/postBuildStep_Debug.old
@@ -1,4 +1,7 @@
@echo off
+rem Obsolete post-build snapshot containing machine-specific absolute paths.
+rem Retained for reference only; normal builds must use postBuildStep_Debug.bat,
+rem which receives all paths through arguments.
pushd ..\..\
setlocal
diff --git a/fixya.bat b/fixya.bat
index 9f8c871..cdcbe15 100644
--- a/fixya.bat
+++ b/fixya.bat
@@ -1,3 +1,6 @@
+@rem Legacy batch processor for C/H files in the current directory.
+@rem Run only on a backed-up tree: FuckYa.exe modifies files in place and its
+@rem exact transformation is defined by the bundled binary utility.
fuckya.exe *.c
fuckya.exe *.h
diff --git a/publish_firmware.bat b/publish_firmware.bat
new file mode 100644
index 0000000..8173406
--- /dev/null
+++ b/publish_firmware.bat
@@ -0,0 +1,53 @@
+@echo off
+rem Command-line entry point for publishing firmware from cmd or CCS.
+rem Arguments: image, version, release notes, transport, and optional "check".
+rem Supplies the default BIN, delegates to PowerShell, and propagates its exit
+rem code so that a publication failure also fails the calling build step.
+setlocal
+
+set "SCRIPT_DIR=%~dp0"
+set "IMAGE_PATH=%~1"
+set "VERSION=%~2"
+set "NOTES=%~3"
+set "TRANSPORT=%~4"
+set "CHECK_MODE=%~5"
+
+if not defined IMAGE_PATH set "IMAGE_PATH=%SCRIPT_DIR%Bin\UKSSTMS320F28335.bin"
+if not defined VERSION goto :usage
+if not defined NOTES set "NOTES=Balsam 167 peripheral firmware %VERSION%"
+if not defined TRANSPORT set "TRANSPORT=tms"
+
+set "CHECK_SWITCH="
+if /I "%CHECK_MODE%"=="check" set "CHECK_SWITCH=-CheckOnly"
+
+powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%scripts\publish-firmware.ps1" ^
+ -ImagePath "%IMAGE_PATH%" ^
+ -Version "%VERSION%" ^
+ -Notes "%NOTES%" ^
+ -Transport "%TRANSPORT%" ^
+ -Product "Balsam 167 peripheral" %CHECK_SWITCH%
+
+set "PUBLISH_EXIT=%ERRORLEVEL%"
+if not "%PUBLISH_EXIT%"=="0" (
+ echo.
+ echo Firmware publication failed with exit code %PUBLISH_EXIT%.
+ exit /b %PUBLISH_EXIT%
+)
+
+echo.
+if /I "%CHECK_MODE%"=="check" (
+ echo Publisher check completed successfully. Nothing was published.
+ exit /b 0
+)
+echo Firmware release %VERSION% published successfully.
+exit /b 0
+
+:usage
+echo Usage:
+echo publish_firmware.bat [image.bin] VERSION [notes] [tms^|rs485^|can^|stm32] [check]
+echo.
+echo Example:
+echo publish_firmware.bat "Bin\UKSSTMS320F28335.bin" "1.0.0" "First catalog release" "tms"
+echo.
+echo Add "check" as the fifth argument to validate access without publishing.
+exit /b 2
diff --git a/scripts/publish-firmware.ps1 b/scripts/publish-firmware.ps1
new file mode 100644
index 0000000..c6c4895
--- /dev/null
+++ b/scripts/publish-firmware.ps1
@@ -0,0 +1,263 @@
+<#
+.SYNOPSIS
+Проверяет либо публикует бинарный образ Balsam в каталоге релизов Gitea.
+
+.DESCRIPTION
+Проверяет входные параметры и образ, получает учётные данные из переменных
+окружения или Windows Credential Manager, затем обращается к API репозитория.
+Режим CheckOnly проверяет доступ без изменения удалённого каталога. Секреты не
+выводятся в журнал; все ошибки завершают скрипт ненулевым кодом через режим Stop.
+#>
+param(
+ [Parameter(Mandatory = $true)][string]$ImagePath,
+ [Parameter(Mandatory = $true)][string]$Version,
+ [Parameter(Mandatory = $true)][string]$Notes,
+ [Parameter(Mandatory = $true)]
+ [ValidateSet("tms", "rs485", "can", "stm32")][string]$Transport,
+ [string]$Product = "Balsam 167 peripheral",
+ [string]$GiteaBase = "https://git.rd12.ru",
+ [string]$Owner = "setcorp",
+ [string]$Repository = "SETRD12-Releases",
+ [string]$Branch = "main",
+ [switch]$CheckOnly
+)
+
+$ErrorActionPreference = "Stop"
+Set-StrictMode -Version Latest
+Add-Type -AssemblyName System.Net.Http
+
+Add-Type -TypeDefinition @'
+using System;
+using System.Runtime.InteropServices;
+
+public static class SetCredentialReader {
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ private struct CREDENTIAL {
+ public UInt32 Flags;
+ public UInt32 Type;
+ public IntPtr TargetName;
+ public IntPtr Comment;
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
+ public UInt32 CredentialBlobSize;
+ public IntPtr CredentialBlob;
+ public UInt32 Persist;
+ public UInt32 AttributeCount;
+ public IntPtr Attributes;
+ public IntPtr TargetAlias;
+ public IntPtr UserName;
+ }
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ private static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ private static extern void CredFree(IntPtr credential);
+
+ public static string[] Read(string target) {
+ IntPtr pointer;
+ if (!CredRead(target, 1, 0, out pointer)) return null;
+ try {
+ CREDENTIAL value = (CREDENTIAL)Marshal.PtrToStructure(pointer, typeof(CREDENTIAL));
+ string user = Marshal.PtrToStringUni(value.UserName) ?? "";
+ string password = value.CredentialBlobSize == 0 ? "" :
+ Marshal.PtrToStringUni(value.CredentialBlob, (int)value.CredentialBlobSize / 2);
+ return new string[] { user, password };
+ } finally {
+ CredFree(pointer);
+ }
+ }
+}
+'@
+
+function Get-AuthHeaders {
+ if ($env:GITEA_TOKEN) {
+ return @{ Authorization = "token $($env:GITEA_TOKEN)" }
+ }
+ if ($env:GITEA_USER -and $env:GITEA_PASSWORD) {
+ $plain = "$($env:GITEA_USER):$($env:GITEA_PASSWORD)"
+ $basic = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($plain))
+ return @{ Authorization = "Basic $basic" }
+ }
+ $saved = [SetCredentialReader]::Read("SET/SETGUI/Gitea")
+ if ($null -ne $saved -and $saved.Count -eq 2 -and $saved[0] -and $saved[1]) {
+ $plain = "$($saved[0]):$($saved[1])"
+ $basic = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($plain))
+ Write-Host "Authentication: Windows Credential Manager (SETGUI)"
+ return @{ Authorization = "Basic $basic" }
+ }
+ throw "Gitea credentials not found. Save them in SETGUI, set GITEA_TOKEN, or set GITEA_USER and GITEA_PASSWORD."
+}
+
+function Invoke-GiteaJson {
+ param(
+ [Parameter(Mandatory = $true)][string]$Method,
+ [Parameter(Mandatory = $true)][string]$Path,
+ [object]$Body,
+ [switch]$AllowNotFound
+ )
+ $parameters = @{
+ Method = $Method
+ Uri = "$GiteaBase/api/v1$Path"
+ Headers = $script:Headers
+ UseBasicParsing = $true
+ }
+ if ($null -ne $Body) {
+ $parameters.ContentType = "application/json; charset=utf-8"
+ $parameters.Body = $Body | ConvertTo-Json -Depth 20 -Compress
+ }
+ try {
+ return Invoke-RestMethod @parameters
+ } catch {
+ $response = $_.Exception.Response
+ if ($AllowNotFound -and $null -ne $response -and [int]$response.StatusCode -eq 404) {
+ return $null
+ }
+ throw
+ }
+}
+
+function Send-ReleaseAsset {
+ param([long]$ReleaseId, [string]$FilePath, [string]$AssetName)
+ $client = [Net.Http.HttpClient]::new()
+ try {
+ foreach ($entry in $script:Headers.GetEnumerator()) {
+ [void]$client.DefaultRequestHeaders.TryAddWithoutValidation($entry.Key, $entry.Value)
+ }
+ $encodedName = [Uri]::EscapeDataString($AssetName)
+ $uri = "$GiteaBase/api/v1/repos/$Owner/$Repository/releases/$ReleaseId/assets?name=$encodedName"
+ $form = [Net.Http.MultipartFormDataContent]::new()
+ try {
+ $stream = [IO.File]::OpenRead($FilePath)
+ $content = [Net.Http.StreamContent]::new($stream)
+ $content.Headers.ContentType = [Net.Http.Headers.MediaTypeHeaderValue]::new("application/octet-stream")
+ $form.Add($content, "attachment", $AssetName)
+ $response = $client.PostAsync($uri, $form).GetAwaiter().GetResult()
+ $text = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
+ if (-not $response.IsSuccessStatusCode) {
+ throw "Asset upload failed: HTTP $([int]$response.StatusCode): $text"
+ }
+ } finally {
+ if ($null -ne $form) { $form.Dispose() }
+ }
+ } finally {
+ $client.Dispose()
+ }
+}
+
+$resolvedImage = (Resolve-Path -LiteralPath $ImagePath).Path
+if ([IO.Path]::GetExtension($resolvedImage) -notin @(".bin", ".hex")) {
+ throw "Firmware image must have .bin or .hex extension."
+}
+$imageInfo = Get-Item -LiteralPath $resolvedImage
+if ($imageInfo.Length -le 0 -or $imageInfo.Length -gt 134217728) {
+ throw "Firmware image is empty or exceeds 128 MiB."
+}
+$match = [regex]::Match($Version, '^(\d+)\.(\d+)\.(\d+)$')
+if (-not $match.Success) {
+ throw "Version must use MAJOR.MINOR.PATCH format, for example 1.0.0."
+}
+$major = [long]$match.Groups[1].Value
+$minor = [long]$match.Groups[2].Value
+$patch = [long]$match.Groups[3].Value
+if ($minor -gt 999 -or $patch -gt 999) {
+ throw "MINOR and PATCH must be between 0 and 999."
+}
+$versionCode = $major * 1000000 + $minor * 1000 + $patch
+$sha256 = [Security.Cryptography.SHA256]::Create()
+try {
+ $hashStream = [IO.File]::OpenRead($resolvedImage)
+ try {
+ $hashBytes = $sha256.ComputeHash($hashStream)
+ } finally {
+ $hashStream.Dispose()
+ }
+} finally {
+ $sha256.Dispose()
+}
+$hash = ([BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
+$safeProduct = ($Product.ToLowerInvariant() -replace '[^a-z0-9]+', '-').Trim('-')
+$tag = "$safeProduct-v$Version"
+$extension = [IO.Path]::GetExtension($resolvedImage).ToLowerInvariant()
+$assetName = "$safeProduct-$Version$extension"
+$encodedTag = [Uri]::EscapeDataString($tag)
+$script:Headers = Get-AuthHeaders
+$script:Headers["User-Agent"] = "Balsam-firmware-publisher/1.0"
+
+Write-Host "Image: $resolvedImage"
+Write-Host "Size: $($imageInfo.Length) bytes"
+Write-Host "SHA: $hash"
+Write-Host "Tag: $tag"
+
+if ($CheckOnly) {
+ $repositoryInfo = Invoke-GiteaJson GET "/repos/$Owner/$Repository"
+ $contentInfo = Invoke-GiteaJson GET "/repos/$Owner/$Repository/contents/update.json`?ref=$Branch"
+ if (-not $repositoryInfo.full_name -or -not $contentInfo.sha) {
+ throw "Gitea access check returned incomplete repository data."
+ }
+ Write-Host "CHECK OK: access to $($repositoryInfo.full_name), update.json and local image is valid."
+ exit 0
+}
+
+$release = Invoke-GiteaJson GET "/repos/$Owner/$Repository/releases/tags/$encodedTag" -AllowNotFound
+if ($null -eq $release) {
+ $release = Invoke-GiteaJson POST "/repos/$Owner/$Repository/releases" ([ordered]@{
+ tag_name = $tag
+ target_commitish = $Branch
+ name = "$Product $Version"
+ body = $Notes
+ draft = $false
+ prerelease = $false
+ })
+} else {
+ $release = Invoke-GiteaJson PATCH "/repos/$Owner/$Repository/releases/$($release.id)" ([ordered]@{
+ name = "$Product $Version"
+ body = $Notes
+ draft = $false
+ prerelease = $false
+ })
+}
+
+$assets = Invoke-GiteaJson GET "/repos/$Owner/$Repository/releases/$($release.id)/assets"
+$oldAsset = @($assets) | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
+if ($null -ne $oldAsset) {
+ Invoke-GiteaJson DELETE "/repos/$Owner/$Repository/releases/$($release.id)/assets/$($oldAsset.id)" | Out-Null
+}
+Send-ReleaseAsset $release.id $resolvedImage $assetName
+
+$manifestPath = "update.json"
+$contentInfo = Invoke-GiteaJson GET "/repos/$Owner/$Repository/contents/$manifestPath`?ref=$Branch"
+$manifestText = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(($contentInfo.content -replace '\s', '')))
+$manifest = $manifestText | ConvertFrom-Json
+if ($null -eq $manifest.firmware) {
+ $manifest | Add-Member NoteProperty firmware ([pscustomobject]@{ catalogVersion = 1; releases = @() })
+}
+if ($null -eq $manifest.firmware.releases) {
+ $manifest.firmware | Add-Member NoteProperty releases @()
+}
+$releases = @($manifest.firmware.releases) | Where-Object {
+ -not ($_.product -eq $Product -and [long]$_.versionCode -eq $versionCode)
+}
+$downloadUrl = "$GiteaBase/$Owner/$Repository/releases/download/$tag/$assetName"
+$entry = [pscustomobject][ordered]@{
+ product = $Product
+ versionCode = $versionCode
+ versionName = $Version
+ imageUrl = $downloadUrl
+ fileName = $assetName
+ sha256 = $hash
+ transport = $Transport
+ notes = $Notes
+}
+$manifest.firmware.releases = @($entry) + $releases
+$manifest.firmware.catalogVersion = [long]$manifest.firmware.catalogVersion + 1
+$updatedJson = $manifest | ConvertTo-Json -Depth 20
+$encodedManifest = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($updatedJson + "`n"))
+Invoke-GiteaJson PUT "/repos/$Owner/$Repository/contents/$manifestPath" ([ordered]@{
+ branch = $Branch
+ sha = $contentInfo.sha
+ message = "Publish firmware $Product $Version"
+ content = $encodedManifest
+}) | Out-Null
+
+Write-Host "Published: $downloadUrl"
+Write-Host "Catalog: $GiteaBase/$Owner/$Repository/raw/branch/$Branch/update.json"