Visual Studio C#: работа с последовательным портом |
![]() |
Добавил(а) microsin |
Эта статья показывает, как записывать и читать данные от устройства, подключенного к последовательному порту (COM-порт) из приложения на языке C# в среде .NET. Мы будем читать и записывать данные через TextBox на форме, и будем работать с потоками. В недалеком прошлом для работы с Serial Port в среде .Net 1.1, мы должны были использовать либо Windows API, либо использовать управление из сторонних библиотек. В среде .Net 2.0 (и в более поздних версиях .NET) компания Microsoft добавила поддержку последовательного порта включением класса SerialPort как части пространства имен System.IO.Ports. Реализация класса SerialPort сделана очень прямо и очевидно. Чтобы создать экземпляр класса SerialPort class, просто передайте опции SerialPort конструктору класса: // Все опции для последовательного устройства // ---- могут быть отправлены через конструктор класса SerialPort // ---- PortName = "COM1", Baud Rate = 19200, Parity = None, // ---- Data Bits = 8, Stop Bits = One, Handshake = None SerialPort _serialPort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One); _serialPort.Handshake = Handshake.None; Для приема данных нам нужно создать обработчик события EventHandler для "SerialDataReceivedEventHandler": // "sp_DataReceived" является вручную созданным методом (подпрограммой) _serialPort.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived); Вы можете также установить другие опции, такие как ReadTimeout и WriteTimeout (таймауты чтения и записи): // milliseconds _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; Как только Вы готовы использовать последовательный порт, Вам нужно открыть его: // Открытие последовательного порта
_serialPort.Open();
Сейчас мы готовы принять данные. Однако чтобы записать эти данные в область ввода TextBox на форме, нам нужно создать так называемого делегата (delegate). Библиотеки .Net не позволяют межпотоковое взаимодействие (cross-thread action), так что нам нужно использовать делегат. Делегат используется для записи в поток пользовательского интерфейса (User Interface, UI) из другого потока (не UI). // Делегат используется для записи в UI control из потока не-UI private delegate void SetTextDeleg(string text); Мы создадим теперь метод "sp_DataReceived", который будет выполнен при поступлении данных в последовательный порт: void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { Thread.Sleep(500); string data = _serialPort.ReadLine(); // Привлечение делегата на потоке UI, и отправка данных, которые // были приняты привлеченным методом. // ---- Метод "si_DataReceived" будет выполнен в потоке UI, // который позволит заполнить текстовое поле TextBox. this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data }); } Теперь создадим наш метод "si_DataReceived": private void si_DataReceived(string data) { textBox1.Text = data.Trim(); } Мы можем теперь принять данные из последовательного порта от устройства и отобразить их на форме. Некоторые устройства отправляют данные сами, без запроса. Однако некоторым устройствам нужно отправить определенные команды, чтобы они ответили на них какими-то своими данными. Для этих устройств Вы будете записывать данные в последовательный порт, и будете использовать предыдущий код, чтобы получить данные обратно. В этом примере будет происходить обмен со шкалой. Для отдельной шкалы отправка команды "SI\r\n" приведет к возврату веса, который имеется на шкале. Эта команда является специфической именно для этого устройства, в Вашем же случае нужно читать документацию по протоколу устройства, чтобы найти команды, принимаемые устройством. Для записи в последовательный порт создайте кнопку "Start" на форме, и добавьте код в событие клика на ней Click_Event: private void btnStart_Click(object sender, EventArgs e) { // Перед попыткой записи убедимся, что порт открыт. try { if(!(_serialPort.IsOpen)) _serialPort.Open(); _serialPort.Write("SI\r\n"); } catch (Exception ex) { MessageBox.Show("Error opening/writing to serial port :: " + ex.Message, "Error!"); } } Это все, что нужно Вам сделать. См. ссылку [1] для загрузки готового проекта Microsoft Visual C# 2010. using System; using System.IO.Ports; using System.Threading; public class PortChat { static bool _continue; static SerialPort _serialPort; public static void Main() { string name; string message; StringComparer stringComparer = StringComparer.OrdinalIgnoreCase; Thread readThread = new Thread(Read); // Создание нового объекта SerialPort с установками по умолчанию. _serialPort = new SerialPort(); // Позволяем пользователю установить подходящие свойства. _serialPort.PortName = SetPortName(_serialPort.PortName); _serialPort.BaudRate = SetPortBaudRate(_serialPort.BaudRate); _serialPort.Parity = SetPortParity(_serialPort.Parity); _serialPort.DataBits = SetPortDataBits(_serialPort.DataBits); _serialPort.StopBits = SetPortStopBits(_serialPort.StopBits); _serialPort.Handshake = SetPortHandshake(_serialPort.Handshake); // Установка таймаутов чтения/записи (read/write timeouts) _serialPort.ReadTimeout = 500; _serialPort.WriteTimeout = 500; _serialPort.Open(); _continue = true; readThread.Start(); Console.Write("Name: "); name = Console.ReadLine(); Console.WriteLine("Type QUIT to exit"); while (_continue) { message = Console.ReadLine(); if (stringComparer.Equals("quit", message)) { _continue = false; } else { _serialPort.WriteLine( String.Format("< {0} >: {1}", name, message) ); } } readThread.Join(); _serialPort.Close(); } public static void Read() { while (_continue) { try { string message = _serialPort.ReadLine(); Console.WriteLine(message); } catch (TimeoutException) { } } } public static string SetPortName(string defaultPortName) { string portName; Console.WriteLine("Available Ports:"); foreach (string s in SerialPort.GetPortNames()) { Console.WriteLine(" {0}", s); } Console.Write("COM port({0}): ", defaultPortName); portName = Console.ReadLine(); if (portName == "") { portName = defaultPortName; } return portName; } public static int SetPortBaudRate(int defaultPortBaudRate) { string baudRate; Console.Write("Baud Rate({0}): ", defaultPortBaudRate); baudRate = Console.ReadLine(); if (baudRate == "") { baudRate = defaultPortBaudRate.ToString(); } return int.Parse(baudRate); } public static Parity SetPortParity(Parity defaultPortParity) { string parity; Console.WriteLine("Available Parity options:"); foreach (string s in Enum.GetNames(typeof(Parity))) { Console.WriteLine(" {0}", s); } Console.Write("Parity({0}):", defaultPortParity.ToString()); parity = Console.ReadLine(); if (parity == "") { parity = defaultPortParity.ToString(); } return (Parity)Enum.Parse(typeof(Parity), parity); } public static int SetPortDataBits(int defaultPortDataBits) { string dataBits; Console.Write("Data Bits({0}): ", defaultPortDataBits); dataBits = Console.ReadLine(); if (dataBits == "") { dataBits = defaultPortDataBits.ToString(); } return int.Parse(dataBits); } public static StopBits SetPortStopBits(StopBits defaultPortStopBits) { string stopBits; Console.WriteLine("Available Stop Bits options:"); foreach (string s in Enum.GetNames(typeof(StopBits))) { Console.WriteLine(" {0}", s); } Console.Write("Stop Bits({0}):", defaultPortStopBits.ToString()); stopBits = Console.ReadLine(); if (stopBits == "") { stopBits = defaultPortStopBits.ToString(); } return (StopBits)Enum.Parse(typeof(StopBits), stopBits); } public static Handshake SetPortHandshake(Handshake defaultPortHandshake) { string handshake; Console.WriteLine("Available Handshake options:"); foreach (string s in Enum.GetNames(typeof(Handshake))) { Console.WriteLine(" {0}", s); } Console.Write("Handshake({0}):", defaultPortHandshake.ToString()); handshake = Console.ReadLine(); if (handshake == "") { handshake = defaultPortHandshake.ToString(); } return (Handshake)Enum.Parse(typeof(Handshake), handshake); } } [Как передавать по одному символу, с задержкой] В случае, когда нужно реализовать обмен с устройством, рассчитанным на взамодействие с пользователем (управляющая консоль). Так как пользователь вводит символы команды медленно, устройство успевает принять все символы и обработать. Если передавать символы быстро (методом SerialPort.Write), по несколько байт, то есть риск потери данных. Во врезке ниже приведен пример класса COMdevice, где реализован метод Write, который передает символы через задержку. [Как перекодировать символы ANSI в UTF8] Очень часть устройства на микроконтроллерах передают русские символы в кодировке ANSI (Windows-1251). Однако среда разработки Visual Studio C# хранит и обрабатывает русскоязычный текст в кодировке UTF8, и при попытке отобразить принятый текст (методом ReadExisting) выводятся кракозябры. Решить проблему можно, если организовать байтовый буфер, и перекодировать массив байт с помощью класса Encoding (методом GetEncoding(1251).GetString). Пример кода в классе COMdevice приведен во врезке ниже. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO.Ports; using System.Threading; namespace MyApp { class COMdevice { private byte[] rxdata; int rxidx; private SerialPort _serialPort; private const int WAIT_ANSWER_TIMEOUT = 500; private log logfile; public string COM = ""; public string errtxt = ""; private void RxReset() { rxidx = 0; rxdata[0] = 0; } //Конструктор public pmini() { _serialPort = new SerialPort(); logfile = new log(this.GetType().ToString()); rxdata = new byte[256]; } private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = (SerialPort)sender; try { while (0 != sp.BytesToRead) { if (rxidx < rxdata.Length - 2) { rxdata[rxidx++] = (byte)sp.ReadByte(); rxdata[rxidx] = 0; } else sp.ReadByte(); } } catch (Exception ex) { logfile.write(ex.Message); errtxt = ex.Message; } } private bool Open(string COM) { bool opened = false; _serialPort.PortName = COM; _serialPort.BaudRate = 115200; _serialPort.Parity = Parity.None; _serialPort.DataBits = 8; _serialPort.StopBits = StopBits.One; _serialPort.Handshake = Handshake.None; _serialPort.Open(); // Создание обработчика события для приема данных: _serialPort.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived); _serialPort.WriteTimeout = 50; opened = true; return opened; } private void Close() { _serialPort.Close(); } private String Received() { return Encoding.GetEncoding(1251).GetString(rxdata); } private bool Write(string senddata) { bool result; int cnt = senddata.Length; int i=0; int tumeoutcnt = 0; errtxt = ""; try { RxReset(); for (i = 0; i < cnt; i++) { char sym = senddata[i]; _serialPort.Write(sym.ToString()); tumeoutcnt = 50; while (!Received().Contains(senddata.Substring(0, i + 1)) && (0 != tumeoutcnt)) { Thread.Sleep(10); tumeoutcnt--; } } } catch (Exception ex) { logfile.write(ex.Message); errtxt = ex.Message; } result = (i == cnt) && (0 != tumeoutcnt); return result; } } } [Ссылки] 1. Проекты Visual Studio на языке C# и Visual Basic, работающие с COM-портом. |