Описание протокола Modbus модулей ZETSENSOR

В устройствах ZETSENSOR реализован открытый коммуникационный протокол Modbus со стандартным набором команд. Данные, необходимые для работы с устройством хранятся в его внутренней памяти в структурах языка C. Чтобы прочесть эти структуры, используются стандартные команды протокола Modbus для чтения (Read Holding Registers и Read Input Registers). Чтение производится с нулевого адреса.

Каждая структура в памяти устройства имеет общий заголовок:

typedef struct _STRUCT_HEAD
{
          unsigned int           size : 12;                // Размер текущей структуры
          STRUCT_TYPE      struct_type : 10;     // Тип текущей структуры
          unsigned int           status : 10;             // Статус канала (ошибка)
          unsigned int           write_enable;          // Разрешение записи в структуру
} STRUCT_HEAD, *pSTRUCT_HEAD;

После чтения данных из устройства, пользователь получает список всех структур устройства. Структуры расположены друг за другом байт за байтом, разделитель между структурами — пустая структура STRUCT_HEAD (size равен 0), признак конца списка структур — две пустые структуры STRUCT_HEAD. Пользователю необходимо перечислить их всех и найти среди них структуры, STRUCT_TYPE которых соответствует типу CHANNEL_PAR_STRUCT:

typedef enum
{
         CHANNEL_PAR_STRUCT = 0x0D0,    // структура канала
} STRUCT_TYPE;

typedef struct _CHANNEL_PAR                  // Структура, определяющая параметры канала
{
         STRUCT_HEAD head;                       // Заголовок структуры
         float value;                                        // Текущее значение канала
         float freq;                                          // Частота обновления выходного сигнала (мс)
         char measure[8];                               // Единица измерения
         char channel_name[32];                    // Наименование канала — текст
         float min_level;                                 // Минимальный уровень (в ед. изм.)
         float max_level;                                // Максимальный уровень (в ед. изм.)
         float reference;                                 // Опора для расчета дБ (в ед. изм.)
         float sense;                                     // Чувствительность датчика В/е.и.
         float resolution;                                // Разрешающая способность сигнала
} CHANNEL_PAR, *pCHANNEL_PAR;

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

Структура пакета потоковых данных ZETSENSOR

Контроллеры на шине Modbus взаимодействуют, используя клиент-серверную модель, основанную на транзакциях, состоящих из запроса и ответа. Далее подробно описывается содержимое Modbus пакета.

Таблица 1. Общая структура команд протокола Modbus

Адрес ведомого устройства Код функции Данные Блок обнаружения ошибок

Таблица 2. Структура команд протокола Modbus-ZELAB

Тип поля Количество байт под поле Номер байт Расшифровка байт
Адрес ведомого устройства 1 0
Код функции 1 1
Адрес обращения 2 2 — 3 2 — high 
3 — low
Квант обращения 2 4 — 5 4 — high 
5 — low
Количество байт обращения 1 6
Данные
CRC16 2 N-1 — N

N-1 — high 
N — low

Адрес ведомого устройства

ZET7070-RS
Схема подключения устройств ZETSENSOR к сети

Каждый датчик имеет свой уникальный адрес в сети. Для MASTER устройств (модули преобразования интерфейсов) этот адрес — 1. Для SLAVE устройств этот адрес может принимать любое значение от 2 до 63.

Примечание: преобразователь интерфейса USB ↔ RS-485 (ZET 7070) не имеет адреса, т.к. он преобразует интерфейсы на аппаратном уровне, поэтому SLAVE устройства на шине RS-485 в общем случае могут иметь адрес 1.

Коды функций протокола Modbus-ZELAB

Таблица 3. Коды функций протокола Modbus-ZELAB

0х03

Команда чтения структуры данных (значения)

0х06

Команда чтения структуры данных (описателей)

0х04

Команда запроса потоковых данных (float)

0х14

Команда запроса потоковых данных (short)

0х10

Команда записи данных

Команда чтения структуры данных (значения (0x03) или описателей (0x06))

Данные команды предназначены для чтения общей информации о датчике.

Структура команды

  • Адрес обращения – адрес, по которому начинаем считывать данные; 
  • Квант обращения – количество данных, которое хотим считать (в словах (слово – 2 байта)); 
  • Кол-во байт для обращения – количество данных, которое хотим считать в байтах; 
  • Данные – данное поле принимает значение только в ответе на данную команду. Хранит в себе считанные данные.

Команда запроса потоковых данных (float (0x04) или short (0x14))

Данные команды предназначены для чтения данных с датчика. Команда 0x04 – данные выравниваются по границе в 4 байта, 0x14 – по границе в 2 байта.

Нода – конкретное устройство, имеющее свой уникальный адрес в сети. В устройствах на шине RS485, одна нода может иметь несколько каналов, в отличии, от устройств на шине CAN, где каждый канал имеет свой адрес ноды.

Структура команды

Для CAN:

  • Адрес обращения – не имеет значения;
  • Квант обращения – размер данных, которые хотим считать в словах;
  • Кол-во байт для обращения – количество данных, которые хотим считать в байтах;
  • Данные – данное поле принимает значение только в ответе на данную команду.

Для RS485:

  • Адрес обращения – адрес на поле “значение канала (dev_structure.channel.value)”, в структуре “канал (dev_structure.channel)”;
  • Квант обращения – размер данных, которые хотим считать в словах;
  • Кол-во байт для обращения – количество данных, которые хотим считать в байтах;
  • Данные – данное поле принимает значение только в ответе на данную команду.

Команда записи данных (0x10)

Данная команда предназначена для записи данных, изменения конфигурации датчика.

Структура команды

  • Адрес обращения – адрес, по которому начинаем записывать данные;
  • Квант обращения – количество данных, которое хотим записать (в словах);
  • Кол-во байт для обращения – количество данных, которое хотим записать в байтах;
  • Данные – хранит в себе записываемые данные.

Блок обнаружения ошибок

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

Используемый алгоритм CRC16

static unsigned char CRCHi[] = {
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 char CRCLo[] = {
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
};

unsigned short CRC16(unsigned char *pBuff, int len)
{
    unsigned char Hi = 0xFF;
    unsigned char Lo = 0xFF;
    unsigned TableIdx;
 
    while(len—)
    {
        TableIdx = Hi ^ *pBuff++;

        Hi = Lo ^ CRCHi[TableIdx];
        Lo = CRCLo[TableIdx];
    }
    return (Hi << 8 | Lo); } Пример команды запроса:

0x02 0x04 0x00 0x04 0x00 0x04 0xb0 0x3b

Данная команда запрашивает от устройства с адресом 2, данные из 1-го канала, 4 слова (8 байт). 

  • 0x02 — адрес устройства
  • 0x04 — команда 
  • 0x00 0x04 — адрес обращения (high low) 
  • 0x00 0x04 — количество слов (high low ) 
  • 0xb0 0x3b — контрольная сумма

Структура ответного пакета на команду запроса потоковых данных

Тип поля

Количество байтов под поле

Номер первого байта

Значение поля

Адрес ведомого устройства

1

0

адрес отвечающего устройства

Код функции

1

1

0х04

Адрес обращения

2

2

адрес обращения (high low)

Количество байт N*2

1

4

N*2

Данные

N*2

5

данные

CRC16

2

5+N*2

контрольная сумма (CRC16)

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

  1. Размер передаваемых данных измеряется не в словах, а в байтах;
  2. Появилось поле данные (данные идут в формате float).

Ответ на команду запроса из примера

Команда запроса была: 0x02 0x04 0x00 0x04 0x00 0x04 0xb0 0x3b

  • 0x02 0x04 0x08 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x2b 0x49 
  • 0x02 — адрес отвечающего устройства 
  • 0x04 — команда 
  • 0x08 — количество переданных байт 
  • 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 — данные 
  • 0x2b 0x49 — контрольная сумма

Полезная информация

Настройки по умолчанию

Для USB-RS-485: 

  • Baud rate: 57600 
  • Parity Enable: 1 

Для USB-CAN: 

  • Baud rate: 1500000 
  • Parity Enabe: 1 
  • CAN baud rate: 1000

См. также: 

  • OPC в аппаратно-программном комплексе ZELAB.
  • ModbusOPC — сервер данных цифровых датчиков,
  • Обмен данными с OPC,
  • Настройка OPC.