Конференция "Начинающим" » Работа с сом портом в асинхронном режиме
 
  • Юрий К (07.02.19 18:44) [0]
    Здравствуйте уважаемые форумчане!
    Я далеко не профи, поэтому прошу совета.

    Я пытаюсь работать с контроллером ( Ардуино ) через сом порт. Вроде все получается ( данные передаются и принимаются обеими сторонами ). Но пока в упрощенном варианте ( функционал нужен более сложный и как реализовать не совсем понятно ).

    В конечном итоге я хочу получить следующий функционал:
    Структура передаваемых/принимаемых данных ( флаг начала сообщения, id получателя, длинна сообщения, данные, контрольная сумма, флаг конца сообщения )
    На одной шине RS-485 планируется одновременная работа нескольких ведомых с мастером ( ПК ).

    ( Речь идет например о том, чтобы включить/выключить электродвигатель или переместить какое-либо исполнительное устройство и пр. )
    Поэтому важно, чтобы мастер был в курсе, что там делает ведомый.

    На каждое сообщение мастера ведомый должен отправить ответ-подтверждение.
    То есть, ПК отправляет команду. Контроллер считывает ее ( если совпадает ID получателя ) и отправляет мастеру подтверждение.

    Возникают следующие возможные комбинации этих событий:
    1. Команда корректно доставлена, исполнена, подтверждение корректно доставлено и получено ( штатный режим )
    2. Команда не дошла до получателя и контроллер ничего не сделал ( сбой передачи )
    3. Команда дошла до получателя, контроллер отправил подтверждение, но оно дошло не корректно ( сбой на приеме )
    4. Команда дошла, но не может быть выполнена по каким либо причинам.

    Когда речь идет о двигателях и пр. механике - потеря связи с мастером чревата тем, что ведомый бесконечно будет гонять двигатель, пока не сожжет его.
    Поэтому я реализовал со стороны контроллера через таймауты ( команды мастер повторяет через 500 мс, если через 1000 мс контроллер не получит команду - двигатель остановится. )

    А вот на стороне ПК я, если честно, не до конца понимаю, как лучше сделать:
    1. Всю работу с контроллером я пытаюсь вывести в отдельный класс ( TObject )
    2. Управление экземпляром класса из основного приложения через методы класса.
    3. Так же через методы и свойства планирую получать информацию о состоянии устройства ( все данные хранить внутри переменных экземпляра класса )

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

    Но есть одно "но" - пока основное приложение обрабатывает полученные данные может наступить следующее событие чтения из порта и данные обработаются не корректно.
    Как поступить?
    Создавать каждый раз новую переменную методом GetMem и передавать основному приложению ссылку? Потом уничтожать ( после обработки ).

    Может быть есть более изящный способ гонять туда-сюда данные. Подскажите.

    Второе. Как быть когда подтверждение от контроллера не пришло?
    Мои размышления:
    1. при отправке команды выставить флаг внутри экземпляра класса, мол ожидание подтверждения.
    2. Одновременно создаем отдельный поток, который будет отслеживать время ожидания.
    3. Как только время таймаута истекло - генерируется событие "нет ответа" или "потеря связи". Отправка команды повторяется.
    4. Если во время ожидания пришло сообщение от контроллера анализируем его и по результатам анализа принимаем решение все нормально или что-то сбойнуло и повторяем отправку команды по новой.

    Вот как-то так... Только я уже начал путаться в реализации сего алгоритма. Получается коряво... Может кто подскажет более изящное решение?
    Заранее спасибо.
  • RWolf © (07.02.19 21:49) [1]

    > способ гонять туда-сюда данные


    Данные кладутся в потокобезопасную очередь, форме посылается сообщение о наличии данных в очереди. Для организации очереди можно использовать TThreadList.
  • ВладОшин © (08.02.19 10:29) [2]
    паттерн "машина состояний"
  • Германн © (09.02.19 02:55) [3]

    > Работа с сом портом в асинхронном режиме
    >
    > Юрий К   (07.02.19 18:44)

    Вот название темы у вас полностью соответствует вашей задаче. А предложенные варианты решения ни к чёрту не годятся. Да и вообще не из той оперы.
    СОМ-порт в IBM PC, с которого и началось семейство тех компьютеров, с которыми мы сейчас работаем (Макинтоши не в счёт, они слишком дороги), изначально предполагался к использованию в асинхронном режиме. Для чего в MS-DOS были назначены соответствующие аппаратные прерывания. MS-DOS канула в лету, но Билл Гейтс и Ко не отказались от асинхронной работы с СОМ-портом. Они создали Event'ы. https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-setcommmask
  • Германн © (09.02.19 03:12) [4]

    > А вот на стороне ПК я, если честно, не до конца понимаю,
    >  как лучше сделать:
    > 1. Всю работу с контроллером я пытаюсь вывести в отдельный
    > класс ( TObject )

    Это неправильно. Обменом данными управляет мастер. Т.е. ПО на компьютере. А ваш класс связанный с контроллером должен только уметь принимать "куски" данных, склеивать их и расшифровывать.
  • Юрий К (09.02.19 11:06) [5]
    to Германн

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

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

    1. Количество экземпляров класса равно количеству контроллеров на шине.
    2. Есть кусок кода, состоящий из глобальных переменных и процедур чтения/записи в порт, который должен принимать от классов данные на запись, и отправлять их в порядке очереди, а так же получать из порта данные, и рассылать всем заинтересованным.
    3. Дальше уже каждый экземпляр решает для него эти данные или нет. И если да, обрабатывает их.
    4. Таймауты ожидания реализовывать внутри каждого экземпляра класса.

    Как-то так...
  • Германн © (10.02.19 02:49) [6]
    to Юрий К

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

    А нафига?
    Я конечно не знаю досконально вашу задачу, но что-то тут мне не нравится.
    Контроллер на основе Ардуина - вполне самодостаточное устройство для выполнения всех тех функций, которые в него заложены. Но вот зачем вам некий класс в программе который будет решать когда связанному с ним контроллеру посылать какие-то запросы?
    Это решать должна основная программа. Она должна знать какие контроллеры включены в систему, что они делают и когда им нужно посылать запросы/команды.
  • Юрий К (10.02.19 15:11) [7]
    Я сейчас пытаюсь реализовать работу ( упрощенно ) по протоколу AISG 2.0 ( Antenna Interface Standards Group ) ( это протокол для управления удаленными устройствами ( РЭТ-моторы регулирующие угол наклона антенн ) в сетях сотовой связи ).

    Зачем мне это надо? Во-первых: При монтаже таких железок неплохо бы убедиться до монтажа, что все они рабочие. т.е. подключил ноут, увидел железяки, все ОК - начал монтаж. Иначе ( заменил РЭТ/кабель и т.п. )
    Это можно сделать и штатным ПО базовой станции, но для этого надо собирать стенд ( системный модуль, удаленный радиомодуль, источник питания 48В ), что в полевых условиях - мало реально и требует много времени ( заливки софта в оборудование ).
    Поэтому хочу обойтись малой кровью - ноут, источник питания, преобразователь интерфейса RS-485 и кабель который в последующем будет подключен к радиомодулю а на этапе теста - к ноуту.

    Во-вторых: таких железок ( списанных с устаревших антенн ) у меня накопилось н-ное количество и хочется их пустить на самоделки. В устройстве есть контроллер ( STM-32 ) драйвер шагового двигателя, сам шаговый двигатель с редуктором и преобразователь интерфейса RS-485. Все это во всепогодном исполнении. То есть используя штатную систему команд протокола AISG 2.0 этими устройствами возможно управлять ( вращать двигатель, получать инфу от датчика тока при перегрузке двигателя и т.п. ).

    Читая даташиты на протокол AISG 2.0 ( https://aisg.org.uk/files/AISG-v2.0.pdf ) в попытках разобраться как он работает,
    натыкаюсь на множество ссылок на более универсальные протоколы.
    Например такой:
    3GPP TS25.463 UTRAN ( https://www.etsi.org/deliver/etsi_ts/125400_125499/125463/06.00.00_60/ts_125463v060000p.pdf )

    Или еще более универсальный HDLS (Higher-levelDataLinkControl):
    http://alice.pnzgu.ru:8080/~dvn/complex/gl7_2.html

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

    И я решил, используя доступную железяку на которую написано масса простого софта ( Ардуино ), и работать с ней доступно практически всем, отработать попытку взаимодействия с ней по протоколу HDLS

    Естественно, пока в упрощенном варианте, постепенно добавляя функционал, пока он не станет полноценным и не будут устранены все баги.

    Задача для новичка, прямо скажу, наверное мало реальная, но как минимум это попытка разобраться как работают железяки, которые приходится монтировать, и понимание принципа их работы, а так же чего им не хватает, поубавит ситуаций, когда сисадмин сети, с не очень прямыми руками, не смог настроить РЭТ, и посылает меня менять/устранять косяк. А эти РЭТы после монтажа часто за 100-200 километров, и "в открытом космосе" т.е. на высоте 50-70 метров и добраться до них можно только методом промышленного альпинизма, вывесившись под площадку обслуживания.
  • Германн © (11.02.19 02:19) [8]

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

    Удачи! Задача не такая уж суперсложная. Главное не усложняйте её реализацию разработкой лишних классов.
  • KSergey © (11.02.19 07:26) [9]
    Я бы таки всё управление сделал в ардуине (условно, в подходящем под задачу по мощности контроллере), в неё бы просто загружал управляющую последовательность, а всю логику управления моторчиками далала бы полностью ардуина, во избежании всех этих сложных "туда-сюда" и "дошло-не дошло".
    И еще. Есть же USB порт, там вроде не сложнее совсем, но при этом он как-то более распространён нынче.
  • Германн © (12.02.19 02:47) [10]

    > KSergey ©   (11.02.19 07:26) [9]
    >
    > Я бы таки всё управление сделал в ардуине

    ТС Ардуин использует только для тестирования своих идей. Реальное железо у него совсем иное.
  • KSergey © (12.02.19 07:45) [11]
    > Германн ©   (12.02.19 02:47) [10]
    > ТС Ардуин использует только для тестирования своих идей.
    >  Реальное железо у него совсем иное.

    Что это меняет?
    Я считаю саму идею всё управление вешать на ПК - провальной в том виде, как это представлено. Ну в смысле чреватой и принципиально не не позволяющей избежать описанных проблем.
  • dshot_600 (12.02.19 19:27) [12]
    Ну в смысле чреватой и принципиально не не позволяющей избежать описанных проблем.

    одруина умеет крутить шаговиками.

    но она не знает куда именно этими шаговиками "надо ехать"

    куда ехать знает человек, но ему нужен интерфейс через который он это расскажет адруине.
    для этого и нужен пульт или комп.
  • KSergey © (13.02.19 09:33) [13]
    > dshot_600   (12.02.19 19:27) [12]

    Ардуина и не должна "знать". Она должна лишь открутить шаговички туда, куда указано загруженными в неё данными.
    Разумеется, подготовить такие данные много проще на более традиционном компьютере.

    Я вот о чем: автор предлагает полностью управлять условными шаговиками непосредственно с компьютера. На мой же взгляд, такой вариант довольно неудачный по надёжности путь, о чем сам же автор и беспокоится, кстати.
    Значит надо просто убрать то самое ненадёжное звено из управления (канал связи) - и щастье.
  • dshot_600 (13.02.19 10:58) [14]
    ну и откуда она сегодня может узнать куда и насколько открутить ?
    из 32 к памяти которую прошили неделю назад?

    автор предлагает полностью управлять условными шаговиками непосредственно с компьютера.

    его ошибка в том, что не надо курить все эти протоколы чтобы крутить шаговиками с ардуины.
    ну а так ты прав.
    пишем код, который минуту крутит клоквайз, минуту каунтерклоквайз и минуту стоит.
    человек с писи здесь не нужен.
  • KSergey © (13.02.19 11:17) [15]
    > dshot_600   (13.02.19 10:58) [14]
    > из 32 к памяти которую прошили неделю назад?

    EEPROM в условной ардуине завсегда есть же.
    Мало - припаять еще не проблема.
  • dshot_600 (13.02.19 13:18) [16]
    его там 1 килобайт в старших моделях 328 и полкило в 168
  • Германн © (14.02.19 02:09) [17]

    > KSergey ©   (12.02.19 07:45) [11]
    >
    > > Германн ©   (12.02.19 02:47) [10]
    > > ТС Ардуин использует только для тестирования своих идей.
    >
    > >  Реальное железо у него совсем иное.
    >
    > Что это меняет?
    > Я считаю саму идею всё управление вешать на ПК - провальной
    > в том виде, как это представлено. Ну в смысле чреватой и
    > принципиально не не позволяющей избежать описанных проблем.
    >
    >

    Вообще-то это меняет всё! Основную свою задачу ТС не озвучил. Не уверен, что основную задачу нужно будет решать через микроконтроллер.
    Например на моём лицевом счету числится разработка нескольких стендов для научно-производственных лабораторий. Делать управление такими стендами только на микроконтроллерах просто глупо и безнадежно.
  • Юрий К (14.02.19 08:34) [18]
    Ну вообще-то, те самоделки, что я задумал напрямую связаны с работой ( монтажом сотовых базух ). Там есть куча простой работы ( например закручивать гайки ) которая выполняется в условиях опасных для жизни ( на весу в безопорном пространстве ).

    Моя задумка такова:
    Несколько независимых друг от друга устройств на контроллерах.
    Одно - аналог грузовой лебедки ( одна ардуина управляет двигателем, вторая - в пульте ДУ ). Лебедкой можно управлять как в ручную ( кнопками ), так и удаленно ( с пульта ), третий вариант через мастера ( при совместной работе лебедки с другими устройствами ).

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

    Все это не сможет работать автономно. Нужен оператор. Оператору нужна связь для канала управления и практически наверняка понадобится канал видео данных ( с вэб камеры с двумя степенями свободы ).

    Реализовать этот на пульте ДУ - мало реально. Или придется поступать как с дронами ( пульт выполняет свою функцию, а видеопоток возвращается на смартфон ).

    Обратная связь нужна для того, чтобы ардуина не зависла на высоте, зафиксированная к металлоконструкциям своими захватами-манипуляторами. Чтобы при потере связи освобождала все рабочие органы и свободно повисала и ее возможно было бы снять, спустив лебедкой ( для устранения косяка ).

    А для лебедки без обратной связи вообще нельзя. Груз может зацепиться за металлоконструкции и т.п. Те. лебедка включает двигатель только тогда, когда есть четкая команда на подъем/спуск. И гарантированно не должна срабатывать от помех или еще чего.

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

    Гораздо проще сделать дистанционно управляемый манипулятор.

    Хотя если подумать. В последующем все операции повторяются циклично ( на разной высоте ). Например крепление кабелей к кабельному мосту. Вполне возможно минимальным набором датчиков реализовать алгоритм автоматического выполнения данных операций.

    Покупка реального промышленного сборочного робота не обсуждается. Это убъет всю рентабельность проекта на многие годы. Там цифры с шестью нулями, но вполне понятно каков примерный вес манипулятора получится для манипуляции деталями весом, например 2-3 кг. Цифры реальные. Это можно сделать вполне компактно и не таскать наверх многосоткилограмогого монстра.

    Вообщем так. Я пока планирую научить ардуинку работать с ноутом по интерфейсу RS-485. Потому как все-таки планирую управлять всей этой хренью по кабелю ( надежнее ) + можно подать удаленное питание и не таскать наверх АКБ большой емкости. Например витая пара. Метраж - допустим для такого интерфейса. Скорости позволяют гонять и видео поток. Но надо управлять несколькими устройствами как минимум: камера, лебедка, манипулятор...

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

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

    Скорее всего вытянуть такой проект в одиночку - не реально. Поэтому пока ставлю себе задачи по проще. Разобраться как работает. Научиться управлять шаговиками. Дальше будет видно. Уже на этом этапе будет вполне понятно - хватает мозгов или нет.
  • dshot_60 (14.02.19 08:48) [19]
    Я пока планирую научить ардуинку работать с ноутом по интерфейсу RS-485.

    это примерно то же самое что учиться ходить по улице с закрытыми глазами, с ультразвуковым излучателем, и наушниках, подключенных к усилителю, который уз превращает в слышимый звук.
  • Юрий К (14.02.19 10:22) [20]
    Вообщем так. Следуя принципу "лучшее - враг хорошего" я пока реализовал работу с портом так ( фактически копируя пример гуру вашего форума ( по моему Rouse )

    В проекте создан отдельный юнит для порта в нем несколько глобальных переменных и процедур и функций:

    var
       PortMode   : TConnectMode = cmDisconnect; //состояние порта
       FormHandle : THandle;                                // Хенндл формы основного приложения, которой будут отправляться сообщения при чтении из порта данных
       CommHandle : integer;                               // Хендл порта
       DCB        : TDCB;                
       Ovr        : TOverlapped;
       Stat       : TComStat;
       CommThread : THandle;                             // Хендл потока чтения
       hEvent     : THandle;
       TransMask  : DWORD;
       Errs       : DWORD;
       Kols       : DWORD;
       RxBuf      : array[0..4096] of byte;             // Входной буфер данных, заведомо большего размера, чем максимальная длинна входящих сообщений
       Rx_i       : integer = 0;                              // Счетчик ( куда записываем входящие данные )
       Rx_i_start : integer = -1;                         // позиция с которой начинается сообщение, о приходе которого информируем основную форму приложения



    Есть правило, которое я запомнил, пытаясь баловаться программированием: "нельзя вынуждать пользователя выполнять какие-то действия вынужденно.
    Ниже функция - опрашивающая доступные в системе порты. Результат возвращает в виде набора строк с именами доступных портов.
    Дабы не плодить экземпляры класса типа TStrings в основном приложении создаю внутри процедуры свой TStringList и пишу данные в него. параметр Items - может быть любым свойством визуального компонента основного приложения, где будут отображаться доступные порты. В моем случае это ComboBox.Items
    Не знаю, корректно ли реализовал или нет. Покритикуйте.

    function  FindPorts( Items : TStrings ) : boolean;
     var i : byte;
         h : THandle;
         List : TStringList;
    begin
      Result := false;
      List := TStringList.Create;
      for i := 1 to 255 do
      begin
        h := CreateFile(PChar('COM'+inttostr(i)),GENERIC_READ or GENERIC_WRITE,0,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,0);
        if h <> INVALID_HANDLE_VALUE then
        begin
          List.Add('COM'+inttostr(i));
          Result := true;
        end;
        CloseHandle(h);
      end;
      if Assigned(Items) then Items.Assign(List);
      List.Free;
    end;



    Процедура открытия порта.
    Процедуре передается два параметра: имя порта и хендл основной формы приложения, которая будет принимать и обрабатывать сообщения от потока чтения данных. Процедуре чтения данных назначаем режим RXFLAG ( т.е. по приходу определенного символа во входящий  буфер ) в моем случае это FRAME_FLAG = $E7. Но этот флаг выставляется в начале и в конце сообщения, поэтому надо уметь различать где начало, а где конец.
    procedure PortInit(Port:String; hHandle : THandle);
       var  ThreadID:dword;
     begin
       CommHandle := CreateFile(PChar(Port),GENERIC_READ or GENERIC_WRITE,0,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,0);
       if CommHandle = INVALID_HANDLE_VALUE then begin CloseHandle(CommHandle); Exit; end;
       SetCommMask(CommHandle,EV_RXFLAG);
       GetCommState(CommHandle,DCB);
       DCB.BaudRate:=CBR_9600;
       DCB.Parity:=NOPARITY;
       DCB.ByteSize:=8;
       DCB.StopBits:=OneStopBit;
       DCB.EvtChar:= chr(FRAME_FLAG);
       SetCommState(CommHandle,DCB);
       CommThread := CreateThread(nil,0,@ReadComm,nil,0,ThreadID);
       FormHandle := hHandle;
       PortMode := cmConnect;
     end;



    Ко всему прочему: я пришел к выводу, что сообщение не всегда приходит полностью за один проход цикла. Поэтому событие, что получено сообщение от контроллера генерируется при соблюдении нескольких условий ( см. код )
    Процедура чтения данных:
    Чтобы не тормозить поток чтения данных из порта ( на время обработки данных ). Передаю ссылки на считанные данные параметрами wParam, lParam процедуры PostMessage, которая возвращает управление потоку не дожидаясь выполнения. ( и вот тут и пригодилась ссылка на хендл формы основного приложения )
    Я рассматривал разные варианты... например PostThreadMessage или ThreadList но мне сложно было это реализовать. Я решил, что пока меня устроит приведенный ниже алгоритм. Главное - пока для меня сделать, чтобы не глючило. Насколько профессионально написано - вопрос второй и для меня сейчас он менее важен. Возможно мое мнение ошибочно.

    procedure ReadComm;
      var Resive : array[0..255] of byte;
          i : byte;
    begin
      while true do
      begin
        TransMask:=0;
        WaitCommEvent(CommHandle,TransMask,@Ovr);
        if (TransMask and EV_RXFLAG)=EV_RXFLAG then
        begin
           ClearCommError(CommHandle,Errs,@Stat);
           Kols := Stat.cbInQue;
           ReadFile(CommHandle,Resive,Kols,Kols,@Ovr);
           for i := 0 to Kols - 1 do
           begin
             RxBuf[Rx_i] := Resive[i];
             if ( Resive[i] = FRAME_FLAG ) then
             begin
               if Rx_i_start < 0 then Rx_i_start := Rx_i
               else begin
                 PostMessage(FormHandle,WM_PORT_READ,Rx_i_start,Rx_i);
                 Rx_i_start := -1;
               end;
             end;
             inc(Rx_i);
             if Rx_i > 4096 then Rx_i := 0;
           end;
           PurgeComm(CommHandle, PURGE_RXCLEAR);
        end;
      end;
    end;



    Процедура записи в порт проста

     procedure WriteComm(Buf : array of byte);
      var KolByte : DWORD;
    begin
      KolByte:=length(Buf);
      WriteFile(CommHandle,Buf,KolByte,KolByte,@Ovr);
    end;



    Процедура остановки потока и закрытия порта тоже

    procedure KillComm;
    begin
     TerminateThread(CommThread,0);
     CloseHandle(CommHandle);
     PortMode := cmDisconnect;
    end;



    Покритикуйте плз.
  • Юрий К (14.02.19 10:22) [21]
    Дальше в основном приложении объявляем процедуру, открытия порта по нажатию кнопки

    procedure TForm1.Button1Click(Sender: TObject);
     var data : array of byte;
    begin
     if ComboBOx1.Text = '' then exit;
     PortInit(Combobox1.Text,Form1.Handle);
    end;



    И процедуру обработки входящих данных в которой:
    а) копируем сообщение из глобального RxBuf в локально объявленный буфер, парсим его, проверяя корректность пришедших данных, и
    фактически задача данной процедуры перемаршрутизировать сообщение "заинтересованному лицу", (процедуре/функции/классу) которое работает с контроллером, приславшим сообщение.

    procedure CommRead ( var msg : TMessage ); message WM_PORT_READ;
    ...
    procedure TForm1.CommRead(var msg: TMessage);
     var TxMsg : TArduinoMsg;
    begin
     if msg.LParam - msg.WParam <= 0 then exit;
     TxMsg := ParseMsg(msg.wParam, msg.lParam);
     Case TxMsg.Error of
       meNoError :;        //Тут обрабатываем сообщение, пришедшее без ошибок
       meErrorLength:;     //Тут обрабатываем сообщение, длинна которого не соответствует заявленной
       meErrorCRC :;       //Тут обрабатываем сообщение у которого контрольная сумма не совпадает с вычисленной
       meErrorFlag:;       //Тут обрабатываем сообщение в котором где-то потерялись флаги начала и конца сообщения
       meErrorMsg :;       //Тут обрабатываем сообщение, которое не задекларировано в списке констант
       meErrorSid :;       //Тут обрабатываем сообщение у которого ошибочный ID мастера.
     end;
    end;



    Покритекуйте плз.
  • RWolf © (14.02.19 11:37) [22]
    Ошибочных кейсов как-то многовато. Я бы оставил просто признак «корректный/некорректный пакет», под который попадали бы все перечисленные нарушения структуры пакета.

    > if msg.LParam - msg.WParam <= 0 then exit;

    Как только Rx_i перейдёт через 4096, это условие остановит обмен.
  • Юрий К (14.02.19 16:18) [23]
    Да. я заметил, что при переполнении значения Rx_i - будет ошибка.

    Ниже код функции ParseMsg в которой это учтено + попытка посчитать "на ходу" crc16.

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

    В реальных железяках, занимающихся передачей данных, времени рассчитывать crc тупо нет. Железяки молотят на скоростях до 10-100 Гб/с и никаких процессоров обсчитать контрольную сумму не хватит. Следовательно сrc вычисляется аппаратно = регистры сдвига + логика И, НЕ, Искл. ИЛИ. На той скорости, с какой работает девайс на передачу.

    Следовательно алгоритм расчета crc заточен под аппаратную реализацию.
    В языках высокого уровня реализовать что-то подобное, мне кажется, не получится. Компилятор преобразует код цикла расчета crc в приблизительно в такой алгоритм:
    1. загрузить в регистр а число №1
    2. загрузить в регистр в число №2
    3. произвести операцию ( например xor или сдвиг влево/вправо ).
    4. Записать результат в ячейку памяти числа №1
    5. Перейти к п. 1
    В результате куча тактов расходуется на перезапись значений из регистров в переменные и обратно.

    Но что-то подобное, мне кажется, должен уметь делать ассамблер потому как он работает напрямую с регистрами процессора. К сожалению ассамблер для меня - темный лес.

    Поэтому пока так. Сейчас буду тестить вычисление crc сравнивая с онлайн ресурсами... будет ли давать правильный результат. Потом буду думать о скорости вычислений.

       function  ParseMsg ( a,b : integer ) : TArduinoMsg;
         var i,j,len : integer;
             crc16 : word;
             crc1,crc2 : byte;
       begin
         if a < b then len := b - a else len := 4096 - a + b;
         if len >= 8 then SetLength(Result.data,len - 8) else
         begin
           Result.Error := meErrorLength;
           Exit;
         end;
         i := a + 1;
         j := 1;
         crc16 := $FFFF;
         while i <> b - 3 do
         begin
           case j of
             1: Result.sid := RxBuf[i];
             2: Result.rid := RxBuf[i];
             3: Result.len := RxBuf[i];
             4: Result.msg := RxBuf[i];
             else Result.data[j+5] := RxBuf[i];
           end;
           inc(j);
           inc(i);
           if i > 4096 then i := 0;
           crc16 := Calccrc16( RxBuf[i],crc16 );
         end;
         case b of
           0: begin crc1 := RxBuf[4096]; crc2 := RxBuf[0];   end;
           1: begin crc1 := RxBuf[0];    crc2 := RxBuf[1];   end;
         else begin crc1 := RxBuf[b-2];  crc2 := RxBuf[b-1]; end;
         end;
         Result.Error := meNoError;
         if Result.sid <> SID then Result.Error := meErrorSid;
         if ( crc1 <> Lo(crc16)) OR ( crc2 <> Hi(crc16)) then Result.Error := meErrorCrc;
         if ( RxBuf[a] <> FRAME_FLAG ) OR ( RxBuf[b] <> FRAME_FLAG ) then Result.Error := meErrorFlag;
         if Result.len <> len then Result.Error := meErrorLength;
       end;



    Функция вычисления crc для одного байта

    Function CalcCRC16( B : byte; CRC16 : Word) : Word;
     Var a : Word;
    Begin
     crc16 := crc16 xor B;
     a := ( crc16 xor ( crc16 shr 4)) and $00FF;
     Result := ( crc16 shr 8 ) xor ( a shl 8 ) xor ( a shl 3 ) xor ( a shr 4 );
    End;

  • RWolf © (15.02.19 11:35) [24]
    Нет смысла думать о производительности CRC на компьютере. Даже ардуина считает его достаточно быстро, чтобы это не мешало обмену — не те скорости.
  • Германн © (16.02.19 03:00) [25]

    > Юрий К   (14.02.19 10:22) [20]
    >
    > Вообщем так. Следуя принципу "лучшее - враг хорошего" я
    > пока реализовал работу с портом так ( фактически копируя
    > пример гуру вашего форума ( по моему Rouse )

    Во-первых.
    Не надо упоминать имя гуру, если вы не можете дать ссылку на его пример!
    Во-вторых я уже не понимаю ваши вопросы.
 
Конференция "Начинающим" » Работа с сом портом в асинхронном режиме
Есть новые Нет новых   [118670   +57][b:0][p:0.005]