[Из песочницы] Автоматизация лабораторных измерений

Я аспирант института металлофизики, занимаюсь исследованием свойств различных металлов и сплавов. Процесс измерения и анализа, как правило трудоемкий, и требует много рутинной работы. В связи с этим была выполнена автоматизация установки для исследования оптических свойств металлов. О том, как это было, расскажу дальше в публикации.

114956f47faa4c05a0d55557637c5b9c.png

Постановка задачи


Разработать программное обеспечение для управления подготовкой и проведением эксперимента на автоматизированном оптическом эллипсометре.Программное обеспечение должно предоставлять возможность
  • задания начальных установок и параметров эксперимента;

  • проведения измерений оптических характеристик образцов;

  • набор первичных данных [массивы данных 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 мВ при оптимальном соотношении сигнал/шум):

ef4b61b891e14bfb9e6926dd1cae5b5a.png

код
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. Бесплатная, быстрая, простая и довольно функциональная библиотека.

9bb553970edb41218e41b300718c84a6.png

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

Фотография
57fa28fe1d1e414b9fbb39d8e228b5c2.jpg

Комментарии (0)

© Habrahabr.ru