Передача данных по Modbus TCP/ip. Число с запятой

ШTopor
Offline
Зарегистрирован: 22.12.2017

Доброго времени суток, господа, таварищи, соплеменники.

Возникла необходимость передавать показания датчиков температуры, датчиков рН по Modbus TCP\ip.

Впринципе передача ведется, но только целыми числами, а мне ну очень нужно точные показания с десятими

Как преобразовать переменные температуры и рН чтобы в регистре Modbus TCP\ip увидеть число с заятой

Собственно описание всего проекта, ардуино МЕГА измеряет характеристики растворов и передает эти данные в сенсорную панель ОВЕН СПК

вот код-франкенштейн ардуино (рН снимает чушь, далекую от реальности, но суть не в этом)

#define sensorPin8  A8
#define sensorPin9  A9
#define Offset           -0.00 

#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
#include <Mudbus.h>
#include <DallasTemperature.h>

Mudbus Mb;
//Function codes 1(read coils), 3(read registers), 5(write coil), 6(write register)
//signed int Mb.R[0 to 125] and bool Mb.C[0 to 128] MB_N_R MB_N_C
//Port 502 (defined in Mudbus.h) MB_PORT


#define samplingInterval 20                                                                   // Интервал в мс между измерениями
#define printInterval    800                                                                  // Интервал в мс между выводами показаний
#define ArrayLenth       40                                                                   // Количество выборок

int pHArray[ArrayLenth];                                                                      // Массив для определения среднего показания напряжения считанного с датчика
int pHArrayIndex=0;
float pHValue8;
float pHValue9;
float t1;
float t2;

OneWire oneWire(43); // вход датчика температуры ванна №1
DallasTemperature ds(&oneWire);
 

DeviceAddress sensor1 = {0x28, 0xFF, 0x60, 0x89, 0xA4, 0x16, 0x03, 0xDF};
DeviceAddress sensor2 = {0x28, 0xFF, 0x53, 0x08, 0xA5, 0x16, 0x03, 0xE8};



int pos = 0; 
byte mac[] = { 0xDE, 0xAA, 0xBE, 0xEA, 0xFE, 0xEE };   //physical mac address
byte ip[] = { 192, 168, 175, 213 };                      // ip in lan (that's what you need to use in your browser. ("192.168.1.178")
byte gateway[] = { 192, 168, 175, 254 };                   // internet access via router
byte subnet[] = { 255, 255, 255, 0 };                  //subnet mask
EthernetServer server(80);                             //server port     
String readString;



void setup() {

 // Open serial communications and wait for port to open:
  Serial.begin(9600);
     
  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip, gateway, subnet);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}


void loop() {
  Mb.Run();
  Mb.R[0]=pHValue8;
  Mb.R[1]=pHValue9;
  Mb.R[2]=t1;
  Mb.R[3]=t2;

   static unsigned long samplingTime = millis();                                             // Определяем переменную samplingTime для хранения времени прошедшего с момента старта (переменная создаётся при первом проходе цикла loop и не теряется по его завершении)
    static unsigned long printTime    = millis();                                             // Определяем переменную printTime    для хранения времени прошедшего с момента старта (переменная создаётся при первом проходе цикла loop и не теряется по его завершении)
    static float pHValue8, voltage;                                                            // Объявляем переменные для хранения значений напряжения и pH
//  Проводим измерения:                                                                       //
    if(millis() - samplingTime > samplingInterval){                                           // Выполняем код в теле оператора if через каждые samplingInterval мс
        pHArray[pHArrayIndex++] = analogRead(A8);                                      // Читаем данные в очередной элемент массива pHArray
        if(pHArrayIndex==ArrayLenth) pHArrayIndex=0;                                          // Если достигли последнего элемента массива pHArray, то сбрасываем номер текущего элемента этого массива в 0
        voltage = averagearray(pHArray, ArrayLenth) * 5.0 / 1023;                             // Получаем среднее напряжение в мВ из массива напряжений pHArray
        pHValue8 = 3.5 * voltage + Offset;                                                     // Преобразуем мВ в pH
        samplingTime = millis();                                                              // Обновляем время для переменной samplingTime
    }
    static float pHValue9, voltage2;
//  Проводим измерения:                                                                       //
    if(millis() - samplingTime > samplingInterval){                                           // Выполняем код в теле оператора if через каждые samplingInterval мс
        pHArray[pHArrayIndex++] = analogRead(A9);                                      // Читаем данные в очередной элемент массива pHArray
        if(pHArrayIndex==ArrayLenth) pHArrayIndex=0;                                          // Если достигли последнего элемента массива pHArray, то сбрасываем номер текущего элемента этого массива в 0
        voltage2 = averagearray(pHArray, ArrayLenth) * 5.0 / 1023;                             // Получаем среднее напряжение в мВ из массива напряжений pHArray
        pHValue9 = 3.5 * voltage + Offset;                                                     // Преобразуем мВ в pH
        samplingTime = millis();                                                              // Обновляем время для переменной samplingTime
    }
    
  
   ds.requestTemperatures(); // считываем температуру с датчиков
     float t1=ds.getTempC(sensor1);
     float t2=ds.getTempC(sensor2);
     
   String InputData = "";
       
  // Create a client connection
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {   
      if (client.available()) {
        char c = client.read();
     
        if (readString.length() < 100) {
          
          readString += c;
          
         }

         
         if (c == '\n') {          
           Serial.println(readString); 
           //html file 
           client.println("HTTP/1.1 200 OK"); 
           client.println("Content-Type: text/html;charset=utf-8");
           client.println();     
           client.println("<HTML>");
           client.println("<HEAD>");
           client.println("<meta http-equiv='refresh'content='7;URL=http://192.168.175.213'>");
           client.println("<html><head><title>ЛПО, корпус№5</title> </head>");
           
           
           client.println("<body><CENTER><H2>Камера химической подготовки</H2></BR><H1>");
          
           
           client.println("<table border=1>");
           client.println("<tr>");
           client.println("<td><body><H2>Ванна №1:</H2><H1>");
           client.println("<H3>Уровень кислотности раствора</H3>");
           client.println(pHValue8);
           client.println("рН");
           client.println("<body><H2>Температура раствора</H2><H1>");
           client.println(t2);
           client.println("C");

           client.println("</td>");
            
           client.println("<td><body><H2>Ванна №3:</H2><H1>");
           client.println("<H3>Уровень кислотности раствора</H3>");
           client.println(pHValue9);
           client.println("рН");
           client.println("<body><H2>Температура раствора</H2><H1>");
           client.println(t1);
           client.println("C");
           client.println("</td>");
           client.println("<br />"); 
           client.println("</BODY>");
           client.println("</HTML>");
           delay(1);
           
           //stopping client
           client.stop();
           
           //clearing string for next read
            readString="";



         }
       }
    }
}
}
//  Функция определения среднего значения напряжения                                          // Эта функция возвращает среднее арифметическое значение данных массива arr без учёта одного максимального и одного минимального значения массива.
double averagearray(int* arr, int number){                                                    //
    int i,max,min;                                                                            // Объявляем переменные для цикла и экстремумов
    double avg;                                                                               // Объявляем переменную для вывода среднего значения
    long amount=0;                                                                            // Определяем переменную для подсчёта среднего значения
    if(number<=0){ Serial.println("Error number for the array to avraging!/n");  return 0;}   // В массиве arr не может быть 0 и менее элементов
    if(number< 5){ for(i=0; i<number; i++){amount+=arr[i];} avg = amount/number; return avg;  // Если в массиве arr менее 5 элементов, то среднее значение является средним арифметическим значением
    }else{                                                                                    // Если в массиве arr более 5 элементов, то среднее значение считаем иначе ...
        if(arr[0]<arr[1]){ min = arr[0]; max=arr[1];}                                         // Определяем минимальное и максимальное число из первых двух элементов массива
        else             { min = arr[1]; max=arr[0];}                                         // Определяем минимальное и максимальное число из первых двух элементов массива
        for(i=2; i<number; i++){                                                              // Проходим по остальным элементам массива
                 if(arr[i]<min){ amount+=min; min=arr[i]; }                                   // Если значение очередного элемента меньше минимального,  то добавляем к значению amount предыдущее минимальное значение  и обновляем значение min
            else if(arr[i]>max){ amount+=max; max=arr[i]; }                                   // Если значение очередного элемента больше максимального, то добавляем к значению amount предыдущее максимальное значение и обновляем значение max
            else               { amount+=arr[i];          }                                   // Если значение очередного элемента находится в пределах между min и max, то добавляем значение этого элемента к amount
        }                                                                                     //
        avg = (double) amount/(number-2);                                                     // Получаем среднее арифметическое значение (без учета значений первых двух элементов массива arr, т.к. они не добавлялись к amount)
    }                                                                                         //
    return avg;                                                                               // Возвращаем полученное среднее значение
}                    

ВРоде переменные обозначил как float. но когда подключаюсь через ModbusPool то в регистрах ничего не вижу, когда тип переменной меняю на int - то визу, но целые

Что делать и как быть?

Буду призхнателен за помощь

 

b707
Offline
Зарегистрирован: 26.05.2017

простой вариант - чтобы передать температуру с десятыми - умножай число на 10, передавай как целое, по приеме дели на 10 обратно

b707
Offline
Зарегистрирован: 26.05.2017

ШTopor пишет:

ВРоде переменные обозначил как float. но когда подключаюсь через ModbusPool то в регистрах ничего не вижу, когда тип переменной меняю на int - то визу, но целые

 

Что с того, что вы обозначили pHValue8 и pHValue9 как флоат?? Вы же их потом приравниваете элементам массива Mb.R, который у вас описан как int

ШTopor
Offline
Зарегистрирован: 22.12.2017

[/quote]

Что с того, что вы обозначили pHValue8 и pHValue9 как флоат?? Вы же их потом приравниваете элементам массива Mb.R, который у вас описан как int

[/quote]

Понятно, мне надо быть внимательнее)

Вроде используя библиотеку Mudbus.h массивы Mb могут быть либо int либо bool

Вот сам код библиотеки:

// For Arduino 0022
// #include "WProgram.h"
// For Arduino 1.0
#include "Arduino.h"

#include <SPI.h>
#include <Ethernet.h>

#ifndef Mudbus_h
#define Mudbus_h

#define MB_N_R 125 //Max 16 bit registers for Modbus is 125
#define MB_N_C 128 //Max coils for Modbus is 2000 - dont need that many so here is a multiple of 8
#define MB_PORT 502

enum MB_FC {
  MB_FC_NONE           = 0,
  MB_FC_READ_COILS     = 1,
  MB_FC_READ_REGISTERS = 3,
  MB_FC_WRITE_COIL     = 5,
  MB_FC_WRITE_REGISTER = 6,
  //Function codes 15 & 16 by Martin Pettersson http://siamect.com
  MB_FC_WRITE_MULTIPLE_COILS = 15,
  MB_FC_WRITE_MULTIPLE_REGISTERS = 16
};

class Mudbus
{
public:
  Mudbus();
  void Run();  
  int  R[MB_N_R];
  bool C[MB_N_C];  
  bool Active;    
  unsigned long PreviousActivityTime;
  int Runs, Reads, Writes;
private: 
  uint8_t ByteArray[260];
  MB_FC FC;
  void SetFC(int fc);
};

#endif

 

Можно ли в самой библиотеке заменить строку int  R[MB_N_R]; на float  R[MB_N_R]; и принесет ли это возможность передавать данные типа Float по модбас? 

Можно ли использовать преобразование типа:

byte *x = (byte*)&t1;

byte *y = (byte*)&t2;

byte *z = (byte*)&pHValue8;

byte *w = (byte*)&pHValue9;



float t1 = ((float) x[3] << 24) +((float) x[2] <<16)+ (x[1]<<8)+x[0];

float t2 = ((float) y[7]<<24)+((float) y[6]<<16)+ (y[5]<<8)+y[4];

float pHValue8 = ((float) z[3] << 24) +((float) z[2] <<16)+ (z[1]<<8)+z[0];

float pHValue9 = ((float) w[7]<<24)+((float) w[6]<<16)+ (w[5]<<8)+w[4];

 

b707
Offline
Зарегистрирован: 26.05.2017

я уже выше подсказал, как сделать проще. Хочешь передать с десятыми - умножай на 10. С сотоыми - умножай на 100.

Другой вариант - передавай отдельно целую часть, отдельно цифры после запятой.

sadman41
Offline
Зарегистрирован: 19.10.2016

Спецификация Modbus не определяет формат хранения/передачи float-величин, поэтому каждый производитель придумывает свой способ. Отсюда вывод - стандартизованные библиотеки не могут оперировать с float в принципе. Хоть меняй им там буквы, хоть не меняй.

В собственной разработке, можно, например, поступить, как советует b707 (с делением/умножением) - быстро и незамысловато (так делает Peacefair в своих счётчиках). 

Кроме того - можно взять float и разбить напополам: 32-bit -> 4 byte -> 2 registers. Но это потребует навыков работы в области битовых операций и понимания порядка следования битов/байтов. В Modbus и AVR они разные - требуется дополнительное жонглирование. Этот способ тоже встречается в фабричных девайсах.

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

ШTopor
Offline
Зарегистрирован: 22.12.2017

sadman41 пишет:

Кроме того - можно взять float и разбить напополам: 32-bit -> 4 byte -> 2 registers. Но это потребует навыков работы в области битовых операций и понимания порядка следования битов/байтов. В Modbus и AVR они разные - требуется дополнительное жонглирование. Этот способ тоже встречается в фабричных девайсах.

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

Спасибо за ответ

Не затруднит ли Вас по подробнее объяснить, как "взять float и разбить напополам: 32-bit -> 4 byte -> 2 registers."

В готовом изделии (СПК Овен) научился уверенно видеть регистры типа float (2x16 Bits).

В описании к СПК говорится, что информация по модбас там передается в виде массива данных типа word

Наверное нужно так

Берем переменную типа Float и преобразуем в переменную типа int, путем умножения на 10 (100, 1000)

Потом эту переменную типа int разбиваем напополам

// переменные текущих показаний
float pHValue8;
float pHValue9;
float t1;
float t2;

int a; // дополнительные переменные
int b;
int c;
int d;

// преобразование
a=t1*10;
b=t2*10;
c=pHValue8*10;
d=pHValue9*10

// разбиваем напополам
byte *x = (byte*)&a;

byte *y = (byte*)&b;

byte *z = (byte*)&c;

byte *w = (byte*)&d;



int a = ((int) x[3] << 24) +((int) x[2] <<16)+ (x[1]<<8)+x[0];

int b = ((int) y[7]<<24)+((int) y[6]<<16)+ (y[5]<<8)+y[4];

int c = ((int) z[3] << 24) +((int) z[2] <<16)+ (z[1]<<8)+z[0];

int d = ((int) w[7]<<24)+((int) w[6]<<16)+ (w[5]<<8)+w[4];

//Передаем массив данных
Mb. R[0]= x[3];
Mb. R[1]= x[2];
Mb. R[2]= x[1];
Mb. R[3]= x[0];
Mb. R[4]= y[3];
Mb. R[5]= y[2];
Mb. R[6]= y[1];
Mb. R[7]= y[0];

 Собственно код выше работал для переменной типа unsignet long 

шестизначного числа типа такого 544433;

Собственно сам вопрос как сделать то же самое для четырех значного числа типа 5423? 

Можно ли использовать написанный выше код, для 4-х значного числа?

 

 

b707
Offline
Зарегистрирован: 26.05.2017

ШTopor пишет:

шестизначного числа типа такого 544433;

Собственно сам вопрос как сделать то же самое для четырех значного числа типа 5423? 

Вы , по-моему, не поняли Садмана. Не нужно никакой математики. Тип флоат в ардуине - 4х байтовый. Регистры модбаса - 2х байтовые. Поэтому очевидный вариант - просто берете указатель от флоат, первые 2 байта по этому адресу пишете в один регистр модюас, вторые - в другой. На приемной стороне собираете флоат в обратном порядке. Это будет работать абсолютно для любого числа. хоть для 544433, хотя 5423, да хоть для семнадцати миллиардов двадцати двух квинтиллионов двадцати одного :)

пример

// переменная типа флоат
float f = 34343.56;
// указатель на переменную f, приведенный к типу int*
int* b = (int*) &f;
// приравниваем 1-2й байты переменной f одному регистру, а 3-4й - следующему
Mb.R[1] = *b;
Mb.R[2] =*(b+1);

 

ШTopor
Offline
Зарегистрирован: 22.12.2017

Спасибо за рекомендации, Вам и Саддаму. Буду пробовать

Да, я мало что понимаю)))

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Т.к. b707 уже пояснил суть, то могу только посоветовать взять union на float, uint16_t[2], uint8_t[4]. Запихивать туда float, переставлять байты: порядок  0_1_2_3 трансформировать в 3_2_1_0 , т.е. переворачивать байтовый массив. Таким образом через "word swap byte swap operation" AVR-овский little-endian превращается в модбасовский big-endian. Затем останется только два uint16_t переложить в регистры слейва (ну или сразу оперировать указателем на union и элементы массива, которые содержат "регистры". Гемморойно, конечно, но иного пути нет. В конце-концов этот просто надо один раз понять. 

 

ШTopor
Offline
Зарегистрирован: 22.12.2017

Спасибо большое)))

Есть float = t1; пусть 53.5 градусов

unsigned int a = t1

byte a1 = ( byte )( a >> 8 );
byte a2 = ( byte )a;

a = ( ( unsigned int )a1<< 8 ) | a2;

Что то типа этого?

 

 

ШTopor
Offline
Зарегистрирован: 22.12.2017

Попробовал Ваши рекомендации, компилятор ругается, типа "*" - недопустимый символ

 

b707
Offline
Зарегистрирован: 26.05.2017

ШTopor пишет:

Есть float = t1; пусть 53.5 градусов

unsigned int a = t1

byte a1 = ( byte )( a >> 8 );
byte a2 = ( byte )a;

a = ( ( unsigned int )a1<< 8 ) | a2;

Что то типа этого?

нет, совсем мимо. Вы в первой же строчке превращаете флоат в инт - и это полностью обесценивает все дальнейшие манипуляции. Если число 53.5 записать в целый тип - получится 53. Если вас устраивают такое округление. нафига вообще связываться с флоат7

 

и добавлю - зачем Вы всюду суете битовые сдвиги? они тут не нужны от слова совсем! У нас речь все время идет о целых байтах и даже словах - а значит адресная арифметика в разы удобнее - короче в записи и в разы быстрее при работе кода.

 

b707
Offline
Зарегистрирован: 26.05.2017

ШTopor пишет:

Попробовал Ваши рекомендации, компилятор ругается, типа "*" - недопустимый символ

 

вы что-то неправильно скопировали

sadman41
Offline
Зарегистрирован: 19.10.2016

Можно вместо uint8_t[4] залепить uint32_t+bitwise. Возможно, что это по ресурсам дешевле выйдет.

Но принцип такой:

typedef union {
 float f;
 uint8_t b[4];
} modbusFloat_t;

...

uint16_t registers[10];
uint8_t swap;

modbusFloat_t* ptrModbusFloat;

// Look thru modbusFloat_t glass to two regs starting from registers[2] 
ptrModbusFloat = (modbusFloat_t*) &registers[2];

// Place float value to regs
*ptrModbusFloat.f = 123.321;

// shuffle bytes: 0_1_2_3 => 3_2_1_0 
// 0_.._3 => 3_.._0
swap = *ptrModbusFloat.b[0];
*ptrModbusFloat.b[0] = *ptrModbusFloat.b[3];
*ptrModbusFloat.b[3] = swap;

// 1_2 => 2_1
swap = *ptrModbusFloat.b[1];
*ptrModbusFloat.b[1] = *ptrModbusFloat.b[2];
*ptrModbusFloat.b[2] = swap;

// registers[2] & registers[3] contain float "modbus frendly" value now

 

nik182
Offline
Зарегистрирован: 04.05.2015

А что не через union? Даже делать ничего не надо. Записали float, послали два word. 

ШTopor
Offline
Зарегистрирован: 22.12.2017

Добрый день,

Попробовал Ваш код.

Принципе все передалось. Число float разбилось на 2 регистра, 

Однако теперь я не могу все это корректно собрать, получается околесица (либо 0.0, либо огромное число, далекое от реальности)

Собираю  в СПК

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

в СПК есть объединение типа 

TempArd1:		REAL; - переменная для отображение
awTempArd1:		ARRAY[0..1] OF WORD; - массив объединения

Как мне преобразовать переменную в Ардуине, чтобы я мог ее правильно собрать с использованием указанного выше объединения

Тут речь зашла про union, Разбить float на 2 word  - как раз то, что мне нужно(наверное) не могли бы Вы быть так добры рассказать по подробнее как их использовать?

 

ШTopor
Offline
Зарегистрирован: 22.12.2017

nik182 пишет:

А что не через union? Даже делать ничего не надо. Записали float, послали два word. 

 

Добрый день, буду Вам очень признателен, если Вы расскажите как это сделать

 

b707
Offline
Зарегистрирован: 26.05.2017

ШTopor пишет:

Добрый день, буду Вам очень признателен, если Вы расскажите как это сделать

 

Почему бы не набрать union c++ в гугле?

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

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Я понятия не имею, как там Овен свои REAL-ы кодирует. Может прямо в little-endian и байты переставлять вообще не требуется. На АСУТП-шных форумах же зубры прям сидят, если судить по понторезам, которые сюда залетают. Спросите там.

Лично я затак не полезу больше в это болото. Показать, как из float сделать два регистра - показал. В принципе, если втупую делать, то надо перебрать все варианты перестановки байт. 

b707
Offline
Зарегистрирован: 26.05.2017

sadman41 пишет:

В принципе, если втупую делать, то надо перебрать все варианты перестановки байт. 

и бит :)

sadman41
Offline
Зарегистрирован: 19.10.2016

В тех реализациях, с которыми я сталкивался, биты не трогали, слава богу.

nik182
Offline
Зарегистрирован: 04.05.2015

Если такие проблемы, почему число в виде строки не отправить? Да регистров больше уйдёт, но зато собирать ничего не надо. А вообще лучший совет #1.

MaksVV
Offline
Зарегистрирован: 06.08.2015

ТС легких путей не ищет