Lüftersteuerung mit MQTT Anbindung - Verbindungsabbrüche

Hallo zusammen

Vor einigen Wochen habe ich beschlossen mein erstes Arduino-Projekt in angriff zu nehmen.

Über 3 Lüfter soll warme Luft aus einem kleinen Serverrack befördert werden. Dazu habe ich 2 Temperatursensoren vorgesehen, welche die Temperatur im und ausserhalb des Gehäuses messen.
Ein PID-Regler regelt die Lüfter auf eine Temperaturdifferenz von 3°C zwischen Innen- und Aussentemperatur. Alle relevanten Werte werden im Anschluss als JSON-String via MQTT an einen Broker gesendet.

Mein Code funktioniert soweit, allerdings habe ich ab und an reconnet's. Ich habe festgestellt, dass die Abbrüche bei höheren Drehzahlen der Lüfter häufiger auftreten. Kann dies mit den Interrupts zur Ermittlung der Lüfterdrehzahl zusammen hängen?

Anbei mein aktueller Code:

#include <Arduino.h>
#include <WiFiNINA.h>
#include <MQTT.h>
#include <ArduinoJson.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>

#include "arduino_secrets.h"

// WiFi Client
WiFiClient net;                                       // Setup WiFi Client

char ssid[] = SECRET_SSID;                            // network SSID
char pass[] = SECRET_PASS;                            // network password
int status = WL_IDLE_STATUS;                          // Wifi radio's status

// MQTT Client
MQTTClient client;                                    // Setup MQTT Client

// OneWire
#define ONE_WIRE_BUS 8
OneWire oneWire(ONE_WIRE_BUS);                        // Setup oneWire instance
DallasTemperature sensors(&oneWire);                  // Pass oneWire reference to Dallas Temperature sensor 

// PID
double SP, PV, CV, Kp=2, Ki=5, Kd=1;
PID FanControl(&PV, &CV, &SP, Kp, Ki, Kd, REVERSE);   //Setup PID

// Set Pins
int FanPwm1Pin = 0;
int FanPwm2Pin = 4;
int FanPwm3Pin = 6;
int FanRpm1Pin = 1;
int FanRpm2Pin = 5;
int FanRpm3Pin = 7;

// Var
unsigned long lastMillis = 0;  
unsigned long Fan1Last = 0;
unsigned long Fan2Last = 0;
unsigned long Fan3Last = 0;
unsigned long Fan1Duration = 0;
unsigned long Fan2Duration = 0;
unsigned long Fan3Duration = 0;

void messageReceived(String &topic, String &payload) {  
  Serial.println("incoming: " + topic + " - " + payload);

} 
  
void connect() {  
  Serial.print("checking wifi...");  
  while ( status != WL_CONNECTED) {  
    status = WiFi.begin(ssid, pass);  
    Serial.print(".");  
    delay(1000);  
  }  
  Serial.println("\nconnected to WiFi!\n");  
  
  client.begin(mqttServer, mqttServerPort, net);  
  
  Serial.println("connecting to broker...");  
  while (!client.connect(device, key, secret)) {  
    Serial.print(".");  
    delay(1000);  
  }  
  
  Serial.println("Connected to MQTT");  
  
  client.onMessage(messageReceived);  
}  

void rpmFan1(){
  unsigned long cm = micros();
  Fan1Duration = (cm - Fan1Last);
  Fan1Last = cm;
}

void rpmFan2(){
  unsigned long cm = micros();
  Fan2Duration = (cm - Fan2Last);
  Fan2Last = cm;
}

void rpmFan3(){
  unsigned long cm = micros();
  Fan3Duration = (cm - Fan3Last);
  Fan3Last = cm;
}

void setup() {  
  Serial.begin(115200);
  // PWM Pins
  pinMode(FanPwm1Pin, OUTPUT);
  pinMode(FanPwm2Pin, OUTPUT);
  pinMode(FanPwm3Pin, OUTPUT);
  analogWrite(FanPwm1Pin, 0);
  analogWrite(FanPwm2Pin, 0);
  analogWrite(FanPwm3Pin, 0);
  // RPM Pins
  pinMode(FanRpm1Pin, INPUT_PULLUP);
  pinMode(FanRpm2Pin, INPUT_PULLUP);
  pinMode(FanRpm3Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(FanRpm1Pin), rpmFan1, RISING);
  attachInterrupt(digitalPinToInterrupt(FanRpm2Pin), rpmFan2, RISING);
  attachInterrupt(digitalPinToInterrupt(FanRpm3Pin), rpmFan3, RISING);
  // MQTT Broker
  connect();  

  sensors.begin();

  //PID
  SP=3;
  FanControl.SetMode(AUTOMATIC);
}  

void loop() {  
  // Check Connection
  if (!net.connected()) {  
    connect();  
  }

  // Fan  
  PV = sensors.getTempCByIndex(0) - sensors.getTempCByIndex(1);
  FanControl.Compute();

  analogWrite(FanPwm1Pin, CV);
  analogWrite(FanPwm2Pin, CV);
  analogWrite(FanPwm3Pin, CV);
  
  // Send Data
  if (millis() - lastMillis > 5000) {  

    StaticJsonDocument<200> doc;

    JsonObject Rpm = doc.createNestedObject("Rpm");
    Rpm["Fan1"] = 100000000 / Fan1Duration * 60 / 200;
    Rpm["Fan2"] = 100000000 / Fan2Duration * 60 / 200;
    Rpm["Fan3"] = 100000000 / Fan3Duration * 60 / 200;

    JsonObject PID = doc.createNestedObject("PID");
    PID["SP"] = SP;
    PID["PV"] = PV;
    PID["CV"] = map(CV, 0, 255, 0, 100);

    sensors.requestTemperatures(); 
    JsonObject Temperature = doc.createNestedObject("Temperature");
    Temperature["IN"] = sensors.getTempCByIndex(0);
    Temperature["OUT"] = sensors.getTempCByIndex(1);

    char payload[200];

    serializeJson(doc, payload);

    client.publish("Server", payload);

    lastMillis = millis();  
  }  
   client.loop();
} 

Für Anregungen und Tipps bin ich dankbar.

Grüsse
Asiris

int FanPwm1Pin = 0;

drei Sachen:

a) da reicht ein byte
b) const machen
c) aus Variablen mit 1 2 3 ... ein array machen und auch im Code die ganzen Codeduplikate entfernen und alle pins in einem array durcharbeiten,

den so etwas

void rpmFan1(){
  unsigned long cm = micros();
  Fan1Duration = (cm - Fan1Last);
  Fan1Last = cm;
}

muss man nicht dreimal hinschreiben!

FOR Schleifen sind dein Freund.
"auto range based for loops" noch mehr!

d) und wenn das gemacht ist, sich mit struct beschäftigen denn fanPwmPin und fanRpmPin scheinen ja in Beziehung zu stehen. Da wäre ein fan[i].pwmPin bzw. fan[i].rpmPin auf dauer übersichtlicher

Guten Morgen noiasca

Danke für dein Feedback. Wie bereits erwähnt ist es mein erstes Projekt und ich bin noch so Vertraut mit der Materie. Einen Teil konnte ich schon umsetzten.
Kannst du mir einen Tipp geben wie ich die Funktion für den Interrupt mit einer Schleife vereinfachen kann?

Über “auto range based for loops” muss ich mich noch schlau machen.

ok,

ISR: Die Interrupt Service Routine, die aufgerufen werden soll. Die Funktion darf keine Parameter haben und nichts zurückgeben.

na gut.
Dann würde ich aber trotzdem einen Funktion mit einem Parameter machen und einfach 3 ISR Funktionen machen die nichts anderes machen als die gleiche Funktion mit unterschiedlichen Parametern aufruft

weiters gilt:
Variablen, die innerhalb der Funktion verarbeitet werden, sollten als volatile gekennzeichnet werden. Siehe dazu auch den Abschnitt zu Interrupt Service Routinen weiter unten.

void rpmFan0()
{
  rpmFan(0)
}
// 3 mal ... gewöhn dir an mit 0 anzufangen zu zählen.

// und das nur einmal:
void rpmFan(byte actual){
  unsigned long cm = micros();
  fan[actual].duration = (cm - Fan1Last);
  fan[actual].last = cm;
}

Habe ich soweit umgesetzt. Ich gebe zu mit der Struktur wird es einfacher zu lesen.

#include <Arduino.h>
#include <WiFiNINA.h>
#include <MQTT.h>
#include <ArduinoJson.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>

#include "arduino_secrets.h"

// WiFi Client
WiFiClient net;                                       // Setup WiFi Client

char ssid[] = SECRET_SSID;                            // network SSID
char pass[] = SECRET_PASS;                            // network password
int status = WL_IDLE_STATUS;                          // Wifi radio's status

// MQTT Client
MQTTClient client;                                    // Setup MQTT Client

// OneWire
#define ONE_WIRE_BUS 8
OneWire oneWire(ONE_WIRE_BUS);                        // Setup oneWire instance
DallasTemperature sensors(&oneWire);                  // Pass oneWire reference to Dallas Temperature sensor 

// PID
double SP, PV, CV, Kp=2, Ki=5, Kd=1;
PID FanControl(&PV, &CV, &SP, Kp, Ki, Kd, REVERSE);   //Setup PID

// Create Struct
struct fan_t
{
  const byte pwmPin;
  const byte rpmPin;
  volatile unsigned long last;
  volatile unsigned long duration;
};
// Var
fan_t fan[]{
  {0, 1,},  //Fan0 -- PWM Pin, RPM Pin
  {4, 5,},  //Fan1 -- PWM Pin, RPM Pin
  {6, 7,}   //Fan2 -- PWM Pin, RPM Pin
};
unsigned long lastMillis = 0;  

void messageReceived(String &topic, String &payload) {  
  Serial.println("incoming: " + topic + " - " + payload);

} 
  
void connect() {  
  Serial.print("checking wifi...");  
  while ( status != WL_CONNECTED) {  
    status = WiFi.begin(ssid, pass);  
    Serial.print(".");  
    delay(1000);  
  }  
  Serial.println("\nconnected to WiFi!\n");  
  
  client.begin(mqttServer, mqttServerPort, net);  
  
  Serial.println("connecting to broker...");  
  while (!client.connect(device, key, secret)) {  
    Serial.print(".");  
    delay(1000);  
  }  
  
  Serial.println("Connected to MQTT");  
  
  client.onMessage(messageReceived);  
}  

void rpmFan(byte i){
  unsigned long cm = micros();
  fan[i].duration = (cm - fan[i].last);
  fan[i].last = cm;
}

void rpmFan0(){
  rpmFan(0);
}

void rpmFan1(){
  rpmFan(1);
}

void rpmFan2(){
  rpmFan(2);
}

void setup() {  
  Serial.begin(115200);
  // PWM/RPM Pins
  for (size_t i = 0; i <= 2; i++)
  {
    pinMode(fan[i].pwmPin, OUTPUT);
    analogWrite(fan[i].pwmPin, 0);
    pinMode(fan[i].rpmPin, INPUT_PULLUP);
  }
  attachInterrupt(digitalPinToInterrupt(fan[0].rpmPin), rpmFan0, RISING);
  attachInterrupt(digitalPinToInterrupt(fan[1].rpmPin), rpmFan1, RISING);
  attachInterrupt(digitalPinToInterrupt(fan[2].rpmPin), rpmFan2, RISING);
  // MQTT Broker
  connect();  

  sensors.begin();

  //PID
  SP=3;
  FanControl.SetMode(AUTOMATIC);
}  

void loop() {  
  // Check Connection
  if (!net.connected()) {  
    connect();  
  }

  // Fan  
  PV = sensors.getTempCByIndex(0) - sensors.getTempCByIndex(1);
  FanControl.Compute();

  for (size_t i = 0; i <= 2; i++)
  {
    analogWrite(fan[i].pwmPin, CV);   // Set CV
  }
  
  // Send Data
  if (millis() - lastMillis > 5000) {  

    StaticJsonDocument<200> doc;

    JsonObject Rpm = doc.createNestedObject("Rpm");
    Rpm["Fan1"] = 100000000 / fan[0].duration * 60 / 200;
    Rpm["Fan2"] = 100000000 / fan[1].duration * 60 / 200;
    Rpm["Fan3"] = 100000000 / fan[2].duration * 60 / 200;

    JsonObject PID = doc.createNestedObject("PID");
    PID["SP"] = SP;
    PID["PV"] = PV;
    PID["CV"] = map(CV, 0, 255, 0, 100);

    sensors.requestTemperatures(); 
    JsonObject Temperature = doc.createNestedObject("Temperature");
    Temperature["IN"] = sensors.getTempCByIndex(0);
    Temperature["OUT"] = sensors.getTempCByIndex(1);

    char payload[200];

    serializeJson(doc, payload);

    client.publish("Server", payload);

    lastMillis = millis();  
  }  
   client.loop();
} 

wird ja!

hast mal den ranged based for gegoogelt?
Ungeprüft:

  for (auto &i: fan )      // bei jeder iteration hast jetzt eine refenrene auf die jeweilige Instanz von fan in i
  {
    pinMode(i.pwmPin, OUTPUT);
    analogWrite(i.pwmPin, 0);
    pinMode(i.rpmPin, INPUT_PULLUP);
  }

spart die hardcoded Array Größe und etwas Tipparbeit.

Tendenziell schreiben viele Programmierer mittlerweile Klassennamen / Strukturen mit einem großem Anfangsbuchstaben und du kannst dir überlegen, ob für dein fan_t (besser Fan) überhaupt ein Name notwendig ist und ob du nicht einfach eine anonyme Struktur nimmst.

Ungeprüft:

struct 
{
  const byte pwmPin;
  const byte rpmPin;
  volatile unsigned long last;
  volatile unsigned long duration;
} fan[]{
  {0, 1,},  //Fan0 -- PWM Pin, RPM Pin
  {4, 5,},  //Fan1 -- PWM Pin, RPM Pin
  {6, 7,}   //Fan2 -- PWM Pin, RPM Pin
};

Ja hab ich mal kurz angeschaut aber noch nicht zu 100% verstanden. Muss mir das nochmals in ruhe zu Gemüte führen. Aber sieht schon knackiger aus und die Erkennung der Array Grösse klingt nett.

Danke für den Input, werde ich heute Nachmittag noch anpassen. Gibt es in irgend einer Form einen Leitfaden wie Variablen, Klassen, etc. bezeichnet werden?

ja da gibt es einiges.
ich halte mich nur an

Use CamelCase for all names.
Start types (such as classes, structs, and typedefs) with a capital letter,
other names (functions, variables) with a lowercase letter.

GROSSBUCHSTABEN nur für precompiler defines,
ich mach keine _ bei member Variablen.
setter Methoden mit set (kommt oft vor)
getter Methoden mit get (wird auch anders empfohlen)

naja, vieleicht hat wer was anderes...
https://www.google.com/search?client=firefox-b-d&q=c%2B%2B+naming+conventions

such dir was aus :wink:

Ich hab mir "range based for loop" nochmals angeschaut (und nun auch verstanden :sweat_smile:) und auch in meinen Code einfliessen lassen. Meine "naming convention" sollte nun auch durchgängig sein. Danke für deine Tipps.

Der Ursache meines eigentlichen Problems bin ich glaube ich auch schon etwas näher gekommen. Wenn die Interrupts nicht mehr aufgerufen werden läuft die Kommunikation ohne Unterbrüche durch.. Hast du eine Idee woran das liegen könnte?

Hier nochmals der Code:

#include <Arduino.h>
#include <WiFiNINA.h>
#include <MQTT.h>
#include <ArduinoJson.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>

#include "arduino_secrets.h"

// WiFi Client
WiFiClient net;                                       // Setup WiFi Client

char ssid[] = SECRET_SSID;                            // network SSID
char pass[] = SECRET_PASS;                            // network password
int status = WL_IDLE_STATUS;                          // Wifi radio's status

// MQTT Client
MQTTClient client;                                    // Setup MQTT Client

// OneWire
#define ONE_WIRE_BUS 8
OneWire oneWire(ONE_WIRE_BUS);                        // Setup oneWire instance
DallasTemperature sensors(&oneWire);                  // Pass oneWire reference to Dallas Temperature sensor 

// PID
double SP, PV, CV, Kp=2, Ki=5, Kd=1;
PID FanControl(&PV, &CV, &SP, Kp, Ki, Kd, REVERSE);   //Setup PID

// Create Struct
struct {
  const byte pwmPin;
  const byte rpmPin;
  volatile unsigned long last;
  volatile unsigned long duration;
} fan[]{
  {0, 1,},  //Fan0 -- PWM Pin, RPM Pin
  {4, 5,},  //Fan1 -- PWM Pin, RPM Pin
  {6, 7,}   //Fan2 -- PWM Pin, RPM Pin
};
unsigned long lastMillis = 0;  

void messageReceived(String &topic, String &payload) {  
  Serial.println("incoming: " + topic + " - " + payload);

} 
  
void connect() {  
  Serial.print("checking wifi...");  
  while ( status != WL_CONNECTED) {  
    status = WiFi.begin(ssid, pass);  
    Serial.print(".");  
    delay(1000);  
  }  
  Serial.println("\nconnected to WiFi!\n");  
  
  client.begin(mqttServer, mqttServerPort, net);  
  
  Serial.println("connecting to broker...");  
  while (!client.connect(device, key, secret)) {  
    Serial.print(".");  
    delay(1000);  
  }  
  
  Serial.println("Connected to MQTT");  
  
  client.onMessage(messageReceived);  
}  

void rpmFan(byte i){
  unsigned long cm = micros();
  fan[i].duration = (cm - fan[i].last);
  fan[i].last = cm;
}

void rpmFan0(){
  rpmFan(0);
}

void rpmFan1(){
  rpmFan(1);
}

void rpmFan2(){
  rpmFan(2);
}

void setup() {  
  Serial.begin(115200);
  // PWM/RPM Pins
  for (auto &i : fan) //size_t i = 0; i <= 2; i++
  {
    pinMode(i.pwmPin, OUTPUT);
    analogWrite(i.pwmPin, 0);
    pinMode(i.rpmPin, INPUT_PULLUP);
  }
  attachInterrupt(digitalPinToInterrupt(fan[0].rpmPin), rpmFan0, RISING);
  attachInterrupt(digitalPinToInterrupt(fan[1].rpmPin), rpmFan1, RISING);
  attachInterrupt(digitalPinToInterrupt(fan[2].rpmPin), rpmFan2, RISING);

  // MQTT Broker
  connect();  

  sensors.begin();

  //PID
  SP=3;
  FanControl.SetMode(AUTOMATIC);
}  

void loop() {  
  // Check Connection
  if (!net.connected()) {  
    connect();  
  }

  // Fan  
  PV = sensors.getTempCByIndex(0) - sensors.getTempCByIndex(1);
  FanControl.Compute();

  for (auto &i : fan)
  {
    analogWrite(i.pwmPin, CV);   // Set CV
  }
  
  // Send Data
  if (millis() - lastMillis > 5000) {  

    StaticJsonDocument<200> doc;

    JsonObject Rpm = doc.createNestedObject("Rpm");
    Rpm["Fan1"] = 100000000 / fan[0].duration * 60 / 200;
    Rpm["Fan2"] = 100000000 / fan[1].duration * 60 / 200;
    Rpm["Fan3"] = 100000000 / fan[2].duration * 60 / 200;

    JsonObject PID = doc.createNestedObject("PID");
    PID["SP"] = SP;
    PID["PV"] = PV;
    PID["CV"] = map(CV, 0, 255, 0, 100);

    sensors.requestTemperatures(); 
    JsonObject Temperature = doc.createNestedObject("Temperature");
    Temperature["IN"] = sensors.getTempCByIndex(0);
    Temperature["OUT"] = sensors.getTempCByIndex(1);

    char payload[200];

    serializeJson(doc, payload);

    client.publish("Server", payload);

    lastMillis = millis();  
  }  
   client.loop();
} 

Ich konnte das Problem mittlerweile lösen. Das Problem tritt auf, wenn das payload beim publishen grösser als 128 Zeichen ist. Ich kann mit noch nicht ganz erklären weshalb dies geschieht (RAM überlauf?).

Weil Du keine andere Puffergröße angegeben hast.

explicit MQTTClient(int bufSize = 128);

Gruß Tommy

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.