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.