RS-485 Arduino Uno

Buongiorno a tutti, ultimamente mi sto cimentando nella comunicazione seriale. Il mio progetto sarebbe quello di leggere dei valori del mio impianto solare ( carica, scarica, voltaggio e consumo settimanale).
Riesco a leggere i dati solo che ora non so come fare a scrivere tramite RS-485 un comando per spegnere o accendere l'impianto.Il mio controller e un Tracer 20A 4215BN MPPT.
Io ho a disposizione i codici del mio cotroller e ho trovato l'istruzione che devo inviare per spegnerlo, come faccio a scrivere un comando tramite il modulo RS-485?
Qualcuno sa aiutarmi? ringrazio chiunque mi dia una dritta :wink:

In allegato vi metto il codice, il link del RS-485 e il foglio dei comandi:

https://www.wish.com/product/59fd93bd650f3c1bf8db3dbb

#include <ModbusMaster.h>

#define MAX485_DE     3
#define MAX485_RE_NEG 2

ModbusMaster node;
 void preTransmission()
 {
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
 }

 void postTransmission()
 {
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
 }

void setup() {
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  Serial.begin(115200);
  node.begin(1, Serial);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);

}

void loop() {
  uint8_t resultMain;

  resultMain = node.readInputRegisters(0x3100, 6);
  if(resultMain == node.ku8MBSuccess)
  {
    Serial.println(" - - - - - - - - ");
    Serial.print("PV Voltage: ");
    Serial.println(node.getResponseBuffer(0x00) / 100.0f);
    Serial.print("PV Current: ");
    Serial.println(node.getResponseBuffer(0x01) / 100.0f);
    Serial.print("Battery Voltage: ");
    Serial.println(node.getResponseBuffer(0x04) / 100.0f);
    Serial.print("Battery Charge Current: ");
    Serial.println(node.getResponseBuffer(0x05) / 100.0f);
    Serial.print("Charging percentage: ");
    
  }
  delay(1000);
}
PV Voltage: 4.20
PV Current: 0.00
Battery Voltage: 12.63
Battery Charge Current: 0.00

1733_modbus_protocol.pdf (234 KB)

Ciao,

quella libreria ti mette a disposizione un metodo per scrivere il singolo registro:

uint8_t ModbusMaster::writeSingleRegister (
uint16_t u16WriteAddress,
uint16_t u16WriteValue )

nel tuo caso il registro dovrebbe essere il 0x903D, che ha 4 possibili impostazioni, però ci sono sicuramente anche i registri 0x0002 e 0x0006 da tenere in considerazione...purtroppo non sapendo come funziona il tuo dispositivo non so essere più preciso.

Grazie mille ORSO2001 per la tua risposta, le mie conoscenze in questo campo sono abbastanza limitate, vedo che te sei esperto e sai come utilizzare queste librerie,che informazioni ti servono?
Io pensavo di utilizzare lo 0x0006 per inviare il comando al controller MPPT solo che non so come scrivergli se voglio che si accenda o si spenga. Riesco solo a leggere i valori dalla porta seriale.
La mia idea sarebbe quella di poter inviare tramite SMS un messaggio tipo: ON o OFF e l'impianto si accende o si spegne, per fare questa operazione pero devo inviare il registro corretto e il valore o 1 o 0.
Sai come posso fare?

dove devo inserire questa parte di codice?

uint8_t ModbusMaster::writeSingleRegister (
uint16_t u16WriteAddress,
uint16_t u16WriteValue )

Grazie mille per il tuo aiuto e interessamento, spero che mi continuerai ad aiutare :slight_smile:

immagino che il tuo SMS sarà "letto" da arduino il quale, verificato cosa vuoi fare, eseguirà questo pseudocodice (seguendo il tuo codice):

se SMS == spegni impianto
    result = node.writeSingleRegister(6,0);
se SMS == accendi impianto
    result = node.writeSingleRegister(6,1);
verifico se result == node.ku8MBSuccess

comunque il protocollo modbus è discretamente semplice da imparare e capire...dai un occhio a QUESTO link

Si esatto ORSO2001,grazie mille per la spiegazione, se la stringa dell'SMS contiene una certa sequenza di lettere farà un'azione se ne contiene altre ne farà un'altra. Per il protocollo modbus devo utilizzare un RS-485 per leggere i dati e un'altro per inviare i comandi?
Ho modificato in questo modo il codice, e' giusto?

Ho fatto il seguente collegamento con arduino:
RS-485 Arduino
DI --> Pin TX
DE--> Pin 3
RE--> Pin 2
R0--> Pin RX

Prima di andare a controllare il contenuto dell'SMS e fare un'azione vorrei riuscire a comunicare correttamente con l'impianto.

void loop() {
  uint8_t resultMain;
  
  resultMain = node.readInputRegisters(0x3100, 6);
  if(resultMain == node.ku8MBSuccess)
  {
    Serial.println(" - - - - - - - - ");
    Serial.print("Battery Voltage: ");
    Serial.println(node.getResponseBuffer(0x04) / 100.0f);
    double TensioneBatteria = node.getResponseBuffer(0x04) / 100.0f;
    Serial.println(TensioneBatteria);
    
  }
  node.writeSingleRegister(6,1);
  delay(10000);
}

Nella porta seriale leggo questi valori, immagino di aver sbagliato qualcosa...

1?-------
Battery Voltage: 12.65
12.65
?

ciao

quando scrivi questo:

node.readInputRegisters(0x3100, 6);

stai leggendo 6 registri a partire dal 0x3100...quindi 3100, 3101, 3102, 3103, 3104, 3105...ci siamo?
questi saranno "appesi" ad un array di uint_16t (int senza segno) in questo modo:

posizione array [0] -> 3100
posizione array [1] -> 3101
posizione array [2] -> 3102
posizione array [3] -> 3103
posizione array [4] -> 3104
posizione array [5] -> 3105

detto questo tu scrivendo:

ode.getResponseBuffer(0x04)

stai chiedendo il valore dell'array[4], cioè il registo 3104 (battery voltage) e lo fai due volte:

    Serial.println(node.getResponseBuffer(0x04) / 100.0f);
    double TensioneBatteria = node.getResponseBuffer(0x04) / 100.0f;

infatti per due volte stampi il valore 12.65.

fai una prova...invece di mettere un delay(10000) esegui la lettura solo se da monitor seriale invii un carattere ... tipo cancella il tuo loop e metti questo:

void loop() {
  uint8_t resultMain;
  static boolean passed = false;

  if (Serial.available()) { // se un carattere passato dal monitor seriale
    char c = Serial.read();
    passed = true; //abilito comunicazione seriale
  }
  if (passed) {
    resultMain = node.readInputRegisters(0x3100, 6);
    Serial.println("start transmission...");
    if (resultMain == node.ku8MBSuccess) {
      Serial.println("Success!");
      Serial.print("Battery Voltage: ");
      Serial.println(node.getResponseBuffer(0x04) / 100.0f);
      Serial.println("end trasmission");
      passed = false;
    }
    else {
      passed = false;
      Serial.println("ERROR!");
    }
  }
}

Si ORSO2001 hai perfettamente ragione, ho tolto delle parti di codice e mi sono dimenticato di modificare i numeri.

double TensioneBatteria = node.getResponseBuffer(0x04) / 100.0f;

questa parte di codice l'ho messa solo per salvare il valore della tensione della batteria in una variabile che utilizzerò in futuro.
Ho testato il codice che mi hai consigliato e funziona correttamente, mi puoi aiutare anche per la trasmissione del codice per accendere e spegnere l'impianto?

Se mi guidi passo a passo come hai fatto fino ad ora te ne sarei grato, ora inizia a diventare più chiaro il codice e riesco a capire cosa fa Arduino.
Grazie mille e spero che tornerai ad aiutarmi cosi da poter portare a termine il mio progetto :slight_smile:

Ciao,

direi che in primis devi decidere in che "formato" vuoi inviare il comando...cioè...immagino che l'SMS dovrà essere trattato come stringa (array di char); quindi il tuo messaggio dovrà contenere un qualche cosa tipo "argomento/stato-valore"...per esempio:
S1 od S0 ...che starebbe per:
S = impianto solare
1 o 0 = acceso o spento
quindi dovresti analizzare se il messaggio contiene S e se si che valore ha...ed agire di conseguenza.
penso che questo sia il più semplice dei modi.

Okay, quella parte sono capace a farla. Ora il problema e' inviare tramite RS485 il giusto comando, leggendo nel fascicolo modbus_protocol ho visto che per dire al mio controller di forzare il carico ON o OFF devo scrivere Coils(read-write) indirizzo 6 e poi 1 o 0 a dipendenza del comando che voglio fare.

In allegato ti metto la cattura della parte che intendo, come faccio a tradurre i comandi del modbus in un linguaggio che Arduino possa capire?

Io ho provato a fare cosi:

node.writeSingleRegister(0x0006,0)

solo che non funziona,per ora riesco solamente a leggere i valori di tensione e corrente, il passo successivo sarebbe quello di testare se riesco ad accendere e spegnere il controller, inizialmente con Arduino attaccato al computer e in futuro tramite un SMS.

Spero di essermi espresso in modo chiaro, se cosi non fosse sono sempre a disposizione per fornire altre informazioni. In attesa di una risposta continuo a provare...

Arduino.PNG

ciao...hai ragione non avevo visto che erano coils e non interi...quindi dovresti provare con:

node.writeSingleCoil(6,valore);

dove valore è 0 per spento o 1 per acceso.

Ciao ORSO2001, grazie mille per l`aiuto che mi stai dando. Il fatto che non riesco ad inviare correttamente i valori non potrebbe essere dovuto al fatto che non utilizzo dei moduli di Arduino originali?

Magari inviano dei dati non del tutto corretti e il mio controller fa fatica a decifrarli, e solo un`ipotesi...

cosa vuol dire "non riesco ad inviare correttamente i valori"?

Praticamente io tempo fa ho comprato su wish un modulo GSM Sim900, ci ho passato una settimana senza capire cosa non andava, l'ho attaccato a un'oscilloscopio e ho visto che il segnale che mandava non coincideva coi dati che gli dicevo di mandarmi...diciamo che i moduli presi su wish o amazon non sempre si e' sicuri che funzionino. Non vorrei che ho lo stesso problema anche con questo RS485...

Ho aggiunto la riga che mi hai detto:

node.writeSingleCoil(6,valore);

ora appena ho tempo la testo. Devo aggiungere questa riga di codice in fondo appena concluso questo if(resultMain == node.ku8MBSuccess)?

Cosi:

  uint8_t resultMain;

  resultMain = node.readInputRegisters(0x3100, 6);
 if(resultMain == node.ku8MBSuccess)
  {
    Serial.println(" - - - - - - - - ");
    Serial.print("Battery Voltage: ");
    Serial.println(node.getResponseBuffer(0x04) / 100.0f);
   
  }
   node.writeSingleCoil(6,1); // lo forzo a ON
   delay(5000);
   node.writeSingleCoil(6,0); // lo forzo a OFF

E dopo chiudo il void loop ?

Ti ringrazio per la pazienza...spero che anche io un giorno potrò aiutare qualcuno, una volta fatta un po di esperienza...

chiariamo come funzionano il modbus e questa libreria...tu invii una richiesta allo slave...lettura...scrittura...lo slave, se riconosciuto che stai "parlado" a lui, ti ritorna una striga con risultato positivo o negativo di vario formato a seconda di cosa hai inviato; oppure se non lo riconosce non invia niente ed a questo punto hai un "time out" sulla richiesta.
perchè dico questo...perchè se dai una letta al manuale della libreria, che è fatto bene, vedrai che ogni metodo ritorna una uint_8t per dire:
se ritorna 0, che è il valore di u8MBSuccess usato nella if, tutto è andato a buon fine
se ritorna un valore diverso da 0 c'è stato un problema...ed ogni numero vuol dire qualche cosa di preciso...fa parte delle eccezioni del protocollo.

quindi ad ogni comando dovresti fare un:
resultMain = "comando"
e valutare il valore di resultMain per capire se tutto ha funzionato o no e gestire l'eccezione...altrimenti non sai cosa sta succedendo.

Adesso si che ho capito, solo un'ultima domanda e poi penso che sono a posto. Intendi di fare cosi:

resultMain = node.writeSingleCoil(6,1);

oppure cosi:

resultMain = node.writeSingleCoil(6,1);
 if(resultMain == node.ku8MBSuccess)
  {
  node.writeSingleCoil(6,1);
    }

Non e' molto chiaro dove intendi che devo scriverlo...
Se ho sbagliato puoi scrivermi il codice cosi capisco meglio cosa intendi? perché a parole e' sempre difficile spiegare.
In attesa di una risposta auguro a tutti una buona giornata :slight_smile:

ciao...prova ad eseguire questo loop...attanzione ad inviare solo 0 od 1 da seriale

PS: scusa ho fatto un copia incolla sbagliato...adesso ci riprovo

prova questo...sempre con 0 od 1 come carattere inviato:

void loop() {
  uint8_t resultMain;
  uint8_t valore;
  static boolean passed = false;

  if (Serial.available()) { // se un carattere passato dal monitor seriale
    char c = Serial.read();
    if (c == '1') {
      valore = 1;
    }
    else if (c == '0') {
      valore = 0;
    }
    passed = true; //abilito comunicazione seriale
  }
  if (passed) {
    resultMain = node.writeSingleCoil(6, valore);
    Serial.println("start transmission...");
    if (resultMain == node.ku8MBSuccess) {
      Serial.println("Success!");
      Serial.println("end trasmission");
      passed = false;
    }
    else {
      passed = false;
      Serial.println("ERROR!");
    }
  }
}

Grazie mille per il tempo che stai dedicando alle mie domande :slight_smile:
Aprendo la libreria ModbusMaster ho notato che non sono dichiarati dei Pin specifici per la comunicazione,
chiaramente dato che utilizzo la SERIALPORT devo usare TX e RX. I Pin DE e RE del RS485 devono essere connessi a dei Pin con delle funzioni speciali o posso usarne 2 qualsiasi? ( nel mio esempio utilizzo I Pin 2 e 3).
L'idea sarebbe quella di trasferire il codice per poterlo utilizzare un Arduino mega 2560 al posto dell'arduino nano che utilizzo al momento.

Come si fa a scegliere i Pin giusti quando si va a cambiare il dispositivo?

A cosa devo stare attento?

Mi scuso per le molte domande ma sono agli inizi e le informazioni che si trovano in internet sono troppe e a volte non molto chiare...
dato che vedo che ti sei interessato al mio progetto provo a farti anche questa domanda, magari mi sai rispondere...

Grazie mille a tutti e buona giornata

ciao...da quel che ricordo in tutti gli arduino la Serial corrisponde ai pin 0 ed 1...se non usi pin per interrupts puoi mantenere la stessa numerazione...diversamente, se usi interrupts, devi vedere il corrispettivo scheda per scheda...ovviamente in un NANO non hai i 50 pin di un MEGA...se guardi bene il data sheet del max485 i pin RE e DE lavorano in contemporanea con logica inversa uno all'altro...quindi puoi usare un solo pin di arduino collegato in parrallelo a DE ed RE.
come dicevo i pin 0 ed 1 sono sempre legati alla Serial...ma sono anche quelli che comunicano con il monitor seriale...quindi li lascerei liberi per un eventuale debug...al posto della Serial potresti utilzzare la libreria softwareSerial che ti permette di definire tu i pin per una comunicazione seriale...ma forse, più che per difficoltà per inesperienza, andresti a complicarti la vita...se ti va la sfida dimmi.

Ciao... ho guardato i datasheet e i PinMapping dei due dispositivi, chiaramente l'Arduino mega contiene molti più Pin, la cosa che mi confonde e' come sono disposti e descritti.
Faccio un'esempio semplice:

Per il modulo DHT11 ( sensore temperatura ), su Arduino uno viene utilizzato il pin numero 2, andando sul datasheet leggo che questo Pin e' un PCINT18/INT0. Sono andato a vedere nell'Arduino mega sperando di trovare da qualche parte lo stesso nome... senza grandi risultati.Se lo collego al Pin 2 leggo che e' un PE4 ( OC3B/INT4 ), cosa significa? mi da dei valori strani e non funziona.

Dopo qualche prova ho trovato il Pin 50 PB3 (OC1A/PCINT5). Posso leggere nelle librerie quale tipo di Pin mi serve e con quale funzione?

Nel mio caso del PCINT18/INT0 quale dei due devo cercare, PCINT18 o INT0 ? fino a definire il Pin so come fare ( #define.. ), ma poi ci sono una marea di Pin certi sono PB4 ( OC2A/PCINT4 ) altri PJ1 ( TXD3/PCINT10 ).

Sono abbastanza confuso a riguardo, forse a livello di librerie io posso dirgli quale usare... ha importanza se su Arduino uno si chiama PCINT18/INT0 e sul mega si chiama PCINT... qualsiasi altro numero?

Per il fatto di modificare le librerie era solo una domanda per poter trasferire il mio programma su Arduino mega e farlo funzionare correttamente... se posso evitare di fare delle modifiche preferirei, non vorrei fare dei danni...