Hallo zusammen,
zunächst einmal vielen Dank an alle aktiven Mitglieder, von denen ich als lesendes/passives Mitglied dieses Forums schon einiges lernen konnte.
Nun habe ich ein Problem, bei dem ich nicht mehr so richtig weiter weiß und baue auf eure Hilfe.
Problemstellung:
Ich möchte gerne ein Thermoelement über einen Arduino Nano mittels eines MAX6675-Moduls auslesen und die Messwerte über RS485 mit dem Modbus RTU-Protokoll an den Raspberry übertragen und dort weiterverarbeiten. Dieser Raspberry läuft 24/7 im Keller und liest dort bisher vor allem 1-Wire Sensoren aus. Der Aufbau der Schaltung ist angehängt.
Das Problem ist, dass die Temperaturwerte stark schwanken (+-10°C teils auch mehr), SOBALD der USB RS485 Transceiver mit Strom versorgt wird.
Was bisher funktioniert:
- reines Auslesen des Thermoelements mit dem Arduino Nano und Ausgabe auf der seriellen Schnittstelle --> relative Genauigkeit mit 3er-Medianfilter von +-1°C
- Ich habe das mit dem Modbus noch nicht ganz durchstiegen. Jedoch habe ich es mit der lib minimalmodbus auf Raspberry-Seite (Python) und Modbus-Master-Slave-for-Arduinoauf Arduino Seite geschafft, die Daten, die ich im Register des Arduinos verändere auf den Raspberry zu übertragen. Kabellänge bisher 10cm. Funktioniert sowohl twisted als auch mit nicht verdrilltem Kabel.
- Ein Testaufbau mit der im Bild beschriebenen Schaltung mit einem 2. Raspberry lieferte die genauen Temperaturwerte.
Problem:
- schließt man den Testaufbau im Keller an, misst der Arduino/das Thermoelementmodul Mist (die genannten hohen Abweichungen).
vermutete Ursache:
- der Thermosensor ist an einem ca.
1,5m2,8m langen Kabel angeschlossen. Beim Testaufbau war es zusammengerollt und im Einsatz dann nicht mehr. Könnte es sein, dass es damit zu einer Antenne wird und sich allerlei Störungen einfängt? Wobei hier die Störung mit dem RS485 USB-Transceiver zusammen hängen muss, da es ohne auch bei ausgerolltem Kabel genaue Messwerte gibt. - legt man die leitende Umhüllung des Thermoelementkabels z.B. auf ein geerdetes Heizungsrohr verändert sich die Schwankungsbreite der gemessenen Werte. (Wird jeoch nicht besser)
Versuche der Lösungsfindung:
- Anschluss eines 10nF Kondensators (Cx im Schaltplan) zwischen T+ und T-
- Austausch der Transceiver-Module (USB und TTL)
- Längere Messzeiten
- anderer USB Port an Raspi
- Anderes USB-Stromversorgungskabel Arduino (wobei nur mit 5V Netzteil versorgt)
- Unterschiedliche Netzteile
- mit und ohne WLan Stick am Raspberry / Tausch des WLan Sticks
Schwachstellen:
- Arduino nur mit 5V versorgt. Der will eigentlich bisschen mehr soweit ich weiß.
- Vermeintliche "Billigprodukte"
- Immer noch unzureichendes Verständnis für Modbus Protokoll
- Der USB Transceiver hat keinen 120 Ohm Abschlusswiderstand
- Ich besitze kein Oszi und würde damit vermutlich in diesem Fall auch nicht wissen was damit zu tun wäre.
- Ich drohe den Überblick zu verlieren
Fragen:
- Ist der Verdacht von Einkopplungen plausibel?
- Was könnte man gegen diese elektromagnetischen Störungen tun?
- Ist der Entstörkondensator Cx sinnvoll? (und mit 10nF sinnvoll bemessen). Adafruit empfiehlt das in manchen Fällen (Adafruit Kondensator).
- Ist es überhaupt clever diese günstigen RS485-Module zu verwendet. Es gibt wohl noch galvanisch entkoppelte und welche die einen GND mitführen.
- Würde es helfen, den fehlenden 120 Ohm Widerstand am USB-Modul anzuschließen? Die Kommunikation geht ja auch ohne aktuell. Hilft er also bei der evtl. erforderlichen Entstörung? Und würden fürs Erste auch 100 Ohm reichen wenn man kein 120R hat.
- Würde es helfen den Ground bei der Datenleitung mitzuführen.
- Würde es helfen, die vermeintliche Schirmung des Thermoelementkabels irgendwo an GND anzuschließen. Da wo der Sensor selbst angeschraubt ist, ist er eigentlich ja schon auf Erde.
- [Bisschen Offtopic] Wie kommt eigentlich die Spannungsdifferenz der Bezugsmassen zwischen 2 galvanisch getrennten Systemen zustande? Zum Beispiel hatte ich den Arduino am Laptop und den Raspi an einem Netzteil. Spannungsdifferenz von GND zu GND 1,8V. Wenn man das verbindet fließt ein Strom von ca. 1mA. Die Spannung baut sich sofort wieder auf nach dem man den "common GND" wieder trennt.
- [Bisschen Offtopic] Ist es legitim die Funktion slave.poll() beim Arduino Sketch öfters im loop() aufzurufen, um die eigentlich unerwünschten delays einzubringen? Oder sollte man komplett anders vorgehen?
Hardware: (s. auch Bilder)
- Raspberry Pi 2 Model B mit Stretch
- Arduino Nano (billige Clon-Variante)
- MAX6675, RS485 Transceiver (es gibt vermutlich bessere)
Arduino (Slave):
/**
* Modbus slave example 3:
* The purpose of this example is to link a data array
* from the Arduino to an external device through RS485.
*/
#include <ModbusRtu.h>
#include "max6675.h"
// Thermoelement
int8_t thermoDO = 4;
int8_t thermoCS = 5;
int8_t thermoCLK = 6;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
byte offset = 0;
const byte nMedian = 3;
int data[nMedian];
int ringBufferData[nMedian];
byte middleElm = (nMedian - 1) / 2;
unsigned int loopcounter = 0;
// Modbus
// assign the Arduino pin that must be connected to RE-DE RS485 transceiver
#define TXEN 3
// data array for modbus network sharing
uint16_t au16data[4] = {0, 98, 55, 99}; // uint16_t beim Arduino dasselbe wie unsigned int (max. Wert: 65535 = 6555°C bei centigrad)
/**
* Modbus object declaration
* u8id : node id = 0 for master, = 1..247 for slave
* port : serial port
* u8txenpin : 0 for RS-232 and USB-FTDI
* or any pin number > 1 for RS-485
*/
Modbus slave(10,Serial,TXEN); // this is slave @1 and RS-485
void setup() {
Serial.begin(19200); // baud-rate at 19200
delay(500);
int i = 0;
data[middleElm] = (int)(thermocouple.readCelsius() * 10);
// For the MAX6675 to update, you must delay AT LEAST 250ms between reads!
delay(500);
au16data[0] = data[middleElm];
slave.start();
}
void loop() {
int i = 0;
for (i = 0; i < nMedian; i++) {
delay(500);
slave.poll( au16data, 16 ); //16bit! (register table for communication exchange uint16_t, size of the register table uint8_t)
delay(50);
data[i] = (int)(thermocouple.readCelsius() * 10); //Einheit: centidegree
// For the MAX6675 to update, you must delay AT LEAST 250ms between reads!
delay(450);
slave.poll( au16data, 16 );
delay(500);
slave.poll( au16data, 16 );
}
delay(150);
//Median Filter
for (int k = 1; k < nMedian; k++) {
for (int i = 0; i < nMedian - k; i++) {
if (data[i] > data[i + 1]) {
int temp = data[i];
data[i] = data[i + 1];
data[i + 1] = temp;
}
}
}
//Serial.println(data[middleElm]);
au16data[0] = data[middleElm];
au16data[1] = loopcounter;
au16data[2] = data[middleElm];
slave.poll( au16data, 16 ); //16bit! (register table for communication exchange uint16_t, size of the register table uint8_t)
loopcounter = loopcounter+1;
}
Display More
Raspberry (Master):
#!/usr/bin/env python3
#imports
import os
import minimalmodbus
import serial
import sqlite3
import time
import datetime as dt
########################################################################
# # Das sind die Standardwerte
# instrument.serial.port # this is the serial port name
# instrument.serial.baudrate = 19200 # Baud
# instrument.serial.bytesize = 8
# instrument.serial.parity = serial.PARITY_NONE
# instrument.serial.stopbits = 1
# instrument.serial.timeout = 0.05 # seconds
# instrument.address # this is the slave address number
# instrument.mode = minimalmodbus.MODE_RTU # rtu or ascii mode
# instrument.clear_buffers_before_each_transaction = True
# # To see which settings you actually are using:
# print(instrument)
def db_anlegen():
connection = sqlite3.connect(path_db)
cursor = connection.cursor()
# Tabelle erzeugen
sql_statement = 'CREATE TABLE Temperatur_tbl (Zeit DATETIME, Temperatur INTEGER)'
cursor.execute(sql_statement)
connection.commit()
connection.close()
def db_insert_values(time, v_1):
connection = sqlite3.connect(path_db)
cursor = connection.cursor()
cursor.execute('INSERT INTO Temperatur_tbl VALUES (?, ?)', (time, v_1))
connection.commit()
connection.close()
########################################################################
#vorläufige Main
path_db = '/var/www/html/phpliteadmin/anonym.db'
temperature = -99
temperature_old = -99
minSaveInterval = 0.25*60 #minutes*seconds/min
maxSaveInterval = 1*60
maxNonSaveSteps = maxSaveInterval // minSaveInterval #Ganzzahldivision
saveCounter = 0
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 10) # port name, slave address (in decimal)
instrument.serial.timeout = 1.2
if not os.path.exists(path_db):
print("Datenbank nicht vorhanden - Datenbank wird anglegt.")
db_anlegen()
print("temp NLoop temp MLoop")
while(True):
try:
temperature = int(instrument.read_register(0, 0)) # Registernumber, number of decimals --> centidegree
#print(temperature)
countLoop = int(instrument.read_register(1, 0)) # Registernumber, number of decimals --> centidegree
tempDuplicate = int(instrument.read_register(2, 0))
countLoop2 = int(instrument.read_register(1, 0))
print(temperature, " ",countLoop," ", tempDuplicate, " " ,countLoop2)
except IOError as e:
print(e)
print("Fehler Modbus")
if (abs(temperature-temperature_old) >= 4) or (saveCounter >= maxNonSaveSteps):
reading_time = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db_insert_values(reading_time, temperature)
saveCounter = 0
else:
saveCounter += 1
if (saveCounter == 0): # nur beim ersten Mal nach letzter Speicherung speichern = Bezugswert für Abweichung
temperature_old = temperature
time.sleep(minSaveInterval)
Display More
Ich hoffe, dass ich die Fragestellung nicht zu unkonkret gestellt habe und bin über jede Anregung und ultimative Lösung dankbar.