c++ - How do I correctly use COMMTIMEOUTS with OVERLAPPED IO mode reading from a Serial port -
i trying use overlapped io mode on windows 7/8 x64 emulate non blocking mode (io_nonblock) behavior supported linux's open flags. code here part of windows portion of cross platform serial api.
i can open comm port in either blocking or non blocking (overlapped) mode using constructor parameters serialcommwnt object. far question goes questions have when comm port opened in overlapped mode (as specified flow control constructor parameter). read method, specify timeout parameter, upon retrieving @ least 1 byte of data serial port, should indicate time remaining of rtimeout parameter when data in serial comm's input buffer (i believe serial driver notifies manual reset event in overlapped structure when receives data).
i read many stackoverflow threads on how handle these apis, many of them refer microsoft win32 api. best information can find far
http://msdn.microsoft.com/en-us/library/ff802693.aspx api confusing overlapped io (expecially when comes passing pointer number of bytes received when calling readfile in overlapped mode), far can tell, none of them addresses how correctly use overlapped io mode in combination commtimeouts. spent last couple of days experimenting combinations of settings both commtimeouts , timeout parameter used ::waitforsingleobject. combination seems work shown. have reliability questions regard timeouts associated manual reset event object associated overlapped io structure , commtimeouts. not entirely sure, seems in order timeout work correctly when reading form serial port, mandatory specify timeout in commtimeouts. tried combination disabled timeouts in setcommtimeouts , instead used explicit timeout in ::waitforsingleobject's timeout parameter, did not work, instead did other way around specifying timeout in commtimeouts , specifying infinite ::waitforsingleobject method call. i'm not sure if there can situations hang forever , if how can handle this. appreciate info on how correctly handle potentially hanging here.
here method use open comm port - in case have timeout questions, specify file_flag_overlapped.
/** * open serial port using parameters set in theconstructor.<p> * port number, speed, overlapped io mode, #data bits & * async mode etc. specified constructor arguments. * * @return os_failed, os_success */ osstatus serialcommwnt::open() { // critical section std::lock_guard<std::recursive_mutex> lock (mmutexguard); osstatus result = os_failed; std::ostringstream os; os << "\\\\.\\com" << mcommport; std::string devicename = os.str(); dword dwflagsandattrs = (mflowcontrol == serialcommbase::fctl_overlapped)? file_flag_overlapped : 0; // open underlying device read , write mosfilehandle = createfile ( devicename.c_str(), generic_read | generic_write, 0, //(share) 0:cannot share com port null, // no security attributes open_existing, // comm devices must use open_existing dwflagsandattrs, // optional file_flag_overlapped null); // htemplate must null comm devices if ( mosfilehandle != invalid_handle_value ) { // reserve 8k communications channel buffer (both directions) bool isok = setupcomm(mosfilehandle, 8200, 8200); // omit call setupcomm use default queue sizes. // current configuration. dcb dcb; securezeromemory(&dcb, sizeof(dcb)); isok = getcommstate (mosfilehandle, &dcb); if (isok) { // fill in dcb: baud=125000, 8 data bits, parity, 1 stop bit. // standard baud rate. card have has custom crystal // changing baud rate 125k. dcb.baudrate = static_cast<dword>(mbaudrate); dcb.bytesize = static_cast<byte>(mbytesize); // enum values ame dcb.parity defines dcb.parity = static_cast<byte>(mparity); dcb.fparity = (mparity == serialcommbase::prty_none)? false : true; dcb.stopbits = onestopbit; // ---------------------------------------------------- // when running in win32 loopback simulator // in loopback mode, must enable rts/cts handshake // mode there seems 4k limit in input // buffer when dbu simulator performs reads. // ---------------------------------------------------- if (mflowcontrol == serialcommbase::fctl_rts_cts) { dcb.foutxctsflow = 1; dcb.frtscontrol = rts_control_handshake; } // not absolutely necessary dtr_control_disable default dcb.fdtrcontrol = dtr_control_disable; isok = setcommstate (mosfilehandle, &dcb); if (isok) { commtimeouts commtimeouts; securezeromemory(&commtimeouts, sizeof(commtimeouts)); // these settings cause readfile return // if there no data available @ port // value of maxdword, combined 0 values both // readtotaltimeoutconstant , readtotaltimeoutmultiplier members, // specifies read operation return // bytes have been received, if no bytes // have been received. //isok = getcommtimeouts (mosfilehandle, &commtimeouts); commtimeouts.readintervaltimeout = maxdword; // readtotaltimeoutconstant - when set ms timeout value // in conjunction readintervaltimeout == maxdword && // readtotaltimeoutmultiplier set 0 used control // timeout read operation. each time read // timeout called, compare existing timeouts in commtimeouts // before changing it. commtimeouts.readtotaltimeoutconstant = 0; commtimeouts.readtotaltimeoutmultiplier = 0; // timeouts not used write operations commtimeouts.writetotaltimeoutconstant = 0; commtimeouts.writetotaltimeoutmultiplier = 0; isok = setcommtimeouts (mosfilehandle, &commtimeouts); if (isok) { // test asynchronous mode if (mflowcontrol == serialcommbase::fctl_overlapped) { // allocate & initialize overlapped // structure support rx & tx mpoverlappedtx.reset(new(overlapped)); mpoverlappedrx.reset(new(overlapped)); if (mpoverlappedtx && mpoverlappedrx) { securezeromemory(mpoverlappedtx.get(), sizeof(overlapped)); securezeromemory(mpoverlappedrx.get(), sizeof(overlapped)); // create unsignaled manual reset (2nd param true) // event used getoverlappedresult. event // signaled readfile indicate when // io operations complete or encounter errors mpoverlappedtx->hevent = createevent( null, true, false, null); if (mpoverlappedtx->hevent != null) { // same rx side mpoverlappedrx->hevent = createevent( null, true, false, null); if (mpoverlappedrx->hevent != null) { setstate(comm_opened); result = os_success; } else { result = handleerror(devicename); } } else { result = handleerror(devicename); } // close handle , set error if (result != os_success) { close(); setstate(comm_open_failed); } } else { // close handle , overlapped event close(); setstate(comm_open_failed); result = os_no_memory; } } else { // blocking mode setstate(comm_opened); result = os_success; } } else { result = handleerror(devicename); close(); } } else { // unable set baud rate or result = handleerror(devicename); close(); } } } else { result = handleerror(devicename); close(); } return result; }
here code performs timed read
/** * read block of data specified raw buffer. * see http://msdn.microsoft.com/en-us/library/ms810467(v=msdn.10).aspx * details overlapped io usage, in particular note setting * timeout each time tricky. * * @param pdata [in/out] data buffer * @param rnumbytes [in] buffer size * @param rtimeout [in/out] timeout specified in milliseconds. * parameter updated reflect * remaining time. * @param rnumbytesread * [out] number of bytes read * * @return os_success, os_wait_timeout, os_invalid_argument or * os_failed */ osstatus serialcommwnt::read( void* pdata, const size_t& rnumbytes, milliseconds& rtimeout, size_t& rnumbytesread) { osstatus result = os_wait_timeout; rnumbytesread = 0; dword numbytesread = 0; dword commerror; comstat commstatus; auto starttime = system_clock::now(); if (mpoverlappedrx) { // update timeout used readfile - note // magic combination works absolute timeout // maxdword, timeoutms, 0. commtimeouts commtimeouts; getcommtimeouts(mosfilehandle, &commtimeouts); if (commtimeouts.readtotaltimeoutconstant != rtimeout.count()) { commtimeouts.readintervaltimeout = maxdword; commtimeouts.readtotaltimeoutconstant = static_cast<dword>(rtimeout.count()); setcommtimeouts (mosfilehandle, &commtimeouts); } // asynchronous overlapped io mode. // reset manual event non-signaled. // no need readfile resets // resetevent(mpoverlappedrx->hevent); bool isok = readfile( mosfilehandle, pdata, (dword)rnumbytes, reinterpret_cast<dword*>(&rnumbytesread), mpoverlappedrx.get()); // result date - valid call // if readfile returns !isok (false) && // last error set error_io_pending //milliseconds elapsedtime; if (!isok) { dword dwlasterror = getlasterror(); if (dwlasterror == error_io_pending) { // pending io, wait complete using commtimeouts timer. // when commtimeouts timer expires signal // manual mpoverlappedrx->hevent dword ovlstatus = ::waitforsingleobject( mpoverlappedrx->hevent, static_cast<dword>( /*rtimeout.count()*/infinite)); switch (ovlstatus) { case wait_timeout: // timeout - update remaining time 0 rtimeout = milliseconds::zero(); result = os_wait_timeout; //elapsedtime = duration_cast<milliseconds>( // system_clock::now() - starttime); break; case wait_object_0: // have data avaialable // read overlapped io isok = ::getoverlappedresult( mosfilehandle, mpoverlappedrx.get(), reinterpret_cast<dword*>(&rnumbytesread), false); result = (isok && rnumbytesread>0)? os_success : os_failed; //elapsedtime = duration_cast<milliseconds>( // system_clock::now() - starttime); // update remaing time (cannot < 0) rtimeout = std::max<milliseconds>( rtimeout - duration_cast<milliseconds>( system_clock::now() - starttime), milliseconds::zero()); break; default: rtimeout = milliseconds::zero(); break; } } else if (dwlasterror == error_handle_eof) { clearcommerror(mosfilehandle, &commerror, &commstatus); result = os_file_eof; } else { clearcommerror(mosfilehandle, &commerror, &commstatus); result = os_failed; } } else { // success //elapsedtime = duration_cast<milliseconds>( // system_clock::now() - starttime); rtimeout = std::max<milliseconds>( rtimeout - duration_cast<milliseconds>( system_clock::now() - starttime), milliseconds::zero()); result = os_success; } } else { // sync mode bool isok = readfile ( mosfilehandle, pdata, (dword)rnumbytes, reinterpret_cast<lpdword>(&numbytesread), null); if ( isok && (numbytesread > 0) ) { rnumbytesread = (size_t) numbytesread; result = os_success; } else { clearcommerror(mosfilehandle, &commerror, &commstatus); // @jc changed simple test if lperrors == 9) // equivalent (ce_break | ce_rxover) //if ((lperrors & (ce_break | ce_frame | ce_overrun | // ce_rxover | ce_rxparity)) != 0x00) { if (commerror == 9) { result = os_failed; // printf ("clearcommerror - lperrors[%02x]", lperrors); } } // update remaing time (cannot < 0) rtimeout = std::max<milliseconds>( rtimeout - duration_cast<milliseconds>( system_clock::now() - starttime), milliseconds::zero()); } return result; }
if (dwlasterror == error_io_pending) { dword ovlstatus = ::waitforsingleobject(mpoverlappedrx->hevent, ...); //... }
this common mistake when programmers use overlapped i/o. core idea use allow device driver start working on job first readfile() call. going take while, i/o does, , serial ports since slow devices.
so ask driver "get started on it" , goes job. driver will, eventually, signal done calling setevent() method on overlapped.hevent. completes waitforsingleobject() call.
what supposed while driver working on something else. job thread ought do, useful while driver working on i/o request. can, example, light msgwaitformultipleobjects() it. pumps message loop ui still responsive. , tells when serial port has new data available.
the flaw in code not figure out else do. calls waitforsingleobject() wait overlapped i/o complete. blocking thread , not doing useful work while driver working on read request. that's common problem.
in other words, have not yet found reason use overlapped i/o. exactly same outcome using synchronous readfile() call. block, current code does, until serial port has data available.
so don't bother it. fixes timeout dilemma too.
Comments
Post a Comment