|
tserialport.h
#ifndef __serialport_h__ #define __serialport_h__
#define wm_comm_break_detected wm_user+1 // a break was detected on input. #define wm_comm_cts_detected wm_user+2 // the cts (clear-to-sennd) sig nal changed state. #define wm_comm_dsr_detected wm_user+3 // the dsr (data-set-reaady) si gnal changed state. #define wm_comm_err_detected wm_user+4 // a line-status error ooccurre d. line-status errors are ce_frame, ce_overrun, and ce_rxparity.
#define wm_comm_ring_detected wm_user+5 // a ring indicator was etec ted. #define wm_comm_rlsd_detected wm_user+6 // the rlsd (receive-lin -sig nal-detect) signal changed state. #define wm_comm_rxchar wm_user+7 // a character w s received and pl aced in the input buffer. #define wm_comm_rxflag_detected wm_user+8 // the event character w s received and placed in the input buffer. #define wm_comm_txempty_detected wm_user+9 // the last character in th e output buffer was sent.
class tserialport { public: // contruction and destruction tserialport(); virtual ~tserialport();
// port initialisation bool initport(tform* pportowner, uint portnr = 1, uint baud = 19200, char parity = 'n', uint databits = 8, uint stopsbits = 1, dword dwcom mevents = ev_rxchar | ev_cts, uint nbuffersize = 512);
// start/stop comm watching bool startmonitoring(); bool restartmonitoring(); bool stopmonitoring();
dword getwritebuffersize(); dword getcommevents(); dcb getdcb();
void writetoport(char* string);
protected: // protected memberfunctions void processerrormessage(char* errortext); static dword _stdcall commthread(lpvoid pparam); static void receivechar(tserialport* port, comstat comstat); static void writechar(tserialport* port);
// thread handle m_hthread;
// synchronisation objects critical_section m_cscommunicationsync; bool m_bthreadalive;
// handles handle m_hshutdownevent; handle m_hcomm; handle m_hwriteevent;
// event array. // one element is used for each event. there are two event handles fo r each port. // a write event and a receive character event which is located in th e overlapped structure (m_ov.hevent). // there is a general shutdown when the port is closed. handle m_heventarray[3];
// structures overlapped m_ov; commtimeouts m_commtimeouts; dcb m_dcb;
// owner window tform* m_powner;
// misc uint m_nportnr; char* m_szwritebuffer; dword m_dwcommevents; dword m_nwritebuffersize; };
#endif __serialport_h__
tserialport.cpp
#include #pragma hdrstop #include "serialport.h" #include #include #pragma package(smart_init) // // constructor // tserialport::tserialport() { m_hcomm = null;
// initialize overlapped structure members to zero m_ov.offset = 0; m_ov.offsethigh = 0;
// create events m_ov.hevent = null; m_hwriteevent = null; m_hshutdownevent = null;
m_szwritebuffer = null;
m_bthreadalive = false; }
// // delete dynamic memory // tserialport::~tserialport() { do { setevent(m_hshutdownevent); } while (m_bthreadalive);
delete [] m_szwritebuffer; }
// // initialize the port. this can be port 1 to 4. // bool tserialport::initport(tform* pportowner, // the owner (cwnd) of t he port (receives message) uint portnr, / portnumber (1..4) uint baud, / baudrate char parity, / parity uint databits, / databits uint stopbits, / stopbits dword dwcommevents, // ev_rx har, ev_cts etc uint writebuffersize) / size to the writebuffer { assert(portnr > 0 && portnr < 5); assert(pportowner != null);
// if the thread is alive: kill if (m_bthreadalive) { do { setevent(m_hshutdownevent); } while (m_bthreadalive); }
// create events if (m_ov.hevent != null) resetevent(m_ov.hevent); m_ov.hevent = createevent(null, true, false, null);
if (m_hwriteevent != null) resetevent(m_hwriteevent); m_hwriteevent = createevent(null, true, false, null);
if (m_hshutdownevent != null) resetevent(m_hshutdownevent); m_hshutdownevent = createevent(null, true, false, null);
// initialize the event objects m_heventarray[0] = m_hshutdownevent; // highest priority m_heventarray[1] = m_ov.hevent; m_heventarray[2] = m_hwriteevent;
// initialize critical section initializecriticalsection(&m_cscommunicationsync);
// set buffersize for writing and save the owner m_powner = pportowner;
if (m_szwritebuffer != null) delete [] m_szwritebuffer; m_szwritebuffer = new char[writebuffersize];
m_nportnr = portnr;
m_nwritebuffersize = writebuffersize; m_dwcommevents = dwcommevents;
bool bresult = false; char *szport = new char[50]; char *szbaud = new char[50];
// now it critical! entercriticalsection(&m_cscommunicationsync);
// if the port is already opened: close it if (m_hcomm != null) { closehandle(m_hcomm); m_hcomm = null; }
// prepare port strings sprintf(szport, "com%d", portnr); sprintf(szbaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, da tabits, stopbits);
// get a handle to the port m_hcomm = createfile(szport, / communication port string (comx)
generic_read | generic_write, / read/write types 0, / comm devices must be opened with exclusive acce ss ss null, / no security attributes open_existing, / comm devices must use open_existing file_flag_overlapped, / async i/o 0); / template must be 0 for comm devices
if (m_hcomm == invalid_handle_value) { // port not found delete [] szport; delete [] szbaud;
return false; }
// set the timeout values m_commtimeouts.readintervaltimeout = 1000; m_commtimeouts.readtotaltimeoutmultiplier = 1000; m_commtimeouts.readtotaltimeoutconstant = 1000; m_commtimeouts.writetotaltimeoutmultiplier = 1000; m_commtimeouts.writetotaltimeoutconstant = 1000;
// configure if (setcommtimeouts(m_hcomm, &m_commtimeouts)) { if (setcommmask(m_hcomm, dwcommevents)) { if (getcommstate(m_hcomm, &m_dcb)) { m_dcb.frtscontrol = rts_control_enable; / set rts bit high! if (buildcommdcb(szbaud, &m_dcb)) { if (setcommstate(m_hcomm, &m_dcb)) ; // normal operation... continu
else processerrormessage("setcommstat ()"); } else processerrormessage("buildcommdcb()"); } else processerrormessage("getcommstate()"); } else processerrormessage("setcommmask()"); } else processerrormessage("setcommtimeouts()");
delete [] szport; delete [] szbaud;
// flush the port purgecomm(m_hcomm, purge_rxclear | purge_txclear | purge_rxabort | pu rge_txabort);
// release critical section leavecriticalsection(&m_cscommunicationsync);
return true; }
// // the commthread function. // dword _stdcall tserialport::commthread(lpvoid pparam) { // cast the void pointer passed to the thread back to // a pointer of tserialport class tserialport *port = (tserialport*)pparam;
// set the status variable in the dialog class to // true to indicate the thread is running. port->m_bthreadalive = true;
// misc. variables dword bytestransfered = 0; dword event = 0; dword commevent = 0; dword dwerror = 0; comstat comstat; bool bresult = true;
// clear comm buffers at startup if (port->m_hcomm) // check if the port is opened purgecomm(port->m_hcomm, purge_rxclear | purge_txclear | purge_r abo rt | purge_txabort);
// begin forever loop. this loop will run as long as the thread is a live. for (;;) {
// make a call to waitcommevent(). this call will return immedi tly
// because our port was created as an async port (file_flag_over app ed // and an m_overlappedstructerlapped structure specified). this cal l will cause the // m_overlappedstructerlapped element m_overlappedstruct.hevent, whi ch is part of the m_heventarray to // be placed in a non-signeled state if there are no bytes avail ble to be read, // or to a signeled state if there are bytes available. if this eve nt handle // is set to the non-signeled state, it will be set to signeled hen a // character arrives at the port.
// we do this for each port!
bresult = waitcommevent(port->m_hcomm, &event, &port->m_ov);
if (!bresult) { // if waitcommevent() returns false, process the last er or to dete rmin rmin // the reason.. switch (dwerror = getlasterror()) { case error_io_pending: { // this is a normal return value if ther are no bytes // to read at the port. // do nothing and continue break; } case 87: { // under windows nt, this value is retur ed for some reason. // i have not investigated why, but it i also a valid reply // also do nothing and continue. break; } default: { // all other error codes indicate a seri us error has // occured. process this error. port->processerrormessage("waitcommevent )"); break; } } } else { // if waitcommevent() returns true, check to be sure the e are // actually bytes in the buffer to read. // // if you are reading more than one byte at a time from he buffer
// (which this program does not do) you will have the si uation occ ur // where the first byte to arrive will cause the waitfor ultipleobj ects() // function to stop waiting. the waitformultipleobjects ) function
// resets the event handle in m_overlappedstruct.hevent o the non- signelead state // as it returns. // // if in the time between the reset of this event and th call to // readfile() more bytes arrive, the m_overlappedstruct. event hand le will be set again // to the signeled state. when the call to readfile() oc urs, it wi ll // read all of the bytes from the buffer, and the progra will // loop back around to waitcommevent(). // // at this point you will be in the situation where m_ov rlappedstr uct.hevent is set, // but there are no bytes available to read. if you pro eed and ca ll // readfile(), it will return immediatly due to the asyn port setu p, but // getoverlappedresults() will not return until the next character arrives. // // it is not desirable for the getoverlappedresults() fu ction to b e in // this state. the thread shutdown event (event 0) and he writefi le() // event (event2) will not work if the thread is blocked by getover lappedresults(). // // the solution to this is to check the buffer with a ca l to clear commerror(). // this call will reset the event handle, and if there a e no bytes to read // we can loop back through waitcommevent() again, then roceed. // if there are really bytes to read, do nothing and pro eed.
bresult = clearcommerror(port->m_hcomm, &dwerror, &comst t);
if (comstat.cbinque == 0) continue; } // end if bresult
// main wait function. this function will normally block the th ead
// until one of nine events occur that require action. event = waitformultipleobjects(3, port->m_heventarray, false, in ini te);
switch (event) { case 0: { // shutdown event. this is event zero so it wil be // the higest priority and be serviced first.
port->m_bthreadalive = false;
// kill this thread. break is not needed, but m kes me feel bette r. exitthread(100); break; } case 1: // read event { getcommmask(port->m_hcomm, &commevent); if (commevent & ev_cts) ::sendmessage(port->m_powner->handle, wm comm_cts_detected, (wpar am) 0, (lparam) port->m_nportnr); if (commevent & ev_rxflag) ::sendmessage(port->m_powner->handle, wm comm_rxflag_detected, (w param) 0, (lparam) port->m_nportnr); if (commevent & ev_break) ::sendmessage(port->m_powner->handle, wm comm_break_detected, (wp aram) 0, (lparam) port->m_nportnr); if (commevent & ev_err) ::sendmessage(port->m_powner->handle, wm comm_err_detected, (wpar am) 0, (lparam) port->m_nportnr); if (commevent & ev_ring) ::sendmessage(port->m_powner->handle, wm comm_ring_detected, (wpa ram) 0, (lparam) port->m_nportnr);
if (commevent & ev_rxchar) // receive character event from port. receivechar(port, comstat);
break; } case 2: // write event { // write character event from port writechar(port); break; }
} // end switch
} // close forever loop
return 0; }
// // // start comm watching // bool tserialport::startmonitoring() { dword lpthreadid; m_hthread =createthread(null, 0, commthread, this, 0, &lpthreadid); if(m_hthread==null) { processerrormessage("create thread error"); return false; } return true; }
// // restart the comm thread // // bool tserialport::restartmonitoring() { resumethread(m_hthread); return true; }
// // suspend the comm thread // bool tserialport::stopmonitoring() { suspendthread(m_hthread); return true; }
// // if there is a error, give the right message // void tserialport::processerrormessage(char* errortext) { char *temp = new char[200];
lpvoid lpmsgbuf;
formatmessage( format_message_allocate_buffer | format_message_from_system, null, getlasterror(), makelangid(lang_neutral, sublang_default), // default language (lptstr) &lpmsgbuf, 0, null );
sprintf(temp, "warning: %s failed with the following error: \n%s\npo rt: %d\n", (char*)errortext, lpmsgbuf, m_nportnr); application->messagebox(temp, "application error", mb_iconstop);
localfree(lpmsgbuf); delete[] temp; }
// // write a character. // void tserialport::writechar(tserialport* port) { bool bwrite = true; bool bresult = true;
dword bytessent = 0;
resetevent(port->m_hwriteevent);
// gain ownership of the critical section entercriticalsection(&port->m_cscommunicationsync);
if (bwrite) { // initailize variables port->m_ov.offset = 0; port->m_ov.offsethigh = 0;
// clear buffer purgecomm(port->m_hcomm, purge_rxclear | purge_txclear | purge_r abo abo rt | purge_txabort);
bresult = writefile(port->m_hcomm, / handle to comm port port->m_szwritebuffer, / / pointer to message buffer in call ing finction strlen((char*)port->m_sz ritebuffer), // length of message to s end &bytessent, / where to store the number of bytes sent &port->m_ov); / overlapped structure
// deal with any error codes if (!bresult) { dword dwerror = getlasterror(); switch (dwerror) { case error_io_pending: { // continue to getoverlappedresu ts() bytessent = 0; bwrite = false; break; } default: { // all other error codes port->processerrormessage("write ile()"); } } } else { leavecriticalsection(&port->m_cscommunicationsync); } } // end if(bwrite)
if (!bwrite) { bwrite = true;
bresult = getoverlappedresult(port->m_hcomm, // handle to com port
&port- m_ov, // overlapped structure &bytes ent, // stores number of bytes sent true); // wait flag
leavecriticalsection(&port->m_cscommunicationsync);
// deal with the error code if (!bresult) { port->processerrormessage("getoverlappedresults() in wri efile()");
} } // end if (!bwrite)
// verify that the data size send equals what we tried to send if (bytessent != strlen((char*)port->m_szwritebuffer)) { printf("warning: writefile() error.. bytes sent: %d; message len th: %d\n", bytessent, strlen((char*)port->m_szwritebuffer)); } }
// // character received. inform the owner // void tserialport::receivechar(tserialport* port, comstat comstat) { bool bread = true; bool bresult = true; dword dwerror = 0; dword bytesread = 0; unsigned char rxbuff;
for (;;) { // gain ownership of the comm port critical section. // this process guarantees no other part of this program // is using the port object.
entercriticalsection(&port->m_cscommunicationsync);
// clearcommerror() will update the comstat structure and // clear any other errors.
bresult = clearcommerror(port->m_hcomm, &dwerror, &comstat);
leavecriticalsection(&port->m_cscommunicationsync);
// start forever loop. i use this type of loop because i // do not know at runtime how many loops this will have to // run. my solution is to start a forever loop and to // break out of it when i have processed all of the // data available. be careful with this approach and // be sure your loop will exit. // my reasons for this are not as clear in this sample // as it is in my production code, but i have found this // solutiion to be the most efficient way to do this.
if (comstat.cbinque == 0) { // break out when all bytes have been read break; }
entercriticalsection(&port->m_cscommunicationsync);
if (bread) { bresult = readfile(port->m_hcomm, // handl to comm port &rxbuff, / rx buffer pointer 1, / read one byte &bytesread, / stores number of bytes read &port->m_ov); / pointer to the m_ov structure // deal with the error code if (!bresult) { switch (dwerror = getlasterror()) { case error_io_pending: { // asynchronous i/o is s ill in progress // proceed on to getover appedresults(); bread = false; break; } default: { // another error has occ red. process this error. port->processerrormessag ("readfile()"); break; } } } else { // readfile() returned complete. it is not neces ary to call getov erlappedresults() bread = true; } } // close if (bread)
if (!bread) { bread = true; bresult = getoverlappedresult(port->m_hcomm, // handl to comm port
&port->m_ov, // overlapped structure
&bytesread, // stores number of bytes read
true); // wait flag
// deal with the error code if (!bresult) { port->processerrormessage("getoverlappedresults( in readfile()");
} } // close if (!bread)
leavecriticalsection(&port->m_cscommunicationsync);
// notify parent that a byte was received ::sendmessage((port->m_powner)->handle, wm_comm_rxchar, (wparam) rxb uff, (lparam) port->m_nportnr); } // end forever loop
}
// // write a string to the port // void tserialport::writetoport(char* string) { assert(m_hcomm != 0);
memset(m_szwritebuffer, 0, sizeof(m_szwritebuffer)); strcpy(m_szwritebuffer, string);
// set event for write setevent(m_hwriteevent); }
// // return the device control block // dcb tserialport::getdcb() { return m_dcb; }
// // return the communication event masks // dword tserialport::getcommevents() { return m_dwcommevents; }
// // return the output buffer size // dword tserialport::getwritebuffersize() { return m_nwritebuffersize; }
//---------------------------------------------------------------- |