C# com-порт получение информации, обработка старт-бита, стоп-бита?

Добрый день!

Прошу помощи в понимании, а может и в решении интересующего меня вопроса.

Сначала обрисую суть поставленного передо мной задания, предупрежу сразу в программировании я, так сказать, очень молодой и это в какой-то степени мой первый проект, который я хочу реализовать на платформе visual studio.

Задача:
Есть некое устройство, которое соединяется с компьютером по RS-485 и передает данные 38400 бит/сек, устройство может передавать пакеты с дискретностью 25 Гц или 50Гц.

Командно-информационный пакет, передаваемый устройством, состоит из 6 блоков, в начале каждого пакета передается маркер начала пакета "0xFF, 0xFF", затем идут 6 блоков данных в конце пакета передается 2-х байтовое поле .В конце идет бит четности (формируется при выполнении операции "Исключающее ИЛИ "четное число логических единиц в байте должно соответствовать биту чётности 0 ) и 2 стоповых бита.

— Длина одного пакета 52 байта.
— Время паузы между двумя пакетами не более 2-х бит.

Характеристики блока данных:
— Длина блока 8 байт.
— Далее 4 значения по 2 байта

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

Первым делом я кинулся искать как работать с COM-портом, наткнулся на пространство имен:
using System.IO;
using System.IO.Ports;


Ну, и первым делом, как мудрый ВАСЯ, определяю доступные порты в системе и вывожу их в combobox.

private void Form1_Load(object sender, EventArgs e)
        {
            //чтение портов доступных в системе
            string[] ports = SerialPort.GetPortNames();
            //Очистка содержимого бокса
            comboBox1.Items.Clear();
            //Добавление найденных портов в бокс
            comboBox1.Items.AddRange(ports);
        }


Далее, по кнопке старт я запускаю само чтение данных приходящих с устройства. Вот тут для меня начинается дремучий лес.
private void button1_Click(object sender, EventArgs e)
        {
//выбранный порт
            Object selectedItem = comboBox1.SelectedItem;

//Создаю объект port1  с параметрами.
//selectedItem.ToString() - порт к которому подключено устройство.
//9600 скорость передачи данных в порт, как я понял 9600 бод - 38400 бит/сек.
//Parity.None бит четности установил в отсутствие так как пока не сильно понимаю что с ним делать об этом далее.
//416  бит данных т.е. 8*52 = 416.
//один стоп бит
            SerialPort port1 = new SerialPort(selectedItem.ToString(), 9600, Parity.None, 416, StopBits.One);
//Открываю порт.
            port1.Open();
//тут я писал разного рода код для вывода полученных данных в текстовое поле но получал один бред.
        }


Теперь вопрос:
Как я понимаю эту проблему — в порт записываются данные, переданные устройством, а мне их необходимо считать.
Как мне правильно дождаться начала пакета "0xFF,0xFF", чтобы после это принимать оставшуюся часть пакета, или прием стоит начать после получения конца предыдущего пакета?

Если у кого-то есть подобные коды обработки ожидания начала или конца пакетов и нет желания объяснять, просьба скинуть участки кода буду разбираться сам.

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

private void button1_Click(object sender, EventArgs e)
        {
            Object selectedItem = comboBox1.SelectedItem;
            SerialPort port1 = new SerialPort(selectedItem.ToString(), 9600, Parity.None, 416, StopBits.One);
            port1.Open();
//создаю массив размером 52 т.е на запись 52 байт переданного с устройства пакетов
            byte[] data1 = new byte[52];
//Ну и запускаю команду чтения в массив data1
            port.Read(data1, 0, data1.Length);
            int databyte = port.ReadByte();
        }


Но приходит мне какая то ересь...
Правильно ли я понимаю этот процесс чтения из com-порта?

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

Кто знает что почитать конкретно по этому вопросу, где доступно написано или есть подобные решения просьба скинуть почитать что бы лучше разбираться.

Пока как то так. Буду признателен любой помощи, подсказке, куску кода.
  • Вопрос задан
  • 57672 просмотра
Решения вопроса 3
@vitvov
Если я правильно понял ваш вопрос, то ваше устройство шлёт постоянно данные и вам нужно только читать.
Все что вам нужно, это создать подключение к порту и подписаться на событие обновления. При возникновении события вы получите массив данных с порта, этот массив вы сохраняете в буфер или разбираете на лету (как вам удобнее). Напишу для вас маленький пример:
//  Наследуем наш клас от SerialPort для более красивого кода
public class MySerialPort : SerialPort
{
        private const int DataSize = 54;    //  я так и не понял, какой размер данных нужен. Укажите правильное число в байтах
        private readonly byte[] _bufer = new byte[DataSize];
        private int _stepIndex;
        private bool _startRead;

        public MySerialPort(string port)
            : base()
        {
            //  все папаметры вы должны указать в соответствии с вашим устройством
            //base.PortName = COM1;
            base.BaudRate = 115200;
            base.DataBits = 8;
            base.StopBits = StopBits.Two;
            base.Parity = Parity.None;
            base.ReadTimeout = 1000;

            //  тут подписываемся на событие прихода данных в порт
            //  для вашей задачи это должно подойт идеально
            base.DataReceived += SerialPort_DataReceived;
        }

        //  открываем порт передав туда имя
        public void Open(string portName)
        {
            if (base.IsOpen)
            {
                base.Close();
            }
            base.PortName = portName;
            base.Open();
        }

        //  эта функция вызвется каждый раз, когда в порт чтото будет передано от вашего устройства
        void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            var port = (SerialPort)sender;
            try
            {
                //  узнаем сколько байт пришло
                int buferSize = port.BytesToRead;
                for (int i = 0; i < buferSize; ++i)
                {
                    //  читаем по одному байту
                    byte bt = (byte)port.ReadByte();
                    //  если встретили начало кадра (0xFF) - начинаем запись в _bufer
                    if (0xFF == bt)
                    {
                        _stepIndex = 0;
                        _startRead = true;
                        //  раскоментировать если надо сохранять этот байт
                        //_bufer[_stepIndex] = bt;
                        //++_stepIndex;
                    }
                    //  дописываем в буфер все остальное
                    if (_startRead)
                    {
                        _bufer[_stepIndex] = bt;
                        ++_stepIndex;
                    }
                    //  когда буфер наполнлся данными
                    if (_stepIndex == DataSize && _startRead)
                    {
                        //  по идее тут должны быть все ваши данные.

                        //  .. что то делаем ...
                        //  var item = _bufer[7];

                        _startRead = false;
                    }
                }
            }
            catch {}
        }
}

использовать это нужно так:
var port = new MySerialPort();
port.Open("COM5");

Это код прототипа, всего лишь техника работы которая может помочь вам.
Ответ написан
gbg
@gbg Куратор тега Программирование
Любые ответы на любые вопросы
Первое. Отделить мух от котлет. Есть физическая часть протокола - это скорость в бодах, стартовые - стоповые биты и четность. Этим занимается железо порта и на высокий уровень это не пролезает. От программиста требуется задать эти параметры при открытии порта -
new SerialPort(selectedItem.ToString(), 9600, Parity.None, 416, StopBits.One);

9600 - скорость, Parity.None - четность, StopBits.One - количество стоповых бит.
Вот тут все нужно выставить по спецификации.
Контроллер UART сам будет ждать стартовый бит, считать бит четности и так далее. Если честность не сойдется, программа получит ошибку чтения. Служебные биты (старт, стоп, четность) программисту не передаются.

Теперь о шине RS485. Это полудуплексный интерфейс - одновременно можно либо передавать, либо слушать. Если ваша задача состоит только в том, чтобы слушать, у вас почти нет проблем.

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

Увы, на C# я не пишу (и считаю, что общаться со внешними устройствами лучше на C++, дабы приход дворника (сборщика мусора) не обломал весь недо-рилтайм), поэтому мои примеры кода вам просто так не подойдут.

Идея такая - читаем из порта все, что в нем есть и складываем к себе в буфер. Это в идеале можно делать в отдельном потоке.
Затем буфер нужно обработать. Это можно сделать тремя способами - регулярным выражением, конечным автоматом и "в лоб".

Начните с написания программы, которая просто читает поток и выводит его на монитор в hex.
Ответ написан
@Emil2014
Я писал на С#,делал на BackgroungWorker. Грубо вырезанный кусок кода....
private void TransferWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // переменная для снятия из очереди
            Packet pkt;
            // массив для передачи
            byte[] wr_buf = new byte[6];
            // массив для приема
            byte[] rd_buf = new byte[6];
            // цикл до отмены работы или опустошения очереди запросов
            /// TODO теперь не выходит по завершению очереди - уменьшить на переключение контекста задач
            while ((_TransferWorker.CancellationPending == false))// && (_toTransfer.Count != 0))
            {


case Mode_transfer.Read_ADC:// Чтение данных
                            Массив data = pkt.obj as Массив;
                            // читаем АЦП
                            wr_buf[0] = Convert.ToByte('A');
                            wr_buf[1] = (byte)0;
                            // Пишем отправляеммый пакет
                            _Serial.Write(wr_buf, 0, 2);
                            // ждем пока весь пакет уйдет или запросят отбой
                            while ((_TransferWorker.CancellationPending == false) && (_Serial.BytesToWrite != 0)) ;
                            // попытка поймать заголовок
                            try
                            {
                                int dummy = 0, lenghtReadingPacket;
                                bool end = false;
                                int counter =0;
                                while (end != true){
                                    dummy = _Serial.BytesToRead;
                                    if (dummy <= 3)
                                    {
                                        System.Threading.Thread.Sleep(1);
                                        if (counter++ > _Serial.ReadTimeout)
                                        {
                                            _dataLostArgs.ErrorPreambulaLen++;
                                            throw new TimeoutException("Preambula not full!");
                                        }
                                    }
                                    else end = true;
                                }
                                dummy = _Serial.Read(rd_buf, 0, 3);
                                var dummy2 = dummy;
                                if ((dummy != 3))
                                {
                                    _dataLostArgs.ErrorPreambulaLen++;
                                    throw new TimeoutException("Preambula not full!");
                                }
                                if (rd_buf[(int)ADC_Packet.Name] != 'D'){
                                    _dataLostArgs.PreambulaLost ++;   
                                    throw new TimeoutException("Data preambula lost!");
                                }
                                // длина пакета в словах
                                lenghtReadingPacket = rd_buf[(int)ADC_Packet.LenghtOfPacket];
                                byte[] rd_buf_ADC = new byte[lenghtReadingPacket * 2 + 1];
                                end = false;
                                counter = 0;
                                while (end != true){
                                    dummy = _Serial.BytesToRead;
                                    if (dummy < (lenghtReadingPacket * 2 + 1))
                                    {
                                        System.Threading.Thread.Sleep(1);
                                        if (counter++ > _Serial.ReadTimeout + 100)
                                        {
                                            var dummy3 = _Serial.Read(rd_buf_ADC, 0, lenghtReadingPacket * 2 + 1);
                                            _dataLostArgs.PoorPacket++;
                                            throw new TimeoutException();
                                        }
                                    }
                                    else end = true;
                                }

                                dummy = _Serial.Read(rd_buf_ADC, 0, lenghtReadingPacket * 2 + 1);
                                if (dummy == (lenghtReadingPacket * 2 + 1))
                                    for (Int32 i = 0; i < lenghtReadingPacket * 2 ; i += 2)
                                    {
                                        int a = rd_buf_ADC[i];
                                        int b = rd_buf_ADC[i + 1];
                                        if (a != 0 || b != 0) data.Add(rd_buf_ADC[i] * 256 + rd_buf_ADC[i + 1]);
                                    }
                                else {
                                    _dataLostArgs.PoorPacket++;
                                    throw new TimeoutException("Not all bytes received!");
                                }
                            }
                            catch (TimeoutException)
                            {
                                /// TODO ПОдумать при зависании считывания АЦП 
                                OnADCDataLost(this, _dataLostArgs);
                            }
                            finally
                            {
                                // Чистим за собой
                                _Serial.DiscardInBuffer();
                                _Serial.DiscardOutBuffer();
                            }
                            break;

Работало на пределе из-за скорости 510416 Бод на RS485. Код довольно грязный... Но смысл был в том что, первое чтение возвращало не весь пакет. Надо ВСЕГДА проверять возвращаемые значения функций чтения. И что-то делать если пакет приходит не весь и кусками. И с задержкой, ставить timeout Exception что бы не провалится в Read навсегда.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@vilgeforce
Раздолбай и программист
Читать из порта пока не будет маркера начала пакета. После этого читать 52 байта, проверить сумму и обработать. Не прокатит?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы