Невозможно получить показания счетчика (Modbus)

У меня есть система, в которой я снимаю показания счетчика и обрабатываю их через Arduino Nano. Мой код выглядит следующим образом:

#include <SoftwareSerial.h>

#define SerialControl 7
#define RS485Tx HIGH
#define RS485Rx LOW

SoftwareSerial RS485Serial(11, 12);
uint8_t window[8]; // Инициализация окна размером 8 байт

byte SendKWh[] = { 0x01, 0x03, 0x40, 0x34, 0x00, 0x02, 0x90, 0x05 };

// 0x01: адрес счетчика, 0x03: чтение кода функции, 0x40: переменная адреса регистра (старший бит начального адреса)
// 0x34: переменная адреса регистрации (младший бит начального адреса)
// 0x00: количество регистров (старший бит), 0x02: количество регистров (младший бит)
// 0x90: код проверки CRC (младший бит кода CRC)
// 0x05: код проверки CRC (старший бит кода CRC)

void dumpWin() {
  for (int i = 0; i < 8; i++) {
    Serial.print(window[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void ReadRx() {
  // Реализация скользящего окна для чтения данных
  if(RS485Serial.available()) {
    uint8_t b = RS485Serial.read();
    dumpWin();
    // Сдвигаем содержимое массива вниз
    for (uint8_t i = 0; i < 7; i++) {
      window[i] = window[i+1];
    }
    window[7] = b; // Следующий кадр вставляется поверх предыдущего
    if ((window[0] == 0x01) && (window[1] == 0x03) &&
      (window[2] == 0x40) && (window[3] == 0x34)) {
      // Здесь что-то делаем с window[4] и window[5]
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(SerialControl, OUTPUT);
  RS485Serial.begin(9600); 
}

void loop() {
  static uint32_t ts = millis();
  if (millis() - ts >= 2000) {
    ts = millis();
    digitalWrite(SerialControl, RS485Tx);
    RS485Serial.write(SendKWh, sizeof(SendKWh));
    RS485Serial.flush();
    digitalWrite(SerialControl, RS485Rx);
    RS485Serial.listen();
  }
  ReadRx();
}

ReadRx() ведет себя странно, а dumpWin() выдает 0 0 0 0 0 0 0 0. Я пытался, но не могу понять, где я ошибаюсь. Ваша помощь оценена по достоинству!

РЕДАКТИРОВАТЬ 2: я изменил код, как предложил @Majko, но по-прежнему window[] O/P равен 0 для всех. Я не могу понять, почему он не может прочитать значения из RS485Serial.

, 👍1

Обсуждение

Не допускайте всех этих задержек. Вы не хотите никаких задержек где-либо., @Majenko

Задержка в ReadRx после digitalWrite?, @Ashish K

ReadRX читает только один байт. Его нужно запускать неоднократно и быстро. Оставляйте rs485 в режиме RX все время, за исключением случаев, когда вы специально хотите передать., @Majenko

Я удалил delay(1500), но O/P для dumpWin() по-прежнему остается 0 для всех., @Ashish K

Что-то не так в этом утверждении RS485Serial.write(SendKWh, sizeof(SendKWh));, @Ashish K

Я думаю, что весь ваш метод работы неправильный. В цикле у вас должен быть небольшой код, который решает, пора ли отправлять. Затем он переключается в режим отправки, отправляет, а затем снова переключается в режим чтения. Тогда все, что остается в цикле, — это функция чтения, которая просто читает, добавляет в массив и обрабатывает, если он совпадает. Ничего больше., @Majenko


1 ответ


1

Вам нужно перестать мыслить столь линейно. Вы работаете с асинхронными системами, поэтому ваш код тоже должен быть асинхронным.

Что-то вроде этого:

#include <SoftwareSerial.h>

#define SerialControl 7

#define RS485Tx HIGH
#define RS485Rx LOW


SoftwareSerial RS485Serial(11, 12);

byte SendKWh[] = { 0x01, 0x03, 0x40, 0x34, 0x00, 0x02, 0x90, 0x05 };

// 0x01: Meter Address, 0x03: Read Function Code, 0x40: Register Address Variable (Start address high bit)
// 0x34: Register Address Variable (Start address low bit)
// 0x00: Register Quantity (High Bit), 0x02: Register Quantity (Low Bit)
// 0x90: CRC check code (CRC code Low bit)
// 0x05: CRC check code (CRC code High bit)

uint8_t window[8]; // Initialising window of 8 bytes

void dumpWin() {
  for (int i = 0; i < 8; i++) {
    Serial.print(window[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void ReadRx() {
  if(RS485Serial.available()) {
    uint8_t b = RS485Serial.read();
    dumpWin();
    // Slide the contents of the array down
    for (uint8_t i = 0; i < 7; i++) {
      window[i] = window[i+1];
    }

    window[7] = b; // Next frame inserted at the top of previous  

    if ((window[0] == 0x01) && (window[1] == 0x03) &&
        (window[2] == 0x40) && (window[3] == 0x34)) {
          // Doing something with window[4] and window[5] here
    }
  }
}



void setup() {
  Serial.begin(115200);
  pinMode(SerialControl, OUTPUT);
  digitalWrite(SerialControl, RS485Rx);
  RS485Serial.begin(9600); 

}

void loop() {
  static uint32_t ts = millis();

  if (millis() - ts >= 2000) {
      ts = millis();
      digitalWrite(SerialControl, RS485Tx);
      RS485Serial.write(SendKWh, sizeof(SendKWh));
      RS485Serial.flush();
      digitalWrite(SerialControl, RS485Rx);
      RS485Serial.listen();
  }

  ReadRx();
}

Обратите внимание, как я использую millis() для асинхронной отправки команды каждые 2 секунды (т. е. без delay()). RS485 остается в режиме ЧТЕНИЯ за исключением короткого момента, пока вы отправляете. Также обратите внимание на использование flush() для ожидания, пока последний символ не будет передан интерфейсом UART (в данном случае программным обеспечением). Это важно, иначе вы можете прервать передачу на полпути (я не думаю, что это такая проблема с SoftwareSerial, но при аппаратном UART отсутствие этого будет фатальным).

Чтение просто считывает один символ и возвращается в цикл. Каждая итерация цикла (если символ доступен) будет читать только один символ. Только когда все 8 получены и содержимое окна совпало, можно что-либо обрабатывать (что поставить вместо комментария).

Этот метод также дает вам возможность реализовать другие вещи в loop(), если они также не используют delay().

,