XY осциллограф
1. Назначение
Плагин представляет собой XY осциллограф, позволяющий выводить данные одного канала АЦП относительного другого. В частности, подобный осциллограф может быть использован для отображения фигур Лиссажу.
- Работа как в реальном масштабе времени, так и в режиме просмотра файла.
- Выбор масштаба времени.
- Можно включить режим компенсации межканальной задержки. В этом режиме временной сдвиг, обусловленный тем, что отсчеты каналов X и Y оцифровываются не одновременно, а последовательно, компенсируется путем вычисления смещенных отсчетов канала Y с помощью кубического интерполяционного многочлена. Этот режим может оказаться особенно полезным при построении фигур Лиссажу.
- Расчет постоянной составляющей (DC), среднеквадратического значения переменной составляющей (AC) и частоты по обоим каналам X и Y.
2. Исходный текст плагина
/*
Плагин XY осциллографа.
Среда разработки LabWindows CVI 9.0.
*/
#include "toolbox.h"
#include
#include
#include
#include
#include
#include "..\\include\\plugin.h"
// индексы визуальных элементов
#define VISUAL_INDEX_GRAPH 0 // график
#define VISUAL_INDEX_AC_X 1 // значение AC канала X
#define VISUAL_INDEX_DC_X 2 // значение DC канала X
#define VISUAL_INDEX_F_X 3 // значение частоты канала X
#define VISUAL_INDEX_AC_Y 4 // значение AC канала Y
#define VISUAL_INDEX_DC_Y 5 // значение DC канала Y
#define VISUAL_INDEX_F_Y 6 // значение частоты канала Y
#define VISUAL_INDEX_N 7 // размер выборки
// индексы параметров
#define PARM_DURATION_SCALE 0 // масштаб времени
#define PARM_DURATION 1 // продолжительность выборки
#define PARM_DRAW_AREA 2 // режим вывода
#define PARM_COMPENSATE_CHANNEL_DELAY 3 // разрешение компенсации межканальной задержки
#define PARM_FREQ_FLAG 4 // разрешение расчета частот (требует некоторого времени)
#define PARM_PRECISION 5 // число знаков после запятой
#define PARM_LINE 6 // тип линии
#define MAX_SIZE 100000 // максимальное число кадров, которые можно обработать за один раз
double FindFRQ(double *TEMP, int N, double DFRQ);
static struct PluginDataInfoStr DataInfo; // структура с настройками АЦП
struct PluginVisualMainStr LgraphVisual; // структура с настройками LGraph2
static int device_index=0; // работаем с первым модулем АЦП
static double *YData, *XData, *YNewData, *Yout, *Yfreq; // указатели на временные буфера
int Points;
int LineTypeArray[2]={VAL_THIN_LINE, VAL_FAT_LINE};
// *********************************************************************************************************
// главная функция обмена данными
void __stdcall PluginDataExchange(struct PluginDataStr *data_str)
{
int i, j, chan, index, n, mode, imax, imin, good, inverse_flag, f_detect_flag=0, n1;
int chan1, chan2, nch;
double pi, *ptr, x_freq, y_freq, freq, dt1, ac, dc, duration;
double x_interp[4], y_interp[4], x_new, y_new, error, dt, channel_delay;
chan1=DataInfo.adc_channels[0]; // запомним номер канала X плагина (от 0 до 31)
chan2=DataInfo.adc_channels[1]; // запомним номер канала Y плагина (от 0 до 31)
n=data_str->n; // сколько кадров будем обрабатывать
dt=1000000./DataInfo.rate[device_index]; // интервал в микросекундах
for(chan=0, nch=DataInfo.nch[device_index]; chan < 2; chan++) // цикл по двум каналам
{
// переложим в XData, YData данные АЦП
index=(chan) ? DataInfo.chan_kadr_offset[device_index][chan2] : DataInfo.chan_kadr_offset[device_index][chan1];
ptr=(chan) ? YData : XData;
for(i=0; i < n; i++, index += nch) *ptr++=data_str->data_to_plugin[index];
ACDCEstimator ((chan) ? YData : XData, n, &ac, &dc); // расчет AC,DC значений
data_str->slow_data[(chan) ? VISUAL_INDEX_AC_Y : VISUAL_INDEX_AC_X]=ac;
data_str->slow_data[(chan) ? VISUAL_INDEX_DC_Y : VISUAL_INDEX_DC_X]=dc;
}
if(DataInfo.parameters_int[PARM_FREQ_FLAG]) f_detect_flag=1; // если включен режим расчета частот
if(DataInfo.parameters_int[PARM_DRAW_AREA]) f_detect_flag=1; // если время берем из периода то надо считать частоты
if(f_detect_flag) // если надо рассчитать частоты
{
x_freq=FindFRQ(XData, n, DataInfo.rate[device_index]); // поиск частоты на канале X
y_freq=FindFRQ(YData, n, DataInfo.rate[device_index]); // поиск частоты на канале Y
data_str->slow_data[VISUAL_INDEX_F_X]=x_freq; // отобразим частоту на экране
data_str->slow_data[VISUAL_INDEX_F_Y]=y_freq; // отобразим частоту на экране
}
if(DataInfo.parameters_int[PARM_DRAW_AREA]) // если будем выводить только один период
{
if(x_freq > y_freq) freq=y_freq; // найдем максимальный период
else freq=x_freq;
if(freq > 0.0)
{
dt1=1000000./freq; // период в мкс
n1=(dt1/dt)+0.5;
if(n1 > 0 && n1 < n) n=n1;
}
}
// определим длительность отображаемой выборки в нужном масштабе
switch(DataInfo.parameters_int[PARM_DURATION_SCALE])
{
case 0:
duration=n*dt/1000000.;
break;
case 1:
duration=n*dt/1000.;
break;
case 2:
duration=n*dt;
break;
case 3:
duration=n;
break;
}
data_str->slow_data[VISUAL_INDEX_N]=duration;
// вычислим межканальную задержку в микросекундах
channel_delay=0;
if(DataInfo.interchannel_delay[device_index] > 0 && chan1 != chan2)
channel_delay=(DataInfo.chan_kadr_offset[device_index][chan2]-DataInfo.chan_kadr_offset[device_index][chan1])*
DataInfo.interchannel_delay[device_index];
// если надо учесть межканальную задержку займемся кубической интерполяцией
if(DataInfo.parameters_int[PARM_COMPENSATE_CHANNEL_DELAY] && channel_delay > 0)
{
for(i=0; i < 3; i++) x_interp[i]=i*dt;
for(i=0; i < n; i++)
{
if(!i) { for(j=0; j < 3; j++) y_interp[j]=YData[i+j]; x_new=-channel_delay; } // первая точка
else if(i == n-1) { for(j=0; j < 3; j++) y_interp[j]=YData[i+j-2]; x_new=dt*2-channel_delay; } // все точки
else { for(j=0; j < 3; j++) y_interp[j]=YData[i+j-1]; x_new=dt-channel_delay; } // последняя точка
PolyInterp (x_interp, y_interp, 3, x_new, &YNewData[i], &error); // интерполяция многочленом третьей степени
}
memcpy(YData, YNewData, n*sizeof(double)); // заменим исходный массив на смещенный на channel_delay мкс
}
// сохраним данные для передачи в ЛГраф
data_str->n_from_graph[0]=n;
data_str->x_data[0]=XData;
data_str->data_from_graph[0]=YData;
data_str->control_index[0]=0; // номер графического элемента, в котором будет нарисован график
data_str->color[0]=VAL_RED; // цвет графика
data_str->line_type[0]=VAL_SOLID; // тип линии (сплошной, точки и т.п.)
data_str->line_mode[0]=LineTypeArray[DataInfo.parameters_int[PARM_LINE]];
}
// функция, расчитывающая частоту в герцах
double FindFRQ(double *TEMP, int N, double DFRQ)
{
double value,d,wt,max,min,dt,mx;
int imax,imin,jj;
WindowConst wc={0,0};
memcpy(Yfreq, TEMP, N*sizeof(double));
MaxMin1D (Yfreq, N, &max, &imax, &min, &imin);
d= (max + min) / 2.0;
for(jj= 0; jj< N; jj++) Yfreq[jj]-=d;
ScaledWindow (Yfreq, N, 7, &wc);
dt=1.0/DFRQ;
AutoPowerSpectrum (Yfreq, N, dt, Yout, &wt);
MaxMin1D (Yout, N/2, &max, &imax, &min, &imin);
Mean (Yout, N/2, &mx);
if(mx == 0) return 0.0;
if(max < mx*40) return 0.0;
jj=PowerFrequencyEstimate (Yout, N/2, -1.0 , wc, wt, 11, &value, &d);
return(value);
}
//*********************************************************************************************************
// информационная функция
void __stdcall PluginInfo(struct PluginInfoStr *p_info)
{
// установим общие переменные
strcpy(p_info->name, "XY oscilloscope"); // название плагина
p_info->version=0x00010000; // версия 1.0
p_info->lgraph_version=0x221; // плагин разработан для версии 2.33
p_info->max_nch=p_info->min_nch=2; // максимальное число каналов, которые может обработать плагин 2
// установим параметры входных каналов плагина
strcpy(p_info->channel_names[0], "Канал X");
strcpy(p_info->channel_names[1], "Канал Y");
p_info->parameters=7; // всего 7 параметров
// Масштаб временного интервала отображения
strcpy(p_info->parameters_names[PARM_DURATION_SCALE], "Масштаб временного интервала отображения");
p_info->parameters_type[PARM_DURATION_SCALE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_DURATION_SCALE][0], "Секунды", 63);
strncpy(p_info->ring_names[PARM_DURATION_SCALE][1], "Миллисекунды", 63);
strncpy(p_info->ring_names[PARM_DURATION_SCALE][2], "Микросекунды", 63);
strncpy(p_info->ring_names[PARM_DURATION_SCALE][3], "Кадры", 63);
p_info->default_parameters_int[PARM_DURATION_SCALE]=0;
// Временной интервал отображения
strcpy(p_info->parameters_names[PARM_DURATION], "Временной интервал отображения");
p_info->parameters_type[PARM_DURATION]=L_TYPE_DOUBLE;
p_info->default_parameters_dbl[PARM_DURATION]=0.1;
// Режим отображения
strcpy(p_info->parameters_names[PARM_DRAW_AREA], "Отображать");
p_info->parameters_type[PARM_DRAW_AREA]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_DRAW_AREA][0], "Всю выборку", 63);
strncpy(p_info->ring_names[PARM_DRAW_AREA][1], "Максимальный период", 63);
// Режим компенсации межканальной задержки
strcpy(p_info->parameters_names[PARM_COMPENSATE_CHANNEL_DELAY], "Компенсировать межканальную задержку");
p_info->parameters_type[PARM_COMPENSATE_CHANNEL_DELAY]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_COMPENSATE_CHANNEL_DELAY][0], "Нет", 63);
strncpy(p_info->ring_names[PARM_COMPENSATE_CHANNEL_DELAY][1], "Да", 63);
p_info->default_parameters_int[PARM_COMPENSATE_CHANNEL_DELAY]=1;
// Режим расчета частот
strcpy(p_info->parameters_names[PARM_FREQ_FLAG], "Рассчитывать частоты");
p_info->parameters_type[PARM_FREQ_FLAG]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_FREQ_FLAG][0], "Нет", 63);
strncpy(p_info->ring_names[PARM_FREQ_FLAG][1], "Да", 63);
p_info->default_parameters_int[PARM_FREQ_FLAG]=1;
// Число знаков после запятой
strcpy(p_info->parameters_names[PARM_PRECISION], "Число знаков после запятой.");
p_info->parameters_type[PARM_PRECISION]=L_TYPE_INT;
p_info->default_parameters_int[PARM_PRECISION]=3;
p_info->min_parameters_int[PARM_PRECISION]=1;
p_info->max_parameters_int[PARM_PRECISION]=10;
// Тип линии
strcpy(p_info->parameters_names[PARM_LINE], "Тип линии");
p_info->parameters_type[PARM_LINE]=L_TYPE_RING;
strncpy(p_info->ring_names[PARM_LINE][0], "Тонкая", 63);
strncpy(p_info->ring_names[PARM_LINE][1], "Толстая", 63);
if(XData == NULL)
{
// выделим память
XData=malloc(MAX_SIZE*sizeof(double));
if(XData == NULL) { strcpy(p_info->error, "Не хватает памяти"); return; }
YData=malloc(MAX_SIZE*sizeof(double));
if(YData == NULL) { free(XData); XData=0; strcpy(p_info->error, "Не хватает памяти"); return; }
YNewData=malloc(MAX_SIZE*sizeof(double));
if(YNewData == NULL) { free(XData); XData=0; free(YData); strcpy(p_info->error, "Не хватает памяти"); return; }
if(Yout == NULL) Yout=malloc(sizeof(double)*MAX_SIZE/2);
if(Yout == NULL) { free(XData); XData=0; strcpy(p_info->error, "Не хватает памяти"); return; }
if(Yfreq == NULL) Yfreq=malloc(sizeof(double)*MAX_SIZE);
if(Yfreq == NULL) { free(XData); XData=0; strcpy(p_info->error, "Не хватает памяти"); return; }
}
}
// *********************************************************************************************************
// обработка данных о параметрах модулей АЦП от LGraph
void __stdcall PluginDataInfo(struct PluginDataInfoStr *d_info)
{
int chan_x, chan_y;
if(!d_info->devices) { strcpy(d_info->error, "Нет модуля АЦП"); return; }
if(!d_info->nch[device_index]) { strcpy(d_info->error, "Не выбраны каналы АЦП"); return; }
if(d_info->adc_nch < 2) { strcpy(d_info->error, "Не выбраны каналы АЦП"); return; }
DataInfo=*d_info; // запомним параметры АЦП
chan_x=d_info->adc_channels[0]; // запомним номер канала (от 0 до 31)
chan_y=d_info->adc_channels[1]; // запомним номер канала (от 0 до 31)
// проверим включен ли канал
if(!d_info->chan_on[device_index][chan_x]) { sprintf(d_info->error, "Не выбран канал X"); return; }
if(!d_info->chan_on[device_index][chan_y]) { sprintf(d_info->error, "Не выбран канал Y"); return; }
// определим сколько точек будем обрабатывать
switch(d_info->parameters_int[PARM_DURATION_SCALE])
{
case 0:
Points=d_info->parameters_dbl[PARM_DURATION]*d_info->rate[device_index]+0.5;
break;
case 1:
Points=d_info->parameters_dbl[PARM_DURATION]*d_info->rate[device_index]/1000.+0.5;
break;
case 2:
Points=d_info->parameters_dbl[PARM_DURATION]*d_info->rate[device_index]/1000000.+0.5;
break;
case 3:
Points=d_info->parameters_dbl[PARM_DURATION];
break;
}
if(Points < 3) { sprintf(d_info->error, "Слишком мало точек"); return; }
if(Points > MAX_SIZE) { sprintf(d_info->error, "Слишком много точек"); return; }
d_info->input_kadrs_min=d_info->input_kadrs_max=Points;
d_info->no_omit_old_data=1;
}
//*********************************************************************************************************
// настройка визуальных элементов
void __stdcall PluginVisualSetting(struct PluginVisualMainStr *main_visual_settings, struct PluginVisualStr p_visual[])
{
int graph_height, graph_width, m_top, m_left;
char *duration_names[]={"T, секунды", "T, миллисекунды", "T, микросекукнды", "T, кадры"};
LgraphVisual=*main_visual_settings;
main_visual_settings->n=8; // создаем 8 визуальных элементов
main_visual_settings->plugin_height=main_visual_settings->height*0.7; // высота 0.7 экрана
// ********** НАСТРОЙКИ ГРАФИКА
graph_height=main_visual_settings->plugin_height-40;
graph_width=main_visual_settings->width-120;
m_top=25;
m_left=graph_width+20;
p_visual[VISUAL_INDEX_GRAPH].type=L_VISUAL_GRAPH;
// настроим графические координаты
p_visual[VISUAL_INDEX_GRAPH].top=20; // координата по вертикали графика
p_visual[VISUAL_INDEX_GRAPH].height=graph_height; // высота графика
p_visual[VISUAL_INDEX_GRAPH].width=graph_width; // ширина графика
p_visual[VISUAL_INDEX_GRAPH].left=10; // горизонтальная координата
// сконфигурируем ось X
p_visual[VISUAL_INDEX_GRAPH].x_axis_mode=1; // по умолчанию включим автомасштаб по оси X
// сконфигурируем ось Y
p_visual[VISUAL_INDEX_GRAPH].y_axis_mode=1; // по умолчанию включим автомасштаб по оси X
//************ настройка остальных визуальных элементов
p_visual[VISUAL_INDEX_AC_X].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_AC_X].label_text, "AC (X)");
p_visual[VISUAL_INDEX_AC_X].top=m_top; // координата по вертикали
p_visual[VISUAL_INDEX_AC_X].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_AC_X].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_DC_X].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_DC_X].label_text, "DC (X)");
p_visual[VISUAL_INDEX_DC_X].top=m_top+50; // координата по вертикали
p_visual[VISUAL_INDEX_DC_X].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_DC_X].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_F_X].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_F_X].label_text, "F (X), Гц");
p_visual[VISUAL_INDEX_F_X].top=m_top+50*2; // координата по вертикали
p_visual[VISUAL_INDEX_F_X].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_F_X].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_AC_Y].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_AC_Y].label_text, "AC (Y)");
p_visual[VISUAL_INDEX_AC_Y].top=m_top+50*3+20; // координата по вертикали
p_visual[VISUAL_INDEX_AC_Y].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_AC_Y].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_DC_Y].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_DC_Y].label_text, "DC (Y)");
p_visual[VISUAL_INDEX_DC_Y].top=m_top+50*4+20; // координата по вертикали
p_visual[VISUAL_INDEX_DC_Y].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_DC_Y].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_F_Y].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_F_Y].label_text, "F (Y), Гц");
p_visual[VISUAL_INDEX_F_Y].top=m_top+50*5+20; // координата по вертикали
p_visual[VISUAL_INDEX_F_Y].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_F_Y].y_precision=DataInfo.parameters_int[PARM_PRECISION];
p_visual[VISUAL_INDEX_N].type=L_VISUAL_NUMERIC;
strcpy(p_visual[VISUAL_INDEX_N].label_text, duration_names[DataInfo.parameters_int[PARM_DURATION_SCALE]]);
p_visual[VISUAL_INDEX_N].top=m_top+50*6+40; // координата по вертикали
p_visual[VISUAL_INDEX_N].left=m_left; // координата по горизонтали
p_visual[VISUAL_INDEX_N].y_precision=DataInfo.parameters_int[PARM_PRECISION];
}
// Функция сообщает плагину, что начался/закончился сбор данных (в данном плагине не используем)
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(XData != NULL) free(XData);
if(YData != NULL) free(YData);
if(YNewData != NULL) free(YNewData);
if(Yout != NULL) free(Yout);
if(Yfreq != NULL) free(Yfreq);
if (!CVIRTEHasBeenDetached ()) CloseCVIRTE ();
break;
}
return 1;
}



