Arduino mit c#

Tag Leute,

ich arbeite mich gerade in mein Arduino Mega ein. Ich kann damit auch schon allerhand umsetzen, dennoch bin ich was Mikrocontroller angeht noch nen blutiger Anfänger.
Ich arbeite als Hobby gerade an einer Steuerung für meine Wasserkühlung.
Mit der Programmierung des Board habe ich keine Probleme. Das klappt alles wie es soll mit den Interrupts usw.

Das Problem ist, dass ich gerne die Werte der Tachosignale, sprich die RPM, die ich errechnet habe, visualisieren möchte. Darüber hinaus möchte das ganze auch standalone regeln, was mich zu meinem eigentlichen Problem führt.

Momentan arbeite ich an der Steuerung über C#. Nach etlichen Tutorials bekomm ich auch schon die Kommunikation hin, so dass ich über das C# Programm einen digitalen Ausgang ansteuern kann... ich weiß, dass ist noch nicht so berauschend aber ein Anfang :wink:

Mein Problem ist jetzt, dass ich gerne auch Werte vom Board an C# übergeben will und eben nicht nur einen. Andersrum möchte ich auch Werte vom C# Programm an das Board übergeben.
Explizit meine ich damit beispielsweise: Ich habe im C# Programm einen Regler, mit dem ich die Drehzahl eines Lüfters zwischen 50 und 100% verstellen kann. Diesen "Wert" muss ich also an das Board übergeben. Zusätzlich aber eben auch Pumpendrehzahl, Leds an/aus usw. usw. Außerdem greife ich eben die Drehzahl des Lüfters mit nem Interrupt ab und will die an C#übergeben.

Genau an dieser Kommunikation hänge ich. Ich hatte gehofft, dass ich das mit dem Beispiel der LED ansteuern über C# in den Griff bekomme. Jedoch wird in dem Beispiel lediglich ein String übergeben, den ich dann am Board auswerte. Es wird also "Ein" und "Aus" an das Board gesendet bzw. übertragen und das Board reagiert eben darauf.

Mit einem einzelnen Wert mag das ja wunderbar klappen, jedoch nicht mit mehreren und ich weiß jetzt eben nicht genau wie ich das bewerkstelligen soll.

Macht man das über ne Art Zeilenvektor? Ich glaub in C# nennt sich das Array, oder?
Ich hatte da an ne Art Startbyte gedacht, dass in C# erkannt wird, dass eine Folge von Werten eingelesen werden soll. Dann alle ins Array Speichern und die einzelnen Elemente direkt zuweisen.
Oder geht das auch einfacher?
Wie realisiere ich das am besten auf dem Weg von C# zum Board?

Kennt da vielleicht einer gute Anleitungen dazu?

Gruß
Jan

Arrays und Vektoren sind zwei verschiedene Dinge. Arrays haben eine feste Größe. Vektoren werden im Hintergrund mit Arrays implementiert, aber du kannst da die Größe dynamisch ändern und Element hinzufügen und entfernen. Kannst du machen, aber du kannst die Werte auch einzeln rausschicken. Bei der geringen Datenmenge spielt das keine Rolle.

Du bist da schon auf dem Richtigen Weg. Sende z.B. ASCII 'S' an den Controller, der wartet darauf, schickt seine Daten und in C# kannst du Daten dann im Serial Eventhandler lesen. Oder umgekehrt.

Du kannst z.B. für die Einstellungen zwei Byte schicken. Das erste gibt dir an was drin ist und das zweite den Wert. Also immer eine Kombination aus Steuerzeichen und Daten. Wenn du es wirklich zuverlässig willst, kannst du dann eine Bestätigung zurückschicken (z.B. das ACK(nowledge) Zeichen). Wenn du immer alle Einstellungen auf einmal schickst, brauchst du natürlich nicht jedes einzeln zu markieren, sondern schickt nur einmal am Anfang ein Zeichen, damit er Arduino weiß was er mit den Daten machen muss.

Hört sich sinnvoll an, danke schon mal!

Von der Theorie habe ich das hoffentlich verstanden... Seh ich das jetzt richtig, dass ich z.B. ein S voran Sende und dahinter den Wert, damit ich das in c# entsprechend auswerten kann? Das S würde dann bedeuten, zu welcher Variable der Wert quasi zugewiesen werden würde?

Gibts dazu irgendwie irgendwelche Programmierungsbeispiele wie man das in c# dann auswertet?
Ich habe mich für diese Programmierung extra in C# einarbeiten müssen und tue mich damit noch etwas schwer.
Meine Kenntnisse in C# sind leider nicht berauschend, ich habe lediglich Grundkenntnisse in C und arbeite sonst eigentlich ausschließlich mit Matlab.
Deswegen bin ich für jedes Beispiel dankbar.

Gruß
Jan

arbeite sonst eigentlich ausschließlich mit Matlab

Hab gehört, "Matlab kann alles": Gibt es da keine seriellen Schnittstellen ?

Ist das was ? -> http://home.iitb.ac.in/~rahul./ITSP/serial_comm_matlab.pdf

Seh ich das jetzt richtig, dass ich z.B. ein S voran Sende und dahinter den Wert, damit ich das in c# entsprechend auswerten kann? Das S würde dann bedeuten, zu welcher Variable der Wert quasi zugewiesen werden würde?

Das ist ein Weg. Wenn es nur ein paar Werte sind kannst du auch immer alle auf einmal schicken. Sagen wir mal du hättest zwei Temperaturen und zwei Motordrehzahlen. Die Reihenfolge ist bekannt, so dass da darüber eine Zuordnung darüber hast zu den Sensoren hast. Du müsstest dann nicht unbedingt Temperaturen und Drehzahlen getrennt kennzeichnen. Du weißt z.B. das der 1. Wert Temp1 ist, der zweite Temp2, der 3. RPM1, usw.

Das kommt auch darauf an wie komplex deine Anwendung ist. In deinem Fall könntest du theoretisch auch einfach kontinuierlich Signale senden ohne dass du die explizit anforderst. Du müsstet dir aber auch da wahrscheinlich den Start eines Pakets irgendwie markieren. Wenn du halt nach dem Aufbauen der Verbinden vom PC aus erst mal ein Kommando an den Arduino schickst, und der erst darauf hin mit dem Senden anfängt hast du einen definierten Anfangspunkt. Danach kann der Arduino dauernd senden ohne dass du jedesmal Werte extra anforderst.

Dein Arduino kann das erste Byte auswerten und darauf entscheiden was gemacht wird. z.B. wenn es ein "S" ist (für Start) schickt er Daten. Wenn es ein "D" ist (für Drehzahl), dann weiß er das noch ein Byte kommt in dem der Wert ist den er einstellen muss. Das muss er dann auslesen und entsprechend Regeln. Über ein weiteres Byte kannst du den Motor adressieren.

C#/.NET hat eine sehr schöne Serial Klasse dafür.Da ist unten ein Beispiel dabei (wie bei allen Klassen auf MSDN):

Ich habe das mal mit C# und Java (Lego NXT) gemacht. Da sah das so aus (nur ein Teil. Das ganze Protokoll das ich da gebastelt habe um fast beliebige Datenmengen zu schicken erspare ich dir mal). Der erste Teil ist zwar von einer früheren Version aber es sollte mal funktioniert haben. Das ist etwas verschachtelt, da ich die Kommunikation da in einer eigenen Klasse habe (die Endversion hatte zwei Verbindungen gleichzeitig). Bei dir kann das erst mal in die Hauptklasse:

namespace NXT_GUI
{
	
	/// <summary>
	/// This class allows communication with a single Lego NXT brick.
	/// </summary>
	public class NxtCommunicator 
{

		/// <summary>
		/// Constructs a LEGO communicator with the specified portname
		/// </summary>
		/// <param name="portname"></param>
		public NxtCommunicator(string portname) 
               {
			_portName = portname;
		}
		private string _portName;

		#region Connection handling

		private SerialPort port = null;

		/// <summary>
		/// Connects to the COM port associated with this communicator
		/// </summary>
		public void Connect() 
               {
			port = new SerialPort(_portName);
			port.Open();
			IsConnected = true;
		}

		/// <summary>
		/// Disconnects the communicator class
		/// </summary>
		public void Disconnect() 
                {
			IsConnected = false;
			if(port.IsOpen) 
                        {
				port.Close();
				port.Dispose();
			}
			port = null;
		}

		
		/// <summary>
		/// Are we connected to the Lego NXT Brick?
		/// </summary>
		public bool IsConnected
                {
			get 
                        {
				return _isConnected;
			}
			set 
                        {
				_isConnected = value;
			}
		}
		bool _isConnected = false;

Die letzte Methode ist etwas, dass du wenn du neu bei C# bist gleich lernen solltest. Da hat man dann variablen praktisch als Felder und nicht mehr separate getter und setter Methoden. Man kann einfach "variable = wert" und "wert = variable" machen, statt "wert = getVariable()". Sieht wie eine Public Variable aus, ist es aber nicht. Wenn man z.B. den Setter weglässt markiert das Intellisense automatisch als Read-only und lässt dich nicht darauf schreiben.

Verfügbare Comports, die man in einer Listbox eintragen kann:

string[] ports = SerialPort.GetPortNames();

Connect auf einen Port, der in der Listbox ausgewählt wurde:

    private void connectButton_Click(object sender, EventArgs e)
    {
      string comPort = "";

      try
      {
        comPort = (string)comPortSelect.SelectedItem;
        _bluetooth = new NxtCommunicator(comPort);
        _bluetooth.Connect();
        WriteLine("Connection to port " + comPort + " established");
      }
      catch (Exception)
      {
        WriteLine("Failed to establish connection to " + comPort);
      }
    }

Schreiben geht dann einfach mit der Write Methode. Auf der untersten Ebene geht das mit Byte-Arrays. Das abstrahiert man dann halt z.B. und schreibt eine Methode der ein Integer Array (mit 2-Byte Int16) übergeben wird und die zerlegt das dann ein Byte-Array und schreibt es raus. Dann kann man ne Methode darüber schreiben, die bestimmte Werte annimmt, diesen ein Kommando voranstellt und sie an die Standard-Integer-Methode übergibt. Die Umwandlung geht mit der BitConverter Klasse:

Die SerialPort Klasse hat dann einen EventHandler der automatisch aufgerufen wird wenn Daten ankommen:

Den muss man einmal an den Port hängen:
_comPort.DataReceived += new SerialDataReceivedEventHandler(OnDataReceived);

Und kann ihn wieder entfernen:
_comPort.DataReceived -= new SerialDataReceivedEventHandler(OnDataReceived);

    public void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
      ....
    }

Hier ist z.B. was der Eventhandler letztendlich macht und wie er sich entscheidet was gemacht werden soll:

public void SerialPortDataReceived()
    {      
      //read header
      int headerLength = 6;
      byte[] header = new byte[headerLength];
      try
      {
        _comPort.Read(header, 0, headerLength);
      }
      catch (IOException)
      {
        return;
      }

      byte command = header[2];

      //evaluate command
      switch (command)
      {
          case (byte)GlobalDefs.Com_Commands.DATA_INTEGERS:
            ReadData(header);
            IntegersReceivedAction(EndianBitConverter.Big.ToUInt16(header, 4));
            break;
          case (byte)GlobalDefs.Com_Commands.CMD_GET_SETTINGS:
            ReadData(header);
            CmdGetSettings_Reply();
            break;
      }
  }

Ich habe das mal gekürzt, da es sonst zu unübersichtlich wird. Aber ich lese da erst mal den 6-Byte Header aus. Die ersten zwei Byte sind die Länge der Nachricht (das kam von dem BT-Chip automatisch). In Byte zwei sind meine definierten Kommandos. Darauf switche ich dann und lese den Rest der Nachricht in den globalen Puffer ein. Danach wird eine Methode aufgerufen, die diese Daten dann verarbeitet und irgendwas auf dem GUI macht, je nachdem was da geschickt wurde. CmdGetSettings_Reply() z.B. trägt dann die Werte auf dem GUI in die Textboxen. Man kann natürlich auch gleich alles auf einmal einlesen.

Das mit dem EndianConverter war nötig da C# und Java unterschiedliche Byte-Ordnungen haben :o

Das ist alles sehr einfach, wenn man mal drin ist. Du musst da auch nicht gleich so kompliziert einsteigen. Ich habe auch einfach mit ein, zwei Methoden in der gleichen Klasse angefangen und erst mal nur ein paar Byte hin-und hergeschickt. Mit dem Debugger kann man sich dann auch den Puffer anzeigen und die einzelnen Bytes anschauen.

michael_x:

arbeite sonst eigentlich ausschließlich mit Matlab

Hab gehört, "Matlab kann alles": Gibt es da keine seriellen Schnittstellen ?

Ist das was ? -> http://home.iitb.ac.in/~rahul./ITSP/serial_comm_matlab.pdf

Deswegen hatte ich das auch anfangs damit programmiert, jedoch hat Matlab die Schwäche, dass man meines Wissens nach keine richtige Standalone Programme damit hinbekommt und das war bei diesem Projekt definitiv ein k.o. Kriterium. Ich habe mal einen Tag lang danach gesucht, aber leider scheint das sehr speziell zu sein. Für alles, was ich gefunden hatte muss zusätzlich noch was von Matlab installiert sein, um die Programme ohne Matlab ausführen zu können(selbst die .exe Dateien)
Ein Freund von mir hat mir dann zu C# geraten. Er als Informatiker hat natürlich leicht reden. Ich als angehender Maschinenbauer tue mich damit noch etwas schwer.

@Serenifly

Ich danke dir vielmals! Leider verstehe ich noch nicht alles. Mein Problem liegt aber in dem Vokabular, weswegen ich mir aus der Bibliothek mal ein C# Buch für Einsteiger besorgt habe. Leider schreibe ich in den nächsten 2 Wochen div. Klausuren, so dass ich eher unregelmäßig zum Lesen komme.
Dennoch kann ich einen Erfolg verbuchen, ich habs hinbekommen 3 Werte vom Arduino zu schicken und die einzelnen Werte 3 Labels zuzuweisen, was mich ein ganzes Stück nach vorne gebracht hat.
Jetzt muss ich das ganze nur noch umgekehrt hinkriegen, also von C# zum Arduino und ich kann mit dem eigentlichen Code beginnen :wink:

Weiß einer von euch zufällig ob man für C# noch mehr "Werkzeuge" bekommen kann? Ich bräuchte so ne Art Progress bar, nur vertikal für meine Prozentanzeige der Lüfterdrehzahl. Gibts sowas irgendwo?
Die vorhandene ist irgendwie nicht so optimal dafür, außerdem lässt die sich nicht ins Vertikale drehen...

Gruß
Jan

C# ist sehr schön für sowas. Java ist ähnlich, aber man merkt, dass die Bibliothek an manchen Stellen etwas unkontrolliert gewuchert ist. .NET ist da oft etwas besser strukturiert. Und die IDE mit Intellisense ist gerade für Anfänger super.

Weiß einer von euch zufällig ob man für C# noch mehr "Werkzeuge" bekommen kann?

Die Dinger heißen "Components" und man kann sie auch selbst schreiben (oder Standard Komponenten modifizieren wenn man eine Klasse davon ableitet). Da gibt es im Netz eine ganze Menge.

z.B. hier:
http://www.codeproject.com/Articles/8422/Vertical-ProgressBar

Ist aber bei der ProgressBar nicht unbedingt nötig, da man die Standard Klasse ableiten kann und nur eine Methode überschreiben muss:

Du könntest auch ein Panel (mit BorderStyle = Fixed3D) nehmen und darauf ein Rechteck zeichnen, dessen Größe du entsprechend anpasst. Dann bist du flexibel:
http://www.techotopia.com/index.php/Drawing_Graphics_in_C_Sharp
Graphics.FillRectangle Method (System.Drawing) | Microsoft Learn (statt Pen wenn man das Rechteck ausfüllen will)

Übrigens bekommt man Probleme wenn man aus anderen Threads direkt auf das GUI-Thread zugreifen will. Wenn du bei der Ausgabe der Daten eine Fehlermeldung über Thread-Verletzungen u.ä. bekommst, musst du die Methode, die die Änderung vornimmt in einen Delegate verpacken:

    public void TextBoxWrite(string text)
    {
      textOutput.Invoke((MethodInvoker)delegate
      {
        textOutput.AppendText(text);
      });
    }

Wenn dein Programm so läuft brauchst du das nicht. Das kommt darauf an wo dein SerialEventhandler läuft.

Der Schlüssel ist dabei die zweite Zeile. Alle Komponenten haben diese Invoke Methode der man seine Aktion übergeben muss. In diesem Fall ist es eine RichTextBox (die beste Komponente für große Textmengen), aber es geht genauso mit normalen TextBoxen oder Panels.

Wahnsinn was du alles weißt. Das sind ja einige Möglichkeiten, mal sehen was ich davon nehme, soll ja auch optisch ganz nett werden :wink:
An das mit dem Rechteck hatte ich auch schon gedacht. In meinem Buch war vorne drin ein Beispiel zum ändern der Größe drin. Vllt probiere ich das mal damit, ich hab ja keinen Zeitdruck :wink:

Als könntest du sehen, was ich hier mache :smiley: Genau das Problem hatte ich mit meinem Eventhandler :smiley:
Aber mit google und ein wenig copy+paste bekommt man das relativ leicht in Griff. :wink:

Nach der vertical Bar hatte ich auch schon mal gesucht, aber irgendwie nichts richtiges gefunden, also danke dafür. Ich melde mich, wenns soweit funktioniert :wink:

Das tritt schließlich fast immer auf wenn man sowas machen will :slight_smile:

Das ist ein Windows Problem und hat nichts mit .NET an sich zu tun. Da gibts noch andere Versionen die mit InvokeRequired abfragen ob man nicht doch auch dem selben Thread ist und sich so eventuell Zeit spart. Oder man kann BeginInvoke nehmen, damit das restliche Programm weiter läuft, während man auf dem GUI-Thread was macht. Bei dir geht das aber so, da du nicht viel machst. Und der anynoyme delegate an sich ist das kürzeste.