ESP32 FreeRTOS Serial.print() funktioniert nicht immer

Hallo an alle,

ich programmiere gerade ein Prüfstand auf einen ESP32S3- DevKitC v1. Kurz zum Aufbau, es ist ein ADS1256 Modul und ein DAC DFR0971 eingebaut. Die Schaltung wurde schon getestet und funktioniert einwandfrei. Jetzt wollte ich das eigentliche Programm beginnen, dabei benötige ich FreeRTOS, da ich einige Sachen parallel betreiben will, ohne die Laufzeit von kritischen Tasks zu beeinflussen. Ihr müsst wissen ich bin neu in Sachen FreeRTOS.

Ich habe begonnen zwei Tasks zu erstellen, der erste Task "adcTask" soll mir die ADC Werte in ein Array schreiben bis es voll ist. Dieses wird dann in ein zweites Array kopiert und dem zweiten Task "averageTask" übergeben. adcTask läuft dabei auf den 1 Core und averageTask auf den 0 Core. Ich habe es so gelöst damit der adcTask nicht von der Mittelwert Berechnung beeinflusst wird.

Das größte Problem was ich jetzt habe ist die Serial.Print() Funktion gibt mir nur manchmal werte aus. Was mache ich Falsch?

In der Loop() habe ich noch mein altes Testprogramm aus kommentiert, damit ich die SPS vergleichen kann.

Vielen dank schonmal an euch für eure Hilfe.
LG Stefan

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#include <ADS1256.h>
#include <SPI.h>
#include <DFRobot_GP8403.h>
#include <Wire.h>

bool check = false;
// Konstanten für ADC
// Globale Variablen für die Werte und den Mittelwert
const int ADC_SAMPLES = 100;
float current_1[ADC_SAMPLES];
float current_2[ADC_SAMPLES];
float voltage_1[ADC_SAMPLES];
float voltage_2[ADC_SAMPLES];
volatile int adcIndex = 0;
float adcAverage = 0;

float clockMHZ = 7.68;  // crystal frequency used on ADS1256
float vRef = 2500;      // voltage reference

// Construct and init ADS1256 object
ADS1256 adc(clockMHZ, vRef, false);

//Variabeln für SPS Berechnung
float sps;
long lastTime, currentTime, elapsedTime;
int counter;

DFRobot_GP8403 dac(&Wire, 0x5F);

void setup() {
  Serial.begin(2000000);

  Serial.println("Starting ADC");

  adc.begin(ADS1256_DRATE_15000SPS, ADS1256_GAIN_2, false);
  Serial.println("ADC Started");

  //DAC init
  while (dac.begin(46, 3) != 0) {
    Serial.println("init error");
    delay(1000);
  }
  Serial.println("init succeed");
  dac.setDACOutRange(dac.eOutputRange10V);
  // DAC auf Null setzen
  dac.setDACOutVoltage(0, 0);
  dac.setDACOutVoltage(0, 1);
  dac.store();

  //Task generieren
  xTaskCreatePinnedToCore(adcTask, "adcTask", 4096, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(averageTask, "averageTask", 4096, NULL, 1, NULL, 0);

  // Set MUX Register to AINO-AIN1 so it start doing the ADC conversion
  adc.setChannel(2, 3);
}

void loop() {
  /*
  currentTime = millis();
  elapsedTime = currentTime - lastTime;
  if (elapsedTime >= 1000) {
    sps = counter * 1.0 / elapsedTime * 1000;
    lastTime = currentTime;
    counter = 0;
    Serial.print(" SR: ");
    Serial.print(sps);
    Serial.print(" V: ");
    Serial.print(voltage, 15);  // print with 2 decimals
    Serial.print(" I: ");
    Serial.println(current, 15);  // print with 2 decimals
  }
  adc.waitDRDY();                               // wait for DRDY to go low before changing multiplexer register
  adc.setChannel(4, 5);                         // Set the MUX for differential between ch2 and 3
  current = adc.readCurrentChannel() / 0.0125;  // DOUT arriving here are from MUX AIN0 and AIN1
  adc.waitDRDY();
  adc.setChannel(2, 3);
  voltage = adc.readCurrentChannel() * 2;  //// DOUT arriving here are from MUX AIN2 and AIN3
  //print the result.
  counter++;
  //Serial.print("C: ");
  //Serial.print(counter);*/
}

//ADC einlesen
void adcTask(void *parameter) {
  for (;;) {
    //ADC Werte einlesen
    adc.waitDRDY();
    adc.setChannel(4, 5);
    current_1[adcIndex] = adc.readCurrentChannel() / 0.0125;
    adc.waitDRDY();
    adc.setChannel(2, 3);
    voltage_1[adcIndex] = adc.readCurrentChannel() * 2;
    adcIndex++;
    //SPS errechnen
    counter++;
    currentTime = millis();
    elapsedTime = currentTime - lastTime;
    if (elapsedTime >= 1000) {
      sps = counter * (1.0 / elapsedTime) * 1000;
      lastTime = currentTime;
      counter = 0;
      //Serial.println(sps);
    }
    //Wenn das Array voll ist wird es in ein neues Array gespeichert
    if (adcIndex >= ADC_SAMPLES) {
      if (check == false) {
        memcpy(current_2, current_1, sizeof(current_1));
        // Indizes zurücksetzen
        adcIndex = 0;
        check = true;
      }
    }
  }
}

//Mittelwert bilden
void averageTask(void *parameter) {
  for (;;) {
    if (check == true) {
      float sum = 0;
      for (int i = 0; i < ADC_SAMPLES; i++) {
        sum += current_2[i];
      }
      adcAverage = sum / ADC_SAMPLES;
      Serial.println(adcAverage);
      check = false;
    }
    vTaskDelay(1);
  }
}

Serial Monitor Output:

15:18:37.526 -> Starting ADC
15:18:37.608 -> ADC Started
15:18:37.608 -> init succeed
//Reset Button gedrückt
15:18:38.374 -> Starting ADC
15:18:38.462 -> ADC Started
15:18:38.462 -> init succeed
15:18:38.587 -> -19.67
15:18:38.675 -> -17.80
15:18:38.761 -> -17.25
//Reset Button gedrückt
15:18:40.379 -> Starting ADC
15:18:40.464 -> ADC Started
15:18:40.464 -> init succeed
//Reset Button gedrückt
15:18:41.769 -> Starting ADC
15:18:41.840 -> ADC Started
15:18:41.840 -> init succeed
15:18:41.975 -> -18.97
15:18:42.065 -> -18.09
15:18:42.156 -> -17.71
15:18:42.276 -> -18.48
15:18:42.367 -> -18.04
15:18:42.456 -> -18.07
15:18:42.578 -> -18.25
15:18:42.667 -> -17.97

Definiere in averageTask mal einen Zähler, den du für jede Ausgabe inkrementierst und mit dem Mittelwert ausgibst. Wenn der Lücken hat, ist es ein komplexeres Problem.

Ok jetzt kenne ich mich gar nicht mehr aus. Habe das mit dem Zähler implementiert. Wenn ich es so mache das ich den Zähler außerhalb der If schleife print:
image

Sieht der Output so aus:
image

jedoch läuft der Zähler nur bis 1000 und bleibt dort stehen.

Wenn ich ihn hingegen in der If abfrage Printen lasse, wie hier zu sehen:
image

wird er gar nicht geprinted.
Output:

15:45:03.366 -> Starting ADC
15:45:03.451 -> ADC Started
15:45:03.451 -> init succeed

Update: Habe gerade ein paar mal auf den Resetbutton gedrückt jetzt sah der Output so aus:

15:48:05.410 -> Starting ADC
15:48:05.500 -> ADC Started
15:48:05.500 -> init succeed
15:48:05.614 -> -19.26
15:48:05.614 -> 1
15:48:05.693 -> -19.10
15:48:05.693 -> 2
15:48:05.814 -> -19.46
15:48:05.814 -> 3
15:48:05.904 -> -17.99
15:48:05.904 -> 4
15:48:06.014 -> -18.91
15:48:06.014 -> 5
15:48:06.092 -> -19.29
15:48:06.092 -> 6
15:48:06.225 -> -18.63
15:48:06.225 -> 7
15:48:06.319 -> -19.25
15:48:06.319 -> 8
15:48:06.406 -> -18.02
15:48:06.406 -> 9
15:48:06.496 -> -19.51
15:48:06.496 -> 10
15:48:06.619 -> -19.20
15:48:06.619 -> 11
15:48:06.703 -> -19.67
15:48:06.703 -> 12
15:48:06.820 -> -19.20
15:48:06.820 -> 13
15:48:06.906 -> -19.81
15:48:06.906 -> 14
15:48:06.980 -> -18.65
15:48:06.980 -> 15
15:48:07.111 -> -17.98
15:48:07.111 -> 16
15:48:07.194 -> -18.47
15:48:07.194 -> 17
15:48:07.317 -> -20.78
15:48:07.317 -> 18
15:48:07.392 -> -17.85
15:48:07.392 -> 19
15:48:07.508 -> -18.34
15:48:07.508 -> 20

ging bis der Zähler 928 erreicht und stoppte dann

Ich sehe bei dir keine Form von

Lesestoff: Memory Barrier

asm volatile("" ::: "memory");

Die Kommunikation von 2 Tasks ist schon nicht trivial, aber dann noch auf 2 Cores....
Das ist anspruchsvoll.

Wenn ich das richtig verstehe meinst du ich habe ein Problem das beide Tasks probieren auf den gleichen Ram Speicher zuzugreifen oder?

edit: okay, Nebenthema :wink:

Aber Semaphore und Mutex sind ja Memory Barrieren oder?

Du nutzt *parameter nicht, der den Tasks mitgegeben wird. Darüber könntest du einen Zeiger auf eine gemeinsame Datenstruktur mitgeben, musst dich aber selbst um die Vermeidung von "race conditions" kümmern, z.B. mit einem Mutex.

Nein!
Für die Implementierung solcher, werden Memorybarrieren benötigt.
Aber sie sind keine.
Zudem nutzt du sowas überhaupt nicht.

(wofür verlinke ich den Artikel überhaupt?)

Das stimmt das ich das nicht nutze dachte mir mit einen check funktioniert da genauso. Ok ich glaube ich muss noch viel lernen über dual Cores.

Sorry aber ich bin kein gelernter Informatiker. Ich habe mir den Artikel durchgelesen. Habe aber vl nicht alles verstanden. Aber sorry das ich was lernen möchte und mir dazu vl ein Paar Informationen von Leuten eintreiben möchte die mehr in den Gebiet wissen als ich.

Eben nicht!
Beide Tasks speichern die Daten in ihren Registern.
Dafür sorgt der Optimizer.
Die Tasks kommen, ohne deine explizierte Ansage, gar nicht auf die Idee, dass die Daten von einem anderen Punkt aus verändert werden könnten.

Ist es besser 2 yP zu verwenden und diese kommunizieren lassen?:thinking:

Wenn man mit dem Kartoffelschälen überfordert ist, könne man auf Nudeln umsteigen.

Oder anders:
Wie kommst du auf die lustige Idee, dass man überhaupt auf Memory Barriers verzichten kann?
Arduino ist voll davon.

Ok irgendwie bin ich glaub ich zu dumm. Das mit den Registern leuchtet mir ein, nur wie ich das implementiere ist mir ein Rätsel. Habe schon mit Semaphoren und Mutex rumprobiert leider auch ohne Erfolg.

Was ich gefunden habe und probiert habe zu implementieren ist eine Queue. RTOS Queues

Habe das aus diesem Beispiel abgeleitet.

Der Code sieht jetzt so aus:

#include <ADS1256.h>
#include <SPI.h>
#include <DFRobot_GP8403.h>
#include <Wire.h>

//Variabeln für SPS Berechnung
float clockMHZ = 7.68;  // crystal frequency used on ADS1256
float vRef = 2500;      // voltage reference

// Construct and init ADS1256 object
ADS1256 adc(clockMHZ, vRef, false);

TaskHandle_t Task0;
TaskHandle_t Task1;
QueueHandle_t queue;

void setup() {
  Serial.begin(115200);

  Serial.println("Starting ADC");

  adc.begin(ADS1256_DRATE_15000SPS, ADS1256_GAIN_2, false);
  Serial.println("ADC Started");

  queue = xQueueCreate(100, sizeof(float));
  //Task generieren
  xTaskCreatePinnedToCore(adcTask, "adcTask", 4096, NULL, 1, &Task0, 1);
  xTaskCreatePinnedToCore(averageTask, "averageTask", 4096, NULL, 1, &Task1, 0);

  // Set MUX Register to AINO-AIN1 so it start doing the ADC conversion
  adc.setChannel(2, 3);
}

void loop() {
}

//ADC einlesen
void adcTask(void *parameter) {
  for (;;) {
    float current;
    float voltage;
    float sps;
    long lastTime, currentTime, elapsedTime;
    int counter;
    //ADC Werte einlesen
    adc.waitDRDY();
    adc.setChannel(4, 5);
    current = adc.readCurrentChannel() / 0.0125;
    adc.waitDRDY();
    adc.setChannel(2, 3);
    voltage = adc.readCurrentChannel() * 2;
    //SPS errechnen
    counter++;
    currentTime = millis();
    elapsedTime = currentTime - lastTime;
    if (elapsedTime >= 1000) {
      sps = counter * (1.0 / elapsedTime) * 1000;
      lastTime = currentTime;
      counter = 0;
      //Serial.println(sps);
    }
    if (xQueueSend(queue, &current, portMAX_DELAY) != pdPASS) {
      Serial.println("no queue space.");
    }
    Serial.println(current);
  }
}

//Mittelwert bilden
void averageTask(void *parameter) {
  for (;;) {
    int i;
    float sum;
    float adcAverage;
    float current_2;
    if (xQueueReceive(queue, &current_2, portMAX_DELAY) == pdPASS) {
      Serial.println(current_2);
    }
    i++;
    sum += current_2;
    if (i == 100) {
      adcAverage = sum / i;
      Serial.println(adcAverage);
      sum = 0;
      i = 0;
    }
  }
}

nur leider funktioniert das auch nicht. Habt ihr noch einen Tipp für mich?

Legt eine lokale Variable ganz am Anfang der Schleife an.
Sie wird nicht vor besetzt, enthält einen Zufallswert, irgendwas.

i = Zufall +1

Es gibt keinerlei Garantie, dass sie überhaupt mal 100 wird.

Vermutlich meinst du sowas:

void averageTask(void *parameter) {
  int i = 0;
  for (;;) {
    float sum;
    float adcAverage;
    float current_2;
    if (xQueueReceive(queue, &current_2, portMAX_DELAY) == pdPASS) {
      Serial.println(current_2);
    }
    i++;
    sum += current_2;
    if (i == 100) {
      adcAverage = sum / i;
      Serial.println(adcAverage);
      sum = 0;
      i = 0;
    }
  }
}

den Rest habe ich nicht überprüft.....
Und gar nix getestet

Das

i++;
sum += current_2;

sollte wohl in den darüber liegenden if-Block mit rein, sonst werden evtl. undefinierte current_2 mitgezählt.

allerdings auch nicht getestet und xQueueReceive nicht angesehen

Mein Eindruck:
Wenn man sich an Arduino-Programmieren gewöhnt hat, vergisst man leicht, wieviel einfacher das Leben ohne Multitasking ist.

Dem kann ich nicht zustimmen!

Multitasking heißt einfach nur: Mehrere Aufgaben quasi gleichzeitig/nebeneinander ausführen.
Und da sind wir schon an dem Punkt, den hier viele "Blockadefrei programmieren" nennen.

Siehe: Nachtwächter Erklärung

Ok das mit dem i war ein dummer Fehler. Der Fehler war das ich den ADC im Setup gestartet habe und nicht direkt im Task.

Habe den Code soweit das er zumindest mal teils funktioniert. Nur leider gibt er nach einer Zeit auf und macht nicht mehr weiter.

#include <ADS1256.h>
#include <SPI.h>

//Variabeln für SPS Berechnung
float clockMHZ = 7.68;  // crystal frequency used on ADS1256
float vRef = 2500;      // voltage reference

// Construct and init ADS1256 object
ADS1256 adc(clockMHZ, vRef, false);

static volatile QueueHandle_t queue;

void averageTask(void *parameter);
void adcTask(void *parameter);

void setup() {
  Serial.begin(115200);

  queue = xQueueCreate(100, sizeof(float));
  if (queue == NULL) {
    Serial.println("Queue could not be created. Halt.");
    while (1) delay(1000);  // Halt at this point as is not possible to continue
  }
  //Task generieren
  xTaskCreatePinnedToCore(adcTask, "adcTask", 4096, NULL, 1, NULL, 1);
  xTaskCreatePinnedToCore(averageTask, "averageTask", 4096, NULL, 1, NULL, 0);

  // Set MUX Register to AINO-AIN1 so it start doing the ADC conversion
  adc.setChannel(2, 3);
}

void loop() {
  delay(10);
}

//ADC einlesen
void adcTask(void *parameter) {
  float current = 0;
  float voltage = 0;
  float sps = 0;
  long lastTime = 0, currentTime = 0, elapsedTime = 0;
  int counter = 0;

  Serial.println("Starting ADC");

  adc.begin(ADS1256_DRATE_15000SPS, ADS1256_GAIN_2, false);
  Serial.println("ADC Started");
  for (;;) {
    //current = analogRead(5);
    //ADC Werte einlesen
    adc.waitDRDY();
    adc.setChannel(4, 5);
    current = adc.readCurrentChannel() / 0.0125;
    adc.waitDRDY();
    adc.setChannel(2, 3);
    voltage = adc.readCurrentChannel() * 2;
    //SPS errechnen
    counter++;
    currentTime = millis();
    elapsedTime = currentTime - lastTime;
    if (elapsedTime >= 1000) {
      sps = counter * (1.0 / elapsedTime) * 1000;
      lastTime = currentTime;
      counter = 0;
      //Serial.println(sps);
    }
    Serial.print("out: " + String(current));

    xQueueSend(queue, (void *)&current, portMAX_DELAY);
  }
}

//Mittelwert bilden
void averageTask(void *parameter) {
  int i = 0;
  float sum = 0;
  float adcAverage = 0;
  float current_2 = 0;
  for (;;) {
    xQueueReceive(queue, &current_2, portMAX_DELAY);
    Serial.println(" in: " + String(current_2));
    i++;
    sum += current_2;
    if (i == 100) {
      adcAverage = sum / i;
      Serial.println("Average: "+String(adcAverage));
      sum = 0;
      i = 0;
    }
  }
}

Output:

Starting ADC
ADC Started
out: 49.14 in: 49.14
out: 43.37 in: 43.37
out: 38.74 in: 38.74
out: 35.12 in: 35.12
out: 32.09 in: 32.09
out: 29.64 in: 29.64
out: 25.68 in: 25.68
out: 22.05 in: 22.05
out: -1.55 in: -1.55
out: -1.55 in: -1.55
out: -2.03 in: -2.03
out: -3.60 in: -3.60
out: -4.10 in: -4.10
out: -3.46 in: -3.46
out: -3.84 in: -3.84
out: -4.72 in: -4.72
out: -0.98 in: -0.98
out: -0.86 in: -0.86
out: -1.24 in: -1.24
out: -2.31 in: -2.31
out: -3.93 in: -3.93
out: -3.12 in: -3.12
out: -3.72 in: -3.72
out: -0.38 in: -0.38
out: -0.74 in: -0.74
out: -2.31 in: -2.31
out: -1.76 in: -1.76
out: -2.26 in: -2.26
out: -3.74 in: -3.74
out: -3.58 in: -3.58
out: -0.43 in: -0.43
out: 0.45 in: 0.45
out: -1.24 in: -1.24
out: -2.24 in: -2.24
out: -2.26 in: -2.26
out: -3.86 in: -3.86
out: -3.74 in: -3.74
out: -0.72 in: -0.72
out: -0.55 in: -0.55
out: -0.17 in: -0.17
out: -1.41 in: -1.41
out: -2.46 in: -2.46
out: -2.43 in: -2.43
out: -3.08 in: -3.08
out: -0.95 in: -0.95
out: -0.98 in: -0.98
out: -1.14 in: -1.14
out: -1.62 in: -1.62
out: -2.74 in: -2.74
out: -2.86 in: -2.86
out: -4.15 in: -4.15
out: 0.10 in: 0.10

Das mit der sum müsste so richtig sein. Das siehst du im Output.

Vl kann mir das einer Erklären. Habe gerade die Baudrate von der Serial erhöht und jetzt läuft der Code. In was für einen Zusammenhang steht das?

Spekulativ: Du läßt adcTask ungebremst Daten nach Serial schicken, aber Serial hat eine endliche Geschwindigkeit, da läuft irgendwann der Puffer voll oder über.

Baue mal testweise eine kleine Verzögerung ein, dann weißt Du, ob ich auf der richtigen Spur bin.

Bei der Suche bin ich über ESP32 Dual Core with FreeRTOS and Arduino IDE gestolpert, beim ersten Lesen schien es mir vielversprechend.

Ich bevorzuge "blockadearm", weil alles seine Zeit braucht.