Передача данных по Modbus TCP/ip. Число с запятой
- Войдите на сайт для отправки комментариев
Доброго времени суток, господа, таварищи, соплеменники.
Возникла необходимость передавать показания датчиков температуры, датчиков рН по 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 - то визу, но целые
Что делать и как быть?
Буду призхнателен за помощь
простой вариант - чтобы передать температуру с десятыми - умножай число на 10, передавай как целое, по приеме дели на 10 обратно
ВРоде переменные обозначил как float. но когда подключаюсь через ModbusPool то в регистрах ничего не вижу, когда тип переменной меняю на int - то визу, но целые
Что с того, что вы обозначили pHValue8 и pHValue9 как флоат?? Вы же их потом приравниваете элементам массива Mb.R, который у вас описан как int
[/quote]
Что с того, что вы обозначили pHValue8 и pHValue9 как флоат?? Вы же их потом приравниваете элементам массива Mb.R, который у вас описан как int
[/quote]
Понятно, мне надо быть внимательнее)
Вроде используя библиотеку Mudbus.h массивы Mb могут быть либо int либо bool
Вот сам код библиотеки:
Можно ли в самой библиотеке заменить строку int R[MB_N_R]; на float R[MB_N_R]; и принесет ли это возможность передавать данные типа Float по модбас?
Можно ли использовать преобразование типа:
я уже выше подсказал, как сделать проще. Хочешь передать с десятыми - умножай на 10. С сотоыми - умножай на 100.
Другой вариант - передавай отдельно целую часть, отдельно цифры после запятой.
Спецификация Modbus не определяет формат хранения/передачи float-величин, поэтому каждый производитель придумывает свой способ. Отсюда вывод - стандартизованные библиотеки не могут оперировать с float в принципе. Хоть меняй им там буквы, хоть не меняй.
В собственной разработке, можно, например, поступить, как советует b707 (с делением/умножением) - быстро и незамысловато (так делает Peacefair в своих счётчиках).
Кроме того - можно взять float и разбить напополам: 32-bit -> 4 byte -> 2 registers. Но это потребует навыков работы в области битовых операций и понимания порядка следования битов/байтов. В Modbus и AVR они разные - требуется дополнительное жонглирование. Этот способ тоже встречается в фабричных девайсах.
Для стыкования с готовым изделием требуется изучить документацию к этому изделию и выяснить, как оно понимает float.
Кроме того - можно взять 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 разбиваем напополам
Собственно код выше работал для переменной типа unsignet long
шестизначного числа типа такого 544433;
Собственно сам вопрос как сделать то же самое для четырех значного числа типа 5423?
Можно ли использовать написанный выше код, для 4-х значного числа?
шестизначного числа типа такого 544433;
Собственно сам вопрос как сделать то же самое для четырех значного числа типа 5423?
Вы , по-моему, не поняли Садмана. Не нужно никакой математики. Тип флоат в ардуине - 4х байтовый. Регистры модбаса - 2х байтовые. Поэтому очевидный вариант - просто берете указатель от флоат, первые 2 байта по этому адресу пишете в один регистр модюас, вторые - в другой. На приемной стороне собираете флоат в обратном порядке. Это будет работать абсолютно для любого числа. хоть для 544433, хотя 5423, да хоть для семнадцати миллиардов двадцати двух квинтиллионов двадцати одного :)
пример
Спасибо за рекомендации, Вам и Саддаму. Буду пробовать
Да, я мало что понимаю)))
Т.к. 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 и элементы массива, которые содержат "регистры". Гемморойно, конечно, но иного пути нет. В конце-концов этот просто надо один раз понять.
Спасибо большое)))
Есть float = t1; пусть 53.5 градусов
Что то типа этого?
Попробовал Ваши рекомендации, компилятор ругается, типа "*" - недопустимый символ
Есть float = t1; пусть 53.5 градусов
Что то типа этого?
нет, совсем мимо. Вы в первой же строчке превращаете флоат в инт - и это полностью обесценивает все дальнейшие манипуляции. Если число 53.5 записать в целый тип - получится 53. Если вас устраивают такое округление. нафига вообще связываться с флоат7
и добавлю - зачем Вы всюду суете битовые сдвиги? они тут не нужны от слова совсем! У нас речь все время идет о целых байтах и даже словах - а значит адресная арифметика в разы удобнее - короче в записи и в разы быстрее при работе кода.
Попробовал Ваши рекомендации, компилятор ругается, типа "*" - недопустимый символ
вы что-то неправильно скопировали
Можно вместо uint8_t[4] залепить uint32_t+bitwise. Возможно, что это по ресурсам дешевле выйдет.
Но принцип такой:
А что не через union? Даже делать ничего не надо. Записали float, послали два word.
Добрый день,
Попробовал Ваш код.
Принципе все передалось. Число float разбилось на 2 регистра,
Однако теперь я не могу все это корректно собрать, получается околесица (либо 0.0, либо огромное число, далекое от реальности)
Собираю в СПК
пробовал менять очередность в массиве, однако без результатно
в СПК есть объединение типа
Как мне преобразовать переменную в Ардуине, чтобы я мог ее правильно собрать с использованием указанного выше объединения
Тут речь зашла про union, Разбить float на 2 word - как раз то, что мне нужно(наверное) не могли бы Вы быть так добры рассказать по подробнее как их использовать?
А что не через union? Даже делать ничего не надо. Записали float, послали два word.
Добрый день, буду Вам очень признателен, если Вы расскажите как это сделать
Добрый день, буду Вам очень признателен, если Вы расскажите как это сделать
Почему бы не набрать union c++ в гугле?
но проблему обратного сбора на второй системе это не решит, скорее наоборот, с унион ее будет решить сложнее. Вам надо четко разобраться с адресацией отдельных байт в флоате и посмотреть порядок их следования в ардуине и второй системе. Без этого вам никакой вариант не поможет.
Я понятия не имею, как там Овен свои REAL-ы кодирует. Может прямо в little-endian и байты переставлять вообще не требуется. На АСУТП-шных форумах же зубры прям сидят, если судить по понторезам, которые сюда залетают. Спросите там.
Лично я затак не полезу больше в это болото. Показать, как из float сделать два регистра - показал. В принципе, если втупую делать, то надо перебрать все варианты перестановки байт.
В принципе, если втупую делать, то надо перебрать все варианты перестановки байт.
и бит :)
В тех реализациях, с которыми я сталкивался, биты не трогали, слава богу.
Если такие проблемы, почему число в виде строки не отправить? Да регистров больше уйдёт, но зато собирать ничего не надо. А вообще лучший совет #1.
ТС легких путей не ищет