C#: управляем девайсом с компа

Некоторые задачи требуют подключения электронного устройства к персональному компьютеру. Самый простой и распространенный способ это сделать — использовать переходник USB-COM, или микросхему-мост USB-UART. Однако, на стороне ПК нам понадобится какая-то программа, с помощью которой мы будем управлять нашим девайсом.

Самое простое решение — использовать что-то готовое, например, терминальную программу PuTTY. Через нее в режиме командной строки можно отправлять команды в наше устройство и получать из него ответ:

Но в некоторых случаях недостаточно простой командной строки, нужен GUI (графический пользовательский интерфейс). Олдскульные парни для этих целей используют что-то типа Borland Delphi или Borland C++ Builder. Но нам в универе преподавали C#, поэтому я его и использую (к тому же я не перевариваю паскаль-подобный синтаксис, только c-like languages)) ). К тому же у C# есть удобный класс работы с последовательным портом, и ни какие костыли нам не понадобятся!

Недавно на работе поставили мне задачу, сделать переходник USB-UART к одному хитрому девайсу и написать для него прогу управления. Сам переходник описывать не буду, там ничего интересного нет, UART да двухполярное питание +-12 вольт. А вот на протоколе обмена немного остановлюсь.

Документация естественно на девайс написана через всем известное место, разбираться пришлось с помощью исходников прошивки на него.

В заголовке исходников оказался хорошо расписан весь протокол обмена. В общих чертах он следующий: первый символ — некая буква, которая указывает, какую именно команду мы хотим выполнить. Затем, параметр, который представляет собой шестнадцатеричное число (может отсутствовать) и в конце команды символ ‘\r’. После получения символа ‘\r’ девайс начинает анализировать, что ему за команда пришла. После выполнения всех необходимых действий он выдает ответ, содержащий эхо входной команды, возвращаемое значение и признак конца ответа, который является символом ‘>’. Таким образом, алгоритм обмена прост: отправляем команду, заканчивающуюся на ‘\r’ и читаем ответ до тех пор, пока не получим символ ‘>’. Потом анализируем ответ.

Итак, приступаем. Создадим класс, который будет отвечать за низкоуровневый обмен с устройством и предоставлять удобный программный интерфейс управления.

Класс называется SDU3 (так называется сам девайс). С помощью using System.IO.Ports; подключим пространство имен, в котором находится класс работы с COM-портом, _serialPort — наш объект СОМ-порта. У класса SerialPort есть очень удобный метод, с помощью которого можно получить все доступные в системе COM-порты. Это очень удобно, когда мы не знаем номер порта, который система присвоила переходнику USB-COM (или USB-UART, что в принципе то же самое) и нам не придется перебирать все порты от COM1 до COM_овер_дохрена. Чтож, добавим такой метод в наш класс SDU3

Вызвав GetPorts() мы получим массив строк с именами доступных портов. После этого искать нужный порт методом перебора намного легче.

Следующий метод — открытие порта

Более подробно все расписано на сайте мелкософта: https://docs.microsoft.com/ru-ru/dotnet/api/system.io.ports.serialport?view=netframework-4.7.1 или тут https://msdn.microsoft.com/ru-ru/library/system.io.ports.serialport(v=vs.110).aspx . Если вдруг ссылки протухнут, всегда можно воспользоваться гуглом, и по запросу c# serial port можно найти нужную инфу.

Ну и добавим несколько полезных методов, которые просто транслируют запрос далее:

Далее, перейдем к самому обмену. Начнем с приема ответа. Согласно протоколу обмена, ответ девайса заканчивается символом ‘>’. То есть, нам надо читать из порта до тех пор, пока не наткнемся на данный символ. В SerialPort как раз для этих целей есть очень удобный метод, который называется .ReadTo(). Таким образом, нам не надо самим анализировать поток данных из порта на предмет наличия онного символа, а просто написать так:

Стоит отметить, что сам символ ‘>’ в возвращаемой строке будет отсутствовать. А помните параметр ReadTimeout, который мы указали как 500 на этапе открытия порта? Так вот, если через 500 миллисекунд после вызова _serialPort.ReadTo(«>») девайс не ответит строкой с завершающим символом ‘>’, возникнет исключение, которое можно отловить в нужном месте кода, и вывести ошибку типа «Девайс отвалился!!!! Проверь провода!!!!!11111».

Перейдем теперь к реализации команд обмена. Начнем с команды получения всех настроек устройства. Она имеет вид «S\r». Согласно «документации» ответ мы получим в следующем виде:

Имена настроек обозначены буквами L, H, V, G, O. После буквы идет символ ‘=’, затем значение параметра в шестнадцатеричном значении, затем «\r\n», затем следующая настройка. Причем порядок следования настроек всегда одинаков, то есть на анализ имен настроек можно забить, а парсить только то, что идет после знака ‘=’.

Давайте разберемся, что надо сделать. Для начала разобьем строку ответа на подстроки по разделителю ‘\r’:

У нас получилось 7 новых строк. Первую строку пропускаем, в ней нет ничего интересного для нас. Во второй строке находится параметр L. Давайте разобьем ее по разделителю ‘=’:

Получим отдельно ‘\nL’ и значение данного параметра в шестнадцатеричном виде ‘xx’, которое далее можно преобразовать в число и сохранить куда надо.

Проделаем данную операцию и со строками 3, 4, 5, 6. Строка 7 не содержит для нас ничего интересного, выбрасываем. Стоит отметить, что символ ‘>’ будет в ней отсутствовать, так как функция sdu3_Read() его отбросит, но это не особо важно.

Для удобства создадим класс, который будет содержать все перечисленные параметры девайса для удобства работы с ними.

Ну и метод StatusToString() добавим, на случай, если мы захотим «красиво» вывести все значения переменных в виде текста. Ну и использовать как напоминалку себе, чтоб не забыть, что какой параметр означает)).

Вот и сам метод получения всех параметров устройства. С помощью _serialPort.DiscardInBuffer() и _serialPort.DiscardOutBuffer() очищаем входные и выходные буферы порта от возможного мусора в них. Далее, отправляем в порт команду чтения всех параметров и читаем ответ в tmp. Далее, ответ парсим описанным выше способом, засовываем в ret и возвращаем считанные значения.

Это была самая сложная функция, дальше будет легче)).

Рассмотрим функцию, которая называется «Установить верхнюю границу дискриминатора» (не спрашивайте, что это означает)) ) «Lxx\r», где xx — шестнадцатеричное число.

Принимаемые значения лежат в диапазоне 0..255. Вот код функции:

С помощью string.Format(«{0:X2}», val) мы преобразуем восьмибитное число в шестнадцатеричный формат, причем с незначащим нулем и с заглавным буквами. Например, число 13 преобразуется в 0D, а не в d, 0d, D, или 0xD. Далее, отправляем запрос в девайс и ждем ответ. Следует отметить, что нам интересен только сам факт ответа, а не он сам: если что-то вразумительное ответил, значит все норм. Остальные функции однотипны, и на реализации каждой из них останавливаться подробно не буду.

В итоге, у нас получился вот такой класс работы с девайсом:

Далее, на основе этого класса уже довольно просто нарисовать GUI управления прибором. У меня получилось вот так:

Подробно останавливаться на реализации не буду, так как там все просто: нажали на кнопку — команда сквозняком пошла в наш класс SDU3.

На этом все, спасибо за внимание!))

Метки: , . Закладка Постоянная ссылка.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *