433MHz virtualWire Datentypen

Hallo allerseits,

ich beschäftige mich gerade zum ersten mal wirklich mit Kommunikation zwischen zwei Arduinos weil ich später ein selbstgebautes Fahrzeug fernsteuern möchte. Ich habe angedacht das mit meinen 433MHz Modulen zu machen, weil die hier rumliegen und ausreichend Reichweite haben. Weiterhin verwende ich die library VirtualWire, da RCSwitch sehr lahm ist wie ich bei Tests gemerkt habe.

Ich bin an dem Punkt, dass die Testsketches (siehe unten) für die 433MHz Kommunikation funktionieren, ich aber Teile davon nicht verstehe und deshalb nicht an meine Bedürfnisse anpassen kann. In dem Sendersketch wird das char/string (?) “Hallo Welt” verschickt. Allerdings ist das glaube ich mit einem Pointer (*) geschrieben und wird daher ins Eeprom geschrieben(?!), was ich nicht möchte. Ohne funktioniert der Sketch leider nicht, er kompiliert, aber die Nachricht kommt nicht mehr an. Auch möchte ich nicht Wörter oder Sätze, sondern veränderliche Zahlenwerte verschicken, z.B. Lenkwinkel = 10 oder Gas = 255.

Frage: Kann ich mit der Library Zahlen versenden und wenn ja wie und welche Variablentypen?
Ich habe noch nie mit strings und chars gearbeitet und irgendwie hat die Recherche bisher wenig Früchte getragen daher danke schonmal für die Hilfe!

Hier der Code des Senders, er ist auf einem Nano aufgespielt:

#include <VirtualWire.h>
#include <VirtualWire_Config.h>

// 433 MHz Sender mit der VirtualWire Library  V 1.27
// Matthias Busse 17.5.2014 Version 1.0
// Daten > D4 


char *msg = "Hallo Welt";// Nachricht
const byte DatenPin = 4;


void setup() {
  
  pinMode(DatenPin, OUTPUT);
  vw_setup(2000);             // Bits pro Sekunde
  vw_set_tx_pin(DatenPin);     // Datenleitung
}
 
void loop(){
  
  vw_send(msg, strlen(msg));
  vw_wait_tx();                         // warten bis alles übertragen ist
  //delay(1000);
}

Und hier der Empfänger-Sketch, der auf einem Uno läuft:

#include <VirtualWire.h>
#include <VirtualWire_Config.h>

// 433 MHz Empfänger mit der VirtualWire Library V 1.27
// Matthias Busse 17.5.2014 Version 1.0
// Data > D5

const byte DatenPin = 2;
int i;



void setup() {

  Serial.begin(9600);
  vw_setup(2000);       // Bits pro Sekunde
  vw_set_rx_pin(DatenPin);     // Datenleitung
  vw_rx_start();        // Empfänger starten
}

void loop() {

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) { // überprüfen ob eine Nachricht eingegangen ist
    for (i = 0; i < buflen; i++) {
      Serial.print((char)buf[i]);    // Nachricht aus buf ausgeben
    }
    Serial.println(" ");
  }
}

Mahimus:
Ich habe noch nie mit strings und chars gearbeitet und irgendwie hat die Recherche bisher wenig Früchte getragen daher danke schonmal für die Hilfe!

...

char *msg = "Hallo Welt";// Nachricht
...

Im Code definierst Du einen sog. C-String (was ein eindimensionales Array von chars ist). msg ist ein Zeiger auf das erste Element der Zeichenkette. Im Unterschied zu einer String-Zeichenkette verbraucht ein C-String genauso viele Bytes, wie die Zeichenkette Zeichen hat - plus 1 Byte, das das Ende der Zeichenkette anzeigt. Das char-Array msg belegt demnach 11 Bytes (10 für den Text, 1 für das „Ende“-Zeichen).

String-Zeichenketten sind auf den ersten Blick komfortabler, char-Zeichenketten sind jedoch sparsamer, was den RAM-Bedarf angeht. Sieh Dir am besten mal die diversen Funktionen an, die es für das Händling von C-Strings gibt. Z. B. hier.

HTH

Gregor

PS: Statt

 char *msg=...

könntest Du auch

 char msg[]=...

schreiben. Das Ergebnis ist das Gleiche: msg ist ein Zeiger auf die erste Speicherzelle, in der die Zeichenkette gespeichert ist.

Danke das hilft schon sehr. Mit Eeprom hab ich also nichts zu tun wenn ich char *msg =… schreibe?

Update: Ich hab hier was hinbekommen, was mich soweit zufriedenstellt und läuft. Ich hab damit einen String mit einem char Buchstaben als Kennung und ein char Zahlenwert, den ich dann empfängerseitig zu einem byte wandle.

Code Sender:

#include <VirtualWire.h>
#include <VirtualWire_Config.h>



char *msg = "Hallo Welt";// Nachricht
char Data[2] = {'P', 165};  
const byte DatenPin = 4;


void setup() {
  
  pinMode(DatenPin, OUTPUT);
  vw_setup(5000);             // Bits pro Sekunde
  vw_set_tx_pin(DatenPin);     // Datenleitung
}

void loop(){

  
  
  vw_send(Data, strlen(Data));  
  vw_wait_tx();                         // warten bis alles übertragen ist
  //delay(300);
  ++Data[1];
}

Code Empfänger:

#include <VirtualWire.h>
#include <VirtualWire_Config.h>


const byte DatenPin = 2;
int i;
char Data[2] = {0, 0};


void setup() {

  Serial.begin(9600);
  vw_setup(5000);       // Bits pro Sekunde
  vw_set_rx_pin(DatenPin);     // Datenleitung
  vw_rx_start();        // Empfänger starten
}

void loop() {

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) { // überprüfen ob eine Nachricht eingegangen ist
    for (i = 0; i < buflen; i++) {
      Data[i] = (char)buf[i];    // Nachricht aus buf 
    }
    Serial.println(Data[0]);
    Serial.println((byte)Data[1]);
  }
}

Mahimus:
… Mit Eeprom hab ich also nichts zu tun wenn ich char *msg =… schreibe?

Nein. Um mit dem EEPROM zu spielen, musst Du <EEPROM.h> einbinden. Ohne das kannst Du das EEPROM eigentlich nur mit dem Schraubstock oder dem Vorschlaghammer … äh … bearbeiten.

Evtl. verwechselst Du das aber auch mit dem Flash-Speicher (da wo das Programm hingeschrieben wird). Wenn Du eine Variable (oder ein n-dimensionales Feld) im Flash speichern möchtest, um RAM zu sparen, kannst Du das PROGMEM-Makro benutzen. Dazu habe ich aber auf die Schnelle keinen Link zur Hand.

Gruß

Gregor

char Data[2] = {'P', 165}; 

...
strlen(Data)

Ist ein Problem, denn strlen() braucht eine Endekennung '\0'

Dann musst Du es halt 1 länger machen.

char Data[3] = {'P', 165,0};

Gruß Tommy

michael_x:
Ist ein Problem, denn strlen() braucht eine Endekennung '\0'

Danke für den Hinweis und auch danke an Tommy. Dennoch funktioniert die Übertragung wie gesagt ohne ersichtliche Probleme. Hast du dafür eine Erklärung?

Hast du dafür eine Erklärung?

Das Byte was als Data[2] gelesen wird, kann ja zufällig immer eine 0 sein.

Du überprüfst im Empfänger übrigens nicht, ob buflen tatsächlich nicht größer als sizeof(Data) ist, und würdest evtl. irgendwohin schreiben.

Danke für die Antwort. Kann ich bei der Inizialisierung statt

Data[2] = {'P', 165};

vielleicht einfach

Data = {'P', 165}; schreiben?

Fügt er dann die 0 automatisch hinzu. Die Arduino Referenz legt das nahe und gibt als Beispiel:

char Str4 = "arduino";

dazu steht da:

Initialisiere mit einer String-Konstante in Anführungszeichen. Der Compiler passt die Größe des Arrays an die String-Konstante und fügt ein abschließendes Nullzeichen an, Str4.

Oder geht das nur bei Wörtern/Strings, die ohne Klammern und in Anführungszeichen stehen?

Fügt er dann die 0 automatisch hinzu?

Nein, warum sollte er.
Sowas ginge:

char Data[] = "P\xA5"; // 0xA5 = 165
char Data[] = "P\245"; // oktal !

Da wird jeweils ein Textende hinzugefügt.

Aber klarer wäre meiner Meinung nach Tommy's Schreibweise

char Data[3] = {'P', 165, 0};

Oder auf strlen und alle Versuche, diese zwei Byte als Text zu behandeln, verzichten.

michael_x:
Oder auf strlen und alle Versuche, diese zwei Byte als Text zu behandeln, verzichten.

Habe ich versucht, ich glaube die funktion vw_send nimmt nur char arrays und möchte auch nicht auf strlen verzichten.

Die will den Puffer und die Länge.

uint8_t vw_send(uint8_t* buf, uint8_t len)

Also kannst Du doch problemlos Data und 2 übergeben.
Das ist zwar nicht besonders flexibel oder elegant, aber es sollte den Zweick erfüllen.

Gruß Tommy

Mahimus:
Danke für die Antwort. Kann ich bei der Inizialisierung statt

Data[2] = {'P', 165};

vielleicht einfach

Data = {'P', 165}; schreiben?

Fügt er dann die 0 automatisch hinzu. Die Arduino Referenz legt das nahe und gibt als Beispiel:

char Str4 = "arduino";

dazu steht da:

Initialisiere mit einer String-Konstante in Anführungszeichen. Der Compiler passt die Größe des Arrays an die String-Konstante und fügt ein abschließendes Nullzeichen an, Str4.

Oder geht das nur bei Wörtern/Strings, die ohne Klammern und in Anführungszeichen stehen?

Grundlagen lernen!

Bei deiner Methode wird natürlich keine 0 angehangen!!
Denn du initialisierst ja nicht mit einem String, sondern jede Zelle einzeln.

char Data {"P\xA5"};
Würde das tun!

Übrigens:

Auch wenn erlaubt, sollte man das = bei der Initialisierungsliste weg lassen.

char Str4 = "arduino";
Alte initialisierung.
C artig.
Leicht mit einer Zuweisung zu verwechseln.

char Str4 = {"arduino"};
Erlaubt, aber unglücklich, da redundantes Zeichen vorhanden.
Leicht mit einer Zuweisung zu verwechseln.

char Str4 {"arduino"};
Moderner strenger/Typesicherer

Tommy56:
Die will den Puffer und die Länge.

uint8_t vw_send(uint8_t* buf, uint8_t len)

Also kannst Du doch problemlos Data und 2 übergeben.

Gruß Tommy

Aber was würde ich denn gewinnen?
Das einzige was ich noch für sinnvoll gehalten hätte ware das als byte zu verschicken und nicht als char. Das funktionierte aber nicht. So wie der Code oben ist funktioniert ja alles, deshalb ist das Thema für mich durch. Ich hab nur noch die Änderung gemacht, die du in #5 vorgeschlagen hast, obwohl es vorher ja auch ging, aber zur Sicherheit...

Ich vermute, Du hast das ganze Prinzip nicht verstanden. Dann ist Hilfe natürlich kaum möglich.

Gruß Tommy

Tommy56:
Ich vermute, Du hast das ganze Prinzip nicht verstanden.

Klär mich auf!

char Data[] {'P', 165};

vw_send(Data, strlen(Data)); // falsch
vw_send(Data, sizeof(Data)); // richtig, bei dieser Definition von Data

Außerdem ist sizeof keine Funktion, deren Ergebnis erst zur Laufzeit ermittelt wird, sondern kann schon vom Compiler optimiert werden (wird durch 2 ersetzt).

Ob du byte (0…255) oder char (-128…127) verschickst, merkt keiner. Der erste Parameter wird als uint8_t* interpretiert, kann auch Nullen enthalten, muss daher die Länge mit übergeben bekommen. Da du den zweiten Wert 165 nennst, wäre byte Data[] übrigens sowieso besser gewesen.

Dass der falsche Code bei dir funktioniert, kannst du Glück oder Pech nennen.
Irgendwann, nach einer Änderung ganz woanders, kann es sein, dass es nicht mehr funktioniert. Wäre erheblich besser gewesen, du hättest den Fehler gleich bemerkt, daher seh ich es als ‘Pech’.


char[] quiz {"Hell\0 World "};
Serial.write(quiz, sizeof(quiz));  // guck mal in die reference, das gibt's auch.
Serial.println(quiz);  // Hell
Serial.println(strlen(quiz)); // 4 
Serial.println(sizeof(quiz)); // 13

Mehr fällt mir zum unverstandenen Prinzip jetzt auch nicht mehr ein

Mahimus:
Das einzige was ich noch für sinnvoll gehalten hätte ware das als byte zu verschicken und nicht als char.

Die Methode möchte einen Zeiger auf Byte. Dadurch kann man völlig beliebige Daten versenden. Ein Array, bzw. ein C String liegt da durch die Verwandtschaft von Arrays und Zeigern am nächsten, aber es sind nicht die einzigen Optionen. Man kann auch die Adresse einer beliebigen Variable nehmen und diese auf uint8_t* casten. Oder man nimmt eine Union aus einem Datentyp und einem Byte-Array gleicher Länge

Danke euch für die Ausführungen. Ich habe hier nochmal den Ausgangscode wie er war bevor ich daran rumgespielt habe, mit Zeiger im vw_send, das habe ich nun verstanden.

#include <VirtualWire.h>
#include <VirtualWire_Config.h>

// 433 MHz Sender mit der VirtualWire Library  V 1.27
// Matthias Busse 17.5.2014 Version 1.0
// Daten > D4 


char *msg = "Hallo Welt";// Nachricht
const byte DatenPin = 4;


void setup() {
  
  pinMode(DatenPin, OUTPUT);
  vw_setup(2000);             // Bits pro Sekunde
  vw_set_tx_pin(DatenPin);     // Datenleitung
} 
 
void loop(){ 
  
  vw_send((uint8_t*)msg, strlen(msg)); 
  vw_wait_tx();                         // warten bis alles übertragen ist
  delay(1000);
}

Seh ich es richtig, dass die chars ohnehin hier zu bytes gemacht werden

(uint8_t*)msg

?
Kann ich demnach in meinem Code das hier machen bzw. würdet ihr den absegnen?

Sender (liest Joystick aus und sendet Wert als byte):

#include <VirtualWire.h>
#include <VirtualWire_Config.h>


const byte DatenPin = 4;
//char *msg = "Hallo Welt";  // Nachricht
byte Power[] = {1, 0, 0};   // byte0 ist Kennung, byte1 enthält Wert, byte2 markiert Ende und ist immer "0" 



void setup() {

  pinMode(DatenPin, OUTPUT);
  vw_setup(5000);             // Bits pro Sekunde
  vw_set_tx_pin(DatenPin);     // Datenleitung
}

void loop() {

  uint16_t Joystick_x = analogRead(A7); //hohe Werte unten
  uint16_t Joystick_y = analogRead(A6); //hohe Werte links

  Joystick_x = constrain(Joystick_x, 0, 1016);
  Joystick_x = map(Joystick_x, 0, 1016, 255, 0);
  Joystick_y = constrain(Joystick_y, 0, 1016);
  Joystick_y = map(Joystick_y, 0, 1016, 255, 0);

  Power[1] = Joystick_x;
  send_433(Power);


}

void send_433(byte Data[]) {

  vw_send(Data, sizeof(Data));  
  vw_wait_tx();                 // warten bis alles übertragen ist
}

Empfänger dazu:

#include <VirtualWire.h>
#include <VirtualWire_Config.h>


const byte DatenPin = 2; 
int i; 
byte Data[] = {0, 0, 0}; 
byte Power = 0; 


void setup() {

  Serial.begin(9600); 
  vw_setup(5000);         // Bits pro Sekunde
  vw_set_rx_pin(DatenPin);     // Datenleitung
  vw_rx_start();          // Empfänger starten
}

void loop() {

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) {  // überprüfen ob eine Nachricht eingegangen ist
    for (i = 0; i < buflen; i++) {   
      Data[i] = buf[i];         // Nachricht aus buf
    }
    Power = Data[1];
    Serial.println(Power);
  }
}

Funktionieren tut das wiederum derzeit so wie es ist. Da ich eurem Rat vertraue und versucht habe alle Vorschläge umzusetzen wollte ich der Funktion vw_send eigentlich einen Zeiger geben, dann blieb der Wert aber konstant auch wenn ich den Joystick bewegt habe. Sollte ich das noch ändern und wenn ja wie kann ich den Wert dennoch variieren?

Wenn ihr sonst noch was zu meckern habt gerne sagen und im besten Fall Gegenvorschläge machen!

void send_433(byte Data[]) {

vw_send(Data, sizeof(Data));

Problem ist, dass dem Aufrufparameter byte Data[] die Größeninfo fehlt.

Über die zwei üblichen Möglichkeiten das zu lösen, haben wir uns ja schon ausgelassen:
A) Wenn 0 immer nur Endekennung ist, geht char* msg als einziger Parameter. Die 0 wird da übrigens nicht mit übertragen.
B) Zwei Parameter (byte* data, size_t len)
der formal richtige DatenTyp size_t könnte natürlich in der Praxis auch auf ein byte reduziert werden.