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


