Ошибка crc rs485

Здравствуйте,

c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Здравствуйте,

имеется:
— MegaD-2561-31I15O-RTC
— MegaD-16I-XT
— Адаптер RS485-TTL-Auto
— теплосчетчик Danfoss Sonometer 500 с M-Bus (кабель с 2 проводами)

пытаюсь получить со счетчика показания, но проблема в том, что я не могу найти описание протокола M-Bus для него. в инструкции лишь сказано:

Теплосчетчик Sonometer 500 поставляется со встроенным M-bus модулем, предназначенным для подключения к распределенным сетям автоматизированного сбора данных. Передача данных осуществляется по кабелю типа медная витая пара с автоматически устанавливаемой
скоростью передачи 300 или 2400 бод.

и

Interfaces (already built-in)
— Optical: ZVEI interface as standard, for communication and testing, M-Bus protocol.
— M-Bus: Configurable telegram (via IZAR@SET software), according to EN13757. Data reading and parameterization are via two wires with polarity reversal protection.

пытался делать запросы типа

Код: Выделить всё

http://192.168.1.111/sec/?uart_tx=010300000020&mode=rs485 # получить 32 регистра с 0 адреса

но все время получаю ошибку

в чем проблема? можно ли как-то перебрать все возможные регистры/адреса? какие параметры можно менять и в каких интервалах, а какие не надо? всегда ли функциональный код должен быть 03? и SlaveId 01 (если у меня всего одно устройство)?

PS: + еще такой момент, у меня плата адаптера с обратной стороны выглядит примерно так:
Изображение
как видно слева помимо А+ и В- есть еще третий контакт с иероглифами, позже выяснилось что это земля. сначала я его вообще не подключал (были ошибки CRC Error), потом подключил к тому же контакту, что и GND (и опять все те же ошибки CRC Error).




d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 06 июн 2022, 00:56

Petros писал(а): ↑

06 июн 2022, 00:36


A m-bus и modbus разве одно и то же?

Нет, не одно и то же.
Сразу не обратил внимание на фото адаптера. Адаптер RS485 не подойдёт. Нужен примерно вот такой адаптер:

TTL-MBUS-Master-Slave-UART-MBUS.jpg_640x640.jpg
TTL-MBUS-Master-Slave-UART-MBUS.jpg_640x640.jpg (22.15 КБ) 1045 просмотров


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 06 июн 2022, 11:47

Petros писал(а): ↑

06 июн 2022, 00:36


A m-bus и modbus разве одно и то же?

что посоветовали в теме https://www.ab-log.ru/forum/viewtopic.php?t=1909 — то и заказал :?

d.v.ermakov писал(а): ↑

06 июн 2022, 00:56


Сразу не обратил внимание на фото адаптера. Адаптер RS485 не подойдёт. Нужен примерно вот такой адаптер:
TTL-MBUS-Master-Slave-UART-MBUS.jpg_640x640.jpg

искать на алиэкспрессе? и есть ли какая-нибудь инфа, как подключать его к меге и работать с ним?

Адаптер RS485 не подойдёт

речь о том что этот адаптер не подойдет? или о том, что другой протокол используется? просто в настройках uart меги есть только RS485…




c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 14 июн 2022, 14:30

заказал вот это https://aliexpress.ru/item/33009939864.html (slave). нужен же именно slave, не master, верно?
так же раздобыл описание протокола для своего счетчика: https://disk.yandex.ru/i/Z1GXEGAx7FMOiw (на форуме, на который Вы дали ссылку, спасибо!)

d.v.ermakov писал(а): ↑

06 июн 2022, 22:17


RS485 и M-Bus имеют немного разную физическую часть. С точки зрения Меги они одинаковые (и то и другое — UART).
В предыдущую тему не вникал. Остальным простительно, M-Bus — редкость, и очень созвучен с MODBUS.

еще раз хочу повторить свой вопрос:
я к тому, что в меге в конфиге все равно надо будет указывать UART=RS485?
но для запросов надо будет использовать mode=raw вместо mode=rs485? https://ab-log.ru/smart-house/ethernet/megad-rs485


Andrey_B

Администратор
Сообщения: 5077
Зарегистрирован: 18 мар 2011, 12:06

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

Andrey_B » 14 июн 2022, 15:05

В настройках UART выбирать RS485.
mode=rs485 отличается от режима raw только автоматическим подсчетом/проверкой CRC (CRC16 Modbus). Больше ничем.
В режиме raw нужно самостоятельно передавать CRC в том формате, в котором это предусматривает конкретный протокол.


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 08 июл 2022, 17:11

d.v.ermakov писал(а): ↑

06 июн 2022, 22:17


Например, https://aliexpress.ru/item/33029994226.html
RS485 и M-Bus имеют немного разную физическую часть. С точки зрения Меги они одинаковые (и то и другое — UART).
В предыдущую тему не вникал. Остальным простительно, M-Bus — редкость, и очень созвучен с MODBUS.

пришла это плата, пытаюсь выбрать свой счетчик согласно протоколу mbus, но ответ всегда пустой.

решил спросить у продавца, правильную ли плату я заказал. https://aliexpress.ru/item/33009939864.html там их 2: TTL to MBUS Master и TTL to MBUS Slave, я заказывал вторую. вот что он ответил:

hi . you buy our item is Slave your meter is Slave . you need buy Master mbus only the master can communicate with the table

видимо все-таки неправильную..

если заказывать другую плату, вот что там сказано по поводу подключения:

1, VIN: power input port 5V-15V, the absolute maximum voltage can not exceed 18V, or damage the module
2, GND: systematically
3,RXD: external serial port (external device) RXD, high level
4, TXD: connect to the external serial port (external device) TXD, need external high
5,OVERLOAD: Short circuit indication output, the default high, short-circuit to low, can be left open
6, TTLVCC: TTL level voltage input, can not be left floating, you must input TTL level voltage
7, M: MBUS bus interface, the initial high level bus 30 ± 1V(customizable)

Note:
1,TTLVCC input 3.3V, TTL signal is 3.3V;
TTLVCC input 5V, the TTL signal is 5V; input other TTL for the input level signal.
2, TTLVCC input voltage can not exceed VIN input voltage.
Other parameters:
Input voltage VIN:Power input port 5V-15V, the absolute maximum voltage can not exceed 18V

правильно ли я понимаю, что VIN подключаем к 5 — 12В, GND — земля, RXD/TXD к Р32/33, OVERLOAD (?) можно не подключать, TTLVCC к 3.3 — 5В?


d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 09 июл 2022, 21:22

Вы правы, нужна плата master, она питает шину M-Bus. Извините, что дал вам неверную ссылку.
Подключения вы описали верно.


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 18 авг 2022, 23:02

спустя полтора месяца наконец пришла плата master, но немного поврежденная:
Изображение
продавец сказал, что это не должно повлиять на работоспособность. подключил к меге, пытаюсь послать запрос, и в ответ опять ничего не получаю… можно ли как-то определить, получила ли мега ответ, но пустой, или ничего не получала?


d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 21 авг 2022, 21:43

c1tru55 писал(а): ↑

18 авг 2022, 23:02


подключил к меге, пытаюсь послать запрос, и в ответ опять ничего не получаю… можно ли как-то определить, получила ли мега ответ, но пустой, или ничего не получала?

Сколотый дроссель может быть причиной неработоспособности одного из источников питания на плате.
1) Нужно проверить все питания, как на плате, так и на шине.
2) Если хотите советов — давайте фото подключений.
3) Точнее всего на ваш вопрос можно ответить, посмотрев осциллографом. Если его нет — хотя бы быстрым мультиметром посмотреть обмен.


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 22 авг 2022, 23:16

d.v.ermakov писал(а): ↑

21 авг 2022, 21:43


2) Если хотите советов — давайте фото подключений.

подключал все так же, как и обсуждали:

c1tru55 писал(а): ↑

08 июл 2022, 17:11


правильно ли я понимаю, что VIN подключаем к 5 — 12В, GND — земля, RXD/TXD к Р32/33, OVERLOAD (?) можно не подключать, TTLVCC к 3.3 — 5В?

VIN/TTLVCC подключал к 5V, OVERLOAD не подключал.

d.v.ermakov писал(а): ↑

21 авг 2022, 21:43


3) Точнее всего на ваш вопрос можно ответить, посмотрев осциллографом. Если его нет — хотя бы быстрым мультиметром посмотреть обмен.

осциллографа нет, да и если бы был толку нет. мультиметром подключался к выходам платы M+ M-, почему-то напряжение в состоянии покоя (т.е. никаких запросов мега не шлет) сказало до 26V при входных 5V, не знаю норма это или нет. от греха подальше отключил от счетчика. убедиться бы как-нибудь, что со счетчика реально снять показания через M-Bus, а то сначала slave платой подключался, потом возможно поврежденной master платой, мб спалил там уже встроенный интерфейс :?


d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 23 авг 2022, 22:33

c1tru55 писал(а): ↑

22 авг 2022, 23:16


мультиметром подключался к выходам платы M+ M-, почему-то напряжение в состоянии покоя (т.е. никаких запросов мега не шлет) сказало до 26V при входных 5V, не знаю норма это или нет. от греха подальше отключил от счетчика.

26 вольт — это нормально: https://ru.wikipedia.org/wiki/Meter-Bus


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 25 авг 2022, 20:52

d.v.ermakov писал(а): ↑

21 авг 2022, 21:43


Сколотый дроссель может быть причиной неработоспособности одного из источников питания на плате.
1) Нужно проверить все питания, как на плате, так и на шине.
3) Точнее всего на ваш вопрос можно ответить, посмотрев осциллографом. Если его нет — хотя бы быстрым мультиметром посмотреть обмен.

между выходами M+ и M- 26.9V. на другом конце витой пары, которая подходит к счетчику тоже 26.9V. когда я посылаю запрос через Мегу — напряжение на мультиметре немного меняется.

также продавец переслал ответ поставщика:

Supplier Reply
First, confirm whether the interface connection is correct. There are signs on the board, especially txd—txd means txd is connected to txd; rxd—rxd is rxd connected to rxd, and cannot be crossed

In addition, ttl vcc must be connected. It should be connected according to the picture. Make sure that all the interfaces are correct. Measure whether the voltage of m+ and m- is about 28v. If so, let the customer confirm whether the two lines from the module to the meter are connected. . Check the software agreement if everything is normal
Such as communication baud rate, verification, data correctness, etc.

If the customer insists that the above questions are correct. You have to use tools to troubleshoot the problem. Use an oscilloscope to connect m+ and m-. Then send data. See if there is a waveform.

In addition, the customer is soldering the socket, so make sure the screws are tightened. If the power supply line is long, the external capacitor should be connected with a capacitor of 10uf or more. Make sure that the power supply ripple is relatively small.

в общем ничего нового, единственное речь про 28V, когда у меня 26.9V. ну и про конденсатор про большой линии питания, но линия питания от Меги у меня 15 сантиметров, а витая пара до счетчика ~ метров 8.

Согласно документации я посылаю запрос

(для selection secondary address с любыми serial number/manufacturer code/identification code/medium code), в ответ ожидаю получить

от счетчика, но ответа нет. Т.е. в одной вкладке открываю урл

Код: Выделить всё

http://192.168.1.111/sec/?uart_tx=680B0B6853FD52FFFFFFFFFFFFFFFF9A16

и после этого в другой открываю

но там всегда пусто. Не знаю, что еще сделать…


d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 05 сен 2022, 17:38

26.9 или 28, думаю, не очень важно. То, что мультиметр что-то показывает — значит запрос, скорее всего, идёт. Попробуйте поменять местами M+ и M-.
Где у вас проблема, понять сложно. Я это называю приёмом родов по телефону. Поверьте, фото многое могут решить. Либо берите осциллограф и смотрите сами.


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 07 сен 2022, 18:11

не знаю почему, но сейчас напряжение между контактами М+ и М- у меня 1.0 — 4.6В. что-то явно не так, хотя между VIN/TTLVCC и GND на входе 4.93В. похоже плата окончательно сдохла.
фото подключения:
Изображение


d.v.ermakov

Сообщения: 1997
Зарегистрирован: 29 май 2015, 21:23
Откуда: Екатеринбург, Нижний Тагил

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

d.v.ermakov » 12 сен 2022, 14:03

По фото ваших подключений видно, что есть проблема. VIN должно быть от 5 до 15 вольт (я бы подключил 12). TTLVCC нужно подключать к 3,3 вольтам, это уровень сигналов микроконтроллера Меги. Поэтому, вероятно, обмен и не шёл.


c1tru55

Сообщения: 55
Зарегистрирован: 09 янв 2022, 14:59

Re: Постоянные ошибки «CRC Error» при попытке считать данные через M-Bus с теплосчетчика Danfoss Sonometer 500

Сообщение

c1tru55 » 04 янв 2023, 19:37

заказал новую плату, подключил все как вы сказали, на выходах M+ и M- напряжение ~28 В. но все равно результата нет. что не посылай — ответ пустой, хотя должен быть не пустой (E5).
или я что-то не понимаю в протоколе MBus, либо у меня счетчик не может с ним работать. уже не знаю, что еще придумать…

PS:
1. как долго в буфере хранится ответ по ?uart_rx=1? и как быстро его можно получать? возможно ли, что я просто не попадаю во временной интервал для его получения?
2. какие настройки должны быть у портов P32 и P33?
3. в статье «MegaD-2561 в качестве шлюза RS-485/Modbus RTU — Ethernet» сказано
RO (Output) — P32 (RX)
DI (Input) — P33 (TX)
т.е. output соединяем с receive, input с transfer (!). правильно ли я соединяю
RXD — P32 (RX)
TXD — P33 (TX)
?


Вася

unread,

Sep 2, 2014, 6:27:14 PM9/2/14

to VladR…@googlegroups.com

Универсальный конфигуратор счётчиков Меркурий 

v.1.7.50 

Чтение профиля или энергии ошибка:

Чтение энергии за месяц: не совпала сумма CRC !

Если где-то решение проблемы описано — дайте ссылку.

Читал описание протокола:

Описание протокола взаимодействия со счётчиком Меркурий 236 версия ПО 8.0.0 M234 версия ПО 9.0.0 v. 131211.pdf

Счетчик должен возвращать по 4 байта на каждый канал, всего 16 байт

, а мой возвращает 12.

Наверное потому что учитывает в одном направлении. 

дамп обмена

<•• 65 01 02 02 02 02 02 02 02 27 55

—> 65 00 2A E0

<•• 65 08 12 E6 12

—> 65 60 E0 C1 97 06 00 29 C0

<•• 65 05 31 00 1B 79

—> 65 00 00 00 00 FF FF 00 00 00 00 FF FF 5E AE

<•• 65 05 31 00 1B 79

—> 65 00 00 00 00 FF FF 00 00 00 00 FF FF 5E AE

<•• 65 05 31 00 1B 79

—> 65 00 00 00 00 FF FF 00 00 00 00 FF FF 5E AE

Серийный номер 02561101 

Дата изготовления 18.07.08 

Версия ПО 02.02.84 

Сетевой адрес 101 

Класс энергии A+ 0.5 

Класс энергии R+ 1.0 

Номинальное напряжение 57.7 В 

Номинальный ток 5 A 

Число направлений 1 

Температурный диапазон -40 гр. Цельсия 

Учет профиля сред. мощностей есть 

Число фаз 3 

Постоянная счетчика 5000 Имп/кВтч 

Суммирование фаз по модулю 

Тарификатор внутренний 

Тип счетчика AR (активная и реактивная) 

Вариант исполнения 1 

Объем энергонезавис. памяти 131×8 

Встроенный модем PLM нет 

Встроенный модем GSM нет 

Оптопорт есть 

Тип интерфейса RS485 

Внешнее питание есть 

Элект. пломба верх. крышки есть 

Встроен. реле отключ. нагруз. нет 

Подсветка ЖКИ нет 

Потариф. учет макс. мощности нет 

Элект. пломба защит. крышки нет 

Интерфейс2 нет 

Встроен. питания интерфейса1 есть 

Контроль ПКЭ есть 

Пофазный учет энергии A+ нет 

Встроенный модем PLC-2 нет 

Профиль2 нет 

Элект. пломба модульного отсека нет 

Перекл. тарифов внеш. напряжением нет 

Коэф. трансформации по напряжению 1 

Коэф. трансформации по току 1 

Vlad Rusanov

unread,

Sep 3, 2014, 8:44:50 AM9/3/14

to VladR…@googlegroups.com

Ошибка CRC возникает либо из-за Эхо ответов (но по логу у тебя этого нет), либо из-за нарушений таймаутов

Проверь для проводных интерфейсов или оптопорта:

Время ожидания ответа 200

Системный таймаут 25

Множитель 4

вторник, 2 сентября 2014 г., 18:27:14 UTC+4 пользователь Вася написал:

Вася

unread,

Sep 3, 2014, 4:46:02 PM9/3/14

to VladR…@googlegroups.com

Связь со счетчиком через виртуальный порт HW Virtual Serial Port

перебирал таймауты — не помогает

были такие 

Время ожидания ответа 500

Системный таймаут 25

Множитель 4

Время ожидания ответа 200

Системный таймаут 25

Множитель 4

сейчас такие 

Время ожидания ответа 3000

Системный таймаут 40

Множитель 6

Ответ на чтение профиля последние 2 байта не CRC — пересчитывал 

, а начало ответа (дата/время, период) правильное

Ответ на чтение мгновенных зафиксированных значений параметров

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

Возможно счетчик древний и поэтому глючит.

Дата изготовления 18.07.08 

Версия ПО 02.02.84 

вот дамп обмена

09.03 16:08:33.283 | MES | <•• 65 00 2A E0

09.03 16:08:33.484 | MES | —> 65 00 2A E0

09.03 16:08:36.300 | MES | <•• 65 01 02 02 02 02 02 02 02 27 55

09.03 16:08:36.600 | MES | —> 65 00 2A E0

09.03 16:08:39.235 | MES | <•• 65 08 12 E6 12

09.03 16:08:39.435 | MES | —> 65 60 E0 C1 97 06 00 29 C0

09.03 16:08:42.255 | MES | <•• 65 08 00 66 1F

09.03 16:08:42.556 | MES | —> 65 02 38 0B 01 12 07 08 A6 AA

09.03 16:08:45.309 | MES | <•• 65 08 03 26 1E

09.03 16:08:45.612 | MES | —> 65 02 02 54 BF B7

09.03 16:08:48.316 | MES | <•• 65 08 05 A6 1C

09.03 16:08:48.416 | MES | —> 65 00 65 A1 F4

09.03 16:08:51.329 | MES | <•• 65 08 02 E7 DE

09.03 16:08:51.529 | MES | —> 65 00 01 00 01 F8 08

09.03 16:09:16.975 | MES | <•• 65 01 02 02 02 02 02 02 02 27 55

09.03 16:09:17.177 | MES | —> 65 00 2A E0

09.03 16:09:19.979 | MES | <•• 65 01 02 02 02 02 02 02 02 27 55

09.03 16:09:20.280 | MES | —> 65 00 2A E0

чтение профиля

09.03 16:09:23.037 | MES | <•• 65 08 13 27 D2

09.03 16:09:23.337 | MES | —> 65 08 90 00 16 00 03 09 14 1E 50 BD

09.03 16:09:26.056 | MES | <•• 65 06 03 08 90 0F 2C 6C

09.03 16:09:26.258 | MES | —> 65 00 16 00 03 09 14 1E 00 00 FF 00 00 FF A8 8E

09.03 16:09:29.062 | MES | <•• 65 06 03 08 90 0F 2C 6C

09.03 16:09:29.362 | MES | —> 65 00 16 00 03 09 14 1E 00 00 FF 00 00 FF A8 8E

09.03 16:09:32.068 | MES | <•• 65 06 03 08 90 0F 2C 6C

09.03 16:09:32.369 | MES | —> 65 00 16 00 03 09 14 1E 00 00 FF 00 00 FF A8 8E

чтенее мгновенных зафиксированных значений

09.03 16:09:37.592 | MES | <•• 65 08 1E E6 17

09.03 16:09:37.906 | MES | —> 65 80 00 00 1F

09.03 16:09:40.645 | MES | <•• 65 08 02 E7 DE

09.03 16:09:40.946 | MES | —> 65 00 01 00 01 F8 08

09.03 16:09:49.177 | MES | <•• 65 01 02 02 02 02 02 02 02 27 55

09.03 16:09:49.378 | MES | —> 65 00 2A E0

09.03 16:09:52.182 | MES | <•• 65 03 08 60 E9

09.03 16:09:52.582 | MES | —> 65 00 2A E0

09.03 16:09:55.123 | MES | <•• 65 08 14 00 90 2A

09.03 16:09:55.423 | MES | —> 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4B 3F

09.03 16:09:58.144 | MES | <•• 65 08 14 04 91 E9

09.03 16:09:58.345 | MES | —> 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4B 3F

09.03 16:10:01.156 | MES | <•• 65 08 14 08 91 EC

09.03 16:10:01.456 | MES | —> 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4B 3F

09.03 16:10:04.201 | MES | <•• 65 08 14 30 90 3E

09.03 16:10:04.503 | MES | —> 65 00 00 00 00 00 00 00 00 00 00 00 00 8E 4E

09.03 16:10:07.221 | MES | <•• 65 08 14 10 91 E6

09.03 16:10:07.422 | MES | —> 65 00 C8 04 00 00 00 00 55 15 D5 00

09.03 16:10:10.168 | MES | <•• 65 08 14 20 91 F2

09.03 16:10:10.468 | MES | —> 65 00 00 00 00 00 00 00 00 00 63 A9

09.03 16:10:13.179 | MES | <•• 65 08 11 40 92 8A

09.03 16:10:13.379 | MES | —> 65 00 86 13 3D 45

Вася

unread,

Sep 15, 2014, 3:17:38 PM9/15/14

to VladR…@googlegroups.com

Проблему в топике решил  — исключил из связи виртуальный порт HW Virtual Serial Port.

Когда он используется почему-то два байта FF FF передаются как один FF.

Не могу создать новую тему.

Поэтому пишу в старой.

Здравствуйте.

Не удается считать показания запросом 5.

Описание протокола

Описание протокола взаимодействия со счётчиком Меркурий 236 версия ПО 8.0.0 M234 версия ПО 9.0.0 v. 131211.pdf

пункт

2.2 Запросы на чтение массивов регистров накопленной энергии.

нормальный ответ 19 байт для номеров массива

0h От сброса.

1h За текущий год.

2h За предыдущий год.

3h За месяц.

4h За текущие сутки

5h За предыдущие сутки

не отвечает

9h На начало текущего года.

65 05 90 00 62 E9

Ah На начало предыдущего года.

65 05 A0 00 76 E9

Bh На начало месяца.

65 05 B8 00 7C E9

Ch На начало текущих суток

65 05 C0 00 5E E9

Dh На начало предыдущих суток

65 05 D0 00 53 29

Универсальный конфигуратор счётчиков Меркурий умеет читать запросом 6.

Можно читать запросом 6 читать память №2.

Как эта память организована ?

В описании протокола я не нашел.

ООО «Байт-Энерго»

unread,

Sep 17, 2014, 1:30:30 PM9/17/14

to VladR…@googlegroups.com

Здравствуйте!

9h — Чтение
программируемых флагов     

Ah — Чтение байт состояния.

Bh — Чтение местоположения
прибора.

Ch — Чтение расписания
утренних и вечерних максимумов мощности

Dh — Чтение значений
утренних и вечерних максимумов мощности 

Искомые значения т.е. на начало периодов (года, месяца, суток) это расчетные величины:

На начало текущего года вычитаем показания «за текущий год» из «от сброса»;

На начало текущего месяца вычитаем показания «за месяц» (текущий) из «от сброса»;

На начало текущих суток вычитаем показания «за текущие сутки» из «от сброса»;

На начало не текущих (года, месяца, суток) математика не намного сложнее;

Удачи.

понедельник, 15 сентября 2014 г., 15:17:38 UTC+4 пользователь Вася написал:

ООО «Байт-Энерго»

unread,

Sep 17, 2014, 1:43:55 PM9/17/14

to VladR…@googlegroups.com

> Можно читать запросом 6 читать память №2.

Можно.

>Как эта память организована?

Формат
ответа при чтении записи средних мощностей приведен на рис. 40. Адрес расположения
любой записи в памяти №3 кратен 00х10
h.

Формат
ответа при чтении записи средних мощностей 

Сетевой адрес

(1 байт)

Байт состояния ответа

Часы

(1 байт)

Минуты

(1 байт)

Число

(1 байт)

Месяц

(1 байт)

Год

(1 байт)

Длительность периода интегрирования

(1 байт)

P+

(2 байта)

P

(2 байта)

Q+

(2 байта)

Q

(2 байта)

CRC

(2 байта)

Рис. 40.

> В описании протокола я не нашел.

Ищите другое описание.

Удачи.

понедельник, 15 сентября 2014 г., 15:17:38 UTC+4 пользователь Вася написал:

Универсальный конфигуратор счётчиков Меркурий умеет читать запросом 6.

ООО «Байт-Энерго»

unread,

Sep 17, 2014, 2:05:55 PM9/17/14

to VladR…@googlegroups.com

Извиняюсь. 

Вопрос был про память № 2 (тарифное расписание), а в описании протокола про неё ни чего нет. 

Зато есть порт монитор и тому подобные утилиты с помощью которых можно посмотреть какие запросы посылает родной конфигуратор в счётчик и что на них получает в ответ.

Не знаю как всё устроено для трехфазников меркурий но для СЭТ-4ТМ (а у них протоколы во многом схожи) актуально следующее:

Запись
расписания праздничных дней

Расписание праздничных дней
составляется на каждый месяц текущего года. Месячное расписание праздничных
дней содержит 4 байта (32 бита). Каждый бит соответствует календарной дате
(левый бит 1-го байта — 1-е число месяца, правый бит 4-го байта — 32-е число
месяца). Для задания праздничного дня необходимо установить бит в позиции,
соответствующий дате праздничного дня.

Ниже
приведена структура и распределение памяти массива расписания праздничных дней.

Месяц

Адрес
массива

1-й
байт

(числа
1…8)

2-й
байт

(числа
9…16)

3-й
байт

(числа
17…24)

4-й
байт

(числа
25…32)

Январь

2000h

Февраль

2004h

Март

2008h

Апрель

200Ch

Май

2010h

Июнь

2014h

Июль

2018h

Август

201Ch

Сентябрь

2020h

Октябрь

2024h

Ноябрь

2028h

Декабрь

202Ch

КС

2030h

            Размер массива расписания
праздничных дней составляет 48 байт. 49-м байтом записывается байт контрольной
суммы массива. КС массива считается простым суммированием всех байт массива
(без учета переноса из старшего разряда).

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

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

Запись тарифного
расписания

            Тарифное
расписание составляется на каждый день недели каждого месяца: понедельник,
вторник, среда, четверг, пятница, суббота, воскресенье, праздничный день.
Каждый день делится на 144 10-ти минутных суточных тарифных интервала. Это позволяет
изменять тарифы с шагом 10 минут. Каждому суточному тарифному интервалу может
быть поставлен в соответствие один из 8-и тарифов (в диапазоне чисел 0…7, где 0
– 1-й тариф…7 – 8-й тариф). Номер тарифа занимает размер один полубайт в
массиве тарифного расписания. Суточное тарифное расписание занимает объем 72
байта. Месячное тарифное расписание занимает объем 576 байта. Тарифное
расписание на год занимает объем 6912 байт. Массив тарифного расписания имеет
контрольную сумму, определяемую простым суммированием всех байт массива,
аналогично КС массива расписания праздничных дней.

На
рисунке 8 приведен пример суточного тарифного расписания на два часа. В примере
:

·        
“А”
— базовый адрес массива суточного тарифного расписания;

·        
первому
тарифу соответствует код 0, второму – код 1… восьмому – код 7;

·        
в
интервале времени от 00:00 до 00:30 действует тариф 1 (код 0);

·        
в
интервале времени от 00:30 до 00:50 действует тариф 3 (код 2);

·        
в
интервале времени от 00:50 до 01:00 действует тариф 6 (код 5);

·        
в
интервале времени от 01:00 до 01:50 действует тариф 2 (код 1);

·        
в
интервале времени от 01:50 до 02:00 действует тариф 8 (код 7).

Адрес

A+0

A+1

A+2

A+3

A+4

A+5

№ тарифа

0

0

0

2

2

5

1

1

1

1

1

7

Время

00:00

00:10

00:20

00:30

00:40

00:50

01:00

01:10

01:20

01:30

01:40

01:50

Рисунок
8 – Пример тарифного расписания на два часа (с 00:00 по 02:00)

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

В таблице 3 приведена структура и пример распределение
памяти массива тарифного расписания на январь месяц. Старший полубайт по адресу
2040
h должен иметь код
номера тарифа, который будет действовать в период времени от 00:00 до 00:10.  Младший полубайт по адресу 2087h  должен иметь код номера тарифа, который будет
действовать в период времени от 23:50 до 00:00 в январе месяце в понедельник.

Распределение
 памяти  всего  массива
 тарифного расписания приведено в таблице 4.

Таблица 3 – Распределение
памяти массива тарифного расписания на январь месяц

Месяц

Адрес
массива

Номера
тарифов 10-ти минутных зон

2040h

Понедельник, 144 интервала
(полубайта), 72 байта

2088h

Вторник,          144 интервала (полубайта), 72 байта

20D0h

Среда,               144 интервала (полубайта), 72
байта

Январь

2118h

Четверг,            144 интервала (полубайта), 72
байта

2160h

Пятница,           144 интервала (полубайта), 72 байта

21A8h

Суббота,            144 интервала (полубайта), 72
байта

21F0h

Воскресенье,   144 интервала (полубайта), 72 байта

2238h

Праздники,      144 интервала (полубайта), 72 байта

Таблица 4 –
Распределение памяти массива тарифного расписания

Месяц

Начальные адреса массивов суточных тарифных
расписаний

Пон.

Вт.

Среда

Четверг

Пятн.

Суб.

Воскр.

Праздн.

Январь

2040h

2088h

20D0h

2160h

21A8h

21F0h

21F0h

2238h

Февраль

2280h

22C8h

2310h

2310h

23A0h

23E8h

2430h

2478h

Март

24C0h

2508h

2550h

2598h

25E0h

2628h

2670h

26B8h

Апрель

2700h

2748h

2790h

27D8h

2820h

2868h

28B0h

28B0h

Май

2940h

2988h

29D0h

2A18h

2A60h

2AA8h

2AF0h

2B38h

Июнь

2B80h

2BC8h

2C10h

2C58h

2CA0h

2CE8h

2D30h

2D78h

Июль

2DC0h

2E08h

2E50h

2E98h

2EE0h

2F28h

2F70h

2FB8h

Август

3000h

3048h

3090h

30D8h

3120h

3168h

31B0h

31F8h

Сентябрь

3240h

3288h

32D0h

3318h

3360h

33A8h

33F0h

3438h

Октябрь

3480h

34C8h

3510h

3558h

35A0h

35E8h

3630h

3678h

Ноябрь

36C0h

3708h

3750h

3798h

37E0h

3828h

3870h

38B8h

Декабрь

3900h

3948h

3990h

39D8h

3A20h

3A68h

3AB0h

3AF8h

КС

3B40

Контрольная сумма
массива тарифного расписания должна считаться и записываться управляющей
программой верхнего уровня при формировании массива тарифного расписания. Если
меняется не все тарифное расписание, а только его часть, например на один день,
то можно произвести замену информации по соответствующим адресам, а контрольную
сумму доверить посчитать и записать счетчику, пользуясь командой  03h21h.

Вася

unread,

Sep 17, 2014, 7:33:16 PM9/17/14

to VladR…@googlegroups.com

Спасибо за ответ и совет анализировать дамп обмена.

Уже так и сделал.

Плохо что нет описания памяти №2.

В описании  протокола описано ну все, что надо и не надо.

А вот как читать самый главный параметр ( показания ) не написано )).

Прям заговор какой-то. Секрет фирмы ))

Протокол обмена данными ЛГК по RS-485
(на базе универсальной гироприставки УГП-1)
Версия 1.0.2

Протокол — под этим понимается, что между всеми устройствами согласованы:

  1. Модель передачи данных
    (как организуется логика обмена, как формируются пакеты и т.д.)
  2. Набор команд
  3. Структура данных каждой из команд

Параметры RS-485

Параметры RS-485 устанавливаются заранее и не переконфигурируются во время работы. По умолчанию параметры задаются:

Параметр Значение
Baud rate 115200
Data bits 8
Parity none
Stop bits 1
Flow control none

Передача данных

  1. Модель передачи данных — запрос-ответ между Master — Slave.

  2. Мастером является заранее определенное устройство в сети RS485.

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

  4. Ответ должен быть получен по возможности без задержки по времени.

  5. Таймаут получения ответа — 0.3 секунды. Подразумевается, что причина задержки не ожидание исполнения операции
    (см. след. пункт). В отсутствии ответа в течении тайм-аута, команда посылается повторно.

  6. Долгие операции разбиваются на несколько запросов. Если запрашиваемая операция не может быть выполнена
    условно мгновенно (поворот прибора, сбор данных), используется несколько запросов. Типовая последовательность:

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

Формат запросов

  1. Запросы/ответы (фреймы) передаются в текстовом* виде в кодировке UTF-8

  2. Регистр имеет значение, как правило используются заглавные буквы

  3. Каждый запрос и ответ имеет общий вид:

    :[кому][от кого][сообщение][crc16];
    
    • Фрейм** начинается с символа : и заканчивается символом ;
    • [кому][от кого] — Адрес получателя (1 байт) и адрес отправителя (1 байт). Например ‘G’, ‘M’
    • [сообщение] — Непустая строка переменной длинны
    • 4 символа CRC16 в шестнадцатеричном виде. Например 0FC1.
    • CRC16 рассчитывается для [кому][от кого][сообщение] (т.е. конверта). Тип
  4. Адреса мастера и устройств задаются заранее:

    • Адрес мастера 'M'
    • Адрес гирокомпаса 'G'

Пример:

:GMS45A7;
  • G — [кому] — гирокомпасу
  • M — [от кого]- от мастера
  • S — [сообщение] — команда выдать статус
  • 45A7 — [crc16] для GMS

* — Текстовый формат имеет очевидный недостаток в увеличении размера сообщений.
В данном случае это не критично — объем данных мал, запросы идут сравнительно редко.
Преимуществом является простота в отладке и использовании.

** — Часть : ... [crc16]; — будем называть фреймом. Часть [кому][от кого][сообщение] — будем называть конвертом.
Тогда можно сказать, что при любом запросе/ответе передается фрейм, внутри которого — конверт, а внутри конверта — сообщение.

CRC16

В качестве CRC16 используется CRC-16/CCITT-FALSE

  • width=16
  • poly=0x1021
  • init=0xffff
  • refin=false
  • refout=false
  • xorout=0x0000
  • проверка=0x29b1 (для uint8_t* = «123456789»)
  • name=»CRC-16/CCITT-FALSE»

Реализации для C и C# приведены в дополнениях. Реализация для Rust из библиотеки rust-crc1

Пример обмена данными

  1. Мастер посылает запрос.
:GMS45A7;

Следует читать так: от мастера(‘M’) гирокомпасу (‘G’), команда выдать статус (‘S’)

  1. Получает ответ:
:MGS ML TR156 L1064 ... 9671;

Мастеру (‘M’) от гирокомпаса (‘G’) , Ответ на запрос статуса (‘S’).
ML TR156 L1064 ... — данные статуса. Расшифровка приведенных данных:

  • M<L>Mode longitude — идет измерение с заданной широтой
  • TRRemaining time — рассчетное время до окончания измерений в секундах
  • LLife time — время с момента включения прибора в секундах

Правила формирования сообщений

(Из общего вида :[кому][от кого][сообщение][crc16]; данный параграф рассматривает часть — [сообщение])

  1. Первый символ/байт сообщения определяет тип сообщения (команду) и дальнейшие данные, если они есть.
    Сообщение может состоять из одного символа, но не может быть пустым.
    Например: сообщение (‘S’) — команда выдать статус. (‘V’) — команда выдать версию

  2. После первого символа могут идти необходимые дополнительные данные.
    Их форма определена только типом сообщения (и, разумеется, протоколом).

  3. Общие правила по формированию сообщений:

    • 1-й символ ответа соответствует первому символу запроса (команде):
      :GMS45A7;                         // S - запрос статуса
      :MGS ML TR156 L1064 ... 9671;     // S - ответ с данными статуса
    
    • Если данные в сообщении содержат несколько полей, они разделяются пробелом,
      а в начале каждого поля ставится символ-аббревиатура поля:
      :MGS ML TR156 L1064 ... 9671;     // ML, R156,... - поля. Символы M, R определяют, что это за поля
    
    • double.NaN передается, как ‘—‘ (два тире):
      :MGD I0 R0 C0 TS0 MI L-- A-- IL-- IT-- E0 S087CA;   // L - широта, '--' означает, что значение double.NaN
    

Управление ЛГК

Список запросов к ЛГК

Команда Расшифровка Описание
V Version Запрос версии протокола и ПО
S Status Запрос статуса
M Mode Начать/остановить измерение
D Data Запрос последних результатов измерения
C Constants Запрос/запись корректировочного угла (и прочих констант)

Алгоритм работы с ЛГК

  1. После включения питания ЛГК, начинается опрос статуса (команда S). ЛГК начинает отвечать через 10-30с с момента подачи питания. Первоначально запрос статуса используется для опредления включения ЛГК.
    (рекомендуемая частота опроса статуса — 1 раз в секунду).

  2. После получения первого ответа на запрос статуса, запрашивается версия протокола (команда V).

  3. После включения ЛГК находится в режиме ожидания. Продолжается периодический опрос статуса.

  4. По запросу оператора, посылается команда — начать измерение (команда GMML или GMMA)

  5. С этого момента запрос статуса позволяет отслеживать ход измерения. Сколько осталось времени, нет ли ошибок.

  6. Так же с момента запуска можно отслеживать ход измерения запрашивая данные * (команда GMD)
    (рекомендуемая частота опроса данных — 1 раз в секунду).

  7. Когда данные готовы (или даже раньше**) используется та же команда на выдачу данных (команда GMD).

  8. ЛГК находится в режиме ожидания. Продолжается периодический опрос статуса.

* Команда выдачи данных может быть вызвана в любое время (полное описание ниже). В качестве результата она возвращает последнее состояние данных. В момент начала измерения данные сбрасываются, а ID данных увеличивается на 1. После измерения (когда прибор вовзращается в режим ожидания) выдаются показания последнего измерения.

** — ЛГК начинает выдавать данные азимута и углов наклонов ДО того, как закончится измерение, с пометкой, что
данные не точны. Нет общих рекомендаций, необходимо ли выводить оператору такие данные. Для некоторых задач
бывает важно быстрее выдать данные хоть и с неполной точностью, а затем, по возможности,
ждать их сходимости. В других случаях лучше «не отвлекать оператора» данными, которые могут быть не точны.

Описание команд

Версия протокола (V)ersion

Команда — V

Ответ:

V P<major>.<minor>.<micro> F<major>.<minor>.<revision>

Пример обмена:

:GMV1502;
:MGV P1.0.0 F2.8.18 41EA;
  • PProtocol — Номер версии протокола (см. ниже описание)
  • FFirmware — Номер версии ПО

Описание:

Версия протокола состоит из 3-х цифр <major>.<minor>.<micro>, Например: 01.02.234

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

Когда изменяются major, minor и micro

<major> — Смена <major> соответcтвует ситуации, когда устройства больше не могут расшифровать
сообщения друг друга из-за изменений в протоколе обмена.

Например: Если шифрование фрейма сменится на COBS или COBS/r, должна быть изменена <major> версия протокола.

<minor> — Соответствует общей согласованности списка команд и данных этих команд. Мастер и устройства одинаковой версии с одинаковой <major>.<minor> должны без ошибок понимать и уметь общаться друг с другом. Если это правило меняется, <minor>версия должна быть увеличена.

Например: В версии 01.02.01 оговорено, что команда ‘S’ — получить статус. В ответе 1-й символ — это флаг статуса ‘S’, время работы устройства определяется, как ‘T[время]’. Это значит, что в любой версии 01.02.xx на запрос S ответом будет статус, а данные статуса будут иметь вышеуказанный флаг и времени работы.

<micro> — Соответствует полной согласованности команд и опциональных добавок в структуры данных.

Например: В версии 01.02.01 оговорено (как и выше), что ‘S’ — команда статуса, структура ответных данных 1 символ ‘S’, ‘T[время]’ время работы. В версии 01.02.02 решено добавить в данные статуса ‘E[код]’ с кодом ошибки.

Когда: Мастер со старой версией (01.02.01) общается с устройством версии 01.02.02, он может запросить статус и декодировать данные нового устройства. Про добавленное поле с кодом ошибки он ничего не знает и пропустит его.

Когда: Мастер с новой версией (01.02.02) общается с устройством версии 01.02.01, при запросе статуса он, зная, что общается с устройством старой версии, не будет пытаться извлечь добавленный код ошибки.

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

Статус (S)tatus

Команда — S

Ответ:

S M<Mode> L<LifeTime> P<Parked> R<RemainTime> E<Error> O<Operation>

(!) Последовательность значений после начального ‘S’ в ответе может быть различной.
Т.е. принимающий парсер должен опираться на M, L, P и т.д., а не на очередность значений.

Описание:

  • MMode — Режим работы/измерения может принимать следующие значения
    • IIdle — Прибор находится в режиме ожидания
    • LLatitudeMeasure — Прибор в режиме измерения с заданной широтой
    • AAutoMeasure — Прибор в режиме измерения с автоматическим определением широты
    • TTest — Прибор в режиме самотестирования
  • LLifeTime — Время в секундах с момента включения прибора
  • PParked — Парковка. 1 — прибор припаркован *, 0 — не припаркован.
    (!) ВАЖНО — Прибор паркуется автоматически в течении нескольких секунд (не более 10) после окончания измерения.
    Пока прибор не припаркован его нельзя транспортировать. Поэтому важно дать оператору понять, когда можно отключать
    и транспортировать прибор!
  • TRTime Remaining — Рассчетное время до конца измренеия, если оно выполняется (иначе 0).
    Реальное время измерения может отличатся.
  • EError — Ошибки (может быть несколько одновременно) и неисправности в работе прибора.
    При исправности выводится ‘0’

    • FFixation error — Прибор плохо закреплен. (Для исправления ошибки перезакрепить прибор)
    • HHigh Noise — Плохие условия работы (внешние факторы), идет отбраковка значений из-за внешних помех. Как правило, ошибка не является признаком неисправности прибора, а служит для определния и устранения внешних факторов, приводящих к увеличению разброса показаний. Например, работа с ЛГК с незаглушенным двигателем, установка ЛГК на хлипкое основание, сильные ветровые нагрузки и т.д — могут приводить к индикации ‘H’.
    • EERROR — Прибор имеет механическую или электрическую поломку и нуждается в ремонте

* — Парковка — механический упор, в который должен вставать лазерный гироскоп (находящийся внутри гирокомпаса)
во время транспортировки. Прибор автоматически встает в режим парковки после окончания измерений, однако ему необходимо
на это несколько секунд (до 10-и). Поэтому сразу после проведения измерений нельзя отключать прибор, не дождавшись
парковки. В противном случае прибор может разъюстироваться во время транспортировки или даже выйти из строя.

Пример обмена:

:GMS45A7;
:MGS ML TR156 L1064 P0 E0 6BC2;

Стоит читать как:
ML — прибор находится в режиме измерения с заданной широтой. TR156 — до конца измерения осталось предположительно
156 секунд. L1064 — с момента включения прибора прошло 1064 секунды.
P0 — прибор нельзя транспортировать (надо завершить измерение и дождаться парковки). E0 — ошибок нет.

:GMS45A7;
:MGS MI R0 L1064 P1 EFE C9B6;`

MI — ЛГК в режиме ожидания команд, R0 и O0 — можно игнорировать.
L1064 — с момента включения прибора прошло 1064 секунды. P1 — прибор готов к транспортировке.
EFE — есть 2 ошибки в работе: F — прибор плохо закреплен,
Е — самотестирование показывает наличие неисправимых проблем, необходим ремонт прибора.

Начало/остановка измерения (M)ode

Команда — M
Запрос:

M<Mode> T<Time> L<Latitude>

Между M и <Mode> может быть пробел.

Ответ:

M<Mode> T<Time> L<Latitude>

(!) Последовательность значений после начального ‘S’ в ответе может быть различной.
Т.е. принимающий парсер должен опираться на M, L, P и т.д., а не на очередность значений.

Описание:

  • MMode — Режим в который нужно перевести прибор

    • IIdle — Режим ожидание. Остановка измерения (если оно шло).
    • LLatitudeMeasure — Начать измерение с заданной широтой. В этом случае отдельно надо передать L
    • AAutoMeasure — Начать измерение с автоматическим определением широты
    • TTest — Начать режим режим самотестирования

    Ниже приведены примеры запроса каждого из режимов

  • TTime — Запрашиваемое время измерения в секундах. Если параметр не присутствует, используется 600с.

  • LLatitude — Широта в градусах (!). Обязано задается в случае режима LatitudeMeasure (т.е. ML)

Фактически ответ всегда повторяет запрос. Это должно быть использовано для проверки корректности обмена данными
(т.е. что запрос принят правильно)

Переключение между режимами:
Каждый раз, когда приходит команда «М», текущий режим меняется или перезапускается.

  • Если во время измерения приходит команда MI (Mode Idle), измерение останавливается,
    прибор уходит на парковку и переходит в режим ожидания. Т.е. Mode Idle может использоваться, как команда «СТОП».
    (Если прибор уже в режиме ожидания, команда Mode Idle ничего не делает).

  • Если идет измерение и приходит команда на измерение с другими настройками, измерение перезапускается с этими настройками.

  • (!) ВАЖНО — Если во время измерения приходит та же команда на проведение измерения в том же режиме с теми же
    настройками — измерение перезапускается (заново!)

Примеры:

1. режим ожидания

Запрос   :GMMI46F0;
Ответ    :MGMIE99A;

MI — перевести прибор в режим ожидания. Если прибор в данный момент проводил измерение, оно будет остановлено.

2. режим измерения с заданной широтой

Запрос   :GMML L55.751244 T300 B479;
Ответ    :MGML T300 L55.751244 759C;

ML — перейти в режим измерения с заданной широтой.
L55.751244 — широта в градусах
T300 — Время измерения ~300 секунд.

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

3. измерение с автоматическим определением широты

Запрос   :GMMA T450 0B8C;
Ответ    :MGMA T450 BD50;

MA — Начать измерение с автоматическим определением широты
T450 — Время измерения ~450 секунд.

Запрос данных (D)ata

Команда — D

Ответ:

D I<Id> R<Ready> C<Completeness> TS<TimeOfStart> M<Mode> L<Latitude> A<Azimuth> IL<Incline> IT<Incline> E<Error> S<SupervisionResults>

Описание:

  • IId — Идентификатор измерения. Увеличивается на 1 каждый новый запуск измерения.
  • RReady — 1 — измерение завершено, данные готовы и точны. 0 — данные не точны.
  • CCompleteness — Прогресс измерения в процентах. 0 — измерение начато, 100 — данные готовы
  • TSTimeOfStart — Время начала измерения в секундах (от времени жизни прибора, см. запрос статуса)
  • MMode — Режим измерения (см. М или S)
  • LLatitude — Широта в градусах. Если измерение с заданной широтой, то заданная широта. Иначе — измеренная.
  • AAzimuth — Азимут в градусах. Измеренное значение азимута
  • ILInclinationLong. — Значение продольного угла наклона в градусах.
  • ITInclinationTrans. — Значение поперечного угла наклона в градусах.
  • Е — Наличие ошибок во время измерения. Е0 — ошибки отсутствуют. Список ошибок см. запрос статуса (команда S).
  • S — Результат проверки измерения на возможные неточности (см. следующий пункт)

Данные могут быть запрошены в любой момент работы прибора. Возвращаются данные последнего проводимого измерения.
Если измерений не было, то вернутся данные с Id=0 и всеми значениями ‘—‘ (аналог double.nan)

Результат проверки измерения:
После заврешения измерения, результат проходит проверку на возможные неточности.
Флаги возможных ошибок передаются в ответе на запрос данных после символа S.
S0 — ошибки отсутствуют, результаты точны.
В одном измерении может встречаться сразу несколько из перечисленных ниже проблем:

  • F — низкая частота колебаний прибора.
    Возможные причины — плохое закрепление прибора или низкое напряжение источника питания.
    Данная ошибка скорее всего приведет к другим ошибкам и, как следствие, к неточному результату измерения азимута.

  • D — результаты измерения скорости Земли отличаются от ожидаемых. Может приводить к неверным результатам азимута.
    Рекомендация — перезапуск измерения. Если ошибка часто возникает, есть основания для проведения исследования прибора.

  • L — результат не прошел проверку по широте. Может возникать только в режиме измерений с заданной широтой (ML).
    Рекомендации — убедиться, что широта, передаваемая пользователем при запуске измерения, соответствует реальной широте местности, на которой находится прибор. Перезапустить измерение.
    При выявлении данной ошибки высока вероятность неточного результата измерения азимута (отклонение порядка 20 угловых минут или больше).

Примеры:

Запрос   :GMD2771;
Ответ    :MGD I3 R1 C100 TS697 ML L55.43532 A132.5433 IL0.0234 IT0.5112 E0 S0CCA8;
  • I3 — Id=3, R1 — данные готовы, TS697 начало измерения на 697 секунде с начала отсчета.
  • L55.43532 A132.5433 IL0.0234 IT0.5112 — Широта, азимут, продольный и поперечный углы наклона.
  • E0 — Нет ошибок во время измерения.
  • ML — Режим измерения — по заданной широте.
  • C100 — Прогресс измерения — 100%
  • S0 — результат успешно прошел проверки на возможные неточности
Запрос   :GMD2771;
Ответ    :MGD I0 R0 TS0 MI L-- A-- IL-- IT-- E0 S02169;

Характерный ответ, если еще не было измерений с момента включения прибора.

Запрос   :GMD2771;
Ответ    :MGD I4 R1 C100 TS697 ML L55.43532 A132.5433 IL0.0234 IT0.5112 E0 SDFL4B44;
  • I3 — Id=3, R1 — данные готовы, TS697 начало измерения на 697 секунде с начала отсчета.
  • L55.43532 A132.5433 IL0.0234 IT0.5112 — Широта, азимут, продольный и поперечный углы наклона.
  • E0 — Нет ошибок во время измерения.
  • ML — Режим измерения — по заданной широте.
  • C60 — Прогресс измерения — 60%
  • SDFL — выявлено сразу 3 потенциальных проблемы: по скорости(D), по колебаниям(F) и по широте(L). Вероятность неточности полученно азимута крайне высока.

Запрос и запись констант (C)onstants

Команда — C

Опционально с SAC<AzimuthCorrection>

Ответ:

C AC<AzimuthCorrection>

Описание:
Команда позволяет записать в ПЗУ прибора корректировочный угол. Или считать ранее записанные, используемые значения.

В случае считывания, команда посылается без дополнительных данных:

Запрос    :GMC5796;             //Без доп. данных команда просто запрашивает текущие константы
Ответ     :MGC AC0.004 A4D3;    //В настоящий момент корректировочный угол 0.004 градуса

Возвращаемые значения:

  • ACAzimuth Correction — Корректировочный угол в градусах. Установленное значение всегда прибавляется/вычитается
    к измеренному значению азимута.

Для записи корректировочного угла дополнительно передается SAC<AzimuthCorrection> — Set Azimuth Correction

Запрос    :GMC SAC0.005B8A1;   //Записать в ПЗУ прибора корректировочный угол 0.005 градуса
Ответ     :MGC AC0.005B4F2;    //Записан угол 0.005 градуса

При записи, старое значение перезаписывается.

Так как данные записываются в ПЗУ прибора, данная команда не используется при автоматическом управлении ЛГК,
а только во время пуско-наладочных работах, при установке ЛГК на целевую платформы (либо при последующей коррекции).

Обработка ошибок обмена данных и исключительных ситуаций

Ошибки передачи сообщений

В общем случае, при ошибке, связанной с обменом сообщениями, ЛГК не отвечает или возвращает сообщение E<данные>
(подробнее ниже). В этих случаях необходимо заново посылать тот же запрос в ЛГК, до тех пор, пока не будет получен
правильный ответ.

Пример с точки зрения запрашивающего устройства:

Запрос                 :GMS45A7;
Ожидание 0.2 сек...    TimeOut
Запрос  повторно       :GMS45A7;
Ответ                  :MGECRCFFD1;  - ECRC = Ошибка, не сошлась контрольная сумма
Запрос  повторно       :GMS45A7;
Ожидаемый ответ        :MGS ML R156 L1064 P0 E0 9671;

Алгоритм ответа ЛГК на ошибки передачи данных:

  1. В случае, если сообщение повреждено/искажено/неправильно так, что адрес [от кого] искажен или неизвестен,
    ЛГК не отвечает на запрос. Т.е. со стороны запрашивающего мастера будет TimeOut и сообщение необходимо послать повторно.

  2. В случае, если адрес запрашивающего устройства (мастера) не искажен и определн правильно, но само сообщение искажено
    или не может быть правильно расшифровано, устройство возвращает ответ с сообщением E<данные>, где «данные»
    описывают ошибку:

    • CRCCRC16 — Контрольная сумма не сходится.

    • Uunknown command — Неизвестная команда.

    • DData — Ошибка в формате/задании данных команды. Как правило, дальше идет текстовое описание ошибки.

      // (В запросе на измерение с заданной широтой не задана широта)
      Запрос     :GMD0F7B;
      Ответ      :MGED NO LATITUDE GIVEN15E0;
      

Таким образом можно составить таблицу:

Ответ Причина Решение
Нет ответа Сообщение не получено ЛГК или сильно искажено Послать запрос повторно
Нет ответа постоянно Прибор выключен или кабель поврежден Включить прибор, проверить кабель
ECRC Не совпадает контрольная сумма Послать запрос повторно
ECRC постоянно/часто Не совпадает контрольная сумма. Постоянно/часто искажаются данные Проверить кабель
EU/ED однократно Неправильно прочитана команда Послать запрос повторно *
EU постоянно Неизвестная команда. Возможно, неправильно реализуется протокол обмена Проверить реализацию протокола обмена
ED постоянно Ошибка в формате данных. Возможно, неправильно реализуется протокол обмена Проверить реализацию протокола обмена

* — CRC16 допускает возможность случайного совпадения, поэтому нельзя исключить ситуацию, когда команда передана с
ошибкой, но контрольная сумма совпала. Поэтому после однократного возникновения ошибок EU/ED можно повторно послать
команду. При постоянном появлении этой ошибки — проверить реализацию протокола.

Отслеживание перебоя питания ЛГК

В режиме ожидания — потребление ЛГК снижено (в режиме ожидания и пониженного потребления
ЛГК оказывается сразу после включения ). Только через некоторое время после начала измерения потребление ЛГК
может возрастать (в режиме ожидания потребление ~10 Вт, во время измерения пиковое потребление может быть ~30Вт)
Если при этом ЛГК подключен к сети с пониженной мощностью (разряженный аккумулятор, неправильно
выставленный регулятор силы тока), то из-за недостатка питания ЛГК может перезагрузиться. Так как ЛГК не имеет
собственных батарей, возможности самостоятельно отслеживать причину такого перезапуска не предусмотрено.
С точки зрения управляющего модуля, картина может выглядеть так:

  1. ЛГК подключен с разряженному аккумулятору (или прочие причины подключения к сети с недостаточной мощностью)

  2. После включения ЛГК находится в режиме ожидания с пониженным потреблением. ЛГК выходит на связь и штатно
    отвечает на все запросы.

  3. На команду начать измерение ЛГК отвечает правильно. Некоторое время после начала измерения, он работает в штатном
    режиме, правильно отвечая на запросы статуса.

  4. Через некоторое время, когда потребление возрастает и сеть не может это обеспечить, ЛГК выключается,
    потребление снижается, ЛГК включается заново в режиме ожидания. Т.е. перезапускается. В течении ~15 секунд ЛГК
    недоступен. На запросы статуса нет ответа.

  5. ЛГК находится в режиме ожидания после перезагрузки, отвечает на запросы статуса, но, очевидно, никакого измерения
    не происходит.

Отработка такой ситуации должна быть заложена в запрашивающее устройство. Есть разные способы решения этой задачи.

Для индикации перезагрузки удобно использовать «время жизни» прибора — время в секундах с момента включения,
которое передается после запроса статуса. Так же в запросе данных есть поле — Id измерения, которое будет равно нулю,
сразу после включения (перезагрузки) прибора.

Изменение в версиях

Версия протокола 1.0.2

В ответе на запрос данных добавлена информация о проверки результатов на возможные неточности.
См. раздел Запрос данных (D)ata

Версия ПО 2.08.24

Изменения в версии ПО — Исправлены ошибки:

  1. Первые несколько секунд посла старта нового измерения значение выполненности измерения сохраняется из прошлого измерения.
  2. Команда запроса констант GMC возвращает правильное значение коррекционного угла только после запуска хотя бы одного измерения.

Не так давно по долгу службы столкнулся с довольно интересной проблемой.

У нас имеется устройство, которое осуществляет интенсивный обмен по внутренней шине RS485, число проходящих пакетов составляет порядка нескольких тысяч в секунду, каждый пакет имеет длину в 7 байт, два из которых предназначены для хранения контрольной суммы CRC16 в ее CMS варианте (полином = 0x8005, стартовое значение = 0xFFFF). Прием осуществляется в FIFO-буфер, который сдвигается вверх с вытеснением после приема каждого последующего байта. Индикатором получения реального пакета является факт совпадения его контрольной суммы со значением, переданным в самом пакете. Никаких заголовков или дополнительных параметров.

Проблема заключалась в следующем – периодически, примерно раз в 5 минут, при передаче данных проскакивал пакет, данные которого давали выброс данных для одного из каналов, причем чаще всего выброс происходил до одного и того же значения. Сначала мы смотрели в сторону физических коллизий, но дело оказалось в другом – время от времени в буфере, где собирались полученные данные, оказывался пакет, состоящий из конца предыдущего пакета и начала следующего, причем контрольная сумма у такого комбинированного пакета оказывалась верной. То есть, налицо коллизия контрольной суммы: пакет не имеет смысла, но дает верную контрольную сумму.

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

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

Обозначение Polynomial Init RefIn RefOut XorOut
CMS 0x8005 0xFFFF false false 0x0000
CCITT 0x1021 0xFFFF false false 0x0000
AUG-CCITT 0x1021 0x1D0F false false 0x0000
BYPASS 0x8005 0x0000 false false 0x0000
CDMA2000 0xC867 0xFFFF false false 0x0000
DDS-110 0x8005 0x800D false false 0x0000
DECT-X 0x0589 0x0000 false false 0x0000
EN-13757 0x3D65 0x0000 false false 0xFFFF
Modbus 0x8005 0xFFFF true true 0x0000
T10-DIF 0x8BB7 0x0000 false false 0x0000
TELEDISK 0xA097 0x0000 false false 0x0000
XMODEM 0x1021 0x0000 false false 0x0000

В данном случае:

  • RefIn — порядок поступления битов из буфера данных: false — начиная со старшего значащего бита (MSB first), true – LSB first;
  • RefOut – признак инвертирования порядка битов на выходе: true – инвертировать.

При эмуляции повреждения пакетов я реализовал следующие модели:

  • Shuffle: заполнение случайного количества байт в пакете случайными значениями
  • Bit shift: сдвиг случайных байт в пакете влево
  • Roll packet: кольцевой сдвиг байт в пакете влево
  • Right shift: сдвиг пакета вправо на один байт, слева дописывается 0xFF (передача идет посредством UART)
  • Left shift: сдвиг пакета влево на один байт, справа дописывается 0xFF
  • Fill zeros: заполнение случайного количества байт в пакете байтами 0x00 (все нули)
  • Fill ones: заполнение случайного количества байт в пакете байтами 0xFF (все единицы)
  • Byte injection: вставка в пакет случайного байта в случайном месте, байты за вставленным сдвигаются в направлении хвоста
  • Single bit: повреждение единственного случайного бита

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

В итоге была получена следующая таблица с количеством ошибок:

Обозначение Shuffle Bit shift Roll packet Right shift Left shift Fill zeros Fill ones Byte injection Sum
CMS 5101 3874 2937 1439 1688 3970 4010 1080 24099
CCITT 2012 1127 3320 1494 1486 1063 1096 1130 12728
AUG-CCITT 2012 1127 3320 1494 1486 1063 1096 1130 12728
BYPASS 5101 3874 2937 1439 1688 3970 4010 1080 24099
CDMA2000 1368 1025 1946 1462 1678 1043 1028 1112 10662
DDS-110 5101 3874 2937 1439 1688 3970 4010 1080 24099
DECT-X 1432 1189 5915 1779 1580 1215 1209 1093 15412
EN-13757 1281 2209 3043 1520 1528 2193 2187 1039 15000
Modbus 5090 3888 3086 1282 1582 3947 3897 1073 23845
T10-DIF 1390 922 1424 1421 1630 994 938 1093 9812
TELEDISK 1394 1049 5398 1451 1512 1096 1066 1065 14031
XMODEM 2012 1127 3320 1494 1486 1063 1096 1130 12728

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

Ну и итоговая таблица качества контрольной суммы, уже без учета дублирующих алгоритмов:

Обозначение Number of collisions Place
CMS 24099 8
CCITT 12728 3
CDMA2000 10662 2
DECT-X 15412 6
EN-13757 15000 5
Modbus 23845 7
T10-DIF 9812 1
TELEDISK 14031 4

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

Исходный код программы приведен ниже.

Исходный код

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>

#define PACKET_LEN    (7)
#define NUM_OF_CYCLES (100000)

static unsigned char reverse_table[16] =
{
  0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE,
  0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF
};

uint8_t reverse_bits(uint8_t byte)
{
  // Reverse the top and bottom nibble then swap them.
  return (reverse_table[byte & 0b1111] << 4) | reverse_table[byte >> 4];
}

uint16_t reverse_word(uint16_t word)
{
  return ((reverse_bits(word & 0xFF) << 8) | reverse_bits(word >> 8));
}

uint16_t crc16_common(uint8_t *data, uint8_t len, uint16_t poly, uint16_t init,
                      uint16_t doXor, bool refIn, bool refOut)
{
  uint8_t y;
  uint16_t crc;

  crc = init;
	while (len--)
  {
    if (refIn)
      crc = ((uint16_t)reverse_bits(*data++) << 8) ^ crc;
    else
	    crc = ((uint16_t)*data++ << 8) ^ crc;
    for (y = 0; y < 8; y++)
    {
      if (crc & 0x8000)
        crc = (crc << 1) ^ poly;
      else
        crc = crc << 1;
    }
	}

  if (refOut)
    crc = reverse_word(crc);
  return (crc ^ doXor);
}

uint16_t crc16_ccitt(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x1021, 0xFFFF, 0x0000, false, false);
}

uint16_t crc16_bypass(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x8005, 0x0000, 0x0000, false, false);
}

uint16_t crc16_xmodem(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x1021, 0x0000, 0x0000, false, false);
}

uint16_t crc16_teledisk(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0xA097, 0x0000, 0x0000, false, false);
}

uint16_t crc16_augccitt(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x1021, 0x1d0f, 0x0000, false, false);
}

uint16_t crc16_cdma2000(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0xc867, 0xffff, 0x0000, false, false);
}

uint16_t crc16_dds110(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x8005, 0x800d, 0x0000, false, false);
}

uint16_t crc16_dect(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x0589, 0x0000, 0x0000, false, false);
}

uint16_t crc16_en13757(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x3d65, 0x0000, 0xffff, false, false);
}

uint16_t crc16_t10dif(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x8bb7, 0x0000, 0x0000, false, false);
}

uint16_t crc16_cms(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, false, false);
}

uint16_t crc16_modbus(uint8_t *data, uint8_t len)
{
  return crc16_common(data, len, 0x8005, 0xFFFF, 0x0000, true, true);
}

bool compare_buf(uint8_t *buf1, uint8_t *buf2)
{
  uint8_t x;

  for (x = 0; x < PACKET_LEN; x++)
  {
    if (buf1[x] != buf2[x])
      return true;
  }

  return false;
}

bool method_shuffle(uint8_t *buf)
{
  uint8_t i, j;
  uint16_t rnd;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  for (i = 0; i < PACKET_LEN; i++)
  {
    for (j = 0; j < 10; j++)
    {
      rnd = (uint16_t)rand();
      if (rnd % 7 == 0)
        buf[i] ^= (1 << (rnd % 8));
    }
  }

  return compare_buf(buf, copy);
}

bool method_bitshift(uint8_t *buf)
{
  uint8_t x, i, j;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  x = (uint8_t)(rand() % PACKET_LEN) + 1;
  for (j = 0; j < x; j++)
  {
    i = (uint8_t)(rand() % PACKET_LEN);
    if (buf[i] == 0)
      buf[i] = 0x01;
    else
      buf[i] <<= 1;
  }

  return compare_buf(buf, copy);
}

bool method_packetroll(uint8_t *buf)
{
  uint8_t x, i, j;
  uint8_t temp;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  x = (uint8_t)(rand() % (PACKET_LEN - 1)) + 1;
  for (j = 0; j < x; j++)
  {
    temp = buf[0];
    for (i = 0; i < PACKET_LEN - 1; i++)
      buf[i] = buf[i + 1];
    buf[PACKET_LEN - 1] = temp;
  }

  return compare_buf(buf, copy);
}

bool method_shiftright(uint8_t *buf)
{
  uint8_t i;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  for (i = 0; i < PACKET_LEN - 1; i++)
      buf[i + 1] = buf[i];
  buf[0] = 0xff;

  return compare_buf(buf, copy);
}

bool method_shiftleft(uint8_t *buf)
{
  uint8_t i;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  for (i = 0; i < PACKET_LEN - 1; i++)
      buf[i] = buf[i + 1];
  buf[PACKET_LEN - 1] = 0xff;

  return compare_buf(buf, copy);
}

bool method_zero(uint8_t *buf)
{
  uint8_t x, i, j;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  x = (uint8_t)(rand() % PACKET_LEN) + 1;
  for (j = 0; j < x; j++)
  {
    i = (uint8_t)(rand() % PACKET_LEN);
    if (buf[i] != 0x00)
      buf[i] = 0x00;
    else
      buf[i] = 0xFF;
  }

  return compare_buf(buf, copy);
}

bool method_one(uint8_t *buf)
{
  uint8_t x, i, j;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  x = (uint8_t)(rand() % PACKET_LEN) + 1;
  for (j = 0; j < x; j++)
  {
    i = (uint8_t)(rand() % PACKET_LEN);
    if (buf[i] != 0xFF)
      buf[i] = 0xFF;
    else
      buf[i] = 0x00;
  }

  return compare_buf(buf, copy);
}

bool method_injection(uint8_t *buf)
{
  uint8_t x, i;
  uint8_t copy[PACKET_LEN];

  memcpy(copy, buf, PACKET_LEN);

  x = (uint8_t)(rand() % PACKET_LEN);
  for (i = PACKET_LEN - 1; i > x; i--)
  {
    buf[i] = buf[i - 1];
  }
  buf[x] = (uint8_t)rand();

  return compare_buf(buf, copy);
}

bool method_single(uint8_t *buf)
{
  uint8_t x;

  x = (uint8_t)(rand() % (PACKET_LEN * 8));
  buf[(uint8_t)(x / 8)] ^= (1 << (x % 8));

  return true;
}

typedef struct
{
  uint16_t crc1;
  uint16_t crc2;
  uint32_t errors;
  uint16_t (*fn)(uint8_t *data, uint8_t len);
  char name[32];
} tCRC;

typedef struct
{
  bool (*fn)(uint8_t *buf);
  char name[32];
} tMethod;

static tMethod methods[] =
{
  {method_shuffle, "Shuffle"},
  {method_bitshift, "Bit shift"},
  {method_packetroll, "Roll packet"},
  {method_shiftright, "Right shift"},
  {method_shiftleft, "Left shift"},
  {method_zero, "Fill zeros"},
  {method_one, "Fill ones"},
  {method_injection, "Byte injection"},
  {method_single, "Single bit"}
};

static tCRC crcs[] =
{
  {0, 0, 0, crc16_cms, "CMS"},
  {0, 0, 0, crc16_ccitt, "CCITT"},
  {0, 0, 0, crc16_augccitt, "AUG-CCITT"},
  {0, 0, 0, crc16_bypass, "BYPASS"},
  {0, 0, 0, crc16_cdma2000, "CDMA2000"},
  {0, 0, 0, crc16_dds110, "DDS-110"},
  {0, 0, 0, crc16_dect, "DECT-X"},
  {0, 0, 0, crc16_en13757, "EN-13757"},
  {0, 0, 0, crc16_modbus, "Modbus"},
  {0, 0, 0, crc16_t10dif, "T10-DIF"},
  {0, 0, 0, crc16_teledisk, "TELEDISK"},
  {0, 0, 0, crc16_xmodem, "XMODEM"}
};

int main(int argc, char * argv[])
{
  uint32_t num_of_cycle;
  uint32_t num_of_sums;
  uint8_t packet[PACKET_LEN];
  uint8_t i;
  uint8_t m;
  //uint8_t buf[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};

  srand(time(NULL));

  printf("------------------------- CRC16 comparison -------------------------n");

  num_of_sums = sizeof(crcs) / sizeof(tCRC);
  for (m = 0; m < sizeof(methods) / sizeof(tMethod); m++)
  {
    printf("r%s:n", methods[m].name);

    for (i = 0; i < num_of_sums; i++)
    {
      crcs[i].errors = 0;
    }
    for (num_of_cycle = 0; num_of_cycle < NUM_OF_CYCLES; num_of_cycle++)
    {
      for (i = 0; i < PACKET_LEN; i++)
        packet[i] = (uint8_t)rand();

      for (i = 0; i < num_of_sums; i++)
        crcs[i].crc1 = crcs[i].fn(packet, PACKET_LEN);

      if (!methods[m].fn(packet))
        continue;

      for (i = 0; i < num_of_sums; i++)
      {
        crcs[i].crc2 = crcs[i].fn(packet, PACKET_LEN);
        if (crcs[i].crc1 == crcs[i].crc2)
          crcs[i].errors++;
      }
      if (num_of_cycle % 1000 == 0)
        printf("r%.2f%%", (float)num_of_cycle / NUM_OF_CYCLES * 100);
    }

    for (i = 0; i < num_of_sums; i++)
      printf("r  %20s: %10dn", crcs[i].name, crcs[i].errors);
  }

  return 0;
}

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

A CRC is not processor-intensive. All it adds to your exclusive-or is a table lookup. The operation on each byte is simply: crc = crc8_table[crc ^ *data++]. See below.

The downside of doing just an exclusive-or is that there are many simple errors that cancel each other, resulting in a false-positive check. A CRC is much better.

#include <stddef.h>

/* 8-bit CRC with polynomial x^8+x^6+x^3+x^2+1, 0x14D.
   Chosen based on Koopman, et al. (0xA6 in his notation = 0x14D >> 1):
   http://www.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
 */

static unsigned char crc8_table[] = {
    0x00, 0x3e, 0x7c, 0x42, 0xf8, 0xc6, 0x84, 0xba, 0x95, 0xab, 0xe9, 0xd7,
    0x6d, 0x53, 0x11, 0x2f, 0x4f, 0x71, 0x33, 0x0d, 0xb7, 0x89, 0xcb, 0xf5,
    0xda, 0xe4, 0xa6, 0x98, 0x22, 0x1c, 0x5e, 0x60, 0x9e, 0xa0, 0xe2, 0xdc,
    0x66, 0x58, 0x1a, 0x24, 0x0b, 0x35, 0x77, 0x49, 0xf3, 0xcd, 0x8f, 0xb1,
    0xd1, 0xef, 0xad, 0x93, 0x29, 0x17, 0x55, 0x6b, 0x44, 0x7a, 0x38, 0x06,
    0xbc, 0x82, 0xc0, 0xfe, 0x59, 0x67, 0x25, 0x1b, 0xa1, 0x9f, 0xdd, 0xe3,
    0xcc, 0xf2, 0xb0, 0x8e, 0x34, 0x0a, 0x48, 0x76, 0x16, 0x28, 0x6a, 0x54,
    0xee, 0xd0, 0x92, 0xac, 0x83, 0xbd, 0xff, 0xc1, 0x7b, 0x45, 0x07, 0x39,
    0xc7, 0xf9, 0xbb, 0x85, 0x3f, 0x01, 0x43, 0x7d, 0x52, 0x6c, 0x2e, 0x10,
    0xaa, 0x94, 0xd6, 0xe8, 0x88, 0xb6, 0xf4, 0xca, 0x70, 0x4e, 0x0c, 0x32,
    0x1d, 0x23, 0x61, 0x5f, 0xe5, 0xdb, 0x99, 0xa7, 0xb2, 0x8c, 0xce, 0xf0,
    0x4a, 0x74, 0x36, 0x08, 0x27, 0x19, 0x5b, 0x65, 0xdf, 0xe1, 0xa3, 0x9d,
    0xfd, 0xc3, 0x81, 0xbf, 0x05, 0x3b, 0x79, 0x47, 0x68, 0x56, 0x14, 0x2a,
    0x90, 0xae, 0xec, 0xd2, 0x2c, 0x12, 0x50, 0x6e, 0xd4, 0xea, 0xa8, 0x96,
    0xb9, 0x87, 0xc5, 0xfb, 0x41, 0x7f, 0x3d, 0x03, 0x63, 0x5d, 0x1f, 0x21,
    0x9b, 0xa5, 0xe7, 0xd9, 0xf6, 0xc8, 0x8a, 0xb4, 0x0e, 0x30, 0x72, 0x4c,
    0xeb, 0xd5, 0x97, 0xa9, 0x13, 0x2d, 0x6f, 0x51, 0x7e, 0x40, 0x02, 0x3c,
    0x86, 0xb8, 0xfa, 0xc4, 0xa4, 0x9a, 0xd8, 0xe6, 0x5c, 0x62, 0x20, 0x1e,
    0x31, 0x0f, 0x4d, 0x73, 0xc9, 0xf7, 0xb5, 0x8b, 0x75, 0x4b, 0x09, 0x37,
    0x8d, 0xb3, 0xf1, 0xcf, 0xe0, 0xde, 0x9c, 0xa2, 0x18, 0x26, 0x64, 0x5a,
    0x3a, 0x04, 0x46, 0x78, 0xc2, 0xfc, 0xbe, 0x80, 0xaf, 0x91, 0xd3, 0xed,
    0x57, 0x69, 0x2b, 0x15};

unsigned crc8(unsigned crc, unsigned char *data, size_t len)
{
    unsigned char *end;

    if (len == 0)
        return crc;
    crc ^= 0xff;
    end = data + len;
    do {
        crc = crc8_table[crc ^ *data++];
    } while (data < end);
    return crc ^ 0xff;
}

/* this was used to generate the table and to test the table-version

#define POLY 0xB2

unsigned crc8_slow(unsigned crc, unsigned char *data, size_t len)
{
    unsigned char *end;

    if (len == 0)
        return crc;
    crc ^= 0xff;
    end = data + len;
    do {
        crc ^= *data++;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
    } while (data < end);
    return crc ^ 0xff;
}
*/

#include <stdio.h>

#define SIZE 16384

int main(void)
{
    unsigned char data[SIZE];
    size_t got;
    unsigned crc;

    crc = 0;
    do {
        got = fread(data, 1, SIZE, stdin);
        crc = crc8(crc, data, got);
    } while (got == SIZE);
    printf("%02xn", crc);
    return 0;
}

A CRC is not processor-intensive. All it adds to your exclusive-or is a table lookup. The operation on each byte is simply: crc = crc8_table[crc ^ *data++]. See below.

The downside of doing just an exclusive-or is that there are many simple errors that cancel each other, resulting in a false-positive check. A CRC is much better.

#include <stddef.h>

/* 8-bit CRC with polynomial x^8+x^6+x^3+x^2+1, 0x14D.
   Chosen based on Koopman, et al. (0xA6 in his notation = 0x14D >> 1):
   http://www.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf
 */

static unsigned char crc8_table[] = {
    0x00, 0x3e, 0x7c, 0x42, 0xf8, 0xc6, 0x84, 0xba, 0x95, 0xab, 0xe9, 0xd7,
    0x6d, 0x53, 0x11, 0x2f, 0x4f, 0x71, 0x33, 0x0d, 0xb7, 0x89, 0xcb, 0xf5,
    0xda, 0xe4, 0xa6, 0x98, 0x22, 0x1c, 0x5e, 0x60, 0x9e, 0xa0, 0xe2, 0xdc,
    0x66, 0x58, 0x1a, 0x24, 0x0b, 0x35, 0x77, 0x49, 0xf3, 0xcd, 0x8f, 0xb1,
    0xd1, 0xef, 0xad, 0x93, 0x29, 0x17, 0x55, 0x6b, 0x44, 0x7a, 0x38, 0x06,
    0xbc, 0x82, 0xc0, 0xfe, 0x59, 0x67, 0x25, 0x1b, 0xa1, 0x9f, 0xdd, 0xe3,
    0xcc, 0xf2, 0xb0, 0x8e, 0x34, 0x0a, 0x48, 0x76, 0x16, 0x28, 0x6a, 0x54,
    0xee, 0xd0, 0x92, 0xac, 0x83, 0xbd, 0xff, 0xc1, 0x7b, 0x45, 0x07, 0x39,
    0xc7, 0xf9, 0xbb, 0x85, 0x3f, 0x01, 0x43, 0x7d, 0x52, 0x6c, 0x2e, 0x10,
    0xaa, 0x94, 0xd6, 0xe8, 0x88, 0xb6, 0xf4, 0xca, 0x70, 0x4e, 0x0c, 0x32,
    0x1d, 0x23, 0x61, 0x5f, 0xe5, 0xdb, 0x99, 0xa7, 0xb2, 0x8c, 0xce, 0xf0,
    0x4a, 0x74, 0x36, 0x08, 0x27, 0x19, 0x5b, 0x65, 0xdf, 0xe1, 0xa3, 0x9d,
    0xfd, 0xc3, 0x81, 0xbf, 0x05, 0x3b, 0x79, 0x47, 0x68, 0x56, 0x14, 0x2a,
    0x90, 0xae, 0xec, 0xd2, 0x2c, 0x12, 0x50, 0x6e, 0xd4, 0xea, 0xa8, 0x96,
    0xb9, 0x87, 0xc5, 0xfb, 0x41, 0x7f, 0x3d, 0x03, 0x63, 0x5d, 0x1f, 0x21,
    0x9b, 0xa5, 0xe7, 0xd9, 0xf6, 0xc8, 0x8a, 0xb4, 0x0e, 0x30, 0x72, 0x4c,
    0xeb, 0xd5, 0x97, 0xa9, 0x13, 0x2d, 0x6f, 0x51, 0x7e, 0x40, 0x02, 0x3c,
    0x86, 0xb8, 0xfa, 0xc4, 0xa4, 0x9a, 0xd8, 0xe6, 0x5c, 0x62, 0x20, 0x1e,
    0x31, 0x0f, 0x4d, 0x73, 0xc9, 0xf7, 0xb5, 0x8b, 0x75, 0x4b, 0x09, 0x37,
    0x8d, 0xb3, 0xf1, 0xcf, 0xe0, 0xde, 0x9c, 0xa2, 0x18, 0x26, 0x64, 0x5a,
    0x3a, 0x04, 0x46, 0x78, 0xc2, 0xfc, 0xbe, 0x80, 0xaf, 0x91, 0xd3, 0xed,
    0x57, 0x69, 0x2b, 0x15};

unsigned crc8(unsigned crc, unsigned char *data, size_t len)
{
    unsigned char *end;

    if (len == 0)
        return crc;
    crc ^= 0xff;
    end = data + len;
    do {
        crc = crc8_table[crc ^ *data++];
    } while (data < end);
    return crc ^ 0xff;
}

/* this was used to generate the table and to test the table-version

#define POLY 0xB2

unsigned crc8_slow(unsigned crc, unsigned char *data, size_t len)
{
    unsigned char *end;

    if (len == 0)
        return crc;
    crc ^= 0xff;
    end = data + len;
    do {
        crc ^= *data++;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
        crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1;
    } while (data < end);
    return crc ^ 0xff;
}
*/

#include <stdio.h>

#define SIZE 16384

int main(void)
{
    unsigned char data[SIZE];
    size_t got;
    unsigned crc;

    crc = 0;
    do {
        got = fread(data, 1, SIZE, stdin);
        crc = crc8(crc, data, got);
    } while (got == SIZE);
    printf("%02xn", crc);
    return 0;
}

Некий датчик температуры и влажности «три в одном» от Taobao реализует последовательную связь RS485

1. Введение в протокол связи в руководстве

2. Объяснение случая

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

1. Обнаружение температуры и влажности

Мы отправляем 010300000002 в рамке запроса и возвращаем 01030401BE011C9BB2

Объяснение возвращенных данных:

01: адрес устройства

03: протокол связи Modbus_RTU означает чтение регистров

04: число возвращаемых четырех байтов, влажность 2 байта + температура 2 байта

01BE: 0x01BE = 446 (десятичное число) -> 44,6% RH (влажность)

011C: 284 (десятичное) -> 28.4C (температура)

9BB2: контрольный код CRC

2. Обнаружение света

ВотНеверно говорить, что он считывает адресные данные 0006. В пункте 4.3 выше упоминается, что легкий адрес — 0004., Теперь мы отправим данные кадра запроса, определенные следующим образом

010300040001, обратный ответный кадр 01030200123849

0012: 0x0012 = 18 (освещенность), единица измерения люкс

Я положил датчик под стол, чтобы освещение было относительно низким.

3. Обнаружение температуры и влажности

Я отправляю данные кадра запроса как 010300000006 и возвращаю 01030C01C1011B00F1BB0010008986

01C1: влажность,

011B: температура

0000: CO2 (нет, поэтому отображается как 0)

F1BB: адрес регистра 0x0003 не определен, поэтому данные не в порядке

0010: свет

0000:PM2.5(Нет, поэтому отображается как 0

8986: проверка CRC

Три, проверка CRC

// Проверка CRC 1
static const UCHAR aucCRCHi[] = {
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40
};

static const UCHAR aucCRCLo[] = {
	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
	0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
	0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
	0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
	0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
	0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
	0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
	0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
	0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
	0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
	0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
	0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
	0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
	0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
	0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
	0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
	0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
	0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
	0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
	0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
	0x41, 0x81, 0x80, 0x40
};
USHORT MBCRC16(UCHAR *pucFrame, USHORT usLen)
{
	UCHAR           ucCRCHi = 0xFF;
	UCHAR           ucCRCLo = 0xFF;
	int             iIndex;
	while (usLen--)
	{
		iIndex = ucCRCLo ^ *(pucFrame++);
		ucCRCLo = (UCHAR)(ucCRCHi ^ aucCRCHi[iIndex]);
		ucCRCHi = aucCRCLo[iIndex];
	}
	return (USHORT)(ucCRCHi << 8 | ucCRCLo);
}

Для кадра запроса мы отправляем 8 байтов (6 + 2), фактические 6 байтов плюс 2 контрольных бита CRC,Проверка CRC — проверка первых 6 данных,

Тогда получите 2 байта

void CCSerialPortCRCDlg::OnBnClickedBtSend()
{
	
	//UpdateData(TRUE);
	 // TODO: Добавить код обработчика уведомлений управления здесь
	
	 BYTE bytSend [8]; // Отправить строку 
	int   i;
	CByteArray   arraySend;
	CString m_input;
	m_TXD.GetWindowText(m_input);	
	 xString2Byte (bytSend, 6, m_input); // строка cstring в байтовый массив
	/*bytSend[0] = 0x01;
	bytSend[1] = 0x03;
	bytSend[2] = 0x00;
	bytSend[3] = 0x00;
	bytSend[4] = 0x00;
	bytSend[5] = 0x07;*/
	 USHORT CRC = MBCRC16 ((UCHAR *) bytSend, 6); // проверка CRC
	bytSend[6] = (CHAR)(CRC & 0xFF);
	bytSend[7] = (CHAR)((CRC & 0xFF00) >> 8);

	arraySend.RemoveAll();
	arraySend.SetSize(8);
	for (i = 0; i<8; i++)
	{
		 arraySend.SetAt (i, bytSend [i]); // Данные хранятся в arraySend   
	}

	m_Comm.put_Output(COleVariant(arraySend));


}

Исходный код хост-компьютера был выпущен на нем. Используется элемент управления VS2015 + mscomm.ocx. Сначала необходимо зарегистрировать элемент управления, в противном случае при прямой компиляции исходного кода будет сообщено об ошибке. Сначала вы можете понять, что такое последовательное программирование.Два вида программирования порта последовательной связи

Исправление: кадр ответа в приведенном выше исходном коде не проверен. Пожалуйста, поймите, когда вы видите его здесь. Код размещен здесь. Например, кадр ответа получает 9 байтов, затем проверка проверяет первые 7 байтов с помощью функции CRC. Являются ли выходные 2 байта равными байту [7] байту [8] кадра ответа.

void CCSerialPortCRCDlg::OnCommMscomm1()
{
	// TODO: добавить код обработчика сообщений здесь
	m_RXD.SetWindowText(_T(" "));
	 UpdateData(TRUE);
	VARIANT Input;
	CString temp;
	long k;
	int len = 0;
	 COleSafeArray OleArray; // Создайте пустой массив любого типа.
	//BYTE rxdata[141];
	if (2 == m_Comm.get_CommEvent())
	{
		 Input = m_Comm.get_Input (); // чтение буфера 
		 OleArray = Input; // переменная типа VARIANT преобразуется в переменную типа ColeSafeArray
		 len = OleArray.GetOneDimSize (); // Получить количество полученных символов. Вы также можете использовать m_com.GetInBufferCount (), чтобы получить количество символов.
		byte *rxdata=new byte[len];
		rxdata = (byte *)malloc(sizeof(byte)*len);
		for (k = 0; k<len; k++)
		{
			 OleArray.GetElement (& k, rxdata + k); // Элемент Kth в массиве OleArray назначается элементу Kth в массиве BYTE.
			
		
			
			 BYTE bt = * (char *) (rxdata + k); // тип символа преобразования элемента байтового массива
			 temp.Format (_T ("% 02X"), bt); // вывести два шестнадцатеричных значения
			m_rxd += temp;
		}
	 
		 USHORT CRC = MBCRC16 ((UCHAR *) rxdata, len-2); // проверка CRC
		rxdata[len - 2] = (CHAR)(CRC & 0xFF);
		rxdata[len - 1] = (CHAR)((CRC & 0xFF00) >> 8);
		temp.Format(_T("%02x"), rxdata[len-2]);
		AfxMessageBox(temp);
		temp.Format(_T("%02x"), rxdata[len-1]);
		AfxMessageBox(temp);
		delete rxdata;
	}
	  UpdateData (FALSE); // Значение переменной-члена отражается в элементе управления. Когда TRUE, значение, введенное пользователем в элементе управления, присваивается переменной-члену


}

Во время обмена
данными могут возникать ошибки двух
типов:

  • ошибки, связанные
    с искажениями при передаче данных;

  • логические ошибки.

Ошибки первого
типа обнаруживаются при помощи фреймов
символов, контроля чётности и циклической
контрольной суммы CRC-16-IBM
(используется число-полином
= 0xA001).

Rtu фрейм

В RTU режиме сообщение
должно начинаться и заканчиваться
интервалом тишины — временем передачи
не менее 3.5 символов при данной скорости
в сети. Первым полем затем передаётся
адрес устройства.

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

Фрейм сообщения
передаётся непрерывно. Если интервал
тишины продолжительностью 1.5 возник во
время передачи фрейма, принимающее
устройство должно игнорировать этот
фрейм как неполный.

Таким образом,
новое сообщение должно начинаться не
раньше 3.5 интервала, т.к. в этом случае
устанавливается ошибка.

Немного об интервалах
(речь идёт о Serial Modbus RTU): при скорости
9600 и 11 битах в кадре (стартовый бит + 8
бит данных + бит контроля чётности +
стоп-бит): 3.5 * 11 / 9600 = 0,00401041(6), т.е. более
4 мс; 1.5 * 11 / 9600 = 0,00171875, т.е. не более 1 мс.
Для скоростей более 19200 бод допускается
использовать интервалы 1,75 и 0,75 мс
соотвественно.

Логические ошибки

Для сообщений об
ошибках второго типа протокол Modbus RTU
предусматривает, что устройства могут
отсылать ответы, свидетельствующие об
ошибочной ситуации. Признаком того, что
ответ содержит сообщение об ошибке,
является установленный старший бит
кода команды. Пример кадра при выявлении
ошибки ведомым устройством, в ответ на
запрос приведён в (Таблица 2-1).

1. Если Slave принимает
корректный запрос и может его нормально
обработать, то возвращает нормальный
ответ.

2. Если Slave не
принимает какого либо значения, никакого
ответа не отправляется. Master диагностирует
ошибку по таймауту.

3. Если Slave принимает
запрос, но обнаруживает ошибку (parity,
LRC, or CRC), никакого ответа не отправляется.
Master диагностирует ошибку по таймауту.

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

Направление
передачи

адрес
подчинённого устройства

номер
функции

данные
(или код ошибки)

CRC

Запрос
(Master→Slave)

0x01

0x77

0xDD

0xC7
0xA9

Ответ
(Slave→Master)

0x01

0xF7

0xEE

0xE6
0x7C

Таблица
2-1. Кадр ответа (Slave→Master) при возникновении
ошибки modbus RTU

Стандартные коды ошибок

01
Принятый код функции не может быть
обработан на подчиненном.

02
Адрес данных указанный в запросе не
доступен данному подчиненному.

03
Величина содержащаяся в поле данных
запроса является не допустимой

величиной
для подчиненного.

04
Невосстанавливаемая ошибка имела место
пока подчиненный пытался выполнить

затребованное
действие.

05
Подчиненный принял запрос и обрабатывает
его, но это требует много времени.

Этот
ответ предохраняет главного от генерации
ошибки таймаута.

06
Подчиненный занят обработкой команды.

Главный
должен повторить сообщение позже, когда
подчиненный освободится.

07
Подчиненный не может выполнить программную
функцию, принятую в запросе.

Этот
код возвращается для неудачного
программного запроса, использующего

функции
с номерами 13 или 14.

Главный
должен запросить диагностическую
информацию или информацию об

ошибках
с подчиненного.

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Ошибка crc age of empires 3
  • Ошибка crash dump error как исправить state of decay
  • Ошибка crash dump and log files were created in game directory
  • Ошибка cpu при запуске компьютера
  • Ошибка cpu на материнской плате msi

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии