Geeignete Literatur gesucht zu Arduino(µC)/C#

Hallo,

bin jetzt ein paar Tage damit beschäftigt, eine C# Form zu schreiben. Einige Sachen gehen bereits auch ohne Bücher und Tutorials recht gut. Doch reicht mein Kenntnisstand teils doch nicht aus.

Kennt jemand ein geeignetes Buch, welches er empfehlen kann? Mich interessiert kein allgemeines Buch über C#, bei dem mein eigentlicher Einsatzzweck viel zu kurz kommt.

Mir geht es hauptsächlich um das Visualisierung und Steuern von Mikrokontrollern. Das Steuern ist hierbei eher weniger das Problem.

Habe jedoch Probleme mit dem Empfangen von Strings, folgender Sketch funktioniert bis auf das empfangen von Strings. Ob das nun guter Programmierstil ist, wage ich zu bezweifeln, da einen großteil VS selber anlegt.

Das Problem hier liegt in der letzen Funktion, diese führt zu einem Abbruch!

Ein Ausnahmefehler des Typs "System.InvalidOperationException" ist in System.Windows.Forms.dll aufgetreten.
Zusätzliche Informationen: Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement label1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
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;          // Serial Port

namespace WindowsFormsApplication4
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            listBox1.Items.Clear();
            foreach (string item in SerialPort.GetPortNames())
            {
                listBox1.Items.Add(item);
            }
        }
        private void setEnableInputs()
        {
            if (serialPort1.IsOpen)
            {
                listBox1.Enabled = false;
                trackBar1.Enabled = true;
                trackBar2.Enabled = true;
                trackBar3.Enabled = true;
                trackBar4.Enabled = true;
                trackBar5.Enabled = true;
                trackBar5.Enabled = true;
                trackBar6.Enabled = true;
                trackBar7.Enabled = true;
                trackBar8.Enabled = true;
                buttonConnect.Enabled = false;
                buttonDisconnect.Enabled = true;
            }
            else
            {
                listBox1.Enabled = true;
                trackBar1.Enabled = false;
                trackBar2.Enabled = false;
                trackBar3.Enabled = false;
                trackBar4.Enabled = false;
                trackBar5.Enabled = false;
                trackBar5.Enabled = false;
                trackBar6.Enabled = false;
                trackBar7.Enabled = false;
                trackBar8.Enabled = false;
                buttonConnect.Enabled = true;
                buttonDisconnect.Enabled = false;
            }
        }

        private void sendData(int id, int value)
        {
            serialPort1.WriteLine(Convert.ToString(value + (id * 256)));
            labelSendData.Text = Convert.ToString(value + (id * 256), 16);
        }

        private void buttonConnect_Click(object sender, EventArgs e)
        {
            serialPort1.PortName = listBox1.Text;
            serialPort1.BaudRate = 115200;
            serialPort1.Open();
            setEnableInputs();
        }

        private void buttonDisconnect_Click(object sender, EventArgs e)
        {
            serialPort1.Close();
            setEnableInputs();
        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            int id = 0;
            int value = trackBar1.Value;
            sendData(id, value);
        }

        private void trackBar2_Scroll(object sender, EventArgs e)
        {
            int id = 1;
            int value = trackBar2.Value;
            sendData(id, value);
        }

        private void trackBar3_Scroll(object sender, EventArgs e)
        {
            int id = 2;
            int value = trackBar3.Value;
            sendData(id, value);
        }

        private void trackBar4_Scroll(object sender, EventArgs e)
        {
            int id = 3;
            int value = trackBar4.Value;
            sendData(id, value);
        }

        private void trackBar5_Scroll(object sender, EventArgs e)
        {
            int id = 4;
            int value = trackBar5.Value;
            sendData(id, value);
        }

        private void trackBar6_Scroll(object sender, EventArgs e)
        {
            int id = 5;
            int value = trackBar6.Value;
            sendData(id, value);
        }

        private void trackBar7_Scroll(object sender, EventArgs e)
        {
            int id = 6;
            int value = trackBar7.Value;
            sendData(id, value);
        }

        private void trackBar8_Scroll(object sender, EventArgs e)
        {
            int id = 7;
            int value = trackBar1.Value;
            sendData(id, value);
        }

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            label1.Text = serialPort1.ReadLine();
        }
    }
}

Screenshot 2014-03-11 17.39.34.png

Das ist ein Multi-Threading Problem. Du kannst nicht aus anderen Threads auf das GUI Thread zugreifen. Dafür gibt es Invoke() und BeginInvoke() um synchron, bzw. asynchron einen thread-sicheren Zugriff zu garantieren.

Am einfachsten geht das über einen anynomen delegate (hier für eine RichTextBox namens communicationOutput):

      public void TextBoxPrint(string text)
      {
         communicationOutput.BeginInvoke((MethodInvoker)delegate
         {
            communicationOutput.AppendText(text);
         });
      }

Die Methode rufst du dann im EventHandler auf und gut ist :slight_smile:

Der Zugriff auf das Steuerelement label1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

Da versuchen sie tatsächlich, verständliche Fehlermeldungen zu erzeugen, sagen aber nicht, dass das Stichwort "delegate" heisst.
Alternativ: "Geht nicht, von einem anderen Thread aus direkt den Text eines Labels zu ändern"
Mit google ( bei Microsoft meinzwegen auch bing ) kommt man bei Problemen übrigens schneller voran als mit Büchern.

Nachtrag: mit diesem Forum hier und Serenifly gehts wohl noch schneller als mit google und bing :wink:

Ja, mit "C# [Problem]" bekommt man bei Google sehr viele Lösungen. Vor allem im StackOverflow Forum.

Wo ich mal länger suchen musste war als es darum ging Nodes in einer TreeView zu verschieben. Da hatte ich einiges gefunden das nicht funktioniert hat. Aber bei einfachem Zeug kommt man normalerweise schnell ans Ziel.

Hallo Serenfly/Michael,

mir gehts nicht hauptsächlich um die Lösung von Problemen. Was ich gerade mache ist mehr oder weniger Try&Error Methode. Mir fehlt noch das Grundlegende KnowHow in C#, welches ich gerne in Verbindung mit dem Mikrokontroller erlernen möchte.
So geht es leider noch nicht, hab mal das Projekt angehangen.

        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string text = serialPort1.ReadLine();
            TextBoxPrint(text);
        }

So gehts nichts.
        public void TextBoxPrint(string text)
        {
            communicationOutput.BeginInvoke((MethodInvoker)delegate
            {
                communicationOutput.AppendText(text);
            });
        }

WindowsFormsApplication4.zip (58.7 KB)

Du musst das schon für dich anpassen. Das war wie gesagt für eine RichTextBox die du nicht hast.

So sollte es bei dir eher passen:

        public void TextPrint(string text)
        {
            label1.BeginInvoke((MethodInvoker)delegate
            {
                label1.Text = text;
            });
        }

Hab eine richTextBox dafür angelegt, mit dem von dir vorgeschlagenen Namen, nur zum testen halt.

Edit: Scheinbar hat VS, "Alles Speichern" nicht gereicht, in der Zip ist die Rtb nicht drin,
bekomme den String nun zumindest schon einmal angezeigt :wink:

Ich habe VS2010. Daher kann ich deine VS2013 Solution nicht starten.
Den Code kann ich aber ansehen. Sollte eigentlich gehen :slight_smile:

Ob es C# Bücher speziell in Verbindung mit µC Anwendungen gibt weiß ich nicht. Aber so µC spezifisch ist das auch wieder nicht. Letztlich ist das auch nur Datenübertragung in einem bestimmten Format.

Serenifly:
Ich habe VS2010. Daher kann ich deine VS2013 Solution nicht starten.
Den Code kann ich aber ansehen. Sollte eigentlich gehen :slight_smile:

Ob es C# Bücher speziell in Verbindung mit µC Anwendungen gibt weiß ich nicht. Aber so µC spezifisch ist das auch wieder nicht. Letztlich ist das auch nur Datenübertragung in einem bestimmten Format.

Ja mir gehts hier hauptsächlich um die Verarbeitung der Seriellen Schnittstelle. Bedingt funktioniert meine Sache bereits, aber halt noch nicht so wie gewünscht. Werde mich mal ein bisschen bei Galileo OpenBook einlesen.

Habe mal beide Hauptteile angehängt. In der Form möchte ich über Aktualisieren den aktuellen Status erhalten. Das geht etwas träge. Dauert schnell mal ein paar Sekunden bis überhaupt was ankommt. Wenn ich Modus auf 4 und zB. Saettigung hochstelle, wird Modus im Arduino einfach auf 0 gesetzt.

Sollte ich die Abfrage anders gestalten im Arduino um diese Fehler, falls die davon kommen, zu unterdrücken.
StartBuchstabe + Wert + Endbuchstaben

Beispiel:

A255E (A - Erste ID (Modus), 255 - Wert, E - Ende)
B255E (B - Zweite ID (Farbton), ....)

pc.cs (4.53 KB)

arduino.ino (9.14 KB)

Ja, das kann man mal probieren. Ob es besser wird, kann ich so aber auch nicht sagen.

Du kannst die Daten auch mit einem abschließenden LF zu senden. Dann liest du einen ganzen String bis zum LF ein und machst das Parsen erst wenn alles da ist. Dadurch kannst du aber auch einfach Kommandos mit unterschiedlicher Länge implementieren. Je nach dem was im ersten Byte steht, weißt du auf dem Arduino was dahinter erwartet wird.

Ich mit C# mal eine Kommunikation mit einem Lego NXT implementiert. Der hat einen Prozessor in der Leistungsklasse des SAM3 auf dem Due. Über Bluetooth konnte ich da tausende Bytes praktisch sofort übertragen. Da habe ich nur das erste Byte als Kommando Byte genommen. Die nächsten zwei Bytes für die Länge. Und der Rest Daten. Es gab nie Fehler also habe ich auf Fehlerbehandlung/Handshaking, etc. verzichtet.

Serenifly:
Ja, das kann man mal probieren. Ob es besser wird, kann ich so aber auch nicht sagen.

Du kannst die Daten auch mit einem abschließenden LF zu senden. Dann liest du einen ganzen String bis zum LF ein und machst das Parsen erst wenn alles da ist. Dadurch kannst du aber auch einfach Kommandos mit unterschiedlicher Länge implementieren. Je nach dem was im ersten Byte steht, weißt du auf dem Arduino was dahinter erwartet wird.

Hab die Überprüfung bei mir bereits drin in einer ähnlichen Form. Ich frage ab, ob das einzelne Char '0' - '9' entspricht, andernfalls setzt er die Zeichenfolge zurück. Soll heißen, jedes Zeichen was dem nicht enspricht (eingeschlossen LF und CR).

Die Zeichenkette, bei mir raw genannt, wird dann auf 0 gesetzt. 0 entspricht aber nicht '0', sodass er auch bei einem weiteren anstehenden Zeichen nichts machen dürfte. Wobei sehe gerade, er könnte theoretisch doch wieder einsteigen.
Reicht es, wenn ich raw als signed deklariere und ihm den Wert - 1 gebe? Oder ist das unnschön?

Denn ich habe gerade getestet, das einzige Problem, dass er aus geht, ist das das eine 0 durchkommt.

	if (Serial.available()) {
			unsigned char inChar = Serial.read();
			static unsigned int raw;
			if (inChar >= '0' && inChar <= '9') {
				raw = (raw * 10) + (inChar - '0');
			}
			else {
				if (raw >= 0 && raw <= 2047) {
					int id = raw / 256;
					int value = raw - (id * 256);

					switch (id) {
					// Setzte value
					}
				}
				raw = 0;
			}
		}

Kann man machen. Bietet sich hier natürlich an, da ASCII nur 128 Zeichen hat

Serial.read() liefert ja auch -1 wenn nichts da ist (was bei dir durch das available() nie vorkommt)

Bin ein bisschen am rumprobieren,
Kann mir jemand erklären, warum es so nicht geht? Weder mit CR noch mit NL noch mit CR+NL
3 Stellige Zahlen werden nur selten aufgenommen. Meistens wird die letzte Stelle, egal ob 0 oder eine andere Zahl abgeschnitten.

		if (Serial.available()) {
			char inChar = Serial.read();
			if (index < MaxChars && inChar >= '0' && inChar <= '9'){
				strValue[index++] = inChar;		// ASCII-Zeichen hinzufuegen
			}
			else {
				strValue[index] = 0;
				printf("%i\t", atoi(strValue));
				printf("%i\n", index);
				index = 0;
				Serial.flush();					// eigentlich nicht nötig
			}
		}

Sollte eigentlich gehen. So mache ich es auch.

Es kann sein dass du bei maxChars mit dem Terminator durcheinander kommst. Ist das die Länge des Arrays oder die Anzahl der Zeichen? Am Besten du gibts da die Länge des Arrays an und fragst auf < length/size - 1 ab. Dann bist du sicher, dass noch Platz für den Terminator am Ende ist.

Das die anzahl der Zeichen des Integers. Die Länge des Arrays ist maxChars+1, da hier noch die 0 in den String muss.

In Putty geht es, warum nun im Serial Monitor nicht?? Habe da immer wieder mal lasten, dass das ganze testen erst wunderbar mit der einen Console klappt und dann in einer anderen überhaupt nicht zuverlässigt läuft.

Nochmal Edit: Putty arbeitet hier scheinbar auch nicht sonderlich zuverlässig. Es größenteils immer das Digit abgeschnitten :((

Also ich mache es so:

const int SERIAL_BUFFER_SIZE = 31;
char stringBuffer[STRING_BUFFER_SIZE];

bool checkSerial()
{
	static byte index;

	if(Serial.available() > 0)
	{		
		char c = Serial.read();
		
		if(c != '\n' && index < SERIAL_BUFFER_SIZE - 1)
		{
			serialBuffer[index++] = c;
		}
		else
		{
			serialBuffer[index] = '\0';
			index = 0;
			return true;
		}
	}
	return false;
}

Im Moment ist das aber nur Test/Spielerei. Da ist noch nichts mit Parsing drin. Ich gebe den Text einfach so wie er ist auf dem TFT aus, aber das geht gut und ohne Verzögerung.

Habe mal meine Serial Klasse angehängt. Ist aber vom Prinzip her auch nicht anders als bei dir. Das ist von einer Klasse namens Communicator abgeleitet, die nur leere virtuelle Methoden enthält, da ich später vielleicht will, dass das genauso auch mit TCP oder UDP geht. Da kann ich dann einfach eine andere Klasse instantiieren und es geht mit den gleichen Methoden und Buttons.

Das heißt ich kann dann das machen:

private Communicator _communicator;

public MainForm()
{
  _communicator = new SerialCommunicator(this);
}

Was noch fehlt ist vielleicht ein kurzes Handshaking ob die Verbindung wirklich richtig ist, also richtige Baudrate und Comport. So sagt er immer, dass die Verbindung da ist, auch wenn das gar nicht wirklich der Fall ist. Das ist etwas doof.

Communicator.cs (438 Bytes)

SerialCommunicator.cs (2.09 KB)

Hallo Serenfly, hast du dein Code-Snippet getestet?

Du schreibst einmal SERIAL_BUFFER_SIZE und einmal STRING_BUFFER_SIZE.

Leider bekomme ich bei deiner Möglichkeit ebenfalls die gleichen Probleme. Vom Prinzip her arbeitet diese ja ähnlich wie bei mir. Ob ich ungleich '\n' Abfrage spielt ansich keine Rolle, das landet ebenfalls wie bei dir dann in dem else Zweig.

Aber auch hier werden immer mal wieder gern abgeschnitten. Nur immer das letzte Zeichen.
An meinem printf_P kann es nicht liegen, da ich es auch gerade einmal normal weg mit Serial.print versucht habe.

//[...]
const int STRING_BUFFER_SIZE = 31;
char serialBuffer[STRING_BUFFER_SIZE];
//[...]
int main(void) {
//[...]
	while (1) {
//[...]
		if (getSerialInput()) printf_P(PSTR("%s"), serialBuffer);
//[...]
	}
}
//[...]
bool getSerialInput(void) {
	static int index;

	if (Serial.available() > 0) {
		char c = Serial.read();
		if (c != '\n' && index < SERIAL_BUFFER_SIZE - 1) {
			serialBuffer[index++] = c;
		}
		else {
			serialBuffer[index] = '\0';
			index = 0;
			return true;
		}
		return false;
	}
}

Oops. Copy/Paste Fehler. Habe das falsche Array ins Post kopiert :slight_smile:

Ja, was da nicht geht ist mir nicht so klar. Vom Prinzip her ist es genau gleich :frowning:

EDIT:
Gerade auch mit mit dem Serial Monitor probiert. Da geht es auch. Ich verwende im Moment 19200 Baud. Ist aber USB und kein BT

Störquellen kann ich eigentlich soweit ausschließen. Uno ist nur mit dem BT Modul an Rx und Tx verbunden und wird über ISP programmiert(ohne 5V Leitung). Als Spannungsversorgung dient ein StepDownWandler, der nicht in unmittelbarernähe zum BT Modul ist. Die Verbindung zwischen PC und Mikrokontroller beträgt weniger als einen halben Meter. Habe richtige Standantennen für Bluetooth am PC (Bluetooth onboard).

Ich habe hier noch 3 unbenutzte BT Module und ein gebrauchtes rumliegen. Werden morgen mal schaun, ob es mit der Baud 115200 zusammenhängt. Werde mal langsam runtergehen und beobachten ob Fehler weiterhinbestehen.

Habe neues BT Modul eingerichtet mit Baud 57600, Problem tritt aber auch hier auf