Я аспирант института металлофизики, занимаюсь исследованием свойств различных металлов и сплавов. Процесс измерения и анализа, как правило трудоемкий, и требует много рутинной работы. В связи с этим была выполнена автоматизация установки для исследования оптических свойств металлов. О том, как это было, расскажу дальше в публикации.
Постановка задачи
Разработать программное обеспечение для управления подготовкой и проведением эксперимента на автоматизированном оптическом эллипсометре.
Программное обеспечение должно предоставлять возможность
- задания начальных установок и параметров эксперимента;
- проведения измерений оптических характеристик образцов;
- набор первичных данных [массивы данных 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. Так выглядит часть управляющего «железа», о котором планирую написать в следующей статье:
Автор: NikitaSatcik