Плагин: двухканальный спектроанализатор
1. Назначение
Плагин позволяет проводить спектральный анализ по двум каналам в реальном масштабе времени.
- Одновременная работа по двум каналам.
- Работа как в реальном масштабе времени, так и в режиме просмотра файла.
- Выбор типа накладываемого на сигнал перед спектральным анализом окна.
2. Исходный текст плагина
/*
Плагин, реализующий двухканальный спектроанализатор, работающий в реальном времени.
Среда разработки LabWindows CVI 9.0.
*/
#include
#include
#include
#include
#include "..\\include\\plugin.h"
#define SPEKTR_SIZE 32768 // число точек для БПФ
static struct PluginDataInfoStr DataInfo; // структура с настройками АЦП
static int device_index=0; // работаем с первым модулем АЦП
// массивы для рассчета спектров
static double SpektrY1[SPEKTR_SIZE/2];
static double SpektrY2[SPEKTR_SIZE/2];
static double SpektrX1[SPEKTR_SIZE/2];
static double SpektrX2[SPEKTR_SIZE/2];
static double Auto_Spectrum[SPEKTR_SIZE/2];
static double SpektrData[SPEKTR_SIZE];
// *********************************************************************************************************
// главная функция обмена данными с рассчетом спектров
void __stdcall PluginDataExchange(struct PluginDataStr *data_str)
{
int i, j, chan, index, n;
WindowConst WinConst;
double df, *x;
char Unit[256];
int chan1, chan2;
Unit[0]=0;
chan1=DataInfo.adc_channels[0]; // запомним номер первого канала (от 0 до 31)
chan2=DataInfo.adc_channels[1]; // запомним номер второго канала (от 0 до 31)
n=data_str->n; // сколько пришло кадров
if(n & 1) n--; // сделаем n четным
for(chan=0; chan < 2; chan++) // цикл по двум каналам
{
// переложим в SpektrData данные от обрабатываемого канала
index=(chan) ? DataInfo.chan_kadr_offset[device_index][chan2] : DataInfo.chan_kadr_offset[device_index][chan1];
for(i=0; i < n; i++, index += DataInfo.nch[device_index]) SpektrData[i]=data_str->data_to_plugin[index];
// наложим на данные выбранное окно
ScaledWindow (SpektrData, n, DataInfo.parameters_int[1], &WinConst);
// рассчет спектра
AutoPowerSpectrum (SpektrData, n, 1./DataInfo.rate[device_index], Auto_Spectrum, &df);
// преобразуем спектр в дБ, Vrms (volts rms)
SpectrumUnitConversion (Auto_Spectrum, n/2, 0, 1, DataInfo.parameters_int[0], df, WinConst,
(chan) ? SpektrY2 : SpektrY1, Unit);
// сохраним данные для передачи в ЛГраф
data_str->n_from_graph[chan]=n/2;
data_str->data_from_graph[chan]=(chan) ? SpektrY2 : SpektrY1;
// сформируем ось X в герцах
for(i=0, x=(chan) ? SpektrX2 : SpektrX1; i < n/2; i++) *x++=i*df;
data_str->x_data[chan]=(chan) ? SpektrX2 : SpektrX1;
data_str->control_index[chan]=chan; // номер графического элемента, в котором будет нарисован график
data_str->color[chan]=VAL_RED; // цвет графика
data_str->line_type[chan]=VAL_SOLID; // тип линии (сплошной, точки и т.п.)
data_str->line_mode[chan]=VAL_VERTICAL_BAR; // тип графика
sprintf(data_str->name[chan], "Канал %u", (chan) ? chan2+1 : chan1+1); // имя графика (для легенды)
}
}
//*********************************************************************************************************
// информационная функция
void __stdcall PluginInfo(struct PluginInfoStr *p_info)
{
int i;
char *rms_names[]={"Vrms (volts rms)", "Vpk (volts peak)", "Vrms2 (volts squared rms)", "Vpk2 (volts squared peak)",
"Vrms/ (volts rms per root Hertz)", "Vpk/ (volts peak per root Hertz)", "Vrms2/ (volts squared rms per Hertz)",
"Vpk2/ (volts squared peak per Hertz) "};
char *windows_names[]={"Rectangular", "Hanning", "Hamming", "Blackman-Harris", "Exact Blackman", "Blackman", "Flat Top",
"4-Term Blackman-Harris", "7-Term Blackman-Harris", "Low Sidelobe"};
// установим общие переменные
strcpy(p_info->name, "Spektr1"); // название плагина
p_info->version=0x00010000; // версия 1.0
p_info->lgraph_version=0x221; // плагин разработан для версии 2.33
p_info->max_nch=2; // максимальное число каналов, которые может обработать плагин 2
p_info->min_nch=1; // минимальное число каналов 1
// установим параметры входных каналов плагина
strcpy(p_info->channel_names[0], "Канал A для расчета спектра");
strcpy(p_info->channel_names[1], "Канал B для расчета спектра");
p_info->parameters=2; // плагин имеет 2 параметра
strcpy(p_info->parameters_names[0], "Единицы измерения"); // название первого параметра
strcpy(p_info->parameters_names[1], "Накладываемое окно"); // название второго параметра
// оба параметра имеют тип текстового списка
p_info->parameters_type[0]=L_TYPE_RING;
p_info->parameters_type[1]=L_TYPE_RING;
// заполним названия параметров
for(i=0; i < 8; i++) strncpy(p_info->ring_names[0][i], rms_names[i], 63);
for(i=0; i < 10; i++) strncpy(p_info->ring_names[1][i], windows_names[i], 63);
}
// *********************************************************************************************************
// обработка данных о параметрах модулей АЦП от LGraph
void __stdcall PluginDataInfo(struct PluginDataInfoStr *d_info)
{
int chan1, chan2;
if(!d_info->devices) { strcpy(d_info->error, "Нет модуля АЦП"); return; }
if(!d_info->nch[device_index]) { strcpy(d_info->error, "Не выбраны каналы АЦП"); return; }
DataInfo=*d_info; // запомним параметры АЦП
chan1=d_info->adc_channels[0]; // запомним номер первого канала (от 0 до 31)
chan2=d_info->adc_channels[1]; // запомним номер второго канала (от 0 до 31)
// проверим включен ли первый канал
if(!d_info->chan_on[device_index][chan1]) { sprintf(d_info->error, "Не выбран канал %u", chan1+1); return; }
// проверим включен ли второй канал
if(!d_info->chan_on[device_index][chan2]) { sprintf(d_info->error, "Не выбран канал %u", chan2+1); return; }
// данные хотим получать строго по SPEKTR_SIZE кадров с отбрасыванием лишних данных
d_info->input_kadrs_min=256;
d_info->input_kadrs_max=SPEKTR_SIZE;
}
//*********************************************************************************************************
// настройка графиков
void __stdcall PluginVisualSetting(struct PluginVisualMainStr *main_visual_settings, struct PluginVisualStr p_visual[])
{
main_visual_settings->n=2; // создаем два графика
main_visual_settings->plugin_height=600; // для двух графиков хватит 600 пикселей по высоте
p_visual[0].type=p_visual[1].type=L_VISUAL_GRAPH; // тип элементов - графики
// настроим графические координаты
p_visual[0].top=10; // координата по вертикали первого графика
p_visual[0].height=p_visual[1].height=250; // высота каждого графика 250 пикселей
p_visual[0].width=p_visual[1].width=main_visual_settings->width-20; // ширина графика
p_visual[0].left=p_visual[1].left=10; // горизонтальная координата
p_visual[1].top=p_visual[0].top+p_visual[0].height+10; // координата по вертикали второго графика
// сконфигурируем ось X (частота)
p_visual[0].x_axis_mode=p_visual[1].y_axis_mode=1; // по умолчанию включим автомасштаб по оси X
strcpy(p_visual[0].axis_x_label, "Частота, Гц"); // название оси X первого графика
strcpy(p_visual[1].axis_x_label, "Частота, Гц"); // название оси X второго графика
// сконфигурируем ось Y (дБ)
p_visual[0].axis_y_min=p_visual[1].axis_y_min=-120; // минимальное значение
p_visual[0].axis_y_max=p_visual[1].axis_y_max=40; // максимальное значение
strcpy(p_visual[0].axis_y_label, "Напряжение, дБ"); // название оси Y первого графика
strcpy(p_visual[1].axis_y_label, "Напряжение, дБ"); // название оси Y второго графика
p_visual[0].x_precision=p_visual[1].x_precision=1; // разметка по X с 1 цифрой после запятой
p_visual[0].y_precision=p_visual[1].y_precision=1; // разметка по Y с 1 цифрой после запятой
}
// Функция сообщает плагину, что начался/закончился сбор данных (в данном плагине не используем)
void __stdcall PluginStartInput(struct PluginDataStr *data_str) {}
void __stdcall PluginStopInput(struct PluginDataStr *data_str) {}
//*********************************************************************************************************
// Функция вызываемая при загрузке - выгрузке DLL плагина
int __stdcall DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
if (InitCVIRTE (hinstDLL, 0, 0) == 0) return 0;
break;
case DLL_PROCESS_DETACH:
if (!CVIRTEHasBeenDetached ()) CloseCVIRTE ();
break;
}
return 1;
}



