Библиотека Tiny_MODBUS_RTU_Slave

Обсуждение и вопросы по урокам Ардуино
Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 23 янв 2018, 20:55

Добрый день.
Применил эту библиотеку для передачи данных на Master SCADA, всё работает. Но захотелось большего, а именно сделать то же самое, но через WiFi. Естественно, в качестве устройства был выбран модуль ESP8266 (NodeMCU). Поскольку в данном случае UART не используется, возникла необходимость перенести функции библиотеки на другую платформу. Начал с "убирания всего лишнего". Убрал широковещательные дела, запись одного и нескольких регистров. В общем, оставил только обработку функции 0x03, то есть чтение регистров. И тут возникла проблема. У меня не работает вот этот кусок кода:

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

for(unsigned int i=0; i<num; i++) {
                  _frameBuffer[i*2 + 3] = (byte)((*(_holdingRegTable + adr)) >> 8);
                  _frameBuffer[i*2 + 4] = (byte)(*(_holdingRegTable + adr));
                  adr++;                 
                }

Если сделать так:

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

for(unsigned int i=0; i<num; i++) {
                 Serial.print ( "dataH = " ); Serial.println (( *(holdingRegisters + adr))>>8 );
                 Serial.print ( "dataL = " ); Serial.println ( *(holdingRegisters + adr) );

                  _frameBuffer[i*2 + 3] = (byte)((*(_holdingRegTable + adr)) >> 8);
                  _frameBuffer[i*2 + 4] = (byte)(*(_holdingRegTable + adr));
                 
                 Serial.print ( "_frameBuffer[" ); Serial.print ( i*2+3 );
                 Serial.print ( "] = " ); Serial.println ( _frameBuffer[i*2+3] );
                 Serial.print ( "_frameBuffer[" ); Serial.print ( i*2+4 );
                 Serial.print ( "] = " ); Serial.println ( _frameBuffer[i*2+4] );                 
                 adr++;                 
}

то dataH и dataL выводятся ненулевые, а вот данные из _frameBuffer - нули. Поэтому ответ на запрос формируется неправильно.
Опыта очень мало, язык C++ совсем не знаю, поможите! :)


Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 23 янв 2018, 21:00

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

Эдуард
Администратор
Сообщения: 484
Зарегистрирован: 30 окт 2016, 20:53

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Эдуард » 23 янв 2018, 23:32

Здравствуйте!
Не понятно, данные должны быть одинаковые. Сделайте формально одинаковые выражения в скобках println (во 2й и 3й строке) и во второй части _frameBuffer[i*2 + 3] =. Они у вас отличаются.

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 24 янв 2018, 15:23

Я не совсем понимаю, что значит "формально одинаковые" выражения.

Сделал вот что:

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

Serial.print ( "Reg_adr = " ); Serial.println ( adr );
Serial.print ( "Data = " ); Serial.println ( *( holdingRegisters + adr ), HEX );
Serial.print ( "dataH = " ); Serial.println ( (byte) ( ( *( holdingRegisters + adr ) ) >> 8 ), HEX );
Serial.print ( "dataL = " ); Serial.println ( (byte)   ( *( holdingRegisters + adr )), HEX );

И получил в Мониторе следующее:

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

Reg_adr = 6
Data = C2FE0000
dataH = 0
dataL = 0
sbuf[15] = 0
sbuf[16] = 0


* sbuf - то же самое, что _frameBuffer
Теперь понятно, почему в буфер приходят нули: считывается из регистра хранения не 16-разрядное число, а 32-разрядное. А вот почему так происходит? Массив holdingRegisters объявлен как unsigned int.

Эдуард
Администратор
Сообщения: 484
Зарегистрирован: 30 окт 2016, 20:53

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Эдуард » 24 янв 2018, 15:37

Здравствуйте!
Формально одинаковые значит одинаковые. В одном случае есть явное преобразование типа (byte), в другом нет и т.п.

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 24 янв 2018, 15:45

Теперь ясно. Ну, примерно это я и сделал в предыдущем сообщении. Число C2FE0000 соответствует -127.0, и так и должно быть.

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 24 янв 2018, 22:17

Здравствуйте ещё раз! :)

Мои изыскания пришли к тому, что неправильно записываются данные в регистры хранения. Я взял из скетча 57_4 такой код:

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

* (float *)holdingRegisters  = temperature1;
* (float *)(holdingRegisters+2)  = temperature2;
и т. д.

Значения температуры - float, как и в упомянутом скетче 57_4.
И так у меня не работает. Но если я в явном виде записываю в holdingRegisters данные пословно (0x1234, 0x5678 и т. д.), то всё начинает работать как надо. Где моя ошибка?

Эдуард
Администратор
Сообщения: 484
Зарегистрирован: 30 окт 2016, 20:53

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Эдуард » 25 янв 2018, 01:42

Здравствуйте!
В принципе все правильно. Если holdingRegisters указатель на int или имя массива int. Преобразуете на указатель типа float. Дальше запись по адресу указателя float 4 байта из temperature1.
Проверяйте, что в temperature1.

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 25 янв 2018, 12:53

Добрый день!
Специально для тестирования обрезал программу до минимума. Только необходимые вычисления и вывод данных на Монитор. Тестирование заключается в различных способах занесения данных в регистры MODBUS.

Способ № 1: после (как бы) получения температур с DS18B20 (они уже float) заносим их с использованием указателей:

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

* (float *) holdingRegisters  = 24.76;
* (float *) ( holdingRegisters + 2 ) = 54.94;
* (float *) ( holdingRegisters + 4 ) = 78.63;
* (float *) ( holdingRegisters + 6 ) = 90.31;


Способ № 2: заносим данные в holdingRegisters непосредственно при инициализации массива в виде 16-разрядных слов:

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

unsigned int holdingRegisters[8] { 0x41C6, 0x147B, 0x425B, 0xC28F, 0x429D, 0x428F, 0x42B4, 0x9EB8 };


Данные в обоих случаях одинаковые, только форматы разные.

В первом случае операции

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

sbuf[i * 2 + 3] = ( byte ) ( ( *( holdingRegisters + adr ) ) >> 8 );
sbuf[i * 2 + 4] = ( byte ) ( *( holdingRegisters + adr ) );

выполняются неправильно, так как значение выражения

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

*( holdingRegisters + adr )

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

Во втором случае всё проходит штатно, так как значение этого выражения получается 16-разрядным.

Получаемые от DS18B20 данные уже float, так как используется библиотека DallasTemperature.
Вложения
Test.zip
(2.14 КБ) 40 скачиваний

Эдуард
Администратор
Сообщения: 484
Зарегистрирован: 30 окт 2016, 20:53

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Эдуард » 26 янв 2018, 22:33

Здравствуйте!
Вот программа, демонстрирующая работу с указателями.

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

int holdingRegisters[10];
byte sbuf[10];

void setup() {
  Serial.begin(9600);

  * (float *) holdingRegisters  = 24.76;

  sbuf[0] =  * (byte *)holdingRegisters;
  sbuf[1] =  * ((byte *)holdingRegisters +1);
  sbuf[2] =  * ((byte *)holdingRegisters +2);
  sbuf[3] =  * ((byte *)holdingRegisters +3);

  Serial.print("holdingRegisters= ");
  Serial.print(holdingRegisters[0], HEX);
  Serial.print(" ");
  Serial.println(holdingRegisters[1], HEX);

  Serial.print("sbuf= ");
  Serial.print(sbuf[0], HEX);
  Serial.print(" ");
  Serial.print(sbuf[1], HEX);
  Serial.print(" ");
  Serial.print(sbuf[2], HEX);
  Serial.print(" ");
  Serial.print(sbuf[3], HEX); 
}

void loop() {
}


Вот, что она печатает.
Снимок.PNG

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 28 янв 2018, 10:13

Добрый день!
Понимаю, что работать должно, ибо вроде бы правильно написано. Но не работает. Пока я не сделал запись в holdingRegisters так:

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

holdingRegisters[1] = ( * ( (unsigned int*)(&temp1) ) >> 16 );
holdingRegisters[0] = ( * ( (unsigned int*)(&temp1) ) & 0x0000FFFF );

не заработало.
Но это ещё полбеды. Если вы заметили, я записываю "половинки" температуры в обратной последовательности, то есть в "нулевой" регистр - младшее слово, в "первый" - старшее. Иначе не работает MODBUS! Пришлось подсмотреть, какие ответы приходят на запросы с обычного, UART'ного модуля. А в нём ваша библиотека, которая изначально рабочая, а в ней последовательность записи слов обычная. Как? :o

PS Немного оффтоп, но имеет отношение к языку C++. Оказывается, если какую-либо переменную объявить не с самого начала, до кода, а прямо внутри кода, то она считается объявленной только в пределах фигурных скобок, внутри которых она объявлена. Может, для профессиональных C++ программистов это обычное дело, а я был разъярён :twisted:

Есть ещё пара вопросов, но они касаются исключительно ESP8266. Имеет ли смысл задавать их здесь?

Эдуард
Администратор
Сообщения: 484
Зарегистрирован: 30 окт 2016, 20:53

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Эдуард » 28 янв 2018, 19:05

Я бы сделал так

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

  holdingRegisters[0] = * ((int *)(& temp1));
  holdingRegisters[1] = * ((int *)(& temp1) +1);

или так

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

  * (float *)(holdingRegisters) = temp1;

Что касается объявления переменных. Существует понятие - область видимости переменных. Это очень удобно. Позволяет использовать динамические переменные, не задумываясь об этом.

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

Papazol
Сообщения: 8
Зарегистрирован: 23 янв 2018, 20:08

Re: Библиотека Tiny_MODBUS_RTU_Slave

Сообщение Papazol » 28 янв 2018, 20:13

Я интуитивно понял, что в каком формате число было записано в регистр, в таком оно и будет считываться. Если эти форматы разные, то не работает. Возможно, это особенность именно ESP8266?
А с порядком следования слов-то как? Я использую для опроса модуля OPC-сервер от Овна, файл конфигурации одинаковый что для RTU, что для TCP (я делаю RTU поверх TCP, так проще). Опция "Использовать перестановку байтов в значении" включена по умолчанию.

Пока урок по ESP8266 пишется, всё-таки спрошу такую вещь. Есть такая функция в библиотеке ESP8266WiFi, как client.write(). Она по описанию может иметь два варианта: client.write(anybyte) и client.write (buffer, lenght). Если нужно отправить клиенту сообщение, состоящее из нескольких байт (типа ответа MODBUS), то теоретически можно ведь сделать так:

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

for (i=0; i<lenght; i++)
{ client.write(buffer[i]); }

Но при этом клиент принимает только 8 байт, сколько бы их не было отправлено. Соответственно, OPC-сервер не принимает такое сообщение.

Работает только такая конструкция:

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

uint8_t buffer[lenght];
client.write(buffer, lenght);


Здесь важно, чтобы объявленная длина буфера (lenght) была в точности равна количеству байтов, которое нужно отправить. Поэтому объявление массива buffer приходится делать прямо в коде, мы ведь заранее не знаем, какой длины получится сообщение. Я сначала думал, что в функции client.write(buffer, lenght) переменная lenght - это просто количество байтов, которое нужно взять из буфера и передать. А оказалось, что нет. Поэтому мне пришлось завести два буфера: один для приёма запроса MODBUS и формирования ответа, а второй - для передачи ответа клиенту. Перед самой передачей приходится, зная количество байт в ответе, объявлять временный буфер с известной длиной, перекладывать в него байты из первого буфера и потом отправлять. Понимаю, что это костыль, поэтому и вопросы:
1. Почему не работает последовательная передача побайтно?
2. Как лучше быть с буфером для передачи?


Вернуться в «Уроки Ардуино»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 5 гостей