Форум программистов, компьютерный форум, киберфорум
Arduino
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.93/130: Рейтинг темы: голосов - 130, средняя оценка - 4.93
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103

COM-порт прием данных и парсинг

11.01.2017, 19:49. Показов 24645. Ответов 27
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет.
Прикупил arduino и не могу разобраться с COM портом.
Пытаюсь принять данные отправленные из программы написанной на C#.
Вроде прием идет, но в ответ прилетает полный бред.

Принимаю таким образом.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void loop(){
 
  String InputData = "";
 
  while (Serial.available()) {
 
       byte InputByte = Serial.read();
 
       if(InputByte == 0x0D){
            ParseData(InputData);
            Serial.flush();        
       }
       else{
            InputData += InputByte;
       }
   }
}
Необходимо оправить
C#
1
byte[] bytesToSend = new byte[11] { 0X01, 0X01, 0X05, 0X01, 0X7F, 0X01, 0X01, 0x14, 0x31, 0x00, 0X0D };
Принять ее arduino и разложить по переменным..
Все бы ничего и можно было бы банально каждый следующий байт закидывать сразу в переменную, но количество меняется - может быть как 11 байт, так и 8.
Причем первый байт говорит о том, что будем делать (читать\писать), а последний говорит о конце строки.

Хоть убейте, но долблюсь уже 3 дня, а так и не смог разложить все по полкам.
Помогите разобраться..
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
11.01.2017, 19:49
Ответы с готовыми решениями:

СОМ порт прием данных
Доброй ночи. Есть устройство с RS232 которое отсылает некоторые данные. Пример кода роботы с СОМ портом взял с книги Архангельский...

Прием данных через COM порт
Здравствуйте, уважаемые.. только начинаю осваивать язык, сильно не пинайте.. задача состоит в следующем, нужно принять с МК через...

Прием данных через COM - порт
Привет! Мне нужно реализовать прием данных с МК на компьютер через COM порт. Пишу в VS 2005, С++. Создаю приложение MFC Aplication. ...

27
Модератор
Эксперт по электронике
8978 / 6744 / 921
Регистрация: 14.02.2011
Сообщений: 23,852
11.01.2017, 20:39
Цитата Сообщение от aleksandr_l Посмотреть сообщение
String
это я так понимаю строка
Цитата Сообщение от aleksandr_l Посмотреть сообщение
0X01, 0X01, 0X05, 0X01, 0X7F, 0X01, 0X01, 0x14, 0x31, 0x00,
данные то не строки а 0 это вообще в Си означает конец строки
используй массив unsigned char
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
12.01.2017, 04:52
Уточняющий вопрос. Вы справились с передачей и у вас проблемы с парсингом пакета, или пооблемы с самой передачей данных. Код, который вы привели вполне рабочий. Serial.flush, правда, лишний и обнуления строки перед следующей посылкой нехватает.

П.С. Тип String умеет хранить бинарные нули.

Добавлено через 7 минут
Расскажите подробнее, почему посылки могут быть разной длины, и какова природа передаваеммых данных?
1
Модератор
Эксперт по электронике
8978 / 6744 / 921
Регистрация: 14.02.2011
Сообщений: 23,852
12.01.2017, 05:33
Цитата Сообщение от Mirmik Посмотреть сообщение
Тип String умеет хранить бинарные нули.
тогда это все что угодно, только не строка
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
12.01.2017, 05:52
Ну... Тут я могу разве что заметить, что в таком уважаемом фреймворке, как Qt тип QString является синонимом типа QByteArray, а язык программирования perl вообще не имеет нуль-терминированных строки и спокойно относится к нулям в строках. Так же поступают C#, Java , Lua etc.

А на Arduino очень глубокий отпечаток положила именно Java...

Даже тип std::string, вроде бы (могу ошибаться) может хранить нулевые байты.
1
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
12.01.2017, 07:00  [ТС]
Цитата Сообщение от Mirmik Посмотреть сообщение
Serial.flush, правда, лишний и обнуления строки перед следующей посылкой нехватает
Собственно сразу 2 вопроса появилось:
1. Почему Serial.flush лишний?
Он же очищает содержимое буффера, а это нам необходимо.
2. Я же строку обнуляю в самом начале loop - String InputData = "";
или я что-то не понимаю?

Цитата Сообщение от Mirmik Посмотреть сообщение
Расскажите подробнее, почему посылки могут быть разной длины, и какова природа передаваеммых данных?
Тут все достаточно просто:
В посылке передаются настройки для блока с несколькими реле.
Каждое реле имеет свою логику работы и могут настраиваться по разному - собственно по этой причине и меняется длина посылки.
Посылка примерно выглядит так:
1 байт - что необходимо сделать (читать\писать)
2 байт - С каким реле будем работать
3 байт - Общий для всех реле
4 байт - Общий для всех реле
5 байт - Общий для всех реле
6 байт - Отсутствует у некоторых реле
7 байт - Отсутствует у некоторых реле
8 байт - Общий для всех реле
9 байт - Отсутствует у некоторых реле
10 байт - Общий для всех реле
11 байт - Признак окончания посылки

Как я вообще представляю себе это дело:
1. arduino получает посылку и после сборки строки кидает ее в ParseData(InputData);
2. Парсинг начинается с разбора первого байта и в зависимости от его значения остатки строки передаются в процедуру чтения или записи.
3. Далее, идет разбор оставшейся строки и все принятые данные раскидываются по своим переменным.

Естественно, что для чтения параметров, нам нет необходимости слать лишние данные, а следовательно и пакет будет уже из 3-х байт.
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
12.01.2017, 10:45
C++
1
String InputData = "";
Ага, вижу теперь. Но, к сожалению место расположения данной инструкции неверно. Дело в том, что байты по последовательной линии приходят постепенно. По одному. Так что,
C++
1
while (Serial.available()) {
практически никогда не будет выполнен 10 раз подряд. Вы будете обнулять строку в середине посылки. (на скорости 9600, он стопроцентно будет работать один байт за цикл). Перенесите его сюда:
C++
1
2
3
4
if(InputByte == 0x0D){
            ParseData(InputData);
            String InputData = "";    
       }
Почему Serial.flush лишний?
Данный метод ожидает окончания передачи исходящих данных. Он не имеет отношения к приёму. Буфер чтения же освобождается по мере чтения. Очищать его не нужно.

Что касается парсинга.
Вы должны изменять алгоритм парсинга в зависимости от принимаемых данных.

ЕСЛИ первый байт - ЗАПИСЬ вызываем функцию, отвечающую за парсинг посылки записи.
Извлекаем второй байт. Если у вас разные типы реле, то в вашей программе где-то так или иначе есть таблица, отвечающая за то, какой тип у номера реле такого-то.
Получаем из этой таблицы информацию о типе реле.
Проверяем, соответствует ли длина посылки заявленному типу. Если нет, генерируем ошибку.
Передаем посылку функции, отвечающей за парсинг значений для данного типа реле.

Это концептуально. Разумеется не обязательно использовать награмождение функций, ваша задача вполне решается с использованием if-else. Важно то, что мы используем данные из заголовка посылки, чтобы скоректировать процедуру ее разбора.

...
Тонкий момент. Если вы используете символ 0X0D как разделитель, это значит, что в теле ваших посылкок он встречаться не должен.

Добавлено через 11 минут
Прошу прощения:
C++
1
2
3
4
if(InputByte == 0x0D){
            ParseData(InputData);
            InputData = "";    
       }
Никакого создания нового объекта!

Добавлено через 4 минуты
А еще строка не должна уничтожаться между вызовами loop, так что нужно или использовать глобальный объект, или не выходить из loop:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void loop(){
 
  String InputData = "";
 
  while(1) if (Serial.available()) {
 
       byte InputByte = Serial.read();
 
       if(InputByte == 0x0D){
            ParseData(InputData);
            InputData = "";       
       }
       else{
            InputData += InputByte;
       }
   }
}
0
 Аватар для Сергей 190
365 / 348 / 74
Регистрация: 18.03.2015
Сообщений: 1,293
12.01.2017, 11:31
Как то все сложно. Если задача принять строку и разложить по переменным, не проще ли так:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int KolicSimvolov = 0, Simvol [55];
 
void loop() {
for (int i=1; i<55; i++) Simvol[i]=0; // Если нужно обнулить приемный буфер 
 
// Получение данных
KolicSimvolov=Serial.available();// Сколько получили байт
delay(100);//Нужна эта задержка иначе будет читать НЕ ВСЕ символы в одну строку
if (KolicSimvolov > 0) {
 for (int i=1; i<=KolicSimvolov; i++){      // Читаем все данные из порта
 Simvol [i]=  Serial.read();        // читаем байт
 delay(10);
}
// Данные получены
 
}
Каждый принятый байт в своей ячейке массива «Simvol[i]».
Команда Serial.read(); читает байт и удаляет его из приемного буфера, поэтому, когда будет прочитан весь буфер, в нем ничего не останется и Serial.flush(); нечего будет делать.
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
12.01.2017, 12:19  [ТС]
Цитата Сообщение от Сергей 190 Посмотреть сообщение
Как то все сложно. Если задача принять строку и разложить по переменным, не проще ли так
Если бы всегда было одно количество передаваемых байт, то я бы и не ломал голову, а тут количество байт меняться может и они могут быть адресованы разным переменным
0
 Аватар для Витальич
1280 / 1185 / 175
Регистрация: 02.12.2013
Сообщений: 4,883
12.01.2017, 12:23
Цитата Сообщение от aleksandr_l Посмотреть сообщение
Если бы всегда было одно количество передаваемых байт,
передавайте вначале пакета количество байтов
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
12.01.2017, 15:25  [ТС]
Сейчас посидел, подумал, поломал голову и родил следующее:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
bool CheckCRC(String Data){
  
      char CRC;
      int i = 0;
      int CoutnData = InputData.length()-1;
      // Складываем все байты массива за исключением последнего - он эталонный
      for( i = 0; i < CoutnData; i++){
          CRC = CRC + InputData[i];
      }
      // Если Сумма совпадает с эталонной, то возвращаем true, иначе false
      if(CRC == InputData[CoutnData]){
          return true;
      }
      else{
          return false;
      }
}
 
void ParseInputData(String Data){
    Serial.println("Контрольные суммы совпадают"); 
    /*
    Далее парсим принятую строку
    */
}
 
 
void loop(){
  // Принимаем все что есть в вуффере
  while (Serial.available()) {
       // Считываем по 1 байту
       char InputChar = Serial.read();
       // если наткнулись на байт окончания пакета, то....
       if(InputChar == 0x0D){
            // Проверяеме, а все ли верно пришло
            if (CheckCRC(InputData) == true){
                // Если все верно, то парсим строку
                ParseInputData(InputData);
            }
            else{
                // Если что-то пошло не так при проверке, то сообщаем об этом
                Serial.println("Ошибка контрольной суммы");  
            }
           // После того как обработали все данные, сбрасываем переменную
           InputData = "";
       }
       else{
            // Если нет байта окончания пакета то добавляем принятый байт в массив.
            InputData += InputChar;
       }
   }
}
Добавил в пакет 1 байт, который равен сумме первых 10.
C#
1
byte[] bytesToSend = new byte[12] { 0X01, 0X01, 0X05, 0X01, 0X7F, 0X01, 0X01, 0x14, 0x31, 0x00, 0XCE, 0X0D };
Не уверен конечно что все правильно и красиво, но работает как положено.
Осталось распарсить и разложить по переменным, но дальше наверное проблем быть не должно.

Добавлено через 4 минуты
Цитата Сообщение от Витальич Посмотреть сообщение
передавайте вначале пакета количество байтов
Так мне же потом нужно раскладывать по переменным для каждого реле.
Там получается как:
1. Что хотим сделать
2. К какому реле относится.
3. Куча параметров.

А если мне нужно прочитать данные, то:
1. Что хотим сделать
2. С какого реле нам параметры нужны.

Вот и получается, что в любом варианте нужно парсить строку

Добавлено через 2 часа 56 минут
Вот ума не приложу что за прикол, может кто подскажет...
Если я передаю в процедуру глобальную переменную, то изменяется массив.

Отправляется - 01 01 05 01 7f 01 01 14 31 00 ce 0d
Приходит уже - 01 01 05 01 7f 01 01 14 31 00 fb 0d

А если я ничего в процедуру не передаю, а напрямую обращаюсь к глобальной переменной, то массив не меняется.
Кстати, заметил еще что если в самой процедуре создать переменную и присвоить ей значение глобальной переменной,
то массив сразу же меняется на 01 01 05 01 7f 01 01 14 31 00 fb 0d
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
12.01.2017, 15:39
... Да... Я вас немного обманул. Класс String для копирования использует функцию strcpy. Следовательно он некорректно копируется при работе с бинарными нулями. Что мы и видим.

Так что ValeryS таки прав.

Проше всего пойти в реализацию функции и изменить ей конструктор копирования (Или, если точнее, функцию String & String::copy(const char *cstr, unsigned int length)) (Нафига ей в таком случае явное указание длины?)... Но с образовательной целью, может быть лучше отказаться от класса String и использовать массивы.

Добавлено через 5 минут
Самому себе я этот класс давным давно переделал и благополучно об этом забыл.
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
13.01.2017, 11:24  [ТС]
Цитата Сообщение от Mirmik Посмотреть сообщение
Но с образовательной целью, может быть лучше отказаться от класса String и использовать массивы.
Полезу читать и разбираться

Добавлено через 19 часов 42 минуты
Ну вот что получилось.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Максимальное количество принимаемых байт
const int MaxCountBuff = 256;
// Массив для принимаемых байт
char InputDataBuffer[MaxCountBuff];
 
void setup() {
  Serial.begin(9600);
 
}
 
bool CheckCRC(char InputData[], int Count){
  
  char CRC;
  // Складываем байты массива
  for( int i = 0; i < Count; i++){
    CRC = CRC + InputData[i]; 
  }
  // Если сумма байт равна последнему байту, то true в противном случае false
  if(CRC == InputData[Count]){
    return true;  
  }
  else{
    return false;
  }
}
 
void ParseInputData(char InputData[], int Count){
  // Проверяем контрольную сумму и если все верно, то разбираем посылку
  if(CheckCRC(InputData,Count) == true){
      Serial.println("CRC Ok");
      /*
       * Обрабатываем принятую посылку
      */
  }
  // в противном случае сообщаем об ошибке
  else{
    Serial.println("CRC Error");
  }
}
 
void loop() {
  // Проверяем, а есть ли данные
  if(Serial.available()){
    // Складываем байты в массив и заодно подсчитываем количество
    int InputCount = Serial.readBytesUntil(0x0D,(char*)&InputDataBuffer[0],MaxCountBuff);
    // полученные данные вместе с количеством байт передаем на обработку
    ParseInputData(InputDataBuffer, InputCount - 1);
    // Очищаем массив
    memset(InputDataBuffer, 0, sizeof(InputDataBuffer));
  }
}
Скажите, как более правильно поступить далее, использовать "switch" или "if else" для разбора посылки?
0
Модератор
Эксперт по электронике
8978 / 6744 / 921
Регистрация: 14.02.2011
Сообщений: 23,852
13.01.2017, 12:54
Цитата Сообщение от aleksandr_l Посмотреть сообщение
использовать "switch" или "if else" для разбора посылки?
на низком уровне, на уровне машинного кода, примерно одинакового, а с точки зрения программиста, по моему мнению, свитч удобней
структурирован, в случае чего легко добавить/убрать ветку
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
14.01.2017, 15:37  [ТС]
Я тут подумал, подумал и пришел к следующему:
А что если записывать данные не в переменные, а в EEPROM и потом просто ресетить контроллер или вызывать функцию чтения параметров?
Просто добавить в посылку перед каждым значением адрес в памяти и через "for" раскладывать.
Как думаете, не лучше ли?
Правда количество циклов записи ограничено...
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
14.01.2017, 23:18
С тем же успехом можно и в коде программы без eeprom их записать.
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
15.01.2017, 06:22  [ТС]
Цитата Сообщение от Mirmik Посмотреть сообщение
С тем же успехом можно и в коде программы без eeprom их записать.
Не понял, это как так?

Я то говорил о том, чтобы настройки сразу писать в EEPROM при сохранении.
Логика будет следующей:
1. Настройки пишутся напрямую в память
2. После записи настроек контроллер ресетится программно.

Пока я придерживаюсь правила - сначала в переменные закинуть, а потом уже сохранить в память (кто знает сколько раз нужно будет в память запись производить пока все устроит по настройкам)
0
techpriest
 Аватар для Mirmik
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
15.01.2017, 09:33
А, ну это можно...
0
 Аватар для Сергей 190
365 / 348 / 74
Регистрация: 18.03.2015
Сообщений: 1,293
15.01.2017, 13:20
Цитата Сообщение от aleksandr_l Посмотреть сообщение
кто знает сколько раз нужно будет в память запись производить пока все устроит по настройкам
Вариант записывать не в EEPROM а на SD карту не рассматривали?
0
3 / 3 / 1
Регистрация: 27.02.2014
Сообщений: 103
15.01.2017, 13:32  [ТС]
Цитата Сообщение от Сергей 190 Посмотреть сообщение
Вариант записывать не в EEPROM а на SD карту не рассматривали?
Было бы вариантом, вот только портов свободных не остается, да и по месту в корпусе ограничение есть.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
15.01.2017, 13:32
Помогаю со студенческими работами здесь

Прием и отправка данных на COM-порт
Всем доброго времени суток. Пишу программу для принятия данных по ком-порту. В принципе машина принимает данные и сохраняет их в...

Прием данных через COM порт
Доброго времени суток Всем ! У меня такой вопрос помогите разобраться с приемом данных с COM порта. Есть Arduino запрограммирован как...

Прием данных через LPT-порт
подскажите как это можно выполнять и как написать программу!заранее спассибо

Прием данных через USB порт
кто-нить может подсказать компоненты для работы с USB из Delphi, если точнее нужно считывание передаваемых данных

Передача и приём данных через COM-порт
Добрый день! Передо мной поставили задачу написать программу для работы с адаптером OBD2 Elm327 через виртуальный COM-порт. Пробую пока...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru