Meßwerte effektiv seriell übertragen

Ich habe ein simpel gestricktes Programm, welches über A0 Spannungen einliest und über die serielle Schnittstelle an den PC weiterleitet.
Bislang habe ich die Meßwerte einzeln übertragen. Nachdem sich nun heraus gestellt hat, dass im realen Betrieb Echtzeitfähigkeit nicht erforderlich ist und ich stattdessen lieber die Verarbeitunsggeschwindigkeit erhöhen sollte, habe ich nun vor immer 1000 Messungen blockweise zu übertragen.

Da strings hier wohl unpraktikabel sind, lautet meine Frage wie ich den Block aus 1000 Messwerten (bei negativen Werten mit Vorzeichen) am elegantesten und speicherschonensten übertrage.
Hardwareseitig wird eine Offsetspannung von ca. 1.5V zugeführt, so dass auch negative Ergebnisse in diesem Bereich möglich sind.

Insgesamt sollen die Bildschirmausgabe minimiert werden und die Verarbeitungsgeschwindigkeit so gesteigert werden, dass evtl. auch bis zu 50600 bei der Datenrate drin sind. Derzeit ist bei 9600 Schluß, wobei auch der PC an Grenzen stoßen könnte, da er jeden Wert samt Overhead und dann Visualisierung einzeln abarbeiten muss.

// Adafruit SSD1306 - Version: 1.3.0
#include <Adafruit_SSD1306.h>
#include <splash.h>

// Adafruit GFX Library - Version: 1.5.6
#include <Adafruit_GFX.h>
#include <Adafruit_SPITFT.h>
#include <Adafruit_SPITFT_Macros.h>
#include <gfxfont.h>

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4 // not used 
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//Adafruit_SSD1306 display(OLED_RESET);

void setup()
{
    Serial.begin(9600);
    	// initialize with the I2C addr 0x3C / mit I2C-Adresse 0x3c initialisieren
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
		   // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed. Not enough RAM"));
    for(;;); // Don't proceed, loop forever
  }
}

// Voltage 

// Flags 
bool DebuggingMode = false;
bool ExperimentalMode = false;

#define PIN_TEST A0
#define PIN_OFFSET A1
#define REF_VOLTAGE 5.0
#define PIN_STEPS 1024.0

  	int Volt=0; // Current Voltage
  	int Volt0=0; // Last Voltage
  	int VoltAVtotal=0;
  	int MinProbe=300;
  	int HitProbe=100;
  	int MaxProbe=3000;
  	int counter1 = 0; // Total number of samples
  	int counter2 = 0; // Total number of hits
  	int Result[30];
  	int VOffset=1646; // Voltage offset to avoid negative voltage
  	int VOffsetTemp=0; //Temp storage for VOffset
  	int VCalibrate=-0;// Measurement error correction
  
  char ResultE [15][3] = {  
  {"XX"}, 
  {"Aa"},
  {"Ab"},
  {"Ac"}, 
  {"Ad"}, 
  {"Ae"}, 
  {"Af"},
  {"Ag"}, 
  {"Ah"}, 
  {"Ai"},
  {"Aj"}, 
  {"??"},
  {"XX"}, 
  {"XX"}, 
  {"XX"},
};

//if (ExperimentalMode == true) {

// https://majenko.co.uk/blog/making-accurate-adc-readings-arduino
// Using internal 1.2V reference
  long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
//};

}

void loop() {
  
  if (counter1=1){
	display.clearDisplay();  // Clear Adafruit Logo
  };
		// set text color 
	display.setTextColor(WHITE);
	// set text size 
	display.setTextSize(1);
	// set text cursor position 
	display.setCursor(1,0);
	// show text 
	display.println(F("Tester"));
	display.setCursor(2,56);
	display.println(F("2020"));
	display.setTextSize(2);
	display.setCursor(2,34);
	display.println(Volt);
	
	Volt=(int)analogRead(PIN_TEST)*REF_VOLTAGE/PIN_STEPS*1000; // Read new Value

	if (ExperimentalMode == true) {
	    
	// Measure Voltage & add offset
 Volt0=Volt; // Save old Value first
 
	// https://majenko.co.uk/blog/making-accurate-adc-readings-arduino
	// Using 1.1V internal reference to increase precision
  unsigned int ADCValue; // 
  double Voltage; // 
  double Vcc;  // 
  
  // Measure Voltage
  Vcc = readVcc();//10.0 ; // was /1000.0;
  ADCValue=(int)analogRead(PIN_TEST)*REF_VOLTAGE/PIN_STEPS*1000; // Read new Value
  Volt = (ADCValue / 1023.0) * Vcc;
  };
  
  counter1++;
  Volt=Volt-VOffset; // add hardware offset
  Volt=Volt+VCalibrate; // add additional offset after calibration (not implemented yet)

 if (counter1<MaxProbe) {
    
  VoltAVtotal=(VoltAVtotal*(counter1-1)+Volt)/counter1; // Average Voltage
  
   Serial.println(Volt);
   
  if (DebuggingMode == true) { 
   Serial.print(F(" Average: "));
   Serial.println(VoltAVtotal);
   Serial.print(F(" Counter: "));
   Serial.println(counter1);
   Serial.print(F(" Offset : "));
   Serial.println(VOffset);
   Serial.print(",");
  }
  
	// Calculations and empiric data

	//display.setCursor(2,15);
	if (Volt > 520 && Volt <= 530){
	Result[2]++;
	}
	else	if (Volt >= 420 && Volt <= 425){ // 453 MAx wie Ta!!
	Result[1]++;
	}
       else if (Volt > 265 && Volt <= 275){ //
       Result[6]++;
	}
	else if (Volt > 451 && Volt <= 455){ // 453 Max wie Ag !!!
	Result[10]++;
	}
	else if (Volt > 157 && Volt <= 167){  //
	Result[5]++;
	}
	else if (Volt > 167 && Volt <= 177){ //
	Result[3]++;
	}
	else if (Volt > 57 && Volt <= 67){ //
	Result[7]++;
	}
	else if (Volt > 108 && Volt <= 114){ //
	Result[8]++;
	}
	else if (Volt < -285 && Volt >= -295){ //
	Result[9]++;
	}
	else if (Volt < -636 && Volt >=-646){ //
	Result[4]++;
	}
	else {
	Result[11]++;
		}
	counter2++; 
 }

	
if (counter1<MaxProbe && counter1>MinProbe|| counter2==HitProbe){
  Serial.println(F(" "));
  Serial.println(F(" ** Results **  "));
  display.clearDisplay();
 
	      for (byte i = 0; i < 15; i = i + 1) {
  Serial.print(ResultE[i]);
  Serial.print(F("  "));
  Serial.print(Result[i]);
  Serial.print(F("   "));
  
  float a=Result[i]; //percentage
  int c=HitProbe;
  float b=0.00;
  b=(float)c/100;
  a=a/b;
  Serial.print(a);
  Serial.println(F("%"));
  
  if (a>1){
  display.print(ResultE[i]);
  display.print(F(" "));
  display.setTextSize(1);
 
  display.print((int)a);
  display.setTextSize(2);	
  display.print(F(" "));
  display.display();
    }
  Result[i]=0;
  counter1=0;
  counter2=0;
  //maxprobe==minprobe;
   }
    delay(25);
}
}

Für Hinweise auf Verbrechen gegen C die ich hier begangen habe, bin ich natürlich auch dankbar. Der Sketch arbeitet aber ansonsten wunschgemäß. 8)
Bisher erschien mir array typ char als das Mittel der Wahl, aber evtl. geht das hier anders besser?

if (counter1=1)
ist immer wahr

if (counter1<MaxProbe)
ist somit auch immer wahr

Das einfachste ist Du speicherst die ADC-Werte in ein INT-Array und überträgst dieses in 2 Byte getrennt über serial. Die Umrechnung kannst Du dann im empfangenden Programm machen.

Um 1000 INT Werte abzuspeichern brauchst Du 2kByte RAM.
Nicht jeder Arduino hat genügend RAM.

Grüße Uwe

Da steht doch ein

counter1++;

in der Schleife?

Hätte auch counter1=0 nehmen können dann wäre es gleich im ersten Durchgang gültig.
Oder verstehe ich dich da falsch?

@uwefed:
Danke für den Input. Ich dachte mir schon sowas, hoffte aber man könne die 4k in den Flashspeicher auslagern, oder so etwas.
Es ist ein Arduino Mini mit 328er CPU und eine schwächere CPU wird aufgrund der notwendigen Auflösung des ADCs und der Geschwindigkeit nicht in Betracht gezogen.
Ich könnte ja auch erst mal mit 100 oder 250 Werten starten. Alles erscheint besser, als jeder Wert einzeln.

if (counter1=1) ist total was anderes als
if (counter1==1)

Das zweite ist das das was Du haben willst.

Ein ATmega328 hat 2kByte RAM. Da kannst Du sicher nicht 1000 Werte zwischen speichern. Ich weiß jetzt wieviel RAM die Ansteuerung des Displays braucht.

Grüße Uwe

was wird deines Erachtens an der "Verarbeitunsggeschwindigkeit" besser, wenn du statt immer einem Wert 1000 überträgst?

Wenn du eine Wurst an Daten überträgst brauchst du mindestens ein Trennzeichen mehr. Das heißt Netto wird die Übertragung länger dauern.

Wozu braucht es das Delay(25) am Ende des Sketch?

Wie viel RAM ist denn nach dem kompilieren noch frei? Für 1000 Werte wird sicher nicht passen, Alternative wäre ein eeprom oder Fram als Zwischenspeicher, oder ein SD-Modul, dessen Libs benötigen aber auch ne Menge RAM.

Ich dachte mir schon sowas, hoffte aber man könne die 4k in den Flashspeicher auslagern,

Nur der Bootloader kann ins Flash schreiben.
Das steht auch so im Datenblatt.

Aber men zu, wenn man unbedingt will, bekommt man das hin.
Bedenke aber die maximale Anzahl Schreibzyklen des Flash...
(steht auch im Datenblatt)

Da steht doch ein
counter1++;
in der Schleife?

Genau!
Angelegt wird counter1 mit 0
dann kommt counter1 =1
dann conter1++ , so wird counter1 dann zu 2
und am Ende ein counter1=0

Das sind die drei Werte, welche counter1 in deinem Code annehmen kann: 0 1 2
Ende der Vorstellung.

uwefed:
Das einfachste ist Du speicherst die ADC-Werte in ein INT-Array und überträgst dieses in 2 Byte getrennt über serial.

Kann ich denn das gesamte Array auf einmal übertragen? Wenn ich eine Schleife für jedes Element durchlaufen muss, bin ich ja nicht wirklich weiter gekommen.
Daher hatte ich ja zunächst an einen langen String gedacht. Den könnte man ja auf eine Rutsch absenden.
Wie aber geht das bei einem INT Array?

@Combie

== vs. =

Comparator vs Zuweisung

Danke für den Hinweis. Habe ich jetzt geändert.
Da das ganze in Dauerschleife lief fiel es nie auf. Aber wenn ich jetzt die Werte sammle und in ein Array o.ä. packe, wird der Zähler natürlich superwichtig.
Da hätte ich mir evtl. einen Wolf gesucht.

Schau Dir mal Serial.write(...) an.

Gruß Tommy

Hallo,

ich habe seit gestern darüber nachgedacht welchen Vorteil du vom sammeln der Messwerte hast. Ich komme zu keinem Ergebnis. Wenn der PC sowieso alle Werte einzeln bearbeiten muss, wo ist dann der Vorteil wenn er einen Sack voller Messwerte an den Kopf geknallt bekommt. Dann kommt dein PC Programm plötzlich ins schwitzen. Dann brauchst du auf dem µC einen Messwertbuffer und auf dem PC. Ich kann keinen Vorteil erkennen. Ich weiß auch nicht warum das PC Programm nur mit 9600 Baud fertig wird. Ich sage mal salopp, wenn man einen PC so blockiert bekommt das nur 9600 Baud drin sind, dann klemmt es gewaltig. Dann wäre USB-C gar nicht möglich.

Wenn es nur um eine geringere Refreshrate der Bildschirmausgabe am PC geht, dann aktualisiere diese seltener. Im Hintergrund kann es trotzdem weitergehen.

Was ich sagen will, dass Problem ist noch nicht klar.

Einiges wurde schon zum Code gesagt. Delay muss noch raus und das Display löschen auch. Es sollte nur das überschrieben werden was aktualisiert werden soll. Komplettes Display löschen ist langsam.

Ich sehe hier auch kein Problem.

Wenn der Ardino binär überträgt sollte das wurscht sein ob das mit einem Write passiert oder
mit einer Schleife. 115Kb solllte der allemal schaffen. 16Mhz ist doch verdammt schnell.
Ich kann nicht erkennen wo hier der Engpass sein soll.
Mann sollte natürlich nicht in Ascii übertragen. a) sind die Umwandlungsroutinen (sprintf e.t.c.) langsam
und b) überträgt man bis zu 6 Byte mehr als nötig (für einen short int).
Für Binärübertragung sollte man sich allerdings ein kleines Protokoll überlegen damit man nicht aus dem Takt
kommt.

Und der PC sollte das doch mit einem müden "Arschgrinsen" schaffen. (schafften die vor 20 Jahren schon)

Ulli

Hi

Wenn's wirklich auf die Geschwindigkeit ankommt und die Übertragung der Engpass ist - man muß die 10Bit vom ADC doch nicht mit 6 Müll-Zeichen auf 2 Byte aufblasen??

Wenn DAS nun aber nicht das Problem ist, wie von Uwe vorgeschlagen:

  • ADC auslesen
  • Wert (int) im Struct ablegen
  • Wert als HI/LO-Byte seriell versenden.

Die Gegenstelle muß damit halt 'um können' - wenn Da aber etwas mehr als ein y/t-Schreiber auf Daten wartet, sollte Das anpassbar sein.

Einen Vorteil sehe ich beim Datensammeln: Du hast keine Lücken, in Denen Du die gerade bekommen Daten zum PC schubst (bei 9600Baud dauert 1 Zeichen 1ms . also immerhin 2ms für das 'int').

Trennzeichen - kA, wie wichtig die Daten sind und ob man Dreher ect.pp. erkennen können müß.
Denke, normal sollten wir ohne Trenner auskommen können.

MfG

Warum, habe ich noch nichts zu dem Thema gesagt, obwohl es doch recht pass genau auf meinen Kernkompetenzen liegt?

Das Verhältnis, von Messungen zu Zeit, ist völlig ungeklärt.
Festes Zeitraster, und wenn, dann welches, oder einfach soviel Messungen wie möglich?

Protokoll ?!?!
Stößt der PC einen Messzyklus an?
Wie kann der PC Bytes zu int kleben, ohne sich zu vertun, Stichwort Synchronisation.

Fehlerprüfung? CRC, o.ä. nötig?

Hi,

In der ganz frühen Phase, war es einfach nur wichtig die Messwerte zu visualisieren und Störungen, Transienten usw. zu identifizieren und "sich mal ein visuelles Bild vom Signal zu machen".
Nun hat sich gezeigt, dass ca. 1000 Meßwerte ausreichen um die nachfolgenden statitischen/stochastischen Auswertungen auf dem PC durchzuführen.
Meine serielle Dauerdatenübertragung ist also obsolet.
Ich könnte natürlich auch immer nach 1000 Meßwerten abbrechen und den PC weiter arbeiten lassen.
Aber..
..das geht mir zu langsam, denn bei eingestellten 9600 Baud dauern 1000 Meßwerte rund 10-20 Sekunden.
Da das nur ca. 4kB representiert stellt sich die Frage an welcher Schraube man hier drehen kann um die 1000 Werte in sagen wir mal 1 -3 Sekunden zu bekommen. Die Messung in der realen Anwendung soll Freihand mit einem mobilen Arduino erfolgen. da macht es einen Unterschied ob der Anwender die Meßpistole 1,2,3 oder 20 Sekunden an sein Meßobjekt möglichst ruhig halten muss.

Den ganzen überflüssigen und langsamen Display Schnickschnack entsorge ich mal, bis auf ein Endergebnis nach 1000 Messungen. Das war hauptsächlich Debugging um zu sehen was der Nano so treibt. Danke für den Hinweis. Aber so ein kleines Display ist für einen alten Typ wie mich einfach ein geiles Spielzeug. Ihr wisst was ich meine? ;D

Serial.write klingt und liest sich toll, aber ich muss dazu wohl meine ?.h file bearbeiten, da die Funktion wohl standardmäßig auskommentiert ist. Da ich derzeit mit dem webbasierenden create.arduino Editor arbeite muss ich erst mal sehen wie ich an diese Dateien rankomme (Habe einen bezahlten Account, da soll das gehen, weiss aber nicht wo und wie.)

  int Block[100];

  void loop() {
  
  Serial.write(Block,100);

  }

(Nur Beispielcode. Hier fehlen natürlich die Einbindung der libs für die serielle Schnittstelle)

Da bekomme ich diesen hier:
No matching function to call for HardwareSerial::...write(int[100],int)

Aber natürlich bin ich für Anregungen offen und dankbar wie man es am besten löst. Brauche hier einfach mehr Speed. Bis ca. Faktor 10. Mehr ist besser.
Ich entrümpel jetzt noch das VB Programm und lasse es die Daten erst mal komplett einlesen, bevor irgend etwas vor allem Grafisches gemacht wird und sehe mal ob dann die Baudrate höher gesetzt werden kann. Bislang gehe ich aber davon aus, dass der Arduino aufgrund der permaneten Anzeige der Meßwerte keine höhere Datenrate schafft.

Serial.write klingt und liest sich toll, aber ich muss dazu wohl meine ?.h file bearbeiten, da die Funktion wohl standardmäßig auskommentiert ist

:o :o :o :o :o

In der ganz frühen Phase, war es einfach nur wichtig die Messwerte zu visualisieren und Störungen, Transienten usw. zu identifizieren und "sich mal ein visuelles Bild vom Signal zu machen".
Nun hat sich gezeigt, dass ca. 1000 Meßwerte ausreichen um die nachfolgenden statitischen/stochastischen Auswertungen auf dem PC durchzuführen.

Warum unterschlägst du hier das ERFORDERLICHE Zeitverhalten.

Oder ist das nicht wichtig?

@TO: Ich weiß nicht, was Du falsch machst, aber meine Verbindung vom Arduino (egal welchem) laufen mindestens mit 115200 Baud problemlos in beide Richtungen.

Gruß Tommy

Hi

Auch sollten bei 9600Baud in 10...20 Sekunden 'knapp' über 4kB übertragen worden sein - irgendwo kneift hier was.
9600Baud = 1 Zeichen pro ms
Bei 10 Sekunden wären Das ungefähr 10.000 Zeichen - klar, wenn ich Da noch 'T32=35.6°C' mitsende, kann ich mir Das auch komplett sparen.

Noch ist das Ganze sehr verworren!

MfG