[C++] Serial port - na verzenden data geen antwoord

Pagina: 1
Acties:

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Ik heb een datakabel voor mijn GSM die middels een virtuele compoort (het is in feite een USB-kabel) kan communiceren met programma's op mijn pc. Ik wil in C++ een programma schrijven dat onder Windows kan communiceren met mijn GSM.

Aangezien ik dit nog nooit eerder heb gedaan wilde ik gaan leren door een communicatieproces van een al bestaand programma te reverse engineeren; dit doe ik met het programma Serial Monitor dat mij exact kan vertellen wat er allemaal op een serial port gebeurt en door wie dat gebeurt. Ook staat hier in hex de data per commando bij, zodat ik met wat knutselen het gedrag van het bestaande programma vrijwel exact kan nabootsen.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Connect to COM port 3
hComPort = CreateFile(
    "\\\\.\\COM3",
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    0,
    NULL
);

(...)

//Set event mask
if (!SetCommMask(hComPort, 8)) {    //no idea why this must be 8
    (...)
}

(...schrijf naar compoort...)

//Wait for event
DWORD mask;
if (WaitCommEvent(hComPort, &mask, NULL)) {
    printf("yay!");
}

Zoals uit het middelste stuk blijkt heb ik op basis van het bestaande programma wat beslissingen genomen waardoor het *exact* het gedrag het programma emuleert. Geen idee wat de waarde 8 is, maar ik weet wel dat EV_RXCHAR geen effect heeft.

Het gedeelte waar ik naar de compoort schrijf bestaat uit een WriteFile(...) en werkt goed, kan Serial Monitor me vertellen. Toch blijft mijn programma hangen en krijg ik nooit de hemelse 'yay!' op mijn scherm te zien ;( iemand een idee?

  • LazySod
  • Registratie: Augustus 2003
  • Laatst online: 01-05 14:45

LazySod

Scumbag with a mission

SetCommMask kun je op MSDN vinden: http://msdn.microsoft.com...evio/base/setcommmask.asp

Aldaar zul je zien dat de functie een waarde ongelijk 0 retourneert als het -goed- gaat en dus zal je huidige if niet werken. Ook kun je daar zien wat die 8 betekent.

Voor de rest heb ik geen enkel idee van hoe met com poorten te werken en verder kan ik je dus niet helpen. Edoch, bovenstaande link zou genoeg informatie moeten leveren over de functies die je nodig hebt.

Proof is the idol before whom the pure mathematician tortures himself. (Sir Arthur Eddington)


  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Lijkt me een bugje in de MSDN - alle voorbeeldprogramma's die gebruik maken van SetCommMask controleren op een waarde anders dan 0 en een call naar GetLastError() geeft ook de waarde 0, waaruit ik wel moet afleiden dat het allemaal goed gaat :)

Verwijderd

Wat is de wijziging in functionaliteit van het bestaande computerprogramma t.o.v. jouw aanpassing?
Immers, op basis van bovenstaand broncodefragment zou ook het bestaande computerprogramma die kreet via printf() op je scherm moeten tonen.

Duidelijk is dat je in een eindeloze loop blijft hangen, aangezien jouw variant van het computerprogramma geen Event ontvangt, ervan uitgaande dat je met je wijzigingen geen subtiele defecten hebt veroorzaakt. Vermeld wordt 'CommEvent', ik neem aan dat dit een event aangaande dataverkeer uit willekeurige richting betreft, dus een ReadFile() zou eveneens een Event opleveren. Het feit dat je computerprogramma t.g.v. deze situatie dermate vastloopt is sowieso geen goede zaak, maar ik veronderstel dat je beseft dat je deze routine te zijner tijd beter in een aparte Thread zou afhandelen. :) ;)

Afgaande op een gezaghebbende referentie is het tweede argument bestemd voor een Event-mask. In dit geval is de waarde 8 genomen, die "EV_CTS" representeert. Dat verklaart gelijk wanneer een Event wordt opgeworpen, zoals ik me zojuist openlijk afvroeg... Als de programmeur van het bestaande computerprogramma werkelijk zo onbenullig is als hij uit zijn commentaar in de broncode luidend "no idea why this must be 8" doet voorkomen, lijkt het me verstandig de broncode van een ander computerprogramma als uitgangspunt te nemen, te meer daar ditzelfde commentaar de indruk wekt dat de 'oorspronkelijke' programmeur ten minste deze routine in zijn broncode baseert op andermans voorbeelden. Jij bent gelukkig niet op je achterhoofd gevallen, en hebt het al met een aannemelijke andere constante geprobeerd ("EV_RXCHAR"). Naar jouw zeggen mocht dat niet baten. Wellicht is "EV_RXFLAG" de aangewezen constante. Het is een beetje onduidelijk gedocumenteerd, maar het lijkt erop dat die een hardware-technische status representeert die vastgelegd is op driver-niveau. Het zou goed kunnen dat EV_RXCHAR een hack is van Microsoft, dat reden ziet deze constante in de Windows API op te nemen (voortgekomen uit het nastreven van terugwerkende compatibiliteit?).

Aangezien je niet de volledige broncode hebt aangeleverd is het moeilijk te bepalen waar exact de fout(en) zit(ten), fouten kunnen immers ook gelegen zijn op minder vanzelfsprekende (en daarmee subtielere) locaties in de broncode zoals ik reeds heb opgemerkt.

Allereerst lijkt het me noodzakelijk dat je verifieert hoe je COM-poorten ingesteld staan. Stel indien nodig een gangbaardere Baud-ratio in, en laat alles zo veel mogelijk aan de hardware over (uit mijn hoofd merk ik op dat je e.e.a. in kan stellen qua softwarematige "flow control"). Je hebt aangegeven dat Serial Monitor in ongewijzigde vorm wel naar behoren functioneert, maar niet of dit ook het geval is met jouw specifieke convertor (een USB-poort waarvoor softwarematig een COM-poort wordt geemuleerd) en op jouw specifieke GSM-telefoon (waarvan ik me kan voorstellen dat deze nogal kieskeurig is wat dataverkeer tussen deze en jouw PC betreft). Dit lijkt allemaal voor de hand te liggen, maar uit ervaring weet ik dat gerommel met COM-datacommunicatie vooral hoofdbrekens oplevert als het gaat om deze subtiliteiten...

Tot slot wil ik aanvullend opmerken, dat je niet mag verzuimen een degelijke buffer-afhandeling e.d. te programmeren (de mate van afhandeling door Windows zelf daargelaten; gezien het gebruik van asynchrone mechanismes als Events is het denkbaar dat je daar tegenwoordig nauwelijks nog naar om hoeft te kijken). Dit is eveneens niet te bepalen uit broncode die je hebt aangeleverd, maar het zal niet de eerste keer zijn dat er iets scheelt aan zulke basis-structuur.

P.S.
Je zou natuurlijk ook kunnen overwegen om een platform-onafhankelijkere routine te ontwerpen, al ware het maar om tot een betrouwbaar en eenduidig ontwerp te komen (ook qua voorbeelden waarop je je zou kunnen baseren)...

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
@Cybarite

Na het lezen van jouw commentaar kreeg ik de indruk dat het oorspronkelijke programma eigenlijk niet zonder threads kon werken (het deed non-blocking voorkomen). Na wat gepruts met het monitorprogramma ben ik erachter gekomen dat de afhandeling van het dataverkeer en het instellen van events door middel van drie verschillende threads gebeurt; iets wat kennelijk benodigd is om de communicatie te bewerkstelligen (mijn opzet is nu nog lineair, non-threaded). Ik ga nu aan de slag om dit gedrag na te bootsen in mijn programma om te zien of het het gewenste effect heeft.

De wijziging in functionaliteit is:
- volledige controle over het dataverkeer en het gebruik van het protocol tussen de PC en mijn GSM
- uitbreiding van functionaliteit (andere programma's bieden slechts een gedeelte van de beschikbare functionaliteit, dit wil ik combineren tot één programma)
- bepaalde softwarematige beperkingen elimineren
- data van mijn GSM direct gebruiken in andere toepassingen zonder interactie van de gebruiker

[ Voor 7% gewijzigd door JeRa op 01-08-2005 01:07 ]


Verwijderd

JeRa schreef op maandag 01 augustus 2005 @ 00:43:
Lijkt me een bugje in de MSDN - alle voorbeeldprogramma's die gebruik maken van SetCommMask controleren op een waarde anders dan 0 en een call naar GetLastError() geeft ook de waarde 0, waaruit ik wel moet afleiden dat het allemaal goed gaat :)
Hoe bedoel je, een call naar GetLastError()?
Is het zo dat die voorbeelden controleren op een return-value anders dan 0, en indien hieraan voldaan is GetLastError() aanroepen? Zo ja, dan is deze situatie inderdaad eigenaardig ja. Wel is het niet ongezien dat vele computerprogramma's, vooral dergelijke voorbeeldjes, simpelweg een serie van gevallen afhandelen en in elk ander geval een uitzondering (je leest het goed, dus een heuse exception) signaleren.

Dat jij een return-value van 0 krijgt uit een aanroep van GetLastError() hoeft nog niet veelzeggend te zijn (aangenomen dat je deze aanroep op het goede moment doet, uiteraard). Waarschijnlijk is er geen sprake van een ware af te vangen fout, het is uiteraard en gelukkig zeer zeldzaam dat die een computerprogramma als dit spontaan doen vastlopen, puur door het besturingssysteem waarop het draait.

Sterker nog, juist dat vastlopen dat jij signaleert is typisch voor een onbehoorlijke (Events)-loop. :o :D

Maar enfin, het lijkt me nu aan jou om de volledige relevante broncode aan te leveren, inclusief broncode van die voorbeelden die strijdig lijken met de MSDN-documentatie. Niet omdat men je niet gelooft, maar om te bepalen hoe maatgevend die computerprogramma's of broncodefragmenten die jij als voorbeeld neemt zijn (gelet op het merkwaardige commentaar in de broncode van de openingspost, bijvoorbeeld). :)

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
De effectieve (cq. ingekort op niet-essentiële stukken) broncode:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//Connect to COM port 3
hComPort = CreateFile(
    "\\\\.\\COM3",
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    0,
    NULL
);

//Handle errors
if (hComPort == INVALID_HANDLE_VALUE) {
    printf("error: ");
    switch (GetLastError()) {
        case ERROR_FILE_NOT_FOUND:
            printf("serial port does not exist");
            break;
        default:
            printf("unknown");
    }
    printf(".\r\n");
    return 1;
} else {
    printf("ok.\r\n");
}

//The following is purely based on reverse engineering and might
//seem useless at some points, though this is just temporary

//Retrieve timeouts (not used)
{
    COMMTIMEOUTS timeouts = {0};
    if (!GetCommTimeouts(hComPort, &timeouts)) {
        printf("error: could not get timeouts.\r\n");
        closePort();
        return 1;
    }
}

//Set parameters
{
    printf("Setting parameters...");
    DCB dcbParams = {0};
    dcbParams.DCBlength = sizeof(dcbParams);
    if (!GetCommState(hComPort, &dcbParams)) {
        printf("error: could not get state.\r\n");
        closePort();
        return 1;
    }
    dcbParams.BaudRate = CBR_115200;
    dcbParams.ByteSize = 8;
    dcbParams.StopBits = ONESTOPBIT;
    dcbParams.Parity = NOPARITY;
    dcbParams.fDtrControl = DTR_CONTROL_ENABLE;
    dcbParams.fRtsControl = RTS_CONTROL_HANDSHAKE;
    dcbParams.XonLim = 100;
    dcbParams.XoffLim = 100;
    if (!SetCommState(hComPort, &dcbParams)) {
        printf("error: could not set state.\r\n");
        closePort();
        return 1;
    }
    printf("ok.\r\n");
}

//Set timeouts
{
    printf("Setting timeouts...");
    COMMTIMEOUTS timeouts = {0};
    timeouts.ReadIntervalTimeout = 1;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    if (!SetCommTimeouts(hComPort, &timeouts)) {
        printf("error: could not set timeouts.\r\n");
        closePort();
        return 1;
    }
    printf("ok.\r\n");
}

//Set event mask
if (!SetCommMask(hComPort, EV_CTS)) {
    printf("error: failed to set event mask.\r\n");
    closePort();
    return 1;
}

//Write data to serial port
{
    char data[] = {'A', 'T', 'Z', 0x0D};
    writeToPort(data, 4);
}

//Wait for event
DWORD mask;
if (WaitCommEvent(hComPort, &mask, NULL)) {
    printf("yay!");
}

//Read data from serial port
{
    char *data = new char[16];
    readFromPort(data, 1);
}

//Close handle
closePort();

Een alternatieve versie van het stukje met SetCommMask die volgens MSDN moet kloppen:
C++:
1
2
3
4
5
6
//Set event mask
if (SetCommMask(hComPort, EV_CTS)) {
    printf("errorcode: %d", GetLastError());
    closePort();
    return 1;
}

Verwijderd

JeRa schreef op maandag 01 augustus 2005 @ 01:06:
@Cybarite

Na het lezen van jouw commentaar kreeg ik de indruk dat het oorspronkelijke programma eigenlijk niet zonder threads kon werken (het deed non-blocking voorkomen). Na wat gepruts met het monitorprogramma ben ik erachter gekomen dat de afhandeling van het dataverkeer en het instellen van events door middel van drie verschillende threads gebeurt; iets wat kennelijk benodigd is om de communicatie te bewerkstelligen (mijn opzet is nu nog lineair, non-threaded). Ik ga nu aan de slag om dit gedrag na te bootsen in mijn programma om te zien of het het gewenste effect heeft.
Tja, ik doe mijn uitspraken puur op basis van de tamelijk beperkte broncode die je tot nog toe hebt aangeleverd en mijn indruk van dat computerprogramma genaamd "Serial Monitor". Wellicht kan je daarmee bestanden uitlezen en overzenden, wellicht heeft het een aparte input-thread (bijv. vanwege de mogelijkerwijs bonte GUI). Die derde thread is wellicht de 'main-thread'.

Ook al is mijn vorenstaande schets geenszins uitputtend/maatgevend, het is op zijn minst een realistische. Ik interpreteer je opmerking "het deed non-blocking voorkomen" als zou het oorspronkelijke computerprogramma doelbewust voor een blocking-modus kiezen (non-blocking voorkomen). Volgens mij heb je je versproken, want de asynchrone afhandeling van bijv. sockets-datacommunicatie alsmede COM-datacommunicatie (niet dat ik wil stellen dat deze programmeertechnisch wezenlijk verschilt - in de praktijk tenminste) gebeurt uitsluitend via opwerping van Events onder Windows (en in deze context wordt het Event-mechanisme ook uitsluitend daarvoor aangewend). De bewuste routine van het computerprogramma waarop jij je baseert en waarvan jij kennelijk de broncode (deels) hebt aangeleverd, werkt ook met Events, en berust dus op het principe van asynchrone verwerking. Hoewel een computerprogramma als het onderhavige onder Windows strikt genomen weldegelijk kan functioneren 'zonder threads' (dat wil zeggen: zonder eigenhandige multi-threading), zal dit inderdaad een praktisch onwerkbare situatie opleveren: soms zit de datacommunicatie muurvast, en die sleept daarin het hele computerprogramma mee de figuurlijke afgrond in.
De wijziging in functionaliteit is:
- volledige controle over het dataverkeer en het gebruik van het protocol tussen de PC en mijn GSM
- uitbreiding van functionaliteit (andere programma's bieden slechts een gedeelte van de beschikbare functionaliteit, dit wil ik combineren tot één programma)
- bepaalde softwarematige beperkingen elimineren
- data van mijn GSM direct gebruiken in andere toepassingen zonder interactie van de gebruiker
Goed, niet alles wat je daar opsomt is relevant voor jouw specifieke probleem, derhalve heb je deze opsomming niet in eerste instantie gegeven, begrijp ik. Waar het mij echter vooral om gaat is de concrete wijziging in broncode, teneinde te bepalen wat jouw variant van het oorspronkelijke computerprogramma defecteerde. Wel, eigenlijk kun je dat op talloze manieren bewerkstelligen (neem bijv. dat "bepaalde softwarematige beperkingen elimineren", wie weet wat je per abuis zoal hebt aangericht)... Natuurlijk ben je niet zonder meer bereid je integrale broncode te publiceren, maar in principe is dat ook niet nodig. Het gaat mijns inziens vooral om de routine (in de ruime zin van het woord) die volgt uit het in de openingspost verstrekte broncodefragment. Wil je dat fragment alsjeblieft aanvullen (dus gaarne niet gedeeltes als de WriteFile()-aanroep(en) weglaten)?

P.S.
Voor (het merendeel van) jouw doeleinden bestaat als ik me niet vergis reeds een serieus (en open-source) computerprogramma. Zie: http://tulp2g.sourceforge.net/. Maar goed, op een oude fiets moet je het leren, zullen we maar zeggen. :P

Verwijderd

Hehe, een vooruitziende blik ontbeert je in ieder geval niet. :D

Morgen laat ik m'n licht wel erover schijnen. Maar er komen hier mensen met meer kennis van zaken wat COM-datacommunicatie in C++ betreft, dus een oplossing is nabij. ;)

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Met 'deed voorkomen' bedoelde ik dat het leek alsof het programma non-blocking was, en de main thread dus niet wachtte totdat er een event was maar soepel verder liep.

Ik verander geen broncode, ik maak een programma from scratch puur gebaseerd op wat ik zie gebeuren op de compoort zodra ik het oorspronkelijke programma start :)

edit: Tulp2G werkt voor geen meter, cruciale instellingen (zoals de COM-poort en de snelheid) worden vergeten terwijl het programma draait en pogingen om contact te maken met mijn GSM falen op elke mogelijke manier.

[ Voor 27% gewijzigd door JeRa op 01-08-2005 02:16 ]


Verwijderd

Okay, maar dan even voor de duidelijkheid: hoe kom je aan die broncode die je als uitgangspunt gebruikt dan? Ik zie dat dat computerprogramma in alle mogelijke vormen heel wat dollars kost, en neem aan dat men niet vrijelijk over de broncode ervan mag beschikken...

/me gaat nu echt slapen :D

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Verwijderd schreef op maandag 01 augustus 2005 @ 02:20:
hoe kom je aan die broncode die je als uitgangspunt gebruikt dan?
Waar zeg ik dat ik broncode als uitgangspunt heb gebruikt? :)

Ik heb dus een serial port monitor gebruikt om te zien wat een programma uitvoert op COM-poort nummer 3, en adhv. documentatie (oa. MSDN) bovenstaande broncode in elkaar geknutseld. Deze broncode is alles behalve netjes, maar het ging mij om het uit zien te vogelen van het opstarten van datacommunicatie tussen m'n PC en m'n GSM :) dit omdat ik het in andere talen al eerder had geprobeerd maar faalde om een onbekende reden. Ik besloot het opnieuw te maken in C++ aangezien ik op die manier mijns inziens de meeste controle over het proces had.

Verwijderd

JeRa schreef op maandag 01 augustus 2005 @ 02:36:
[...]

Waar zeg ik dat ik broncode als uitgangspunt heb gebruikt? :)

Ik heb dus een serial port monitor gebruikt om te zien wat een programma uitvoert op COM-poort nummer 3, en adhv. documentatie (oa. MSDN) bovenstaande broncode in elkaar geknutseld. Deze broncode is alles behalve netjes, maar het ging mij om het uit zien te vogelen van het opstarten van datacommunicatie tussen m'n PC en m'n GSM :) dit omdat ik het in andere talen al eerder had geprobeerd maar faalde om een onbekende reden. Ik besloot het opnieuw te maken in C++ aangezien ik op die manier mijns inziens de meeste controle over het proces had.
Aha, maar je schreef o.a. "reverse-engineeren" en "zoals uit het middelste stuk blijkt"... Mijn interpretatie van dat laatste is natuurlijk een dwaling mijnerzijds, maar in combinatie met het eerste kreeg ik de indruk dat je aan een broncodefragment was gekomen en op basis daarvan uitspraken deed (na wijziging her en der). Je bedoelde echter jouw eigen programmering (voortgekomen uit je bevindingen met Serial Monitor) uit te leggen. Goed, misverstand dus. Ik ga nu alles herlezen teneinde een oplossing te vinden. ;)

Verwijderd

Okay, mijn eerste suggestie volgt nu.

Wat me direct opvalt is jouw instelling van die DCB-struct. Het is dus een "device control-block" volgens MSDN, geen "driver control-block" zoals ik in eerste instantie vermoedde, maar het concept is gelijkwaardig. Nu begrijp ik de uitleg waarnaar ik eerst verwees beter: een Event van het type "EV_RXFLAG" wordt opgeworpen indien een specifiek character ontvangen wordt, dat in de DCB-struct dient te worden opgegeven. Op zich is dat wel toepasbaar voor jouw computerprogramma, bijv. als je GSM-telefoon regelmatig een 'ontwakingscode' verzendt naar de desbetreffende datacommunicatiepartner. Goed, eigenlijk zou je meer hebben aan een ontwakingscode verzonden van je PC naar je GSM-telefoon, maar in principe heb je niet de controle over de ontvangsroutines in de software van je GSM-telefoon (die overigens direct via USB datacommuniceert, dus dat zou meteen een graadje lastiger worden :D). :P Maar dat allemaal terzijde...

Ik zie in bijv. de Baud-ratio die jij in je DCB-struct instelt een mogelijk oorzaak van je probleem. Vergeet mijn opmerkingen over kieskeurigheid niet...
Probeer het eens met de volgende DCB-instellingen in de DCB-struct.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dcbParams.BaudRate = CBR_9600;
dcbParams.fBinary = TRUE;
dcbParams.fParity = FALSE;
dcbParams.fOutxCtsFlow = FALSE;
dcbParams.fOutxDsrFlow = FALSE;
dcbParams.fDtrControl = DTR_CONTROL_DISABLE;
dcbParams.fDsrSensitivity = FALSE;
dcbParams.fTXContinueOnXoff = TRUE;
dcbParams.fOutX = FALSE;
dcbParams.fInX= FALSE;
dcbParams.fErrorChar = FALSE;
dcbParams.fNull = FALSE;
dcbParams.fRtsControl = RTS_CONTROL_DISABLE;
dcbParams.fAbortOnError = TRUE; // Zie documentatie voor correcte afhandeling!
dcbParams.wReserved = 0;
dcbParams.Parity = NOPARITY;
dcbParams.ByteSize = 8;
dcbParams.StopBits = ONESTOPBIT;

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Die DCB parameters leverden geen resultaat. De baud rate van 115200 is bewust gekozen omdat de COM poort sowieso weigerde bij andere rates, en omdat andere programma's die wél contact kunnen maken met mijn GSM prima werken op deze baud rate.

Verwijderd

Heb je overigens die examples van Microsoft zelf al eens toegepast?
(Zie: http://msdn.microsoft.com...communications_events.asp)

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Dat ga ik zo eens doen. Overigens, het valt op dat zij SetCommMask op een manier gebruiken die tegenstrijdig is met de MSDN, wat ik eigenlijk al verwachtte.

Ik zie nu in de monitor dat beide programma's die wél werken een zogenaamde IOCTL_SERIAL_SET_QUEUE_SIZE uitvoeren. Dit doet mijn programma niet, dus misschien moet er een queue worden ingesteld voordat het gaat werken. Echter kan ik niet vinden welke functie ervoor zorgt dat een SET_QUEUE_SIZE wordt uitgevoerd?

edit: het voorbeeld dat je gaf blijft hangen op WaitCommEvent. pointer vergeten mee te geven, sorry. Ik ga nog even knutselen, hij doet al meer :)

[ Voor 17% gewijzigd door JeRa op 01-08-2005 17:02 ]


Verwijderd

Zie: http://msdn.microsoft.com.../devio/base/setupcomm.asp voor die functie.

Ik vroeg me af hoe je aan die "IOCTL_SERIAL_SET_QUEUE_SIZE" zou komen, na gebruik van Portmon begrijp ik dat je dat in Serial Monitor te zien krijgt.

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    HANDLE hComPort;
    OVERLAPPED ovr;

{
    //Connect to COM port 3
    hComPort = CreateFile(
        "\\\\.\\COM3",
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL
    );
    if (hComPort == INVALID_HANDLE_VALUE) {
        //Handle the error
        printf("error: ");
        switch (GetLastError()) {
            case ERROR_FILE_NOT_FOUND:
                printf("serial port does not exist");
                break;
            default:
                printf("unknown");
        }
        printf(".\r\n");
        return 1;
    } else {
        printf("ok.\r\n");
    }

    //Set parameters
    {
        printf("Setting parameters...");
        DCB dcbParams = {0};
        dcbParams.DCBlength = sizeof(dcbParams);
        if (!GetCommState(hComPort, &dcbParams)) {
            printf("error: could not get state.\r\n");
            closePort();
            return 1;
        }
        dcbParams.BaudRate = CBR_115200;
        dcbParams.ByteSize = 8;
        dcbParams.StopBits = ONESTOPBIT;
        dcbParams.Parity = NOPARITY;
        dcbParams.fDtrControl = DTR_CONTROL_ENABLE;
        dcbParams.fRtsControl = RTS_CONTROL_HANDSHAKE;
        dcbParams.XonLim = 100;
        dcbParams.XoffLim = 100;
        if (!SetCommState(hComPort, &dcbParams)) {
            printf("error: could not set state.\r\n");
            closePort();
            return 1;
        }
        printf("ok.\r\n");
    }

    //Set timeouts
    {
        printf("Setting timeouts...");
        COMMTIMEOUTS timeouts = {0};
        timeouts.ReadIntervalTimeout = 1;
        timeouts.ReadTotalTimeoutConstant = 0;
        timeouts.ReadTotalTimeoutMultiplier = 0;
        timeouts.WriteTotalTimeoutConstant = 0;
        timeouts.WriteTotalTimeoutMultiplier = 0;
        if (!SetCommTimeouts(hComPort, &timeouts)) {
            printf("error: could not set timeouts.\r\n");
            closePort();
            return 1;
        }
        printf("ok.\r\n");
    }

    //Setup queue size
    SetupComm(hComPort, 1024, 1024);

    //Create an event object
    ovr.hEvent = CreateEvent(
        NULL,
        FALSE,
        FALSE,
        NULL
    );
    ovr.Internal = 0;
    ovr.InternalHigh = 0;
    ovr.Offset = 0;
    ovr.OffsetHigh = 0;

    //Read buffer
    {
        char *data = new char[256];
        readFromPort(data, 256);
    }

    //Set event mask
    setEventMask(EV_CTS);

    //Wait for event
    DWORD mask;
    WaitCommEvent(hComPort, &mask, &ovr);

    //Write data to serial port
    {
        char data[] = {'A', 'T', 'Z', 0x0D};
        writeToPort(data, 4);
    }

    //Close handle
    closePort();
}

unsigned int readFromPort(char *data, unsigned int len)
{
    DWORD bytesRead;
    ReadFile(hComPort, data, len, &bytesRead, &ovr);
    return bytesRead;
}

unsigned int setEventMask(DWORD mask)
{
    if (!SetCommMask(hComPort, mask)) {
        printf("error: SetCommMask failed with error %d.\r\n", GetLastError());
        closePort();
        exit(1);
    }
}

unsigned int writeToPort(char *data, unsigned int len)
{
    DWORD bytesRead;
    WriteFile(hComPort, data, len, &bytesRead, &ovr);
    return bytesRead;
}

Dit is wat ik nu heb, en dit lijkt véél meer op het gedrag van de bestaande programma's. Overlapped IO zorgt automatisch voor aparte threads waardoor die ook in Serial Monitor als aparte threads te zien zijn. Afgezien van het feit dat ik niet meer bij *data kan door de scope, krijg ik geen respons van mijn GSM, terwijl IRP_MJ_READ een keer terug zou moeten komen met data. Ook als ik een WaitCommEvent na het schrijven naar de serial port plaats met een EV_RXCHAR krijg ik geen data binnen.
Ik vroeg me af hoe je aan die "IOCTL_SERIAL_SET_QUEUE_SIZE" zou komen, na gebruik van Portmon begrijp ik dat je dat in Serial Monitor te zien krijgt.
Nou, uit Serial Monitor dus. Vlak na het instellen van de timeouts roepen de bestaande programma's kennelijk SetupComm aan met twee keer de waarde 1024 voor de queues, en uit na-apend standpunt doe ik dat maar na ;)

Overigens _O_ voor je hulp tot nu toe :)

[ Voor 20% gewijzigd door JeRa op 01-08-2005 20:35 ]


Verwijderd

Hmm, maar flow control heb je nu dus ingeschakeld door middel van je DCB-struct... Wellicht is het zo dat niet zonder meer data wordt teruggezonden maar dat (eerst) CTS of DSR-signalen worden verstuurd, en daarvoor geen Event opgeworpen wordt vanwege je beperkte Event-mask.

Probeer het volgende eens:

C++:
1
setEventMask(EV_BREAK | EV_CTS | EV_DSR | EV_ERR | CE_RXPARITY | EV_RING | EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY);
, en verderop in je broncode:
C++:
1
2
3
WaitCommEvent(hComPort, &mask, &ovr);

printf("Wat heb ik me nou daar! Een Event van het type: "%X".\r\n", mask); 
.

P.S.:
De verwerking van die code-tags laat te wensen over... ;)

[ Voor 4% gewijzigd door Verwijderd op 01-08-2005 21:12 ]


  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Die code-tags werken perfect hoor ;) quotes within quotes enzo. Ik krijg het volgende op m'n scherm:

Wat heb ik me nou daar! Een Event van het type: 0.

Lijkt me een beetje vreemd eigenlijk. Met WaitCommEvent geef ik 'm de instructie te wachten op een event, en vervolgens blijkt er geen event te zijn? :?

Verwijderd

Ai, in C++ waren "-tekens bestemd voor het afbakenen van strings. ;( |:(

Oftewel:
C++:
1
2
3
WaitCommEvent(hComPort, &mask, &ovr);

printf("Wat heb ik me nou daar! Een Event van het type: '%X'.\r\n", mask);
, dat zou 'correct' moeten functioneren. Wat niet wil zeggen dat je resultaat nu ineens zal veranderen, mijn broncode-voorbeeldje was in ieder geval gewoon brak. :D

Maar goed, in MSDN staat dat er ingeval er een fout optreedt de waarde van de variabele die de Event-mask dient te ontvangen gelijk is aan 0... Bovendien is in het geval dat een "overlapped operation" niet direct kan worden uitgevoerd de return-value van WaitCommEvent() gelijk aan FALSE.

C++:
1
2
3
4
5
6
if (WaitCommEvent(hComPort, &mask, &ovr)) {
char data[] = {'A', 'T', 'Z', 0x0D};
writeToPort(data, 4);
} else {
printf("Er is een fout opgetreden bij het afwachten van opwerping van een Event, namelijk: %X.\r\n", GetLastError());
}


Misschien dat dit toch behoorlijk werkt (en MSDN dus geen foute vermelding heeft), maar dat er verwarring optrad omtrent de afzonderlijke 'foutcodes' die deze functie oplevert? :?

P.S.:
De code-tags maken van "C++" "c++", en mijn pogingen om ze in een zin te passen door leestekens erachter te plaatsen ging duidelijk de mist in. Verder kan ik geen indentation toepassen bij handmatig ingevoerde broncode, maar dat is te wijten aan dit invoervenstertje. :P

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Hij geeft errorcode 997, wat uit mijn hoofd (want ik ben 'm al duizenden keren tegengekomen :+) ERROR_IO_PENDING betekent :)

Verwijderd

Haha. :D

Sluit dat Serial Monitor e.d. eerst eens af dan...

En sla je vervolgens voor je hoofd. :+

Mijn ervaring is in ieder geval dat wanneer een COM-poort is geopend door een computerprogramma onder Windows, dit als het ware de file-handle lockt, dat message slot "\\\\.\\COM3" dus...

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Ik wil me best voor m'n hoofd slaan, maar m'n programma geeft nog steeds ERROR_IO_PENDING en de bestaande programma's voor m'n gsm blijven gewoon werken, ook al werken er tien programma's op COM3 tegelijkertijd. Ik zie iets heel simpels over het hoofd, want zelfs de HyperTerminal onder Windows kan zonder moeite contact leggen ;(

Verwijderd

Hey Jelle,

Ik heb 'vandaag' geen tijd meer gevonden, maar een aanwijzing: http://msdn.microsoft.com...p/completion_routines.asp.

(Vooral de tabel is veelzeggend als je jouw situatie ermee vergelijkt, als ik in alle vluchtigheid geen dingen over het hoofd zie. In ieder geval lijkt het me dat je je file in overlapped-modus dient te openen vergezeld van alle benodigde extra broncode.)

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Bedankt voor de link, zoals je in m'n laatste broncode kunt zien had ik de hele boel al in overlapped-modus geopend wat ervoor zorgt dat alles inderdaad asynchroon is (ik zie meerdere threads voorbijkomen). Ik zal als ik vanavond terug ben van werk eens kijken welke waardes ik allemaal terugkrijg.

edit: overigens ben ik wel benieuwd naar hoe je aan m'n voornaam komt (is niet lastig, maar misschien kennen we elkaar ergens van? :+)

[ Voor 20% gewijzigd door JeRa op 03-08-2005 11:17 ]


Verwijderd

Okay, dan weer een hap-snap-voorstel mijnerzijds om tot een oplossing te komen: misschien is het zinnig om nu die flow control functies (DTR, RTS) die je in je DCB-struct hebt ingesteld weer eens tijdelijk uitzetten? Wellicht heeft dat gevolgen voor je routines namelijk, zoals ik eerder vermoedde.

Het is spijtig dat ik hier niet je C++-compiler paraat heb, en niet echt veel aaneengesloten vrije tijd heb. Niet dat ik denk dat ik het allemaal wel even oplos hoor (:X), maar ik heb vroeger ook wel eens geklooid met seriele datacommunicatie in ik meen de programmeeromgeving Delphi, en in de nabije toekomst kan ik het waarschijnlijk wel gebruiken. Zeker na jouw ervaringen ben ik van plan om ditalles platform-onafhankelijk te maken. Vaag staan me namelijk routines bij die stukken eenvoudiger in elkaar zitten, die voor UNIX waren geprogrammeerd. Dit gemijmer van me heeft op zich best wel een nut: het verkrijgen van het inzicht dat het misschien helemaal zo gek nog niet is om je routine synchroon te maken (op de klassieke manier(en) dus). Ik heb begrepen dat je al multithreading uit je mouw hebt geschud alsof het niets is (in het beste geval althans :+), wellicht is het een idee om naast je huidige broncode een (volgende :+) variant te scheppen die een eigen IO-thread heeft en die ook als zodanig gebruikt? In ieder geval weet ik wel dat ik het programmeren van asynchrone sockets onder Windows (wat ook met behulp van die Events functioneert) me vele hoofdbrekens heeft opgeleverd, van eenzelfde karakter als de jouwe (vreemde non-reactiviteit in combinatie met een gebrek aan (interessante) foutcodes).

P.S.
Je naam zat gewoon in mijn hoofd. :D Die zal je wel ooit eens op FOK! hebben losgelaten. ;)

Verwijderd

Ben je nog iets opgeschoten? :)

  • JeRa
  • Registratie: Juni 2003
  • Laatst online: 30-04-2025
Heb de afgelopen week even geen tijd gehad vanwege een ander project, maar wellicht ga ik deze week weer eens verder :)

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 02-05 15:31
Man, je werkt wel in een behoorlijk rare volgorde. Als je nou eens zou beginnen met blocking io, zonder verschillende threads? En waarom gebruik je niet gewoon "COM3:" als device ipv een handle naar de driver?

Als jouw communicatie niet op gang wil komen moet je je code simpeler maken, niet moeilijker.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Verwijderd

farlane schreef op dinsdag 09 augustus 2005 @ 10:19:
Man, je werkt wel in een behoorlijk rare volgorde. Als je nou eens zou beginnen met blocking io, zonder verschillende threads? En waarom gebruik je niet gewoon "COM3:" als device ipv een handle naar de driver?

Als jouw communicatie niet op gang wil komen moet je je code simpeler maken, niet moeilijker.
Gewone synchrone oftewel blocking IO lijkt mij inderdaad ook de te bewandelen weg, mocht dit Ev ents-mechanisme met geen mogelijkheid aan de praat te krijgen blijken (daar kun je zo onderhand wel van spreken... hij heeft ook redelijk minutieus gedebugged heb ik de indruk).

Hij gebruikt een file-handle. Hoe bedoel je eigenlijk "handle naar de driver"?

[ Voor 3% gewijzigd door Verwijderd op 09-08-2005 22:10 ]


Verwijderd

Zowiezo knap dat je meerdere threads voorbij ziet komen als je nergens een thread aanmaakt :9

Je bent al aardig op weg maar ik zou aanraden om het volgende te doen:

Ten eerste een aparte thread aanmaken voor het reageren op events wanneer je het commask EV_RXCHAR gebruikt. Verder zou ik ook twee events aanmaken. 1 voor lezen en 1 voor schrijven. Deze globaal declareren:

code:
1
OVERLAPPED sRead_Event,sWrite_Event;


Om de thread globaal in de gaten te houden ook nog twee variablen voor de thread:

code:
1
2
HANDLE hTHREAD;
DWORD  dwTHREADID;


Tot aan setupcomm is alles ok. Na setupcom de events initialiseren met:

code:
1
2
3
4
5
6
7
8
9
   sRead_Event = CreateEvent( NULL,    // no security
                                 TRUE,    // explicit reset req
                                 FALSE,   // initial event reset
                                 NULL ) ; // no name

   sWrite_Event = CreateEvent( NULL,    // no security
                                  TRUE,    // explicit reset req
                                  FALSE,   // initial event reset
                                  NULL ) ; // no name


Na deze initialisatie de thread oproepen. Ik geef voor de zekerheid ook nog even een purgecomm command zodat de buffers goed staan:
code:
1
PurgeComm( hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;


De thread wordt aangeroepen:

code:
1
2
3
4
5
6
7
8
9
10
11
12
HANDLE hReadThread;
DWORD  dwThreadId;

if (NULL == (hReadThread = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)ReadCommThread, 0, 0, &dwThreadId)))
{
  CloseHandle (hCom);
}
else
{
  hTHREAD = hReadThread; // global settings
  dwTHREADID = dwThreadId; // global settings
}


vanaf dit punt wordt de thread gestart welke steeds bekijkt of er een read event getrigger wordt. Je kunt met de taskmanager bekijken of je thread gestart (of beeindigd) wordt in je programma:

taksman opstarten -> view menu -> select columns -> thread count aanvinken.

Vervolgens de ReadCommThread functie. Die zou je als volgt op kunnen bouwen:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int ReadCommThread(LPSTR lpData)
{

 OVERLAPPED  threadEvent ;
 int        nLength ;
 memset( &threadEvent , 0, sizeof( OVERLAPPED ) ) ;

  BYTE       bHolder[513] ; 
  memset(bHolder,0,sizeof(bHolder));
  DWORD dwEventMask = 0;

  DWORD dwIncommingReadSize = 0;
  DWORD dwTotalSize = 0;

   threadEvent.hEvent = CreateEvent( NULL,    // no security
                            TRUE,    // explicit reset req
                            FALSE,   // initial event reset
                            NULL ) ; // no name

  SetCommMask(hCom, EV_RXCHAR); // Catch recieve events

  while ((hCom != INVALID_HANDLE_VALUE)) // Check if COM port is connected
  {
    dwEventMask = 0;
    WaitCommEvent(hCom, &dwEventMask, NULL); // Wait for event

    if ((dwEventMask & EV_RXCHAR) == EV_RXCHAR) // Event is happening
    {
      do
      {
        if (nLength = ReadBlock((LPSTR) bHolder, 512 ))
        {
          // Do something with the received value
        }
      } while (nLength > 0);

    }

  }

    CloseHandle( threadEvent.hEvent ) ;

   // if thread is ending clear values
        dwTHREADID = 0;
        hTHREAD = NULL;

  return 1;
}


Zoals je ziet wanneer je een event krijgt zal de functie ReadBlock opgeroepen worden. Deze functie probeert de hele read buffer in 1x in te lezen en te verwerken. Hierin komt ook je probleem met ERROR_IO_PENDING terug. Dit wil in de meeste gevallen zeggen dat er nog nog IO bezig is dus het is niet echt een foutmelding (afhankelijk van de foutwaarde). Ook zul je in deze functie je globale read event zien terugkomen.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int ReadBlock(LPSTR lpszBlock, int nMaxLength)
{
   BOOL       fReadStat ;
   COMSTAT    ComStat ;
   DWORD      dwErrorFlags;
   DWORD      dwLength;
   DWORD      dwError;
   char       szError[ 10 ] ;



   //sleep(1000); //buffer timing 

   ClearCommError( hCom, &dwErrorFlags, &ComStat ) ;   // only try to read number of bytes in queue
   dwLength = min( (DWORD) nMaxLength, ComStat.cbInQue ) ; 

   if (dwLength > 0)
   {
        fReadStat = ReadFile( hCom, lpszBlock,dwLength, &dwLength, &sRead_Event ) ;
        if (!fReadStat)
        {
          if (GetLastError() == ERROR_IO_PENDING)
          {
            while(!GetOverlappedResult( hCom, &sRead_Event , &dwLength, TRUE ))
            {
              dwError = GetLastError();
              // normal result if not finished
              if(dwError == ERROR_IO_INCOMPLETE)
                continue;
              else
              { // an error occurred, try to recover
                ClearCommError(hCom, &dwErrorFlags, &ComStat);
                break;
              } // else
            } // while
          } //if
          else
          { // some other error occurred
            dwLength = 0 ;
            ClearCommError(hCom, &dwErrorFlags, &ComStat);
          }//else

        } // if
   } // if dwlength

  return ( dwLength );
}


Zoals je ziet wordt hier de buffer volledig ingelezen. Het resultaat wordt in een variabele gezet en de hoeveelheid uitgelezen bytes wordt als return waarde meegegeven. Er is ook de mogelijkheid om byte voor byte in te lezen. Dit zul je dan in je thread meteen kunnen doen door readfile met als inleesgrootte 1 mee te geven. Om commando's te versturen zou je bijvoorbeeld console input kunnen sturen naar writefile. Ik gebruik zelf een GUI dus zul je dat stuk zelf moeten invullen.

Het is ook erg belangrijk dat wanneer je stopt je de thread goed afsluit. Je zou een functie kunnen maken om dit te doen:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int TerminateCommWatchThread(void)
{
  // This is needed to get WaitCommMask out of blocking the thread 
  SetCommMask( hCom, 0 ) ;

  // Drop DTR
  EscapeCommFunction( hCom, CLRDTR);

   // Purge 
  PurgeComm( hCom, PURGE_TXABORT|PURGE_RXABORT| PURGE_TXCLEAR|PURGE_RXCLEAR);

  CloseHandle( sRead_Event.hEvent ) ;
  CloseHandle( sWrite_Event.hEvent ) ;
  CloseHandle(hTHREAD) ;
  CloseHandle(hCom);
  return 1;
}


Gebruik portmonitor van sysinternals om te controleren of alles goed verloopt en wat je schrijft en ontvangt.

Ow ja een kleine aanvulling. Is het niet zo dat (tenminste de oudere modellen weet ik het wel van) dat de connectie van een GSM maar tot 9600 baud aankan ? Het kan zijn dat nieuwere modellen snellere rates hebben, maar dat zou je in de manual kunnen vinden.

Suc6 !

[ Voor 3% gewijzigd door Verwijderd op 20-08-2005 14:44 ]

Pagina: 1