Постановка задачи
Разработать программное обеспечение для управления подготовкой и проведением эксперимента на автоматизированном оптическом эллипсометре.
Программное обеспечение должно предоставлять возможность
- задания начальных установок и параметров эксперимента;
- проведения измерений оптических характеристик образцов;
- набор первичных данных [массивы данных I(?) при фиксированной длине волны ? и фиксированном (постоянном) значении величины I(?) в заданном диапазоне длин волн ?min ? ? ? ?max (где ? – азимут анализатора) или массивы I(?) при фиксированных азимутах анализатора ?1, ?2, ?3,... ?n];
- обработку массивов I(?) или I(?) для вычисления оптических постоянных;
- управление временем выдержки до начала измерений в данной точке спектра или азимута анализатора для измерения установившегося уровня сигнала, а также длительностью «стояния» в данной точке спектра (азимута) для «накопления» сигнала;
- визуализацию измерительных данных;
- сохранение данных в файл.
Эксперимент
В процессе измерения программа должна управлять шаговыми приводами, выполняя двумерное сканирование. Минимальный шаг сканирования зависит от возможностей установки. Количество точек на диапазон определяется величиной диапазона и шагом сканирования.
Параметры эксперимента (диапазон измерения, шаг сканирования) в процессе измерений не изменяются. Сканирование происходит дискретно, по шагам.
Измерение выполняется только в моменты остановки приводов. После выполнения измерения дается команда перемещения на следующий шаг, и производится транспортное перемещение. Напряжение на ФЭУ регулируется с помощью ШИМ.
Софт
В качестве языка программирования я выбрал C#, так как у него «из коробки» реализовано много полезных функций для работы с USB, COM-портами, нативная поддержка Microsoft Office и т.д.
Итак, для получения данных и отправки команд микроконтроллеру мы будем использовать виртуальный COM-порт:
// Open ADC Com
private void buttonADC_Click(object sender, EventArgs e)
{
String s = "";
if (comboBox1.Text != "")
{
s += comboBox1.Text;
}
try
{
hPort = PACNET.UART.Open(s + ",9600,N,8,1");
Log.Items.Add("Порт АЦП открыт");
buttonADC.Enabled = false;
comboBox1.Enabled = false;
}
catch
{
MessageBox.Show("Порт " + serialPort1.PortName +
"невозможно открыть!", "Ошибка!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Log.Item.add("Невозможно открыть порт!");
}
}
Для преобразования аналогового сигнала я использовал 16-битный АЦП фирмы ICP Das, для которого на официальном сайте есть SDK, поэтому чтение данных с АЦП тривиальное:
float readSequenceADC(int iChannel, int iterations)
{
float result = 0;
for (int i = 0; i < iterations; i++)
{
result += readADC(iChannel);
Thread.Sleep(150);
}
result /= iterations;
return result;
}
Немного математики, расчет оптических констант:
public void count(float a, float b, float c)
{
double x = a;
double y = b;
double z = c;
double ksi = Math.Atan(Math.Sqrt(x / z));
double delta = Math.Acos((x + z - 2 * y) / (2 * Math.Sqrt(x * z)));
double denominator = Math.Pow((1 - Math.Sin(2 * ksi) * Math.Cos(delta)), 2);
double numerator = Math.Pow(Math.Sin(phi), 2) * Math.Pow(Math.Tan(phi), 2) *
Math.Sin(2 * ksi) * Math.Cos(2 * ksi) * Math.Sin(delta);
double nk = numerator / denominator;
double numerator1 = Math.Pow(Math.Cos(2 * ksi), 2) - Math.Pow(Math.Sin(2 * ksi), 2) *
Math.Pow(Math.Sin(delta), 2);
double nk2 = Math.Pow(Math.Sin(phi), 2) + (Math.Pow(Math.Sin(phi), 2) * Math.Pow(Math.Tan(phi), 2) *
(numerator1 / denominator));
double x1 = 0;
double x2 = 0;
double D = nk2 * nk2 - 4 * ((-1) * nk * nk);
if (D < 0)
{
x1 = 0;
x2 = 0;
}
else if (D == 0)
{
x1 = -nk2 / 2;
x2 = -nk2 / 2;
}
else if (D > 0)
{
x1 = (-nk2 + Math.Sqrt(D)) / 2;
x2 = (-nk2 - Math.Sqrt(D)) / 2;
}
if (x1 < 0 && x2 < 0)
{
// "X1, x2 < 0"
}
else if (x1 > 0)
{
k = Math.Sqrt(x1);
}
else if (x2 > 0)
{
k = Math.Sqrt(x2);
}
n = Math.Sqrt(nk2 + k * k);
}
Функция определения дрейфа «нуля» сигнала:
// Intensity Drifting Check
private void Worker3()
{
while (isWorking == true)
{
try
{
for (int i = 0; i < 1000; i++)
{
Thread.Sleep(200);
I = readADC(3);
Intensity.Add(I);
this.Invoke((MethodInvoker)(() => Log.Items.Add("I = " + I)));
DrawGraph(i, I, 0);
}
PrintExel.ExportText(Intensity);
ClearGraph();
isWorking = false;
}
catch (Exception e) { MessageBox.Show("Error:\n" + e.Message); }
}
}
Фотоэлектронный умножитель жутко нелинейный, поэтому на разных участках спектра у него разная чувствительность и нужно поддерживать постоянный сигнал в установленных рамках (для наших измерений это диапазон 300 — 850 мВ при оптимальном соотношении сигнал/шум):
private void maxControl()
{
double val = 1;
while (val > 0.85)
{
this.radRadialGauge1.Value -= 7;
serialPort1.Write("decrease");
val = Convert.ToInt32(Math.Abs(readADC(3)));
Thread.Sleep(1000);
}
}
private void minControl()
{
double val = 0;
while (val < 0.4) // val < 300
{
this.radRadialGauge1.Value += 7;
serialPort1.Write("increase");
val = Convert.ToInt32(Math.Abs(readADC(3)));
Thread.Sleep(1000);
}
}
Визуализация данных
Отображение результатов измерения в реальном времени — очень важный момент, так как позволяет контролировать процесс и правильность выполнения эксперимента. Проанализировав статью на Хабре «Средства построения графиков для .NET» выбрал ZedGraph. Бесплатная, быстрая, простая и довольно функциональная библиотека.
private void DrawGraph(int x, double n, double k)
{
GraphPane pane = zedGraphControl1.GraphPane;
PointPairList listN = new PointPairList();
PointPairList listK = new PointPairList();
LineItem myCurve;
listN.Add(x, n);
listK.Add(x, k);
myCurve = pane.AddCurve("", listN, Color.Blue, SymbolType.Default);
myCurve = pane.AddCurve("", listK, Color.Red, SymbolType.Default);
zedGraphControl1.AxisChange();
zedGraphControl1.Invalidate();
}
Ну и напоследок, сохранение в файл:
public static void ExportToExcelIntensity(ArrayList arrLamda, ArrayList arrI0, ArrayList arrI45, ArrayList arrI90)
{
try
{
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = true;
excelApp.Workbooks.Add();
Microsoft.Office.Interop.Excel.Worksheet workSheet = excelApp.ActiveSheet;
workSheet.Cells[1, "A"] = "WaveLength";
workSheet.Cells[1, "B"] = "I0";
workSheet.Cells[1, "C"] = "I45";
workSheet.Cells[1, "D"] = "I90";
int row = 1;
for (int i = 0; i < arrLamda.Count; i++)
{
row++;
workSheet.Cells[row, "A"] = arrLamda[i];
workSheet.Cells[row, "B"] = arrI0[i];
workSheet.Cells[row, "C"] = arrI45[i];
workSheet.Cells[row, "D"] = arrI90[i];
}
workSheet.Range["A1"].AutoFormat(Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2);
excelApp.DisplayAlerts = false;
workSheet.SaveAs(string.Format(@"{0}\OpticalIntensities.xlsx", Environment.CurrentDirectory));
excelApp.Quit();
}
catch (Exception ex)
{
MessageBox.Show("Error:\n" + ex.Message);
}
}
Итог
Разработан Аппаратно-программный комплекс, предназначенный для автоматизации лабораторных измерений, основанный на WinForms. На счет производительности и актуальности выбора именно этого ЯП для научных вычислений можно ознакомиться в статье.
P.S. Так выглядит часть управляющего «железа», о котором планирую написать в следующей статье:
Комментарии (13)
DSolodukhin
26.07.2016 21:29Скажите, а какую модель вы используете для пересчета эллипсометрических параметров в n и k? И вытаскиваете ли вы еще какую-нибудь информацию из данных эллипсометрии кроме показателей преломления и поглощения?
NikitaSatcik
27.07.2016 15:04Я использую метод вращающегося анализатора RAE(фотоэлектрический метод Битти). На практике мы измеряем степень поляризации и находим ? и ?. В программе я вывожу на экран оптические постоянные, также можно считать толщину тонких пленок, коэффициенты отражения и пропускания. В итоге можно посчитать оптическую проводимость и диэлектрическую функцию.
golf2109
26.07.2016 21:29+1Цитата —
Фотоэлектронный умножитель жутко нелинейный, поэтому на разных участках спектра у него разная чувствительность и нужно поддерживать постоянный сигнал в установленных рамках (для наших измерений это диапазон 300 — 850 мВ при оптимальном соотношении сигнал/шум):
а каково оно — оптимальное соотношение сигнал/шум для данного случая?
судя по графику видно шумы на уровне единиц процента от величины сигнала, тогда вопрос, зачем использовать 16-разрядный АЦП для измерений, (тем более за такие деньги) если из 16-ти разрядов использовано 10-12.
Мой искренний совет автору — посмотрите в сторону использования LabVIEW для данной методики автоматизации.
Аналогичная программа на LabVIEW, (созданная мною), управляющяя установкой на основе монохроматора МДР-23 занимает половину листа формата А4 golf2109.blogspot.com/2016/06/stepper-control-command.html
P.S.
С# я также использовал вначале, но потом перешел на LabVIEW и не жалею,
NikitaSatcik
27.07.2016 14:26Что касается соотношения сигнал/шум, я меряю темновой шум(обычно это 1-10 мВ), а потом из массива градуировки ФЭУ(зависимость чувствительности фотоприемника от длины волны), подбираю напряжение так, чтобы быть близко к максимуму чувствительности ФЭУ, не превышая диапазон АЦП(обычно не больше 1000 мВ).
Да и АЦП на 16 разрядов лишним никогда не будет, тем более что кроме ФЭУ я использую фоторезистор(для ИК области), а у него сигнал на выходе около 1 мВ, после усилителя чуть больше(до 150-200 мВ), но и шумы усиливаются тоже. А так как этот АЦП выполнен в виде коробочки, я беру его в лабу для измерения ТКС, где очень маленькие сигналы.
Вот как раз с LabVIEW я и начинал автоматизацию, но в итоге перешел на С#, потому-что LabVIEW работал не стабильно и часто вылетал, особенно когда обрабатывалось много данных в несколько потоков, например несколько тысяч измерений за 1 раз (полный проход от ультрафиолета до инфракрасной области спектра). Ну и вторым фактором стало то что LabVIEW это по сути не стандартный яп, а так как я в основном пишу на Java, под C# для меня проще и быстрее писать и отлаживать программу, чем перетаскивать пиктограммы в LabVIEW.
P.S.
С# я также использовал вначале, но потом перешел на LabVIEW и не жалею
Спасибо за совет, но от LabVIEW я уже отошел окончательно и безповоротно, и тоже как и Вы, ничуть не жалею.
В дальнейшем я планирую перейти на С++ и QT, когда будет посложней проект.
MichaelV
27.07.2016 00:04Очень интересно! А есть что-нибудь про управляющие контроллеры? Я тут почитал:konsom.ru/solutions/dispetcherskij-kontrol-i-upravlenie/sistemy-avtomatizatsii-tehnologicheskih-protsessov-asu-tp и заинтересовался. Для себя разбираюсь.
golf2109
27.07.2016 17:14Цитата — я меряю темновой шум(обычно это 1-10 мВ)
вообще то в фЭУ есть только такая характеристика как темновой ток, который и создает на нагрузочном сопротивлении или входном сопротивлении усилителя или АЦП напряжение. У Вас оно 1-10 мВ, то есть, при диапазоне АЦП 1000 мВ Вы получаете 10-битное разрешение при 1 мВ или при 10 мВ вообще 7-битное. При таких шумах можно вообще использовать Arduino или недорогую платку из серии STM32 Discovery, а не АЦП за пару сотен долларов.
А насчет фоторезистора, так вообще не понятно, — у него нет «сигнала на выходе 1 мВ», у него есть только сопротивление. И если через него пропускать малый ток, который даст на нем 1 мВ, то шуметь он будет очень сильно, кроме того его паказания плавают от температуры и поданного напряжения на него, поэтому точность будет хуже чем с ФЭУ.
А насчет LabVIEW — небольшая справка, — система управления БАК (большого андронного коллайдера) построена на LabVIEW/NikitaSatcik
28.07.2016 12:03Темновой ток — темновым током называют малый электрический ток, который протекает через фоточувствительный детектор, например, фотодиод, фотоэлектронный умножитель при отсутствии поглощенных фотонов. В моем случае, я закрываю световой поток от источника света на входе ФЭУ, и получаю, как я возможно неправильно выразился — "темновой шум".
А насчет фоторезистора, так вообще не понятно, — у него нет «сигнала на выходе 1 мВ», у него есть только сопротивление. И если через него пропускать малый ток, который даст на нем 1 мВ, то шуметь он будет очень сильно, кроме того его паказания плавают от температуры и поданного напряжения на него, поэтому точность будет хуже чем с ФЭУ.
Вот схема: из которой все становится ясно, мы модулируем сигнал(на частоте 500 Гц), поэтому зависимость выходного напряжения от сдвига фаз между основным и опорным сигналами имеет вид косинуса, после интегрирования и синхронного детектора мы получаем постоянный сигнал.
Синхронное детектирование позволяет сдвинуть сигнал в область, где избыточный и генерационно-рекомбинационный шумы уже не такие большие, как на нулевой частоте(без модуляции).
По поводу БАК, я думаю там большую роль играет то, что куча специалистов делает "железо" изначально под лабвью, как например те же National Instruments продают дорогущие, но стабильно работающие модули DAQ(оборудование для сбора данных).
golf2109
28.07.2016 15:14Если не обижу, то критические замечания к схеме,-в данном случае так и просится мостовое включение фоторезистора и использование не обыкновенного ОУ, а измерительного, тогда можно будет обойтись без RC цепочек по питанию. Если не секрет, что используется в качестве источника света и как осуществляется модуляция и какой синхронный детектор используестя?
NikitaSatcik
28.07.2016 15:54Это схема, от которой я отталкивался, в установке же я спаял как раз по мостовой схеме, резистор подбирал такой-же, как сопротивление у фоторезистора(820 кОм в темноте). Там сейчас стоят ОУ AD820.
Синхронный детектор был сделан в 90-х годах на кафедре в институте, к нему есть схема на 2 страницы А1.
Источники света — галогенка на 100 Вт и ДРШ-100.
Модуляция — механический обтюратор, опорный сигнал в виде оптопары, которая крепится на диске обтюратора.
KvanTTT
28.07.2016 12:16Не используйте функцию Math.Pow для целых степеней (особенно для 2), т.к. она медленная. Вместо этого перемножайте вручную.
Oxoron
Друг, это статья на msdn. Про C#. Объективность её несколько сомнительна. Думаю, если твой набор требований USB\COM\графика (и MS Office можно заменить на csv) — подойдет любой из серьезных языков.
Впрочем, если это научная статья, никто не обратит внимания.