serielle Kommunikation über COM mit WINAPI



  • Hallo

    Ich habe mir vorgenommen die recht langsame und mir persöhnlich unsympatische Methode mit MSCommControl und damit Visual Basic hinter mir zu lassen und die COM Schnittstelle direkt über die WINAPI an zu sprechen. Da ich schon erste Erfolge mit der WINAPI hatte informierte ich mich auf MSDN und schrieb einen ersten Versuch, der allerdings nicht funktioniert. Auf anderen Seiten fand ich ähnlichen Code (z.T. in Klassen verpackt), der aber in der wesentlichen Punkten mit meinem übereinstimmt (zumindest meiner Einschätzung nach). Schaut euch das mal an und sagt mir was ich falsch mache: Vielen Dank!

    #include <windows.h>
    #include <stdio.h>
    
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
    {
    	static TCHAR szAppName[] = TEXT ("COMM");
    	HWND		hwnd;
    	MSG			msg;
    	WNDCLASS	wndclass;
    
    	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
    	wndclass.lpfnWndProc	= WndProc;
    	wndclass.cbClsExtra		= 0;
    	wndclass.cbWndExtra		= 0;
    	wndclass.hInstance		= hInstance;
    	wndclass.hIcon			= LoadIcon (NULL, IDI_APPLICATION);
    	wndclass.hCursor		= LoadCursor (NULL, IDC_ARROW);
    	wndclass.hbrBackground	= (HBRUSH) GetStockObject (WHITE_BRUSH);
    	wndclass.lpszMenuName	= NULL;
    	wndclass.lpszClassName	= szAppName;
    
    	if (!RegisterClass (&wndclass))
    	{
    		MessageBox (NULL, TEXT (" Dieses Programm setzt Windows NT voraus!"), TEXT ("Warnung:"), MB_ICONERROR);
    		return (0);
    	}
    
    	hwnd = CreateWindow (szAppName, TEXT ("COMM"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
    
    	ShowWindow (hwnd, iCmdShow);
    	UpdateWindow (hwnd);
    	while (GetMessage (&msg, NULL, 0, 0))
    	{
    		TranslateMessage (&msg);
    		DispatchMessage (&msg);
    	}
    	return msg.wParam;
    }
    
    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	HDC				hdc;
    	PAINTSTRUCT		ps;
    	HANDLE			RS232;
    	DCB				settings;
    	static unsigned char	data;
    	TCHAR			szbuffer[50];
    
    	switch(message)
    	{
    	case WM_CREATE:
    		RS232=CreateFile(TEXT("COM2"),GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    		if (RS232 == INVALID_HANDLE_VALUE) 
    		{
    			MessageBeep(-1);
    		}
    
    		if(!GetCommState(RS232,&settings))
    		{
    			MessageBeep(-1);
    		}
    		settings.BaudRate=CBR_9600;
    		settings.ByteSize=8;
    		settings.Parity=NOPARITY;
    		settings.StopBits=ONESTOPBIT;
    
    		settings.DCBlength         = sizeof(DCB);
    		settings.fBinary           = TRUE; // muss immer "TRUE" sein!
    		settings.fParity           = TRUE;
    		settings.fOutxCtsFlow      = FALSE;
    		settings.fOutxDsrFlow      = FALSE;
    		settings.fDtrControl       = DTR_CONTROL_ENABLE;
    		settings.fDsrSensitivity   = FALSE;
    		settings.fTXContinueOnXoff = TRUE;
    		settings.fOutX             = FALSE;
    		settings.fInX              = FALSE;
    		settings.fErrorChar        = FALSE;
    		settings.fNull             = FALSE;
    		settings.fRtsControl       = RTS_CONTROL_ENABLE;
    		settings.fAbortOnError     = FALSE;
    		settings.wReserved         = 0; // muss immer "0" sein!
    
    		if(!SetCommState(RS232,&settings))
    		{
    			MessageBeep(-1);
    		}
    
    		SetTimer(hwnd,1,500,NULL);
    		data=0;
    		return 0;
    
    	case WM_TIMER:
    		if(!TransmitCommChar(RS232,(unsigned char)65))
    		{
    			MessageBeep(-1);
    		}
    		InvalidateRect(hwnd,NULL,true);
    		data++;
    		return 0;
    
    	case WM_PAINT:
    		hdc = BeginPaint(hwnd,&ps);
    		TextOut(hdc,0,0,szbuffer,sprintf(szbuffer,TEXT("data: %d"),data));
    
    		EndPaint(hwnd,&ps);
    		return 0;
    
    	case WM_DESTROY:
    		CloseHandle(RS232);
    		KillTimer(hwnd,1);
    		PostQuitMessage (0);
    		return 0;
         }
    
         return DefWindowProc (hwnd, message, wParam, lParam) ;
    }
    


  • Mach es nicht selber sondern verwende eine fertige Klasse!!! Serielle Kommunikation ist*nicht* trivial!
    http://www.codeproject.com/system/serial.asp



  • Oder diese:

    http://www.winapi.net/index.php?inhalt=t3

    Ich möchte es gern selber können. Meiner Erfahrung nach ist serielle Kommunikation über RS232 sehr wohl trivial. Auf dem Microcontroller (ATmega16) hat es geklappt, in VB bzw VBA hat es geklappt (mit MSCommControl). *So* schwierig kann es nicht sein. Die Initualisierung stimmt exakt mit dieser http://msdn2.microsoft.com/en-us/library/aa363201.aspx
    überein, und funktioniert auch, nur steht hier nirgens wie man Daten schickt bzw empfängt. Also versuchte ich es mal mit 'WriteFile' oder 'TransmitCommChar', was aber leider *nicht* funktioniert.



  • Frederick schrieb:

    Meiner Erfahrung nach ist serielle Kommunikation über RS232 sehr wohl trivial. Auf dem Microcontroller (ATmega16) hat es geklappt...

    da hast du recht.
    nur unter windows ist es nicht trivial.
    sonst aber überall...



  • Wir halten also fest: RS232 ist trivial, nur der Zugriff unter Windows nicht. Gut. Aber irgendwie *muss* es gehen.

    *So* falsch kann mein Ansatz aber nicht sein, immerhin funktioniert es in der oben genannnten Klasse ja auch.



  • Frederick schrieb:

    Wir halten also fest: RS232 ist trivial, nur der Zugriff unter Windows nicht. Gut. Aber irgendwie *muss* es gehen.

    Was hindert Dich daran den Source der von mir vorgeschlagenen Lösung anzuschauen???

    Warum sollen wir hier Lösungen abieten, die schon zig Mal gemacht wurden?



  • Ich habe mir den Quellcode gelesen, allerdings muss ich sagen er mir ähnlich komplex und umfangreich erscheint wie Betriebssystem Kernel. Was ich aber (glaube ich) erkennen konnte, war dass dort im Prinzip die selben Funktionen mit den gleichen Parametern verwendet werden, wie ich sie verwendet habe. Insgesamt hilft mir das also nicht weiter.

    Was hindert dich daran dir mal meinen Code anzusehen, der im übrigen sehr viel kürzer und primitiver ist, und vielleicht einen entdeckten Fehler mit zu teilen. Die WinMain kannst du überspringen, das Interessante steht in WM_CREATE und WM_TIMER. Bei CreateFile und SetCommState tritt kein Fehler auf, was heißen könnte, dass diese Zeilen stimmen. Nur beim Senden stimmt was nicht.



  • Ich habe noch was entdeckt:

    Der von WriteFile oder TransmitCommChar zurückgegeben Fehlercode hat die Nummer 6 und heißt 'ERROR_INVALID_HANDLE'. Allerdings werde ich daraus nicht schlau, denn wenn der von CreateFile zurück gelieferte Handel ungültig wäre, hätten die beiden oberen Überprüfungen auch schon ansprechen müssen.



  • schreib ein 'static' vor der definition von 'HANDLE RS232;'
    🙂



  • Das ist einer jener Momente in denen ich mich in den A_____ beißen könnte!!!

    So oft habe ich dieses verfl___ static vergessen und so oft bin ich dahinter gekommen.

    Aber letztendlich ist der Zugriff auf COM *doch* trivial!

    Vielen, Vielen Dank.



  • Frederick schrieb:

    Aber letztendlich ist der Zugriff auf COM *doch* trivial!

    ist er auch. nur nicht unter windoofs 😉



  • So, dass Problem mit dem Senden ist ja jetzt gelöst, nur wie empfängt man Daten?

    Irgendwo hab ich was von der WM_COMMNOTIFY Nachricht gehört, nur existiert diese angeblich seit WIN 3.1 nicht mehr. Als Ersatz gibt es eine Funktion die offenbar darauf wartet dass ein Ereignis eintritt: WaitCommEvent. Nur stelle ich mir die Sache dann etwas schwierig vor: Was ist wenn kein Ereignis eintritt? Ist dann meine gesamte Anwendung blockiert? Das wären ja Methoden aus dem Mittelalter. Da kann ich ja gleich alle 10ms ReadFile ausführen und hoffen das was ankommt?

    Kann mir jemand erklären was WaitCommEvent wirklich tut, bzw welche Alternativen es gibt?



  • Frederick schrieb:

    ..., bzw welche Alternativen es gibt?

    Zuerst einmal blockiert ReadFile nur so lange wie man es einstellt. Dazu gibt es die Funktion SetCommTimeouts.

    Man kann auch vor dem Lesen erst nachschauen wieviele Bytes im Eingangspuffer liegen, das geht mit ClearCommError.

    Wer es kompliziert mag, kann auch die Schnittstelle im Overlapped Modus öffnen, da kommt ReadFile immer gleich zurück und man kann mit GetOverlappedResult nachschauen, ob das Lesen fertig ist.



  • Es gibt doch schon für jeden Sch____ Windows Nachrichten, warum wurde die bereits vorhandene Nachricht wieder abgesetzt?

    Wenn man nähmlich case WM_COMMNOTIFY: schreibt akzeptiert das der Compiler, nur bei den Ereignissen wie CN_RECEIVE streikt er.



  • Frederick schrieb:

    Es gibt doch schon für jeden Sch____ Windows Nachrichten, warum wurde die bereits vorhandene Nachricht wieder abgesetzt?

    Mit seriellen Schnittstellen arbeitet man in Workerthreads. Da gibt es keine Nachrichten.



  • Ich nehme doch auch keinen Thread wenn ich darauf warten will, dass der Benutzer eine Taste drückt!

    Meiner Meinung nach mimmt man dann Threads wenn zb eine langwierige Berechnung im Hintergrung laufen soll, aber nicht wenn man auf einen Interrupt wartet. Da Der UART sowieso mindestens einen Interrupt an die CPU schickt wenn er Daten empfängt, bräuchte Windows ihn lediglich in Form einer Nachricht an die Anwendung weiter zu leiten. Dass man ein Ereignis ständig abfragt ist doch blanker Unsinn!

    PS: in MSDN findet man fast alles, sogar eine Liste aller Windowsfunktionen und Datentypen, aber wo findet man eine Liste von Windows Nachrichten???



  • Frederick schrieb:

    Meiner Meinung nach mimmt man dann Threads wenn zb eine langwierige Berechnung im Hintergrung laufen soll,

    Oder eine langwierige Datenübertragung ...

    Frederick schrieb:

    Dass man ein Ereignis ständig abfragt ist doch blanker Unsinn!

    Natürlich. Das macht man ja auch bei seriellen Schnittstellen nicht.

    Frederick schrieb:

    PS: in MSDN findet man fast alles, sogar eine Liste aller Windowsfunktionen und Datentypen, aber wo findet man eine Liste von Windows Nachrichten???

    Was hat das mit dem Thema zu tun ?

    Es gibt keine Windows Nachrichten für die serielle Schnittstelle. (Mit Ausnahme der Nachrichten die z.B. gesendet werden, wenn eine am USB angeschlossene Schnittstelle entfernt wird.



  • nn schrieb:

    Natürlich. Das macht man ja auch bei seriellen Schnittstellen nicht.

    Entweder wartet ReadFile bis alle Bytes gelesen sind oder der Timeout abgelaufen ist. => Du brauchst einen Thread, weil sonst die Oberfläche hängt.

    Beim Overlapped IO wartet man z.B. mit WaitForSingleObject.
    => Du brauchst einen Thread, weil sonst die Oberfläche hängt.

    Die einzige Variante, wo man in gewisserweise aktiv wartet ist die Variante mit ClearCommError. Das macht man entweder mit einem Timer oder in einem Thread, der den Buffer checkt und danach eine Weile mit Sleep wartet.



  • OK, danke.

    (Sch_____ Windows)



  • Frederick schrieb:

    OK, danke.

    (Sch_____ Windows)

    Also im Prinzip geht es unter Linux nicht viel anders.
    Die API-Funktionen sind andere, aber nicht das Funktionsprinzip.

    Wenn man herausgefunden hat, wie es geht ist es eigentlich doch trivial. 🤡


Log in to reply