Files
Balsam_167/Source/Internal/measure.c

1336 lines
57 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 <math.h> // Это чтобы мерить амплитуду! 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;i<tpl_cans;i++)
{
if(TermoRS) sens_type[i] = TERMO_RS;
if(TermoAD) sens_type[i] = TERMO_AD;
}
/* Внешние каналы OWEN следуют непосредственно за локальными температурами. */
for(i=0;i<Owen;i++)
{
sens_type[tpl_cans+i] = TRM_OBEH;
}
/* Настройка четырёхканального тракта измерения тока/напряжения. */
if(Kurrent)
{
/* Ниже этого RMS-уровня контроль перекоса фаз не выполняется. */
Curr_Edge = 200;
for(i=0;i<4;i++)
{
/* Базово все четыре силовых входа считаются каналами напряжения. */
sens_type[pow_addr+i]=VOLTAGE;
/* XOR с 1 образует пары 0<->1 и 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<can;i++)
{
/* Диагностики занимают битовые позиции, начиная с 0. */
Maska[m_FAST][ i /16]|=(1<<( i %16)); // Диагностика.
/* Показания размещены в том же пространстве со смещением 24 бита. */
Maska[m_FAST][(i+24)/16]|=(1<<((i+24)%16)); // Показания.
}
/* Первые три слова быстрой карты копируются в область масок уставок. */
for(i=0;i<3; i++)
Maska[m_SLOW][i+3] = Maska[m_FAST][i]; // Уставки.
/* BKSD передаёт две калибровки температуры, остальные исполнения — четыре. */
if(Desk==dsk_BKSD) Maska[m_SLOW][7]|= 0x0300; // Калибровка термодатчиков.
else
Maska[m_SLOW][7]|= 0x0F00; // Калибровка термодатчиков.
if(Kurrent)
{
/* Четыре нулевых уровня и четыре коэффициента силовых каналов. */
Maska[m_SLOW][7]|= 0x000F; // Ноль датчиков напряжения.
Maska[m_SLOW][7]|= 0x00F0; // Калибровка датчиков напряжения/тока.
/* Шесть RMS-значений: по три фазы для двух измерительных групп. */
Maska[m_FAST][6]|= 0x3F00; // Фазные значения.
}
/* При наличии OWEN включается поле периода его опроса. */
if(Owen) Maska[m_FAST][6]|= 0x0008; // Период опроса OWEN.
if(Desk==dsk_LOAD)
{
/* Дополнительные поля, используемые только стендом нагрузки. */
Maska[m_SLOW][7]|= 0x0030; // Калибровка ЦАП.
Maska[m_FAST][7]|= 0xE000; // Дублирование команд.
Maska[m_FAST][1]|= 0x3000; // Управляющий ток СИФУ.
} }
/* Эти служебные поля включаются независимо от исполнения. */
Maska[m_FAST][1]|= 0x0001; // Дискретные входы.
Maska[m_SLOW][6]|= 0x0007; // Яркость ламп и периоды посылок.
Maska[m_SLOW][7]|= 0xE000; // Адрес и командные поля.
}
/**
* @brief Фильтрует логическое условие интегрирующим счётчиком.
*
* При активном условии счётчик увеличивается до порога, после чего функция
* возвращает 1. При снятии условия счётчик уменьшается; в переходной области
* возвращается переданное предыдущее состояние `pre`.
*
* @param term Текущее логическое условие: ненулевое значение означает аварию.
* @param count Указатель на накопительный счётчик условия.
* @param edge Порог подтверждения условия в циклах вызова.
* @param pre Состояние, сохраняемое на этапе накопления или отпускания.
*
* @return 1 при подтверждённом условии; 0 при полностью снятом условии;
* иначе значение `pre`.
*
* @pre `count` указывает на доступную переменную счётчика.
*/
int er_anal(int term, unsigned int * count, long edge, int pre)
{
/* Активное условие постепенно заполняет счётчик подтверждения. */
if (term)
{
/* После достижения edge состояние считается устойчиво подтверждённым. */
if((*count)>=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<lev_count[pair]) Numb = lev_count[pair];
if(Numb<lev_count[thrd]) Numb = lev_count[thrd];
// Формирование защит.
error.all = 0;
/* В переходном состоянии WAKE новые защиты не формируются. */
if(WAKE) goto fin;
/* Ignore оставляет диагностику видимой, но ниже может запретить Stop. */
ignor = sens_error[sens].bit.Ignor;
/*
* Если измеренная фаза отстаёт от максимальной более чем на 30 % и общий
* уровень выше Curr_Edge, условие должно сохраняться time_3sec циклов.
*/
if(er_anal( ((Numb-lev_count[chan])/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(chan<tpl_cans)
{
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;
}
/*
* Имитация заменяет локальный датчик средним кодом исправных датчиков
* того же типа. Каналы с флагами из okay исключаются из набора доноров.
*/
if(sens_error[chan].bit.Imit)
if(chan<tpl_cans)
{
j=0;s=0;
for(i=0;i<start_data;i++)
{
if( sens_type[i]==sens_type[chan])
if(!(sens_error[i].all & okay.all))
{
s+=ADC_table[i];
j++;
} }
/* При наличии доноров вычисляется среднее; без доноров s остаётся равным нулю. */
if(j) s/=j;
Numb = s;
}
/* Неотрицательный kun означает, что текущий канал расположен в зоне калибровки. */
kun = chan - cal_addr;
/* При own!=0 внешний TRM_OBEH подменяет локальное значение АЦП. */
if(own)
if(sens_type[chan]==TRM_OBEH)
{
ovn = chan-tpl_cans;
/* Внешняя температура приходит в десятых долях градуса. */
Numb= 0.1 * ext_temp[ovn];
/* Любая внешняя диагностика преобразуется в заведомо аварийное значение. */
if(ext_diag[ovn])
Numb = -100;
}
/* Локальный аналоговый вариант температурного тракта. */
if(!own)
if(TermoAD)
{
/* В режиме калибровки сырой код записывается в соответствующую точку. */
if(cTermoCal|bTermoCal)
if(!WAKE)
{
/* В шкафном исполнении уровень кода выбирает старшую пару калибровок. */
if(Desk==dsk_SHKF)
if(Numb>2200) 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_lo_edge[chan])
{
error.bit.Out = 1;
/* Останов формируется только при одновременном снижении в парном канале. */
if(sens_error[sens_pair[chan]].bit.Out)
{
error.bit.Stop = 1;
} }
// Повышенное напряжение.
/*
* Эта защита присутствует в исходнике, но намеренно отключена блочным
* комментарием; исполняемый код её не учитывает.
*/
/* 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);
}
/** @} */