/** * @file * @brief Обработка измерительных каналов, индикации и защит устройства. * * @details * Модуль выполняет настройку каналов датчиков, фильтрацию результатов АЦП, * пересчёт токов, напряжений и температур в физические величины, формирование * диагностических признаков, а также подготовку масок передаваемых данных. * Обработчик CPU Timer1 обслуживает сторожевой таймер, период запуска CAN-обмена * и световую индикацию. * * Модуль использует глобальные параметры конфигурации, таблицы измерений и * аппаратные функции, объявленные в подключаемых заголовочных файлах проекта. * * @note Исходная исполняемая логика сохранена; добавлены комментарии * в формате Doxygen и поясняющие комментарии непосредственно внутри кода. */ /** * @defgroup sensor_processing Обработка датчиков и диагностик * @brief Инициализация, измерение, фильтрация и контроль измерительных каналов. * @{ */ #include "DSP2833x_Device.h" // DSP2833x Headerfile Include File #include "DSP2833x_SWPrioritizedIsrLevels.h" #include "filter_bat2.h" #include "package.h" #include "measure.h" #include "package.h" #include "peripher.h" #include "ADC.h" #include "DAC.h" #include "RS485.h" #include "message.h" #include "log_to_mem.h" #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]; /** @brief Флаг завершения начальной загрузки и готовности модуля. */ int READY=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В 1.000, // ток 1.000, // напражение 0.0076, // питание 31В 0.0076, // питание 24В 0.0076, // питание 24В 0.0076, // питание 15В 1.0000, // термодатчик мелкосхема }; /** @brief Шаблон начального состояния цифрового фильтра. */ FILTERBAT def_FILTERBAT = DEF_FILTERBAT; /** @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); K_50 = 261.8/(K150_D - K100_D); K_T1 = 129.0/(K150_1 - K100_1); K_T2 = 129.0/(K150_2 - K100_2); } /* * Вариант аналоговых термодатчиков: для каждой ветви используется * собственная пара калибровочных точек K300/K400. */ if(TermoAD) { K_T1 = 100.0/(K400_1 - K300_1); K_T2 = 100.0/(K400_2 - K300_2); } // 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 // Второй светодиод трактуется как «Авария». /* * Разрешается запись в защищённые регистры, обновляется статистика таймера * и настраивается маска прерываний. EINT разрешает вложенные прерывания * с приоритетом, оставленным маской MINT13. */ EALLOW; CpuTimer1.InterruptCount++; IER |= M_INT13; // Set "global" priority IER &= MINT13; // Set "global" priority EINT; 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 для шкафа. else clear_LED_OUT_1(); if(heat_lamp) set_LED_OUT_2(); // Она же alm_lamp для шкафа. else clear_LED_OUT_2(); } /* * Конец активной части ШИМ. В режиме теста выходы не гасятся по Brightness, * поэтому лампы проверяются с полной заданной логической активностью. */ if(!TST) if(count_bright == Brightness) { clear_LED_OUT_1(); clear_LED_OUT_2(); clear_RES_OUT_1(); } /* На фронте теста немедленно запускается новый цикл мигания с известной фазы. */ if(TST & !preTest) { count_blink = BLINK_TIME; count_mode = 0; } 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; work_lamp = blink_over; power_lamp= blink_over; } 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; } else { // 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: case adr_TRN2: TPL_CANS = TPL_TRN; tpl_cans = TPL_TRN*2; cal_addr = tpl_cans; break; case adr_POW1: case adr_POW2: TPL_CANS = TPL_POW; tpl_cans = TPL_POW*2; cal_addr = tpl_cans; break; case adr_ENG1: TPL_CANS = TPL_ENG; tpl_cans = TPL_ENG; cal_addr = tpl_cans; break; 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; sens_type[1] = POWER_38O; sens_pair[1]=0; sens_type[2] = POWER_31; sens_pair[2]=3; sens_type[3] = POWER_31; sens_pair[3]=2; sens_type[4] = POWER_31; sens_pair[4]=5; sens_type[5] = POWER_31; sens_pair[5]=4; sens_type[6] = POWER_24; sens_type[7] = POWER_27; sens_type[8] = POWER_24; sens_type[9] = POWER_15; sens_type[10] = POWER_24; sens_type[11] = POWER_24; sens_type[12] = POWER_24; sens_type[13] = TERMO_AD; 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) { if(i&1) sens_data[i]=560; else sens_data[i]=390; } } /* В COMM все четыре силовых канала запускаются с установленным признаком Ignore. */ if(Desk==dsk_COMM) { Modbus[pow_addr+0].bit.bitE = 1; // Ignore Modbus[pow_addr+1].bit.bitE = 1; // Ignore Modbus[pow_addr+2].bit.bitE = 1; // Ignore 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: /* Для 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 ignor, sens, pair, ist, thrd, fazz, feed; ERROR error; /* В начале нового четырёхканального цикла фиксируется сводка прошлого цикла. */ if(Desk == dsk_LOAD) if(!chan) { sig.all = chk.all; 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; sens_error[sens].bit.Bypas = 1; sens_data[sens] = 0; 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); 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 { // Вычисление амплитуды по двум измеренным фазам. /* 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]); /* if(!no_write) { Test_mem_limit(8); Log_to_mem(Current); Log_to_mem(aCurrent); Log_to_mem(Numb); Log_to_mem(lev_count[chan]); Log_to_mem(lev_count[pair]); Log_to_mem(lev_count[thrd]); 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; 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; 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]), &low_count[feed],time_3sec,0)) { error.bit.Out = 1; error.bit.Stop = 1; } /* Для тока та же sens_lo_edge используется как порог предупреждения Over. */ if(sens_type[sens]==CURRENT) if(Level > sens_lo_edge[sens]) { error.bit.Over = 1; } } /* if((chan==1)||(chan==3)) if(error.all) if(!no_write) { Test_mem_limit(12); Log_to_mem(Current); Log_to_mem(aCurrent); Log_to_mem(Numb); Log_to_mem(lev_count[chan]); Log_to_mem(lev_count[pair]); Log_to_mem(lev_count[thrd]); Log_to_mem(Amplitude); Log_to_mem(Level); Log_to_mem(Zero_lev[chan]); Log_to_mem(Curr_Edge); Log_to_mem(error.all); 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; if(kun>=0) { TCaliber[kun] = Numb; calc_sensor_koef(); return; } } /* Калибровочные/служебные позиции за пределами tpl_cans здесь не измеряются. */ if(chan>=tpl_cans) return; // Нечётность 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; calc_sensor_koef(); } return; } 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; chk.bit.OutHeat = 1; } else // Перегрев. /* * Если Hyper уже был активен, он удерживается до снижения температуры * ниже верхней уставки на величину Cooling — это температурный гистерезис. */ if(((Temper>sens_hi_edge[chan]-Cooling) && (sens_error[chan].bit.Hyper)) || (Temper>sens_hi_edge[chan]) ) { error.bit.Hyper = 1; error.bit.Stop = 1; chk.bit.OverHeat= 1; } else // Предупреждение по температуре проверяется только при отсутствии обрыва и перегрева. if(Temper>sens_lo_edge[chan]) { error.bit.Over = 1; chk.bit.SubHeat = 1; } 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; sens_error[chan].bit.Bypas = 1; sens_data[chan] = 0; 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); } /** @} */