Da die Arduino EEPROM-Adressen ja 8 Bit groß sind (?!), dimensioniere ich meine BitArrays auch so.
Zum senden via serialPort1.Write() muss ich diese ja noch in ByteArrays umwandeln. Dann anschließend senden.
C#:
new BitArray(einstellungen).CopyTo(senden, 0);
serialPort1.Write(senden, 0, 1);
Das funktioniert mit diesem Arduino-Code schon sehr gut.
Arduino-Sketch:
if (Serial.available() > 0)
{
inhalt = Serial.read(); //liest die Daten
EEPROM.write(0, inhalt);
delay(50);
}
Ich gebe den Inhalt der EEPROM-Adresse 0 auch zum Debuggen im Serial-Monitor aus. Für 1 einzelnes Byte funktioniert das alles perfekt. Ich muss aber mehrere ByteArrays nacheinander schicken und ebenfalls im EEPROM abspeichern.
Jetz mein Problem: wie kann ich das "timen"? Momentan schreibt der Arduino mal das erste ByteArray in mehrere Adressen (also immer das gleiche) und mal ist in der ersten Adresse auch der erste gesendete Wert (wie gewollt), aber in den folgenden sind völlig fremde Werte.
Ich denke mal, dass das Stichwort hier Übertragungsprotokoll ist.
Habe auch den ganzen Tag gegoogelt und rumprobiert aber bin immernoch nich weiter.
Arduino UNO hat 1024 Byte EEprom und darum eine 10 Bit Adresse. Du kannst auch eine 8 Bit Adresse verwenden dann aber auch nur 256 Speicherzellen ansprechen.
Ich kann zwar kein C# aber das untenstehende entspricht nicht dem obrigen?
einstellungen[0] = checkBox1.Checked;
Wenn Du die Adresse für jedes Enrhltene Byte wechselst kannst Du auch mehrere empfangene Bytes abspeichern. Aber achtung EEproms brauchen zum Abspeichern ca 5mS. Da mußt Du schauen daß der Eingangsbuffer der Seriellen Schnittstelle nicht überläuft und Daten verloren gehen.
Was Du gepostet hast, ist kein Sketch, sondern ein Auszug daraus und zudem nicht jener, der den von Dir beschriebenen Fehler verursacht. Könntest Du den ganzen Sketch posten und zwar jenen, der das Problem auch aufzeigt, nicht jenen, der funktioniert?
Was willst Du mit "Serial.flush();"?
Das löscht nicht den Serielen Buffer. Serial.flush() - Arduino Reference
Auch wenn es das täte dann würde keine Daten mehr vorhanden sein um diese mit
inhalt = Serial.read(); //liest die Daten
EEPROM.write(1, inhalt);
zu lesen.
Das Problem bei Deinem Sketch ist wahrscheinlich wirklich die Synchronisierung. Es werden einfach zwei Bytes von der seriellen Schnittstelle gelesen und dann in die Zellen 0 und 1 des EEPROMs geschrieben. Wenn Du jetzt nur ein Byte Versatz hast (aus welchem Grund auch immer), wirst du immer den Inhalt, den Du eigentlich in Byte 0 schreiben wolltest, in Byte 1 finden und vom letzen Mal noch den alten Byte1-Inhalt in Byte 0. Wie Du die Synchronisierung machst, ist wahrscheinlich egal. Am besten ist wahrscheinlich eine Startsequenz wie z.B. 0x5A00FF5A und danach die eigentlich Nutzdaten, wobei das bei binären Daten natürlich nicht so einfach ist, denn die Startsequenz könnte ja noch der Inhalt der letzten Übertragung sein (auch wenn das nicht sehr wahrscheinlich ist). Wenn Du sicher gehen willst, wartest Du eine Bestätigung des Arduinos nach der Sequenz ab, bevor Du mit dem Schreiben der Nutzdaten beginnst.
Das mit dem Serial.flush hab ich irgendwo in einem Beispiel gesehen
Ich verstehe den Vorschlag mit der Startsequenz nicht so ganz.
Benutze ich denn die serielle Übertragung so ungewöhnlich? Ich dachte, dass dies oft so gemacht wird.
Also Daten schicken und nacheinander in EEPROM-Adressen abspeichern.
Benutze ich denn die serielle Übertragung so ungewöhnlich? Ich dachte, dass dies oft so gemacht wird.
Also Daten schicken und nacheinander in EEPROM-Adressen abspeichern.
Nicht wirklich ungewöhnlich, aber je nachdem, was Du übertragen willst, muss das entsprechende Protokoll stimmen. In Deinem Fall muss der Arduino wissen, wo ein Übertragung beginnt, damit er weiss, welches Byte nun an Adresse 0 und welches an Adresse 1 kommt (u.s.w.). Du kannst auch (anstatt der vorgeschlagenen Start-Sequenz) einen Handshake nehmen, also z.B. der Steuer-PC sendet so lange 0 oder 0xFF (jeweils mit einer kurzen Pause dazwischen, um dem Arduino Zeit zu geben, zu antworten) bis der Arduino im Antwort gibt, dass er bereit ist. Danach schickt er die Anzahl Bytes, die übertragen werden sollen und dann den Inhalt, ein Byte nach dem anderen. Auf dem Arduino würde ich dann die Bytes erst mal entgegennehmen und nicht gleich speichern, da das Abspeichern im EEPROM relativ langsam ist. Wenn das ganze Paket angekommen ist, speicherst Du es in einem Stück.
hiks:
Das mit dem Serial.flush hab ich irgendwo in einem Beispiel gesehen
Ich verstehe den Vorschlag mit der Startsequenz nicht so ganz.
Benutze ich denn die serielle Übertragung so ungewöhnlich? Ich dachte, dass dies oft so gemacht wird.
Also Daten schicken und nacheinander in EEPROM-Adressen abspeichern.
Danke erstmal für eure Hilfe.
serial.flush() leerte in den IDE vor 1.0.0 den Eingangsspeicher. Darum hast Du praktisch die Übertragenen Zeichen nach dem ersten gelöscht. Das geschieht aber willkührlich da Du kein Timing verwendest und in einer Übertragung es passieren kann daß im Moment des flush noch Daten unterwegs sind und darum einige Zeichen mittendrin verloren gehen nicht aber das Ende.
In der IDE Version ab 1.0.0 Wartet Arduino bis die Daten die im Ausgangsspeicher sind gesendet wurden
Waits for the transmission of outgoing serial data to complete. (Prior to Arduino 1.0, this instead removed any buffered incoming serial data.) .
Beide möglichen Funktionen von flush sid für Deinen Sketch falsch.
Ich habe mir die ganze Problematik nochmal durch den Kopf gehen lassen ( natürlich zusammen mit euren tollen Tipps ) und bin zu einer vorläufigen Lösung gekommen. Vorläufig deswegen, da das ganze später auch im laufenden Betrieb funktionieren soll ( das Beschreiben und Auslesen des EEPROMs ist ja nur eine kleine "Nebenfunktion" des Hauptprogramms ).
Die Lösung war letztendlich: "Nicht soviel nachdenken; einfach machen!" XD
HIER DER C# - CODE:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Collections;
using System.Timers;
namespace Alarmanlage_test1
{
public partial class Form1 : Form
{
bool[] einstellungen = new bool[8];
bool[] einstellungen1 = new bool[8];
byte[] DTS = new byte[2]; // DataToSend : 1-Byte-Array, welches gesendet wird (Bitmaske für die Einstellungen)
char DSA = 'a'; // DataSendACK : GUI ist bereit zum Empfang der Daten
char DRA = 'd'; // DataReceivedACK : Die externen Daten wurden erfolgreich übernommen
string eingang;
public Form1()
{
InitializeComponent();
button3.Text = "disconnected";
button3.Enabled = false;
}
// Verbindungsaufbau
private void button1_Click(object sender, EventArgs e)
{
serialPort1.PortName = "COM" + numericUpDown1.Value.ToString();
serialPort1.BaudRate = 9600;
serialPort1.DataBits = 8;
serialPort1.Open();
button1.Text = "connected";
button1.Enabled = false;
button3.Text = "disconnect";
button3.Enabled = true;
eingang = "";
}
// SENDEN
private void button2_Click(object sender, EventArgs e)
{
//--------------------------------------------------------------
//-------------- EINSTELLUNGEN -------------------------------
//--------------------------------------------------------------
if (checkBox1.Checked == true) einstellungen[0] = true;
else einstellungen[0] = false;
if (checkBox2.Checked == true) einstellungen[1] = true;
else einstellungen[1] = false;
if (checkBox3.Checked == true) einstellungen[2] = true;
else einstellungen[2] = false;
if (checkBox4.Checked == true) einstellungen[3] = true;
else einstellungen[3] = false;
if (checkBox5.Checked == true) einstellungen[4] = true;
else einstellungen[4] = false;
if (checkBox6.Checked == true) einstellungen[5] = true;
else einstellungen[5] = false;
if (checkBox7.Checked == true) einstellungen[6] = true;
else einstellungen[6] = false;
if (checkBox8.Checked == true) einstellungen[7] = true;
else einstellungen[7] = false;
//--------------------------------------------------------------
//-------------- EINSTELLUNGEN 2 -------------------------------
//--------------------------------------------------------------
if (checkBox9.Checked == true) einstellungen1[0] = true;
else einstellungen1[0] = false;
if (checkBox10.Checked == true) einstellungen1[1] = true;
else einstellungen1[1] = false;
if (checkBox11.Checked == true) einstellungen1[2] = true;
else einstellungen1[2] = false;
if (checkBox12.Checked == true) einstellungen1[3] = true;
else einstellungen1[3] = false;
if (checkBox13.Checked == true) einstellungen1[4] = true;
else einstellungen1[4] = false;
if (checkBox14.Checked == true) einstellungen1[5] = true;
else einstellungen1[5] = false;
if (checkBox15.Checked == true) einstellungen1[6] = true;
else einstellungen1[6] = false;
if (checkBox16.Checked == true) einstellungen1[7] = true;
else einstellungen1[7] = false;
//--------------------------------------------------------------
new BitArray(einstellungen).CopyTo(DTS, 0);
new BitArray(einstellungen1).CopyTo(DTS, 1);
while (true)
{
textBox2.Text += serialPort1.ReadLine() + "\n";
}
}
private void button3_Click(object sender, EventArgs e) // Verbindung unterbrechen
{
serialPort1.Close();
button1.Enabled = true;
button1.Text = "connect";
button3.Text = "disconnected";
button3.Enabled = false;
}
}
}
Hiermit baue ich mir, wie gehabt, aus zwei 8-Bit-Arrays EIN 2-Byte-Array. Danach schicke ich es dem Arduino. "Ob er will und nich!" Die while(true) - Schleife ist natürlich nur zum Debuggen.
HIER DER Arduino - CODE:
#include <EEPROM.h>
char DSA = 'a'; // DataSendACK : GUI ist bereit zum Empfang der Daten
char DRA = 'd'; // DataReceivedACK : Die externen Daten wurden erfolgreich übernommen
char* stat[]={" an", " aus"};
int eingang[1]; // zum Einlesen der gesendeten Requests
int eingang2[1]; // zum Einlesen der gesendeten Requests
char eprom, eprom1;
void setup()
{
Serial.begin(9600); //öffnet die serielle Datenübertragung mit 9600 bit/s
}
void loop()
{
if ( Serial.available() > 0 )
{
eingang[0] = Serial.read();
eingang2[0] = Serial.read();
EEPROM.write(0, eingang[0]);
delay(10);
EEPROM.write(1, eingang2[0]);
delay(10);
}
Serial.println(" ");
Serial.print("eingang 1: ");
Serial.println(EEPROM.read(0), DEC);
Serial.println(" ");
Serial.print("eingang 2: ");
Serial.println(EEPROM.read(1), DEC);
delay(2000);
}
Jetzt kann ich gemütlich meine einzelnen IOs mit den Bitoperatoren ( also if(EEPROM.read(1) & 2) dann Eingang zwei aktiv, usw.) aktivieren und deaktivieren.
Für den laufenden Betrieb werde ich wahrscheinlich die Funktion serialEvent() verwenden müssen.
Also freut euch auf neue, spannende Fragen von mir!
Der delegate ist hier nötig weil es sonst zu einem Thread-übergreifenden Zugriff kommt und du zur Runtime eine exception bekommst. Durch Invoke(), bzw. BeginInvoke() wird die Methode auf dem GUI-Thread ausgeführt.
Da hatte ich mich schonmal durchgekämpft ( gelesen ). Damit könnte ich doch dann die ganze Zeit gesendete Daten in einer Textbox ausgeben?! Sozusagen "nebenbei" ? Die ganze Sache mit den Threads und C# ist für mich noch neu.
Aber "static" darf die Funktion nicht sein, oder? Dann kennt sie meine textbox2 und meinen serialport1 nicht mehr.
Ja, der Event Handler wird immer aufgerufen wenn Daten im Empfangspuffer sind. Ohne irgendwas zu blockieren.
Das InvokeRequired braucht man nicht unbedingt, da man eigentlich davon ausgehen dass man auf einem anderen Thread ist und der delegate in diesem Fall auch nicht merkbar langsamer wird. Aber das ist die Musterlösung.
EDIT:
Wegen dem static. Ja, Tschuldigung. In dem MSDN Beispiel ist das static weil das eine Konsolen-Anwendung ist. Das hatte ich übersehen und dann dummerweise hinzugefügt.
Hast du den EventHandler auch beim Serial Objekt angemeldet?
Dafür ist die Zeile:
serialPort1.DataReceived += new SerialDataReceivedEventHandler(DataReceived);
Oder da du anscheinend die SerialPort Komponente auf dem GUI hast statt das Objekt per Hand zu erzeugen, kannst du auch in die Properties der Komponente gehen, dann auf Events (das orange Blitz Symbol) und dort doppelt auf DataReceived klicken, bzw. den Namen des EventHandlers reinschreiben.
Rein technisch ist das ein Observer Pattern:
Deshalb muss man sagen auf welche Events man reagieren will. Nur die Event Handler Methode zu schreiben reicht nicht. Bei deinen ButtonClick EventHandler wird der Code dafür lediglich automatisch im Hintergrund erzeugt.
EDIT:
Ich habe gerade gemerkt, dass ReadLine() hier doch besser ist als ReadExisting(). Letzteres fügt willkürlich Line Feeds ein, oder scheint diese nicht korrekt aus dem Puffer zu entfernen. Die Methode ist glaube ich eher für Binärdaten als Text gedacht.
Wenn ich nämlich im falschen Moment die serielle Verbindung mit serialPort.Close() beende, bekomme ich folgende Fehlermeldung:
Eine nicht behandelte Ausnahme des Typs "System.IO.IOException" ist in System.dll aufgetreten.
Zusätzliche Informationen: Der E/A-Vorgang wurde wegen eines Threadendes oder einer Anwendungsanforderung abgebrochen.
"Anwendungsanforderung" hört sich danach an, dass die DataReceived-Funktion gerade auf SerialDataReceivedEventArgs e zugreifen will und der Port schon geschlossen ist?!
Wie kann ich das verhindern? Habe noch keine Lösung gefunden...
Dann fange die Exception mit einem try/catch-Block, was sich bei I/O Operationen generell anbietet:
Ob du den Fehler dann irgendwo anzeigst oder nichts machst bleibt dir überlassen. Aber das Programm läuft so oder so weiter. Du kannst z.B. in der Textbox einen Meldung anzeigen.
Hier würdest du ein try/catch um serial.Close() machen:
try
{
serial.Close();
}
catch
{
}
Hier werden alle Exceptions gefangen. Man muss den Typ der Exception nicht unbedingt angeben. Du kannst auch sicherheitshalber im Eventhandler ein try-catch um das Auslesen machen.
Wir haben mal wieder ein Problem. Ich hatte zum Testen einen Arduino Uno auf dem nur der Code für die serielle Übertragung drauf war. Da hat jetzt alles funktioniert (alle Daten wurden übernommen).
Nun haben wir diesen Code in unseren Mega 2560 eingebunden und es werden keine Daten mehr übernommen.
C#:
private void btn_send_Click(object sender, EventArgs e)
{
//--------------------------------------------------------------
//-------------- Werte für die Eingänge im BitArray einstellungen 0-7 speichern-------------------------------
//--------------------------------------------------------------
for ( i = 0; i <= 7; i++ )
{
einstellungen[i] = cb_in.GetItemChecked(i);
}
//--------------------------------------------------------------
//-------------- Werte für die Ausgänge im BitArray einstellungen 8-15 speichern -------------------------------
//--------------------------------------------------------------
for ( j = 8, i = 0; j <= 15 && i <=7; j++, i++ )
{
einstellungen[j] = cb_out.GetItemChecked(i);
}
//--------------------------------------------------------------
//-------------- BitArray einstellungen in 2-ByteArray DTS umwandeln-------------------------------
//--------------------------------------------------------------
new BitArray(einstellungen).CopyTo(DTS, 0);
//--------------------------------------------------------------
//-------------- Vorwahl und Telefonnr. zusammensetzen-------------------------------
//--------------------------------------------------------------
//TELNR = Convert.ToInt32(tb_vorwahl.Text) + Convert.ToInt32(tb_nummer.Text);
//PIN = Convert.ToInt32(tb_pin.Text);
TELNR = tb_vorwahl.Text + tb_nummer.Text;
TELNR2 = tb_vorwahl2.Text + tb_nummer2.Text;
PIN = tb_pin.Text;
//--------------------------------------------------------------
//-------------- DTS, TELNR und PIN an Arduino über den SerialPort senden-------------------------------
//--------------------------------------------------------------
s_arduino.DiscardInBuffer();
s_arduino.DiscardOutBuffer();
try
{ // Einstellungen
s_arduino.Write("2"); // DEBUG: 2 = an, sonst aus
s_arduino.Write(DTS, 0, 2);
if (tb_pin.TextLength==0)
PIN = "0000";
//s_arduino.Write(PIN);
if (PIN.Length < 4)
{
dif = (4 - PIN.Length);
for (i = 1; i <= dif; i++)
PIN += " ";
//s_arduino.Write(PIN);
}
s_arduino.Write(PIN);
// HANDYNUMMER
if (String.IsNullOrEmpty(TELNR))
TELNR = "000000000000";
//s_arduino.Write(TELNR);
if (TELNR.Length < 12)
{
dif = (12 - TELNR.Length);
for (i = 1; i <= dif; i++)
TELNR += " ";
//s_arduino.Write(TELNR);
}
s_arduino.Write(TELNR);
// FESTNETZNUMMER
if (String.IsNullOrEmpty(TELNR2))
TELNR2 = "0000000000000000";
if (TELNR2.Length < 16)
{
dif = (16 - TELNR2.Length);
for (i = 1; i <= dif; i++)
TELNR2 += " ";
}
s_arduino.Write(TELNR2);
// UHRZEIT
s_arduino.Write(Uhrzeit);
}
catch
{
lbl_warn.Text = "Die Einstellungen konnten nicht übernommen werden! Bitte versuchen Sie es erneut!";
}
//while (s_arduino.BytesToRead == 0)
//{
// Application.DoEvents();
//}
textBox2.Text = Uhrzeit;
textBox2.Text += Uhrzeit.Length;
}
Arduino Sketch ( nur die Übertragung + Debug ):
/******************************************************************************/
/* SERIELLE DATEN ÜBERNEHMEN */
/******************************************************************************/
if ( Serial.available() > 0 )
{
REQ[0] = Serial.read();
eingang[0] = Serial.read();
eingang[1] = Serial.read();
for(int n = 0; n <= 3; n++)
PIN[n] = Serial.read();
for(int n = 0; n <= 11; n++)
TELNR[n] = Serial.read();
for(int n = 0; n <= 15; n++)
TELNR2[n] = Serial.read();
for(int n = 0; n <= 19; n++)
UHRZEIT[n] = Serial.read();
EEPROM.write(0, eingang[0]);
delay(10);
EEPROM.write(1, eingang[1]);
delay(10);
for ( int i=0; i<=1; i++ )
tag[i] = UHRZEIT[i];
for ( int i=0; i<=1; i++ )
monat[i] = UHRZEIT[i+3];
for ( int i=0; i<=3; i++ )
jahr[i] = UHRZEIT[i+6];
for ( int i=0; i<=3; i++ )
stunde[i] = UHRZEIT[i+11];
for ( int i=0; i<=3; i++ )
minute[i] = UHRZEIT[i+14];
for ( int i=0; i<=3; i++ )
sekunde[i] = UHRZEIT[i+15];
anforderung = atoi(REQ);
}
/******************************************************************************/
/* DEBUG SERIAL */
/******************************************************************************/
if ( anforderung == 2 )
{
for ( int i = 0; i <= 7; i++)
{
Serial.print("Eingang ");
Serial.print(i+1);
Serial.print(": ");
if((EEPROM.read(0) & (1<<i)))
{
Serial.println(stat[0]); // " an"
}
else Serial.println(stat[1]); // " aus"
}
Serial.println("");
for ( int i = 0; i <= 7; i++)
{
Serial.print("Ausgang ");
Serial.print(i+1);
Serial.print(": ");
if((EEPROM.read(1) & (1<<i)))
{
Serial.println(stat[0]); // " an"
}
else Serial.println(stat[1]); // " aus"
}
Serial.println("");
Serial.print("PIN: ");
for ( int i = 0; i <= 3; i++)
{
Serial.print(PIN[i]);
}
Serial.println("\n");
Serial.print("TELNR: ");
for ( int i = 0; i <= 11; i++)
{
Serial.print(TELNR[i]);
}
Serial.println("\n");
Serial.print("TELNR2: ");
for ( int i = 0; i <= 15; i++)
{
Serial.print(TELNR2[i]);
}
Serial.println("\n");
Serial.print("UHRZEIT: ");
for ( int i = 0; i <= 18; i++)
{
Serial.print(UHRZEIT[i]);
}
Serial.println(" ");
Serial.print("Tag: ");
Serial.println(atoi(tag));
Serial.print("Monat: ");
Serial.println(atoi(monat));
Serial.print("Jahr: ");
Serial.println(atoi(jahr));
Serial.print("Stunde: ");
Serial.println(atoi(stunde));
Serial.print("Minute: ");
Serial.println(atoi(minute));
Serial.print("Sekunde: ");
Serial.println(atoi(sekunde));
Serial.print("TEST_Leerzeichen: ");
Serial.println(testleer);
anforderung = 0;
}
Ich kann mir nicht erklären, warum es bei dem Uno funktioniert und beim 2560 nicht. Wenn der "kleine" Sketch auf dem 2560 ist dann funktionierts auch. Oder kommt die Übertragung irgendwie durcheinander weil das Programm so groß ist?
Wir bitten dringend um Hilfe!