• Witaj na Forum Arduino Polska! Zapraszamy do rejestracji!
  • Znajdziesz tutaj wiele informacji na temat hardware / software.
Witaj! Logowanie Rejestracja


Ocena wątku:
  • 0 głosów - średnia: 0
  • 1
  • 2
  • 3
  • 4
  • 5
Arduino nano Modbus i czujnik VL53L1X
#1
Witam,
męczę temat wysyłania danych po modbus z czujnika odległości VL53L1X.
Założenie jest takie że arduino pracuje jako slave a master co jakiś czas odpytuje po modbusie i otrzymuje odpowiedź z odległością zmierzoną przez czujnik.
Komunikacja po RS485 uruchomiona i działa, arduino wysyła dane ale jak dopisać do tego sketcha obsługę modbus żeby odpytując 40001 otrzymać zmienną z pomiaru odległości?
Kod:
#include <Wire.h>
#include <VL53L1X.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(11, 10); // RX, TX port dla rs485
VL53L1X sensor;

void setup()
{
  Serial.begin(115200); //baudrate serial po USB
  mySerial.begin(9600); //baudrate dla rs485
  Wire.begin();
  Wire.setClock(400000); // 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Brak czujnika!");
    while (1);
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(15000);
  sensor.startContinuous(150);
  Serial.println("nowy odczyt");
}

void loop()
{
  Serial.println(String(sensor.read()));
  mySerial.println(String(sensor.read()));
}
 
Odpowiedź
#2
To co pokazałeś to żaden modbus tylko komunikacja szeregowa po RS485. Jak chcesz mieć w tym urządzeniu modbus to musisz znaleźć bibliotekę albo ją sam wymyślić. Oczywiście żaden modbus nie jest do tego niezbędny, może być i tak jak jest że Arduino wysyła dane w stałej częstotliwości np. co 100ms aktualny pomiar, albo robi go tylko na zapytanie, albo robi co ile się da, a wysyła tylko na zapytanie ostatni dostępny.
W pokazanym przykładzie dwa razy odczytujesz czujnik i dwa razy wysyłasz, raczej bez sensu bo mogą to być różne wartości, ja bym robił raczej 1 odczyt i dwa wysłania w wybranym interwale czasowym zdolnym do obsłużenia przez czujnik. Jeśli czujnik jest dość wydajny to może nawet zapchać Ci magistralę na 9600, bo masz odczyty 400kHz a wysyłasz 9,6kHz.
W bibliotekach Arduino jest mnóstwo do wyboru, akurat ja nie korzystałem z tej strony modbus, używam jednego Arduino do odczytu licznika i wysyłania danych do wizualizacji na Thinkspeak.
To czego szukasz to pewnie slave, zawiera to biblioteka np. Modbus-Master-Slave-for-Arduino @author Samuel Marco i Armengol. Większość bibliotek to Master do odczytu istniejących slave z tym protokołem.
 
Odpowiedź
#3
(09-10-2021, 22:09)kaczakat napisał(a): To co pokazałeś to żaden modbus tylko komunikacja szeregowa po RS485. Jak chcesz mieć w tym urządzeniu modbus to musisz znaleźć bibliotekę albo ją sam wymyślić. Oczywiście żaden modbus nie jest do tego niezbędny, może być i tak jak jest że Arduino wysyła dane w stałej częstotliwości np. co 100ms aktualny pomiar, albo robi go tylko na zapytanie, albo robi co ile się da, a wysyła tylko na zapytanie ostatni dostępny.
W pokazanym przykładzie dwa razy odczytujesz czujnik i dwa razy wysyłasz, raczej bez sensu bo mogą to być różne wartości, ja bym robił raczej 1 odczyt i dwa wysłania w wybranym interwale czasowym zdolnym do obsłużenia przez czujnik. Jeśli czujnik jest dość wydajny to może nawet zapchać Ci magistralę na 9600, bo masz odczyty 400kHz a wysyłasz  9,6kHz.
W bibliotekach Arduino jest mnóstwo do wyboru, akurat ja nie korzystałem z  tej strony modbus, używam jednego Arduino do odczytu licznika i wysyłania danych do wizualizacji na Thinkspeak.
To czego szukasz to pewnie slave, zawiera to biblioteka np. Modbus-Master-Slave-for-Arduino @author Samuel Marco i Armengol. Większość bibliotek to Master do odczytu istniejących slave  z tym protokołem.


Mam zainstalowaną tą bibliotekę Modbus-Master-Slave-for-Arduino @author Samuel Marco i Armengol..
mam przykład w example.. ale nie potrafię podmienić zmiennej z AnalogRead na moją wstawioną...
Odczytuje dwa razy bo... za pierwszym razem wysyłam na konsole PC (debug) a drugi odczyt mySerial jest już fizycznym odczytem do wysłania w modbusie po zapytaniu o tą wartość oczywiście
Kod:
/*
    Modbus slave - simple RS485 example

    Control and Read Arduino I/Os using Modbus RTU.

    This sketch shows how you can use the callback vector for reading and
    controlling Arduino I/Os.

    * Maps digital outputs to modbus coils.
    * Maps digital inputs to discreet inputs.
    * Maps analog inputs to input registers.

    The circuit: ( see: ./extras/ModbusSchematic.png )
    * An Arduino.
    * 2x LEDs, with 220 ohm resistors in series.
    * A switch connected to a digital pin.
    * A potentiometer connected to an analog pin.
    * A RS485 module (Optional) connected to RX/TX and a digital pin.

    Created 08-12-2015
    By Yaacov Zamir

    Updated 31-03-2020
    By Yorick Smilda

    https://github.com/yaacov/ArduinoModbusSlave
*/

#include <ModbusSlave.h>

#define SLAVE_ID 1          // The Modbus slave ID, change to the ID you want to use.
#define SERIAL_BAUDRATE 9600 // Change to the baudrate you want to use for Modbus communication.
#define SERIAL_PORT mySerial  // Serial port to use for RS485 communication, change to the port you're using.
#include <SoftwareSerial.h>
SoftwareSerial mySerial(11, 10); // RX, TX
// Comment out the following line if your not using RS485
//#define RS485_CTRL_PIN 8 // Change to the pin the RE/DE pin of the RS485 controller is connected to.

// The position in the array determines the address. Position 0 will correspond to Coil, Discrete input or Input register 0.
uint8_t input_pins[] = {2, 3, 4};    // Add the pins you want to read as a Discrete input.
uint8_t output_pins[] = {8, 9, 10};  // Add the pins you want to control via a Coil.
uint8_t analog_pins[] = {A0, A1, A2}; // Add the pins you want to read as a Input register.

// You shouldn't have to change anything below this to get this example to work

uint8_t input_pins_size = sizeof(input_pins) / sizeof(input_pins[0]);    // Get the size of the input_pins array
uint8_t output_pins_size = sizeof(output_pins) / sizeof(output_pins[0]); // Get the size of the output_pins array
uint8_t analog_pins_size = sizeof(analog_pins) / sizeof(analog_pins[0]); // Get the size of the analog_pins array

#ifdef RS485_CTRL_PIN
// Modbus object declaration
Modbus slave(SERIAL_PORT, SLAVE_ID, RS485_CTRL_PIN);
#else
Modbus slave(SERIAL_PORT, SLAVE_ID);
#endif

void setup()
{
    // Set the defined input pins to input mode.
    for (int i = 0; i < input_pins_size; i++)
    {
        pinMode(input_pins[i], INPUT);
    }

    // Set the defined analog pins to input mode.
    for (int i = 0; i < analog_pins_size; i++)
    {
        pinMode(analog_pins[i], INPUT);
    }

    // Set the defined output pins to output mode.
    for (int i = 0; i < output_pins_size; i++)
    {
        pinMode(output_pins[i], OUTPUT);
    }

    // Register functions to call when a certain function code is received.
    slave.cbVector[CB_WRITE_COILS] = writeDigitalOut;
    slave.cbVector[CB_READ_DISCRETE_INPUTS] = readDigitalIn;
    slave.cbVector[CB_READ_INPUT_REGISTERS] = readAnalogIn;

    // Set the serial port and slave to the given baudrate.
    SERIAL_PORT.begin(SERIAL_BAUDRATE);
    slave.begin(SERIAL_BAUDRATE);
}

void loop()
{
    // Listen for modbus requests on the serial port.
    // When a request is received it's going to get validated.
    // And if there is a function registered to the received function code, this function will be executed.
    slave.poll();
}

// Modbus handler functions
// The handler functions must return an uint8_t and take the following parameters:
//    uint8_t  fc - function code
//    uint16_t address - first register/coil address
//    uint16_t length/status - length of data / coil status

// Handle the function codes Force Single Coil (FC=05) and Force Multiple Coils (FC=15) and set the corresponding digital output pins (coils).
uint8_t writeDigitalOut(uint8_t fc, uint16_t address, uint16_t length)
{
    // Check if the requested addresses exist in the array
    if (address > output_pins_size || (address + length) > output_pins_size)
    {
        return STATUS_ILLEGAL_DATA_ADDRESS;
    }

    // Set the output pins to the given state.
    for (int i = 0; i < length; i++)
    {
        // Write the value in the input buffer to the digital pin.
        digitalWrite(output_pins[address + i], slave.readCoilFromBuffer(i));
    }

    return STATUS_OK;
}

// Handle the function code Read Input Status (FC=02) and write back the values from the digital input pins (discreet input).
uint8_t readDigitalIn(uint8_t fc, uint16_t address, uint16_t length)
{
    // Check if the requested addresses exist in the array
    if (address > input_pins_size || (address + length) > input_pins_size)
    {
        return STATUS_ILLEGAL_DATA_ADDRESS;
    }

    // Read the digital inputs.
    for (int i = 0; i < length; i++)
    {
        // Write the state of the digital pin to the response buffer.
        slave.writeCoilToBuffer(i, digitalRead(input_pins[address + i]));
    }

    return STATUS_OK;
}

// Handle the function code Read Input Registers (FC=04) and write back the values from analog input pins (input registers).
uint8_t readAnalogIn(uint8_t fc, uint16_t address, uint16_t length)
{
    // Check if the requested addresses exist in the array
    if (address > analog_pins_size || (address + length) > analog_pins_size)
    {
        return STATUS_ILLEGAL_DATA_ADDRESS;
    }

    // Read the analog inputs
    for (int i = 0; i < length; i++)
    {
        // Write the state of the analog pin to the response buffer.
        slave.writeRegisterToBuffer(i, analogRead(analog_pins[address + i]));
    }

    return STATUS_OK;
}
 
Odpowiedź
#4
Raczej to jakaś inna biblioteka, bo w tym co wkleiłeś przyznają się do niej Yaacov Zamir i Yorick Smilda.
No to właśnie nie odczytuj dwa razy, tylko zrób sobie zaraz przed tą operacją chwilową zmienną lokalną, do niej odczytaj i ją dwa razy wydrukuj, będziesz miał te same wartości do porównania na dwóch wyjściach.
Może to być też zmienna globalna i wtedy zamiast robić odczyt przy zapytaniu od razu zwracasz ostatnia wartość.
W przykładzie zacznij od wywalenia wszystkiego, co niepotrzebne, żeby nie robić za dużo zmian zostaw sobie tablicę na 1 element.
Swoją funkcję wciskasz w tą sekcję:
Kod:
for (int i = 0; i < length; i++)
    {
        // Write the state of the analog pin to the response buffer.
        slave.writeRegisterToBuffer(i, analogRead(analog_pins[address + i]));
    }
slave.writeRegisterToBuffer(i, sensor.read( ) 
Oczywiście jak ustawisz rozmiar tablicy na 1 to tylko raz się przekręci. Jak sensor.read daje w wyniku jakieś ułamki to zrób z tego liczbę całkowitą, np. odczyty w m długości z dokładnością do 1mm (0.001) przechowuj w mm - x1000.
slave.writeRegisterToBuffer(i, 1000*sensor.read( ) ;
uint8_t analog_pins[] = {A0, A1, A2}; tu pewnie uint8_t będzie za mały, wstaw 16 lub 32.
 
Odpowiedź
#5
Jest zrobione Smile Działa Big Grin
Jeszcze jedno pytanie jak mogę wstawić następną zmienną by odczytywać z niej dane w kolejnym offsecie ?
teraz odczytuje pod adresem 30001 a kolejna chcę odczytać w następnym czyli 30002.
Działający kod:
Kod:
#include <ModbusSlave.h>
#include <Wire.h>
#include <VL53L1X.h>
#define SLAVE_ID 2          // The Modbus slave ID, change to the ID you want to use.
#define SERIAL_BAUDRATE 9600 // Change to the baudrate you want to use for Modbus communication.
#define SERIAL_PORT mySerial  // Serial port to use for RS485 communication, change to the port you're using.
#define RS485_CTRL_PIN 13
#include <SoftwareSerial.h>
SoftwareSerial mySerial(11, 10); // RX, TX
VL53L1X sensor;

uint16_t analog_pins[] = {A0}; // Add the pins you want to read as a Input register.

uint16_t analog_pins_size = sizeof(analog_pins) / sizeof(analog_pins[0]); // Get the size of the analog_pins array

#ifdef RS485_CTRL_PIN
// Modbus object declaration
Modbus slave(SERIAL_PORT, SLAVE_ID, RS485_CTRL_PIN);
#else
Modbus slave(SERIAL_PORT, SLAVE_ID);
#endif

void setup()
{
 
  Serial.begin(115200);
  mySerial.begin(9600);
  Wire.begin();
  Wire.setClock(400000); // use 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1);
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(15000);
  sensor.startContinuous(150);

    // Set the defined analog pins to input mode.
    for (int i = 0; i < analog_pins_size; i++)
    {
        pinMode(analog_pins[i], INPUT);
    }

    // Register functions to call when a certain function code is received.
    slave.cbVector[CB_READ_INPUT_REGISTERS] = readAnalogIn;

    // Set the serial port and slave to the given baudrate.
    SERIAL_PORT.begin(SERIAL_BAUDRATE);
    slave.begin(SERIAL_BAUDRATE);
}

void loop()
{
    slave.poll();
}

// Handle the function code Read Input Registers (FC=04) and write back the values from analog input pins (input registers).
uint16_t readAnalogIn(uint8_t fc, uint16_t address, uint16_t length)
{
    // Check if the requested addresses exist in the array
    if (address > analog_pins_size || (address + length) > analog_pins_size)
    {
        return STATUS_ILLEGAL_DATA_ADDRESS;
    }

    for (int i = 0; i < length; i++)
    {
        // Write the state of the analog pin to the response buffer.
       slave.writeRegisterToBuffer(i, sensor.read( )) ;
    }
    return STATUS_OK;
}
 
Odpowiedź
#6
Jeżeli to coś zupełnie z innej beczki, to musisz inaczej napisać tą część z for, teraz jest zrobiona tak, bo numery pinów analogowych można było ułożyć w tablicy i jednym licznikiem 'i' wskazywać indeksy dwóch tablic.
slave.writeRegisterToBuffer(0, sensor.read( )) ;
slave.writeRegisterToBuffer(1, jakiesInneDaneTypuUint16) ;
Najprościej te dane trzymać w jednym typie i zwiększyć tablicę tych analogów.
Tego nie musiałeś zmieniać, chyba że przewidujesz liczbę elementów powyżej 256:
uint8_t analog_pins_size = sizeof(analog_pins) / sizeof(analog_pins[0]); // Get the size of the analog_pins array
To jest do obliczenia wymaganej wielkości tablicy na przechowywane w rejestrach dane, ile będzie takich komórek po 16bitów (czy jakie tam dane wrzucisz).
Jak zaczniesz rozumieć co do czego to pozmieniaj nazwy, bo za chwilę będziesz się głowił po co Ci była ta tablica analogów przy czujniku na I2C.
 
Odpowiedź
#7
Zrobiłem to tak:  
Odczyt działa ale :
jak odpytuje adres 30001 z długością 1 to zwraca zmierzoną odległość .
jak odpytuje adres 30001 z długością 2 to dostaje dwa parametry odległość i temp.
Jak odpytam adres 30002 z długością 1 to dostaje odległość zamiast temperatury.
 
Kod:
#include <ModbusSlave.h>
#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <VL53L1X.h>
#define SLAVE_ID 2          // slave id modbus // (do dopisania) mam na pcb zamontowany dipswitch 4 do zmiany adresu (ID 1 lub 2) oraz do zmiany predkości transmisji (19200/9600/115200/38400/57600)
#define SERIAL_BAUDRATE 9600 // predkosc modbus jak wyżej do dopisania mozliwosc konfiguracji predkosci
#define SERIAL_PORT mySerial  // port programowy dla modbus
#include <SoftwareSerial.h>
SoftwareSerial mySerial(11, 10); // RX, TX
VL53L1X sensor;

uint16_t analog_pins[] = {0,1}; // Add the pins you want to read as a Input register.
uint8_t output_pins[] = {3,13}; //piny wyjsciowe przekaznik i led
uint16_t analog_pins_size = sizeof(analog_pins) / sizeof(analog_pins[0]); // Get the size of the analog_pins array
uint8_t output_pins_size = sizeof(output_pins) / sizeof(output_pins[0]); // Get the size of the output_pins array

Modbus slave(SERIAL_PORT, SLAVE_ID);
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
void setup()
{

  sensors.begin();
  Serial.begin(115200);//debug
  mySerial.begin(9600);//modbus
  Wire.begin();
  Wire.setClock(400000); // use 400 kHz I2C

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1);
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(100);
  sensor.startContinuous(100);
   
   
    // Set the defined output pins to output mode.
   
     
     pinMode(3, OUTPUT); //przekaznik
     pinMode(13, OUTPUT); //led wbudowana


    // Register functions to call when a certain function code is received.
    slave.cbVector[CB_READ_INPUT_REGISTERS] = readAnalogIn; //odczyt rej
    slave.cbVector[CB_WRITE_COILS] = writeDigitalOut; //zapis rejestrow
   
    // Set the serial port and slave to the given baudrate.
    SERIAL_PORT.begin(SERIAL_BAUDRATE); //inicjalizacja debug
    slave.begin(SERIAL_BAUDRATE); //inicjalizacja portu modbus (uart programowy)
}

void loop()
{
    slave.poll();
}

// Handle the function code Read Input Registers (FC=04) and write back the values from analog input pins (input registers).
uint8_t readAnalogIn(uint8_t fc, uint16_t address, uint16_t length) //
{
    // Check if the requested addresses exist in the array
    if (address > analog_pins_size || (address + length) > analog_pins_size)
   {
        return STATUS_ILLEGAL_DATA_ADDRESS;
   }
    sensors.requestTemperatures();
    for (int i = 0; i < length; i++)
    {
        // Write the state of the analog pin to the response buffer.
       slave.writeRegisterToBuffer(0, sensor.read( )) ; //odczyt odleglosci
       
       slave.writeRegisterToBuffer(1, sensors.getTempCByIndex(0)); //odczyt temperatury
       //Serial.println(sensors.getTempCByIndex(0));
       //slave.writeRegisterToBuffer(i, sensor.read( )) ;
    }
    return STATUS_OK;
}


uint8_t writeDigitalOut(uint8_t fc, uint16_t address, uint16_t length)   //sterowanie przekaznikiem dziala jak wysle po modbus 0x0000 dla 05 force single coil
{
    // Check if the requested addresses exist in the array
    if (address > output_pins_size || (address + length) > output_pins_size)
    {
        return STATUS_ILLEGAL_DATA_ADDRESS;
    }

    // Set the output pins to the given state.
    for (int i = 0; i < length; i++)
   
        // Write the value in the input buffer to the digital pin.
        digitalWrite(output_pins[address + i], slave.readCoilFromBuffer(i)); //zalaczenie przekaznika i led
   

    return STATUS_OK;
}
 
Odpowiedź
#8
Ja z licznika odczytuje porcje 4 bajtowe, gdzie informacje są zapisane w 4, 2 lub 1 bajcie. Użyj maskowania bitów, suma/iloczyn/przesunięcie/itp., tu sklejam z dwóch rejestrów 16 bitowych:
kwh=((uint32_t) node.getResponseBuffer(0x07))<<16;
kwh=kwh|(node.getResponseBuffer(0x08));
VAR3=kwh/1000.0;
 
Odpowiedź
  


Skocz do:


Przeglądający: 1 gości