Arduino Nano в качестве мастера modbus rtu

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019
Добрый день!
 
Использовал библиотеку https://github.com/4-20ma/ModbusMaster
 
Последовательный порт для вывода результатов не использую
Вывод на дисплей работает нормально
 
Подключение:
RS485 - Arduino
      TX - DI
      RX - RO
 RE/DE - D8
Соответственно 485 и устройство А к А,  В к В
Питание у "устройства" свое, плата преобразователя 485 питается от 5В Arduino
 
Ведомое устройство, к сожалению, назвать не могу? *секрет*.. но вот его параметры:
Скорость 38400, 8N1
Slave ID 4
Всего нужно считать 22 регистра с 1000 по 1021 (параметр точно 0х03), данные в формате HEX
 
Все стандартно: не работает, на первой строке дисплея выдает сообщение из else (Error get data)
Посмотрите, пожалуйста, скетч (он не мой, нашел на каком-то форуме, там у товарища все работает. Я только привел его визуально в порядок, сделал комментарии и т.д.)
 
При этом если подключить "устройство" к ПК через USB-485 конвертер и воспользоваться программой https://sourceforge.net/projects/qmodmaster/ то там все прекрасно работает и считывается. 
 
Физическое подключение вроде бы верное, так как при снятии питания с "устройства" перебор регистров на дисплее сильно замедляется, так же происходит если отключить от него 485..
 
// Подключаем библиотеки
#include <ModbusMaster.h>
#include <LiquidCrystal.h>

// Макроопределения пинов
// Определяем пины для подключения LCD дисплея и инициализируем библиотеку LiquidCrystal.h
const int rs = 2, en = 3, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

// Автоматический выбор приема/передачи RS-485
#define MAX485_DE 8

// Светодиод индикации успешных пакетов
#define LED_SUCCESS 13

// Создаем экземпляр объекта ModbusMaster
ModbusMaster node;

void preTransmission(){
  digitalWrite(MAX485_DE, true);
}

void postTransmission(){
  digitalWrite(MAX485_DE, false);
}


void setup()
{
  // Указываем количество столбцов и строк на LCD дисплее
  lcd.begin(16, 2);

  // Конфигурируем пины на выход
  pinMode(MAX485_DE, OUTPUT);
  pinMode(LED_SUCCESS, OUTPUT);
  
  // Устанавливаем RS485 на прием
  digitalWrite(MAX485_DE, false);
  
  // Запускаем Modbus на скорости 38400
  Serial.begin(38400);

  // Определяем Modbus ведомый ID 4, используем Serial
  node.begin(4, Serial);
  
  // Обратные вызовы позволяют нам правильно настроить приемопередатчик RS485
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);  
}

// какая то хрень для светодиода
void led_blink(int blk)
{
  for (int j=0; j < blk; j++)
  {
    digitalWrite(LED_SUCCESS, HIGH);
    delay(100);
    digitalWrite(LED_SUCCESS,LOW);
    delay(100);
    }
}

void loop()
{
  // Поочередно перебираем регистры с 1000 по 1021 (с каждым новым циклом +1 к значению) 
  for (int holdreg = 0x3E8; holdreg < 0x3FD; holdreg++)
  {
    // Выводим номер текущего регистра на LCD дисплей
    lcd.setCursor(0,0); 
    lcd.print("Reg: ");
    lcd.print(holdreg);
    delay(100);

    // Объявляем переменную и пишем в нее значение считанного регистра
    uint8_t result = node.readHoldingRegisters(holdreg, 1);

    // Если успешно считали регистр то выполняем if, иначе else
    if (result == node.ku8MBSuccess)
    {
      led_blink(5); // что это и для чего
      lcd.print("Value for:");
      lcd.print(holdreg);
      lcd.setCursor(0,1);
      lcd.print(node.getResponseBuffer(0));
      delay(1500);
      digitalWrite(LED_SUCCESS,LOW);
      }
      
      else 
      {
        led_blink(1); // что это и для чего
        lcd.setCursor(0,1);
        lcd.print("Error get data");
        }
   }
}

 

 

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Убрал конструкцию с if - else, в каждом из регистров якобы лежит FFFF

Вообще фигня какая-то, что есть такого в программе под винду, чего нет в скетче

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Вообще фигня какая-то, что есть такого в программе под винду, чего нет в скетче

В библиотеке некорректно идёт переключение на приём RS-485. Конкретно в этих строчках:

  u8ModbusADUSize = 0;
  _serial->flush();    // flush transmit buffer
  if (_postTransmission)
  {
    _postTransmission();
  }

Переключаться надо не когда софтовый сериал буфер опустел, а когда последний байт полностью ушёл из UDR. Автор тестировал на MAX488, а он полнодуплексный. Так что у вас последний байт обрезается, и слэйв вас не понимает.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Переключаться надо не когда софтовый сериал буфер опустел, а когда последний байт полностью ушёл из UDR. Автор тестировал на MAX488, а он полнодуплексный. Так что у вас последний байт обрезается, и слэйв вас не понимает.

Для меня это очень сложно, сможете мне в этом помочь?

Скажите, пожалуйста, что нужно сделать, чтобы все заработало?

 

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Простейший способ для вас - вставить задержку в текст коллбэка окончания передачи, до переключения на приём. На 38400 один байт идёт где-то 260 мкс. Ставить можно на период до 4.5 байт, в Modbus есть период тишины между пакетами. Например, delayMicroseconds(1000). Хотя и задержка, но не такая уж большая.

Есть решение изящнее, но попробуйте так пока.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Простейший способ для вас - вставить задержку в текст коллбэка окончания передачи, до переключения на приём

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

// Устанавливаем RS485 на прием
  digitalWrite(MAX485_DE, false);

 Само собой это не помогло :)

 

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Kirill Iashin пишет:

 Само собой это не помогло :)

Порылся в папке библиотеки, если в файле ModbusMaster.h

    // idle callback function; gets called during idle time between TX and RX
    void (*_idle)();
    // preTransmission callback function; gets called before writing a Modbus message
    void (*_preTransmission)();
    // postTransmission callback function; gets called after a Modbus message has been sent
    void (*_postTransmission)();

В этих строках попробовать залезть, то не компилируется

Ок, полез в ModbusMaster.cpp 

void ModbusMaster::postTransmission(void (*postTransmission)())
{
  delayMicroseconds(1000);
  _postTransmission = postTransmission;
}

Там влепил задержку, компилируется, но не помогает

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Не не там, только здесь:

void postTransmission(){
  delayMicroseconds(1000);
  digitalWrite(MAX485_DE, false);
}

Попробуйте разные задержки. И выведите в блоке else что вам возвращает библиотека, какую ошибку, на LCD.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Не не там, только здесь:

Попробовал несколько, вплоть до 10мс, только регистры на дисплее медленнее стали перебираться..

Schwarz78 пишет:

выведите в блоке else что вам возвращает библиотека, какую ошибку, на LCD.

Как это сделать?

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Как это сделать?

Выведите result на LCD.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Выведите result на LCD.

Блин, я то уж начал монструозые конструкции искать типа таких:

bool getResultMsg(ModbusMaster *node, uint8_t result) 
{
  String tmpstr2 = "\r\n";
  switch (result) 
  {
  case node->ku8MBSuccess:
    return true;
    break;
  case node->ku8MBIllegalFunction:
    tmpstr2 += "Illegal Function";
    break;
  case node->ku8MBIllegalDataAddress:
    tmpstr2 += "Illegal Data Address";
    break;
  case node->ku8MBIllegalDataValue:
    tmpstr2 += "Illegal Data Value";
    break;
  case node->ku8MBSlaveDeviceFailure:
    tmpstr2 += "Slave Device Failure";
    break;
  case node->ku8MBInvalidSlaveID:
    tmpstr2 += "Invalid Slave ID";
    break;
  case node->ku8MBInvalidFunction:
    tmpstr2 += "Invalid Function";
    break;
  case node->ku8MBResponseTimedOut:
    tmpstr2 += "Response Timed Out";
    break;
  case node->ku8MBInvalidCRC:
    tmpstr2 += "Invalid CRC";
    break;
  default:
    tmpstr2 += "Unknown error: " + String(result);
    break;
  }
  Serial.println(tmpstr2);
  return false;
}

Вывел result, на всех регистрах говорит "224"

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Это ku8MBInvalidSlaveID. Типа ответил не тот, кого спрашивали.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Типа ответил не тот, кого спрашивали.

Ух ты, а где можно увидеть остальные коды ошибок? Сколько лазал по мануалам разработчика библиотеки не видел этого.. А то лишний раз хорошим людям голову по мелочам забиваю :)

Второй вопрос, а как это лечить если на линии больше никого нет?

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Ух ты, а где можно увидеть остальные коды ошибок? Сколько лазал по мануалам разработчика библиотеки не видел этого.. А то лишний раз хорошим людям голову по мелочам забиваю :)

Второй вопрос, а как это лечить если на линии больше никого нет?

В файле ModbusMaster.h прописаны коды ошибок.

Лечить сложно сказать как. Может быть электрическая проблема - искажение данных в линии. Может быть прием обрезанной с головы посылки. Может быть со скоростью проблемы. Но поскольку ваше устройство точно работает, ошибка точно на вашей стороне, и это сильно упрощает дело.

Попробуйте ещё библиотеку поменять. Мне вот эта больше нравится: https://github.com/BlackBrix/Simple-Modbus-Master. Написана получше. И у меня работал Slave этого автора из коробки.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Kirill Iashin пишет:

Schwarz78 пишет:

Типа ответил не тот, кого спрашивали.

Ух ты, а где можно увидеть остальные коды ошибок? 

Так, с этим вроде разобрался, 224 это 0xE0, поиском в ModBusMaster.h нашел:

/**
    ModbusMaster invalid response slave ID exception.
    The slave ID in the response does not match that of the request.
    @ingroup constant
    */
    static const uint8_t ku8MBInvalidSlaveID             = 0xE0;

 

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

В файле ModbusMaster.h прописаны коды ошибок.

Да, спасибо, пока писал и вы об этом же сказали))

Schwarz78 пишет:

это сильно упрощает дело

Хорошо, попробую заменить железную часть, может быть действительно есть с этим проблемы. Однако, пробовал простенький скетч, который выводит всякую чушь в Serial, железо, собственно, использовал это же, но там насколько я знаю, линии RE/DE у 485 не использовались, так как это передача в одну сторону, от arduino к 485-USB и в ПК.

Schwarz78 пишет:

Мне вот эта больше нравится

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

А вы говорили об каком-то "изящном" способе, может быть с ним чего-то получится?

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

А вы говорили об каком-то "изящном" способе, может быть с ним чего-то получится?

Просто имел ввиду ту же задержку не делать блокирующей. Это не поможет.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Сейчас попробовал скормить скетчу заведомо неверный slave ID

Выдает ошибку E2, типа: "Ответ не был получен в выделенный период ожидания". 

Буду пробовать менять железо, а может и библиотеку, хотя с этой даже как-то подружился немного :)

В любом случае, спасибо за помощь! Хорошего вечера!

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Сейчас попробовал скормить скетчу заведомо неверный slave ID

Выдает ошибку E2, типа: "Ответ не был получен в выделенный период ожидания". 

Буду пробовать менять железо, а может и библиотеку, хотя с этой даже как-то подружился немного :)

В любом случае, спасибо за помощь! Хорошего вечера!

Это говорит о том, что устройство вас слышит и понимает, и отвечает, но ваш мастер его не понимает. Копайте в эту сторону. Ну и смена библиотеки. То, что я рекомендовал - на порядок лучше вашей, проверено. Там автор очень хороший сишник.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Ну и смена библиотеки

Попробовал использовать библиотеку, что вы предложили

// Подключаем библиотеки
#include <SimpleModbusMaster.h> 
#include <LiquidCrystal.h>

// Макроопределения пинов
// Определяем пины для подключения LCD дисплея и инициализируем библиотеку LiquidCrystal.h
const int rs = 2, en = 3, d4 = 4, d5 = 5, d6 = 6, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define baud 38400    // Скорость обмена по последовательному интерфейсу (UART)
#define timeout 1000  // Длительность ожидание ответа (таймаут ModBus)
#define polling 200  // Скорость опроса по ModBus
#define retry_count 2 // Количесво запросов ModBus до ошибки и останова обмена
#define TxEnablePin 8 // Автоматический выбор приема/передачи RS-485 (RE/DE)
#define Slave_ID 4    // Адрес ведомого устройсва
// Общий объем доступной памяти на ведущем устройстве для хранения данных
#define TOTAL_NO_OF_REGISTERS 4

// Способ создания новых пакетов.
enum
{
  PACKET1, // Адрес пакета 0
  PACKET2, // Адрес пакета 1
  PACKET3, // Адрес пакета 2
  PACKET4, // Адрес пакета 3
  TOTAL_NO_OF_PACKETS //Данная строка не должна быть изменена!
};

// Создаем массив пакетов для конфигурации
Packet packets[TOTAL_NO_OF_PACKETS];
// Создаем массив регистров ведущего
unsigned int regs[TOTAL_NO_OF_REGISTERS];

void setup()
{
  // Initialize each packet
  modbus_construct(&packets[PACKET1], Slave_ID, READ_HOLDING_REGISTERS, 1000, 1, 0);
  modbus_construct(&packets[PACKET2], Slave_ID, READ_HOLDING_REGISTERS, 1001, 1, 1);
  modbus_construct(&packets[PACKET3], Slave_ID, READ_HOLDING_REGISTERS, 1002, 1, 2);
  modbus_construct(&packets[PACKET4], Slave_ID, READ_HOLDING_REGISTERS, 1003, 1, 3);
  // Initialize the Modbus Finite State Machine
  modbus_configure(&Serial, baud, SERIAL_8N1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS, regs);
  lcd.begin(16, 2); //Указываем тип LCD дисплея 16 символов, 2 строки)
}

void loop()
{
  modbus_update();
  lcd.setCursor(0, 0);
  lcd.print(regs[0]);
  lcd.print(" | ");
  lcd.print(regs[1]);
  lcd.setCursor(0, 1);
  lcd.print(regs[2]);
  lcd.print(" | ");
  lcd.print(regs[3]);
}

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

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Разобрался, дело было не в бобине, как говорится..

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

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

Большое спасибо за помощь!

 

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

У меня такое было, когда девайс пожёг китайский модуль RS485 - что-то так подал в линию, что микросхема впала в горячку. После чего часть пакетов начали приходить битыми.

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Товарищи, есть еще вопрос в продолжение этой темы.

При считывании регистра (два байта), получаю, например, A26, а хотелось бы 0A26..

Куда пропал ноль? Это довольно важно, так как, например, первые два регистра в моем случае - это серийный номер устройства.

Должен быть номер: 1234 0567, а приходит 1234 567.. где опять потерялся ноль?

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Товарищи, есть еще вопрос в продолжение этой темы.

При считывании регистра (два байта), получаю, например, A26, а хотелось бы 0A26..

Куда пропал ноль? Это довольно важно, так как, например, первые два регистра в моем случае - это серийный номер устройства.

Должен быть номер: 1234 0567, а приходит 1234 567.. где опять потерялся ноль?

А как именно получаете? Приведите код, иначе непонятно.

Подозреваю, что библиотека вашего LCD жрёт лидирующий ноль просто напросто. Даже скорее всего. Lcd.print - это форматированный вывод, конечно. Берите сырое значение, и делайте с ним что угодно. Нули не пропадают, их может скрывать функция вывода на экран)

Kirill Iashin
Offline
Зарегистрирован: 28.02.2019

Schwarz78 пишет:

Приведите код, иначе непонятно.

Библиотека из стандартных примеров Arduino. Вот так спрашиваю:

lcd.print(node.getResponseBuffer(0), HEX); // Первые два байта серийного номера (0 регистр)
lcd.print(node.getResponseBuffer(1), HEX); // Вторые два байта серийного номера (1 регистр)

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

Schwarz78 пишет:

Подозреваю, что библиотека вашего LCD жрёт лидирующий ноль просто напросто

А может сам дисплей быть "не такой как все"?

Schwarz78 пишет:

Берите сырое значение, и делайте с ним что угодно

Это как? Через lcd.write?

lcd.write(node.getResponseBuffer(0), HEX);

Получается для разных типов данных использовать разные варианты вывода?

Но ведь это будет не String? Как бы хуже не сделать..

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Вы используете форматированный вывод, он убирает лидирующие нули с экрана, для читаемости. Из реальных значений нули никуда не пропадают. Попробуйте почитать это для начала: https://cpp.com.ru/shildt_spr_po_c/. Функция printf(). Там всё написано очень простым языком.

Schwarz78
Offline
Зарегистрирован: 19.01.2019

Kirill Iashin пишет:

Но ведь это будет не String? Как бы хуже не сделать..

Рано вам о String думать) Думайте о char[], то есть об обычных строках символов.

Вот ещё посмотрите: http://arduino.ru/Reference/Serial/Print

Ваш lcd.print явно наследует от этого общего с print родителя. Но вам точно рано об этом думать. Подумайте, как вам получить целое 16-разрядное число от вашего "устройства", вам больше и не надо.

Duet
Offline
Зарегистрирован: 17.01.2015

Как в ModbusRtu https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino задавать стартовый регистр для входящих пакетов?

Duet
Offline
Зарегистрирован: 17.01.2015

Как вывести в print шестнадцатеричные числа в десятичной системы в формате 001? Числа от 1 до 999 кодируются в два байта 0х0000, к примеру число 3 выводиться в принт как 3, а нужно в трехзначном виде 003 или число 25 как 025. Не соображу как это сделать.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

выводить лидирующие нули, если <=9 то два, если <=99 то один

uint16_t a[] = {7, 17, 177};

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < 3; i++) {
    Serial.print("a=");
    if (a[i] <= 9)Serial.print("00");
    if (a[i] > 9 && a[i] <= 99)Serial.print("0");
    Serial.println(a[i]);
  }
}

void loop() {
}

 

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

if (a[i] < 10)Serial.print('0');
if (a[i] < 100)Serial.print('0');

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

sadman41 пишет:
if (a[i] < 10)Serial.print('0'); if (a[i] < 100)Serial.print('0');

таки да )))
Скетч использует 262108 байт (25%) памяти устройства. Всего доступно 1044464 байт.
Глобальные переменные используют 26912 байт (32%) динамической памяти, оставляя 55008 байт для локальных переменных. Максимум: 81920 байт.

против твоих
Скетч использует 262104 байт (25%) памяти устройства. Всего доступно 1044464 байт.
Глобальные переменные используют 26900 байт (32%) динамической памяти, оставляя 55020 байт для локальных переменных. Максимум: 81920 байт.
 

Duet
Offline
Зарегистрирован: 17.01.2015

Понял, уже что-то получается) 

Как указать диапазон чисел от и до? После 1000 выводит и до 100 выводит, а вот как от 100 до 1000 указать не знаю.

    u8g2.setCursor(50, 60);                                 // указываем положение курсора
    if (au16data[1]< 999)u8g2.print('0');                   // выводим строку на дисплей
    
    u8g2.setCursor(60, 60);                                 // указываем положение курсора 
    if (au16data[1]< 99)u8g2.print('0');                    // выводим строку на дисплей


    u8g2.setCursor(50, 60);                                 // указываем положение курсора 
    if (au16data[1] > 998)u8g2.print(au16data[1]);          // выводим строку на дисплей

    u8g2.setCursor(60, 60);                                 // указываем положение курсора 
    if (au16data[1] > 100 < 1000)u8g2.print(au16data[1]);   // ????

    u8g2.setCursor(70, 60);                                 // указываем положение курсора 
    if (au16data[1] < 100)u8g2.print(au16data[1]);          // выводим строку на дисплей
   
    u8g2.sendBuffer(); 

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

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

Duet
Offline
Зарегистрирован: 17.01.2015

Понял. if (au16data[1] > 100 && au16data[1] < 1000)u8g2.print(au16data[1]);

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Duet пишет:

Понял. if (au16data[1] > 100 && au16data[1] < 1000)u8g2.print(au16data[1]);

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

u8g2.setCursor(50, 60);    
if (a[i] < 10)Serial.print('0');
if (a[i] < 100)Serial.print('0');
Serial.print(a[i]);

 

 

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

Ещё можно открыть для себя snprintf().

Duet
Offline
Зарегистрирован: 17.01.2015

Я не совсем правильно вопрос задал. С установкой нулей я сделал как Садман подсказал. Остальное для того чтоб числа с нулями не пересекались на дисплее.

    u8g2.setCursor(50, 60);                                 // указываем положение курсора
    if (au16data[1]< 999)u8g2.print('0');                   // выводим строку на дисплей
    
    u8g2.setCursor(60, 60);                                 // указываем положение курсора 
    if (au16data[1]< 99)u8g2.print('0');                    // выводим строку на дисплей


    u8g2.setCursor(50, 60);                                 // указываем положение курсора 
    if (au16data[1] > 998)u8g2.print(au16data[1]);          // выводим строку на дисплей

    u8g2.setCursor(60, 60);                                 // указываем положение курсора 
    if (au16data[1] > 100 && au16data[1] < 1000)u8g2.print(au16data[1]);   // 

    u8g2.setCursor(70, 60);                                 // указываем положение курсора 
    if (au16data[1] < 100)u8g2.print(au16data[1]);          // выводим строку на дисплей
   
    u8g2.sendBuffer();

 

Duet
Offline
Зарегистрирован: 17.01.2015

Собрал строчку, но что то длинная портянка получилась, а всего то одна строчка на дисплее "-9999.9999", точка зафиксирована на дисплее.

#include <Arduino.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#include <ModbusRtu.h>
#define ID 1 
Modbus slave(ID, 0, 0); 
uint16_t au16data[3];
int8_t state = 0; 

char rob1 = '-';
char rob2 = '+';

//au16data[0]  mm                
//au16data[1]  десятитысячные
//au16data[2]; 0x00- 0x01+
                       
U8G2_LC7981_240X128_F_6800 u8g2(U8G2_R0, 52, 50, 48, 46, 44, 42, 40, 38, /*enable=*/ 36, /*cs=*/ 34, /*dc=*/ 26, /*reset=*/ 30); // Connect RW with GND
  
void setup(void) {
  slave.begin( 9600 );    
  u8g2.begin();
  u8g2.enableUTF8Print(); 
}
void loop(void) { 
  state = slave.poll(au16data, 3);
  if (state>4) 
  {      
    u8g2.clearBuffer();                                     
    u8g2.setFont(u8g2_font_unifont_t_symbols);               
//Минус    
  if (au16data[2] == 1 && au16data[0]<= 9) 
  {
   u8g2.setCursor(32, 60);
   u8g2.print(rob1);   
  }
  if (au16data[2] == 1 && au16data[0] <= 99 && au16data[0] >= 10) 
  {
   u8g2.setCursor(24, 60);
   u8g2.print(rob1);   
  }
  if (au16data[2] == 1 && au16data[0] >= 100 && au16data[0] <= 999) 
  {
   u8g2.setCursor(16, 60);
   u8g2.print(rob1);   
  }
  if (au16data[2] == 1 && au16data[0] >= 1000 && au16data[0] <= 9999) 
  {
   u8g2.setCursor(8, 60);
   u8g2.print(rob1);   
  }
  
//Плюс
  if (au16data[2] == 0 && au16data[0]<= 9) 
  {
   u8g2.setCursor(32, 60);
   u8g2.print(rob2);   
  }
  if (au16data[2] == 0 && au16data[0] <= 99 && au16data[0] >= 10) 
  {
   u8g2.setCursor(24, 60);
   u8g2.print(rob2);   
  }
  if (au16data[2] == 0 && au16data[0] >= 100 && au16data[0] <= 999) 
  {
   u8g2.setCursor(16, 60);
   u8g2.print(rob2);   
  }
  if (au16data[2] == 0 && au16data[0] >= 1000 && au16data[0] <= 9999) 
  {
   u8g2.setCursor(8, 60);
   u8g2.print(rob2);   
  }

//ММ    
    u8g2.setCursor(40, 60);
    if (au16data[0] <= 9)u8g2.print(au16data[0]);
    u8g2.setCursor(32, 60); 
    if (au16data[0] >= 10 && au16data[0] <= 99)u8g2.print(au16data[0]); 
    u8g2.setCursor(24, 60); 
    if (au16data[0] >= 100 && au16data[0] <= 999)u8g2.print(au16data[0]); 
    u8g2.setCursor(16, 60); 
    if (au16data[0] >= 1000 && au16data[0] <= 9999)u8g2.print(au16data[0]); 
    
//Точка
    u8g2.setCursor(45, 60);                                 
    u8g2.print('.');
    
//Нули  
    u8g2.setCursor(50, 60);                                 
    if (au16data[1] <= 999)u8g2.print('0');                     
    u8g2.setCursor(58, 60);                                 
    if (au16data[1] <= 99)u8g2.print('0');                   
    u8g2.setCursor(66, 60);                                 
    if (au16data[1] <= 9)u8g2.print('0');                    

//после точки
    u8g2.setCursor(50, 60);                                 
    if (au16data[1] >= 1000)u8g2.print(au16data[1]);          
    u8g2.setCursor(58, 60);                                 
    if (au16data[1] >= 100 && au16data[1] <= 999)u8g2.print(au16data[1]);  
    u8g2.setCursor(66, 60);                                 
    if (au16data[1] >= 10 && au16data[1] <= 99)u8g2.print(au16data[1]);          
    u8g2.setCursor(74, 60);                                 
    if (au16data[1] <= 9)u8g2.print(au16data[1]);             
    u8g2.sendBuffer();  
  } 
}

Есть вариант проще, подсказали в разделе программирования:


float f= au16data[0]+(float)au16data[1]*0.0001;
if (au16data[2]) f=-f;
   u8g2.setCursor(24, 72);
   u8g2.print(f,4);

Но строку типа данных float нельзя отцентровать по точке, строка плавающая в зависимости от количества чисел или можно?

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

Добрый день.

Можно ли редактировать в этой библиотеке (https://github.com/4-20ma/ModbusMaster) параметры связи, такие как четность, стоповые биты и длина данных? В тексте программы я этой возможности не нашел. как можно решить эту проблему?

Спасибо.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Leonid Kuksin пишет:

Добрый день.

Можно ли редактировать в этой библиотеке (https://github.com/4-20ma/ModbusMaster) параметры связи, такие как четность, стоповые биты и длина данных? В тексте программы я этой возможности не нашел. как можно решить эту проблему?

Спасибо.

@param &serial reference to serial port object (Serial, Serial1, ... Serial3)

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

ua6em : @param &serial reference to serial port object (Serial, Serial1, ... Serial3)

Не понял, что это. Как этим воспользоваться для задания четности, стоповых бит и длины данных?

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

К модбасу эти параметры не относятся. Смотри как настраивается сериал. https://www.arduino.cc/reference/en/language/functions/communication/serial/begin/

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

Cпасибо.

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

Добрый день. Schwarz78

Я попробовал рекомендованную Вами библиотеку  https://github.com/BlackBrix/Simple-Modbus-Master.

Задача у меня простая - прочитать с датчика температуры и влажности (Slave addr 1, 9600, Serial_8N2) значения температуры (HR0) и влажности (HR1). Однако читаются нули. Датчик исправен (проверен с помощью другого Modbus мастера). Привожу текст скетча:

/*
--------------------------------------------------------------------------------------------------------------------------------------
Задача:
прочитать со Slave(address 1, 9600, SERIAL_8N2) два регистра
типа uint16_t по адресам 0 (значение температуры Т) и 1 (значение влажности Hum). 
*/
//-------------------------------------------------------------------------------------------------------------------------------------
#include <SimpleModbusMaster.h>
#include <SoftwareSerial.h>

#define swRx 4
#define swTx 5

#define LED 9

#define TxEnablePin      3
// The total amount of available memory on the master to store data
#define TOTAL_NO_OF_REGISTERS 1
//////////////////// Port information ///////////////////
#define baud 9600
#define timeout 1000
#define polling 200 // the scan rate
#define retry_count 10

SoftwareSerial swSer(swRx, swTx);

// This is the easiest way to create new packets
// Add as many as you want. TOTAL_NO_OF_PACKETS
// is automatically updated.
enum
{
  PACKET1,
//  PACKET2,
  TOTAL_NO_OF_PACKETS // leave this last entry
};

// Create an array of Packets to be configured
Packet packets[TOTAL_NO_OF_PACKETS];

// Masters register array
unsigned int regs[TOTAL_NO_OF_REGISTERS], T, Hum;
//----------------------------------------------------------------------------------------
void setup()
{
  swSer.begin(115200);
  pinMode(LED, OUTPUT);

  // Initialize each packet
  modbus_construct(&packets[PACKET1], 1, READ_HOLDING_REGISTERS, 0, 2, 0);
  //modbus_construct(&packets[PACKET2], 1, PRESET_MULTIPLE_REGISTERS, 1, 1, 0);

  // Initialize the Modbus Finite State Machine
  modbus_configure(&Serial, baud, SERIAL_8N2, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS, regs);


  // Print hello message
  swSer.println("Modbus communication over RS-485");
  swSer.println("Modbus to I(4-20mA) converter. V1.0");
  delay(100);
}

void loop()
{
  modbus_update();

  T = regs[0];
  Hum = regs[1];


  swSer.print("T="); swSer.println(T);
  swSer.print("Hum="); swSer.println(Hum);
  swSer.println();

  delay(5000);
}

Что не так?

Спасибо

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

В обычной SoftwareSerial не было возможности назначать параметры, посмотрели, в вашей есть?

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

Да, работает.

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

Скажите, почему задача прочитать два регистра, а количество регистров 1 (строка 18)?

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

В данном случае читается только значение температуры = 0. Я устанавливал 2 - читались два параметра, равные нулю.

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

SoftwareSerial на 115200 - это безудержный оптимизм. 9600 ставьте.

Leonid Kuksin
Offline
Зарегистрирован: 08.01.2022

Да бог  с ним, с SoftwareSerial на 115200 (хотя он работает). Как насчет моей проблемы?

Спасибо.