Serielle Schnittstelle mit C/C++ ansteuern

#562174
Lesenswert?

Hallo Leute,

ich möchte die Serielle Schnittstelle meines Rechners aus C oder C++ 
ansteuern. Die Bisherigen Lösungsansätze die ich von Google bekommen 
habe, haben alle mit Visual C/C++ zu tun. Ich benutze jedoch DevC++, 
d.h. ich habe keine microsoftspezifischen Bibliotheken.

Ich habe ein µC mit dem ich über die COM-Schnittstelle kommunizieren 
möchte. Mein kleines Protokoll schreibe ich in C/C++, je nach dem wie 
die Ansteuerung sich am besten lösen lässt.

Kennt jemand eine Lösung wie ich das ohne Visual C/C++ lösen kann?

Gruß
Konstantin
#566187
Lesenswert?

HI Leute,

bin mit meiner Programmierung etwas weiter gekommen. Nun möchte ich die 
am PC verfügbaren COM-Ports zur Auswahl stellen.
Wie kann ich die verfügbaren COM-Ports einlesen? Ich meine damit, wenn 
der Rechner COM-Ports on board hat, dann sind es höchst wahrscheinlich 
COM1 und vtl. COM2. Wenn ich jetzt ein USB-to-COM Adapter anschließe 
bekommt er bei einem PC z.B. die Bezeichnung COM9, bei einem anderen 
COM8 usw. Deswegen möchte ich bei meinem Programm die möglichen 
COM-Ports zur Auswahl stellen.
Wer weiß wie es geht?

Gruß
Konstantin
#566424
Lesenswert?

Am sichersten, indem du die Registry ausliest.
1
  HKEY hKey;
2
  LONG res;
3
  DWORD dwIndex = 0, dwName, dwData;
4
  char chName[MAX_PATH];
5
  CString strData;
6
    DWORD   dwSize = 1024;
7
  
8
  TRACE("Öffne Registry...\n");
9

10
  res = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
11
    "HARDWARE\\DEVICEMAP\\SERIALCOMM", 
12
    0,
13
    KEY_READ,
14
    &hKey);
15
  
16
  if(!res)
17
  {
18
    do
19
    {
20
      dwName = MAX_PATH;
21
      dwData = MAX_PATH;
22
      res = RegEnumValue(hKey,
23
          dwIndex++,
24
          chName,  
25
          &dwName,
26
          NULL,
27
          NULL,
28
          (BYTE *)strData.GetBufferSetLength((int)dwSize), 
29
          &dwData);
30
      if (res != ERROR_NO_MORE_ITEMS)
31
      {
32
        m_ComPorts.AddString(strData);
33
        m_BTN_COMOpenClose.EnableWindow(TRUE);
34
      }//if (res != ERROR_NO_MORE_ITEMS)
35

36
    }//do
37
    while (res != ERROR_NO_MORE_ITEMS);
38
    
39
    TRACE("Schließe Registry\n");
40

41
    RegCloseKey(hKey);

m_ComPorts ist bei mir eine Drop-Down Box auf der MFC-Oberfläche. Du 
müsstest dann die Strings halt woanders abspeichern.

Wie ich schon in anderen Threads schrieb, kann es beim Durchprobieren 
aller Ports zu sehr interessnaten effekten an eventuell angeschlossenen 
Geräten kommen....
#567101
Lesenswert?

Eigentlich spricht nichts dagegen. Den Datentyp CString kenne ich nicht. 
Demnach weiß ich nicht was du mit
1
(BYTE *)strData.GetBufferSetLength((int)dwSize)
 meinst.

strData wird als erstes als ein BYTE Pointer gecastet. Aber was ist mit
1
GetBufferSetLength((int)dwSize)
, was macht diese Funktion bzw. wie mache ichs mit dem string?

Gruß
Konstantin
Persönliche Seite #567134
Lesenswert?

GetBufferSetLength liefert einen Pointer auf den vom CString-Objekt 
verwalteten Textpuffer.

Der Pointer kann danach mit klassischen C-Funktionen wie sprintf, strcpy 
etc. bearbeitet werden; die Anzahl der zur Verfügung stehenden 
Speicherstellen wird mit dem Parameter von GetBufferSetLength 
festgelegt.

Nachdem der Puffer so bearbeitet wurde, muss er unbedingt mit 
ReleaseBuffer wieder freigegeben werden.
#567285
Lesenswert?

Ah. Wir reden von dem Registry Zugriffscode da oben.

Ersetze den CString durch ein char Array
1
//  CString strData;
2
//  DWORD   dwSize = 1024;
3

4
   char strData[1024];
5

6
  ...
7

8
      res = RegEnumValue(hKey,
9
          dwIndex++,
10
          chName,  
11
          &dwName,
12
          NULL,
13
          NULL,
14
          &strData,
15
          &dwData);
16

17
  ...


#567859
Lesenswert?

@Karl heinz Buchegger:
Also mit dem char-Array klappt es nicht. Der Fehler:
1
cannot convert `char (*)[1024]' to `BYTE*' for argument `7' to 
2
`LONG RegEnumValueA(HKEY__*, DWORD, CHAR*, DWORD*, DWORD*, DWORD*, BYTE*, DWORD*)'

Soll ich vtl. ein Array vom Datentyp BYTE übergeben? Also so:
1
BYTE strData[1024];
2
  ...
3
      res = RegEnumValue(hKey,
4
          dwIndex++,
5
          chName,  
6
          &dwName,
7
          NULL,
8
          NULL,
9
          strData,
10
          &dwData);
11
  ...

Ich habe es ausprobiert. Es gibt keine Fehler. Nur wie lese ich es aus? 
Wenn ich es mit einer FOR-Schleife mache kommt irgend ein Sch... raus.

Meine FOR-Schleife:
1
for ( int i = 0 ; i < 1024 ; i++ )
2
   {
3
      cout << strData[i];
4
   }
Persönliche Seite #567922
Lesenswert?

Was ist "irgendein Sch..."?

Du solltest den zweiten NULL-Parameter (also den vor "strData") übrigens 
besser durch einen Pointer auf DWORD ersetzen, denn darin wird der 
Datentyp des Registryeintrages geliefert:
1
BYTE strData[1024];
2
DWORD dwType;
3
  ...
4
      res = RegEnumValue(hKey,
5
          dwIndex++,
6
          chName,  
7
          &dwName,
8
          NULL,
9
          &dwType,
10
          strData,
11
          &dwData);

Das ist sinnvoll, da in der Registry auch durchaus ganz andere Dinge als 
Strings (REG_SZ) gespeichert werden können.

Auch solltest Du nach Aufruf der Funktion den Inhalt von dwData genauer 
untersuchen - da drin nämlich wird gespeichert, wieviele Bytes überhaupt 
aus der Registry gelesen wurden. Ein REG_DWORD braucht nämlich nur 4 
Bytes; Deine for-Schleife aber gibt 1024 davon aus.

Da die Funktion iterativ aufgerufen wird, musst Du vor jedem einzelnen 
Aufruf dwData wieder auf die Größe des übergebenen Puffers setzen.

Du solltest Dir mal die Dokumentation der Funktion RegEnumValue 
durchlesen, so schlecht ist die gar nicht:
http://msdn2.microsoft.com/en-us/library/ms724865.aspx
Persönliche Seite #568158
Lesenswert?

Du solltest Dir UNBEDINGT die Dokumentation von RegEnumValue durchlesen. 
Und von RegOpenKeyEx.

-- Du wertest den Rückgabewert von RegEnumValue nicht aus, bevor Du 
lpData[] ausgibst. Das aber darfst Du nur, wenn der Wert ERROR_SUCCESS 
zurückgegeben wird.

Bereits beim ersten Durchlauf auf meinem System aber wird der Wert 259 
zurückgegeben, und das ist der Fehlercode für

  "Das System kann den angegebenen Pfad nicht finden."

Und das liegt hieran:

-- Du übergibst RegEnumValue einen falschen Wert für hKey - der 
Rückgabewert von RegOpenKeyEx steht in Result, nicht in hKey.
Denselben falschen Wert übergibst Du auch RegCloseKey.

-- Was hat lpcValueName mit der Anzahl der in lpData[] stehenden Bytes 
zu tun? Du solltest hier besser lpcbData verwenden.

-- Du definierst das Array lpData mit 1024 Elementen, weist aber 
lpcbData den Wert MAX_PATH (260) zu. Was jetzt?

-- Du musst vor JEDEM EINZELNEN Aufruf von RegEnumValue die Werte 
lpcValueName und lpcbData mit der Größe der Arrays lpValueName und 
lpData initialisieren.

So funktionierts:
1
do
2
{
3
  lpcValueName =  MAX_PATH;
4
  lpcbData =  MAX_PATH;
5

6
  ret = RegEnumValue( Result,
7
                dwIndex++,
8
                lpValueName,
9
                &lpcValueName,
10
                NULL,
11
                NULL,
12
                lpData,
13
                &lpcbData);
14

15
  if (ret == ERROR_SUCCESS)
16
  {
17
    for ( int i = 0 ; i < lpcbData ; i++ )
18
      {
19
        cout << lpData[i];// << endl;
20
      }
21
  }
22
  cout << endl << endl;
23
}
24
while (ret != ERROR_NO_MORE_ITEMS);
#568584
Lesenswert?

@  Rufus t. Firefly:

Ich danke dir vielmals. Ich habe mir die Doku von RegEnumValue gestern 
noch ausgedruckt und durchgelesen. Hatte trotzdem noch etwas Probleme 
damit.

Nun habe ich die Fehler die du mir genannt hast ausgebessert. Das 
Programm läuft soweit. Nur zeigt mir das Programm den letzten Eintrag in 
der Regestry an. In der Regestry sieht es bei mir folgender Maßen aus:
1
1. COM3
2
2. COM2
3
3. COM8
COM8 ist ein USBtoCOM-Adapter. Ziehe ich diesen heraus liefert mir das 
Programm nur COM2 an den Bildschirm. Schließe ich den Adapter wieder an, 
liefert zeigt es mir nur COM8 an. Wie bekomme ich die anderen Einträge 
her? In der Doku steht, dass die Einträge in der Regestry nicht geordnet 
sind und dass die Einträge einen beliebigen Index haben. Wenn ich doch 
mit
1
dwIndwx++
 durchlaufe und auf ERROR_NO_MORE_ITEMS überprüfe, komme ich doch nie 
(außer zufällig) an die anderen Einträge, oder?

Was kann ich da machen?

Gruß
Konstantin
Persönliche Seite #568597
Lesenswert?

Das klingt sehr merkwürdig.

Mal in der Registry nachgesehen, wie das zum betreffenden Zeitpunkt 
aussieht?
Das Phänomen tritt auf meinem Rechner (XPSP2) nicht auf.

Ich habe zu Testzwecken auch den zugeordneten Gerätenamen ausgegeben:
1
if (ret == ERROR_SUCCESS)
2
{
3
  for (i = 0; i < lpcValueName; i++)
4
    cout << lpValueName[i];
5
  cout << "  ";
6

7
  for (i = 0; i < lpcbData; i++)
8
    cout << lpData[i];
9
  cout << endl;
10
}
So sieht es mit USB-Seriell-Adapter aus:
1
\Device\Serial0  COM1
2
DiPort1  COM5
3
DiPort2  COM6
4
DiPort3  COM7
5
DiPort4  COM8
6
DiPort5  COM9
7
\Device\VCP1  COM4

und so ohne:
1
\Device\Serial0  COM1
2
DiPort1  COM5
3
DiPort2  COM6
4
DiPort3  COM7
5
DiPort4  COM8
6
DiPort5  COM9

DiPort ist meine ISDN-Karte. Und, ja, mein Rechner hat nur noch eine 
serielle Schnittstelle.

PS: Bitte - "Reg_i_stry"
#568615
Lesenswert?

Konstantin Ribel wrote:
> Also hier noch mal mein Code.

:-)
1
      ret = RegEnumValue( Result,
2
                          dwIndex++,
3
                          lpValueName,
4
                          &lpcValueName,
5
                          NULL,
6
                          NULL,
7
                          lpData,
8
                          &lpcbData);
9
                          
10
      if (ret == ERROR_NO_MORE_ITEMS)
11
      {

Warum wertest du die retournierten Dtaen nur dann aus, wenn
RegEnumValue dir mitteilt, dass es keine Werte mehr hat?

Hinweis: Geh solche Sachen im Debugger durch, dort hättest
du das in 20 Sekunden gesehen, dass in der Schleife nichts
ausgegeben wird, obwohl RegEnumValue immer wieder
aufgerufen wird.
Persönliche Seite #568616
Lesenswert?

Das kann ja auch gar nicht funktionieren - Du gibst die gefundenen Daten 
NUR aus, wenn ret == ERROR_NO_MORE_ITEMS ist:
1
do
2
{
3

4
  //lpcValueName =  MAX_PATH;
5
  //lpcbData =  MAX_PATH;
6
      
7
  ret = RegEnumValue( Result,
8
                          dwIndex++,
9
                          lpValueName,
10
                          &lpcValueName,
11
                          NULL,
12
                          NULL,
13
                          lpData,
14
                          &lpcbData);
15
                          
16
  if (ret == ERROR_NO_MORE_ITEMS)
17
  {
18
    cout << " ";
19
    for (int i = 0; i < lpcValueName; i++)
20
      cout << lpValueName[i];
21
    cout << "  ";
22
            
23
    for ( int i = 0 ; i < lpcbData ; i++ )
24
      cout << lpData[i];
25
    cout << endl << endl;
26
  }
27
}
28
while (ret != ERROR_NO_MORE_ITEMS);

Und die erneute Initialisierung von lpcValueName / lpcbData ist wichtig, 
den solltest Du nicht weglassen.

Die Funktion erwartet in diesen beiden Variablen die Länge des zur 
Verfügung stehenden Puffers und schreibt in diese Variablen die Anzahl 
der genutzten Bytes hinein.

Damit steht vor dem Aufruf 260 (MAX_PATH) drin, nach dem ersten Aufruf 
aber nur noch 4 (willkürlich, soll nur als Beispiel dienen).
Das geht solange gut, wie die Funktion nicht über Werte/Namen mit mehr 
als 4 Zeichen Länge stolpert - das aber tut sie spätestens bei COM10 und 
liefert "COM1" ...


Also: Lies Dir nochmal genau den von mir geposteten Code durch.
#572335
Lesenswert?

Hab ein neues Problem.

Es scheint, dass die RS232-Schnittstelle ein Buffer hat. Wenn ich mit 
meinem µC permannent Zeichen an den Rechner sende und dann das zeichen 
ändere dauert es immer etwas bis das neue Zeichen am PC ausgegeben wird.

Wie kann ich dieses Buffer leeren, dass ich gleich die gerade 
ankommenden Zeichen lesen kann?

Gruß
Konstantin
Gast #572343
Lesenswert?

Der Pc braucht eben auch etwas , um die ankommenden daten zu verarbeiten 
.
dann kommt es automatisch  zu einer latenz .
um "bestimmte" zeichen zu versenden , musst du deine nachrichten mit 
einen bestimmenden merkmal versehn . und dies gebenen falls abfragen um 
sicher zu gehen , das es das richtige zeichen ist .
#572704
Lesenswert?

Vielleicht auch mal lieber einen Thread programmieren, der die COM immer 
im Hintergrund abfragt....ist sinnvoller.
Und Dtenübertragung sollte grundsätzlich ein Frame-Format haben, mit 
Start, Stopp-Kennzeichnung, Checksumme, Längenkennzeichnung usw.
Ich implementiere immer das einfache Format, was auch der Bluetooth-Chip 
LMX9820 benutzt, sehr effizient und simpel.
Gast #572892
Lesenswert?

Es geht auch ohne "pollen". Wenn ein Zeichen (oder mehrere) innerhalb 
einer festgelegten Zeit empfangen wurden, läßt man sich einen Event 
schicken und holt die Daten ab.
SetCommTimeouts und WaitCommEvent helfen da weiter.
Sollte auch bei virtuellen COM-Ports funktionieren.

Blackbird
Gast #807895
Lesenswert?

hallo, ich fange auch mit dec-c++ an und suche nach einem programm für 
die serielle schnittstelle. kannst du das mal posten. ich habe mich 
schon dämlich gesucht im google-center.

wäre dir sehr dankbar für die hilfe.

mfg
Gast #811076
Lesenswert?

Hallo Leute,

Für die ganze Port Problematik hat Js. Payne ein erstklassiges Aktive X
geschrieben. ( www.jspayne.com ) das sollte alle Probleme beseitigen. Am
schönsten ist das es gleich mit allen Windows Versionen funktioniert.
Gast #891184
Lesenswert?

Hallo,

ich habe nun ein ähnliches Problem:
Ich möchte auch auf die serielle Schnittstelle unter WinXP zugreifen.
Am liebsten würde ich meine Anwendung in Java programmieren, nur scheint 
es dort seeehr schwer auf den Com-Port zuzugreifen.
Alternativ würde ich die Anwendung dann mit wxDev-C++ erstellen (wie 
auch der Erstautor dieses Beitrags). Leider klappt sein Link zur 
"windows.h" nicht mehr.

Brauche ich wirklich nur diese eine Datei zu includen und kann dann 
direkt diverse Zugriffsfunktionen nutzen?!
Wo bekomme ich diese Datei? (legal?)

Ich habe dieses jspayne-active-X-Element ausprobiert, bekam es aber 
leider unter wxdev-C++ nicht zu laufen... Ich verstehe absolut nicht wie 
ich die Datei io.ocx in wxDev-C++ einbinden soll oder was ich dann damit 
machen kann etc...

Es wäre toll wenn mich jemand ein bisschen in die richtige Richtung 
bringen könnte.

Schöne Grüße,
Alex
(Firma: Ucore Fotografie www.ucore.de) Persönliche Seite #891566
Lesenswert?

Hallo Simon,

das hilft mir leider nicht weiter. Die Suche hatte ich auch schon bemüht 
und traf dabei auf diesen Artikel, der sich noch am ehesten mit meinem 
Problem beschäftigt.

Also nochmal kurz und knapp:
A) Wer kann mir Hinweise geben, wie die serielle Schnittstelle unter 
Windows XP über Java angesprochen werden kann?

oder

B) Wer kann mir Hinweise geben, wie die serielle Schnittstelle mit 
WxWidgets bzw. WxDevC++ angesprochen werden kann?

Wie gesagt, über sachdienliche Posts würde ich mich sehr freuen, da alle 
Artikel und Webseiten die ich bisher fand nicht funktionierten! (Oder 
aber Visual C++ benötigten oder ähnliches nicht frei verfügbares)

Schöne Grüße,
Alex
Persönliche Seite #891628
Lesenswert?

Alex Bürgel wrote:
> B) Wer kann mir Hinweise geben, wie die serielle Schnittstelle mit
> WxWidgets bzw. WxDevC++ angesprochen werden kann?

wxWidgets enthält keine Möglichkeit eine serielle Schnittstelle 
anzusteuern. Du musst dafür auf Windows-Systemfunktionen zurückgreifen.

> Wie gesagt, über sachdienliche Posts würde ich mich sehr freuen, da alle
> Artikel und Webseiten die ich bisher fand nicht funktionierten! (Oder
> aber Visual C++ benötigten oder ähnliches nicht frei verfügbares)

Die benötigen kein Visual C++. Sie benötigen nur ein Satz von 
Headerdateien und Libs, mit denen du auf die Windows-API zugreifen 
kannst.
http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx

Du brauchst das Windows SDK dafür. (Soweit ich weiß)
(Firma: Ucore Fotografie www.ucore.de) Persönliche Seite #891671
Lesenswert?

Hallo Simon K.,
ich glaube ich werde es in C# lösen. Da muss ich mich zwar drin 
einarbeiten aber es ist Java (meiner bevorzugten Lösung) auf den ersten 
Blick gar nicht so unähnlich...

Zudem gibt es da eine kostenlose grafische IDE die direkt eine Klasse 
"SerialPort" bereitstellt. Klappte auf Anhieb :-)

Schöne Grüße,
Alex

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren