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 ведёт измерение или пульт, главный цикл выполняет связь и команды.

+
+
+ + + + + + + + + + +
+ + + + 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"