|
Introduction
This is a proposal for a C++ class to handle simple serial communications, which means send and receive some bytes, and control the state of serial communication signals. In many cases, what one needs is to be able to communicate by a serial port to certain devices or electronic circuits. As simplicity is the main goal in this class, it is developed for synchronous read and write operation instead of overlapped ones and also assuming that there is no hardware (or software) flow control so the communications signals can be freely controlled. If you need an event driven serial communication (overlapped read/write, signals changes controlled by even, etc.) you can look in this site for the article Serial communication for Win32 with modem support By thierrydollar
Using the code
To use the class CSerialPort you must call CSerialPort::Open then does read or write operation, set or test the state of communications signals and closes the port once finished (not mandatory because the destructor does it). The read and write methods make no assumption about type and format of data send or received, you must take into account if you are handling a character or binary format, if there is Unicode, mbcs, etc. The open function if defined as follows: virtual BOOL Open(LPCTSTR PortName, DWORD BaudRate, BYTE ByteSize, BYTE Parity, BYTE StopBits,
DWORD DesiredAccess = GENERIC_READ|GENERIC_WRITE);
There is no assumption about communication parameters because they are specific to each serial communication and must be known in order to establish a successful communication. Even, if the most frequent name of serial ports are “COM1:” to “COM4:”, there can be more than 4 serial ports in a machine and the serial driver is not forced to follow the “COMxx” name convention so a serial port can have any name in Win32 platforms. It would be a good idea to have a static function to obtain the names of installed serial ports but, as far as I know, there is not a documented way to do that. There is a simple way to know about installed ports: assuming that ports names follow the “COMxx:” convention they try to open all possible ports and if there is an error and GetLastError() returns ERROR_FILE_NOT_FOUND then the port isn’t installed. There is another way to know installed ports names and it is searching in registry but it is undocumented and platform depend.
With article there is a simple sample of using the mentioned class, the example is a program that reads bar codes from a serial bar code reader. Normally bar code readers send the read bar code ended by carriage return and line feed characters (this can be configured and even could be different for specific manufacturer) and the code is an ASCII string. There is a class (CBarCodeReader ) derived from CSerialPort that encapsulate the described protocol and its read method returns the read bar code (if any) directly in an string. There is not much more to say, the rest is in the code and it is simple (at least should be :-) )
Remarks
Remember that this is a simple instead of complete way of using serial devices. The proposed code can be used in Win32 Platforms and Windows CE versions. Pocket PC developers (I didn’t try others Windows CE versions) must take into account that manufacturers can have specific implementations of serial APIs (as other APIs). Even if it is true that such implementations must agree to Pocket PC implementation, I have found small differences from one device to another. For instance: in a Dell Axim Pocket PC device the signal CD must be externally supplied (the voltage present) in order to read or do any operation with serial port, there is no function errors if you try without that signal on, but you won’t obtain any result. I have tried the same in Compaq iPAQs and it is possible to read without any specific external signal on (as should be).
| You must Sign In to use this message board. |
|
| | Msgs 1 to 18 of 18 (Total in Forum: 18) (Refresh) | FirstPrevNext |
|
|
 |
|
|
 |
|
|
hi, i was wondering:
1) can the serial port being use to control a RC toy car?? i mean the RC controller will only act as the signal transmitter while the GUI (MFC dialog-based) will act as the controller. i was thinking about using the serial port as the communication..so, is it possible? 2) if it is, how can it be done?
these two q's has given me a headache lately...LOL.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am interning at a company and they have asked me to retrieve temperature and humidity from a new therm. chamber they just bought, with a Watlow Series F4S/D controller. We have added a RS232(serial port) onto the machine. I have checked the http://www.watlow.com/prodtechinfo (Data communication referemce) and it help me understand more about the communication information between the controller and the RS232 and what programs to use. But I was looking for more of an example to work off
Adri-
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
As we are using characters 0x13 and 0x19 for flow control. { ... dcb.XonChar = 0x13; dcb.XoffChar = 0x19; ... } Will this cause any problem if we try to send these characters as data.
Also can any one help me find what are the different flow controls available and how to configure port to use each of these?
Thnx N Rgds Vinayak
Chitragar
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Hi, I don't what you want. As far as I know OCRB is a standardized font designed for OCR devices and I don't find the exact relation with this article. Can you explain more ?
Regards, Idael
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I have a problem on a Win98 laptop of which I suspect that the serial port is functioning poorly as I often get time-outs. The suspicion I have is that the comm port is closed somehow and the function IsOpen() does not detect this. What happens is that the program continuously tries to write a string located in buffer to the weaving loom through the serial port:
int r = CSerialPort::Write(Buffer,Size);
if (r>0) { // process
return 1; } else { FILE* Out = fopen("Test.txt","a+"); fprintf("Open or not : %d\n",IsOpen()); fclose(Out);
// Close() // Open("COM1:");
return 0;
// return 0 to try again next time // this is located in a while loop in a thread }
What I get is when I try to write again after a reading time out that the write function always returns zero, zero bytes are written. I've written out the status of the port once but it is always 1, open. Hence the application gets no new data to and from the weaving loom. However, when I close the port and open it again it can occur after one or two times that I am successfully able to write and read again. Any idea what causes this behaviour?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I found the error, it is specific to a Compaq Armada 1750 laptop. The serial port can have a conflict with the infrared port in Win98 and Win2000 and disabling the infrared port seems to do the trick. When not doing this, it is best to close and reopen the comm port to avoid the application stops as it can't transmit data.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
how can interact with your code with my dialog based application .i mean , if i have to get the motor revolutions per minute in my dialog based application in an edit box ,where should i use your calsses (in my application code)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
As you didn't give more details I assume that you are talking about MFC dialog based application. I also assume that you have some device from where you can read the revolution per minute of a motor using a RS-232 link. In the code accompanying this article I include a MFC dialog based application as an example. The given app shows how to read information form a serial barcode reader. It queries the serial port for data at regular interval using a Windows timer. In the main dialog you can fin the following definitions:
CBarCodeReader m_BarCodeReader;
UINT m_Timer; The other step is adding in the OnInitDialog message handler the following line:
m_Timer = SetTimer(1, 300 , 0); Add a message handler for the message WM_TIMER and read the serial port in the OnTimer message handler. In the test app I gave the OnTimer is as follows:
void CTestDlg::OnTimer(UINT nIDEvent) { if ( m_BarCodeReader.Read(m_BarCode) > 0 ) { UpdateData(FALSE); MessageBeep(MB_ICONASTERISK); } CDialog::OnTimer(nIDEvent); } The rest is open your port calling CSerialPort::Open or CBarCodeReader::Open depending on which one you used. You can do it in OnInitDialog message handler or in response to some button action (as in my demo app) or when your app logic requires it. In your case if your device gives you the speed of the motor as an ASCII characters ended in the characters carriage return and line feed (as in the default configuration for many serial barcode readers) you can the explained code without many modifications. If the speed is given as ASCII but ended in other character or as a fixed length string then you can create (cut and paste) a class similar to CBarCodeReader and change the method Read according to your needs. But if your device give you the values as binary data or any non ASCII protocol then you must use CSerialPort directed and you OnTimer should like this:
void CTestDlg::OnTimer(UINT nIDEvent) { BYTE buffer[255]; DWORD read; while ( (read = m_SerialPort.Read(buffer, sizeof(buffer))) > 0 ) { } CDialog::OnTimer(nIDEvent); } Resuming: - You must declare in your dialog a variable of type
CSerialPort. - In the
OnInitDialog handler you must initialize the timer. - Add a
WM_TIMER message handler to your dialog and in the OnTimer handler read the serial port data. - You must open the port in the
OnInitDialog handler or any other.
The described method is suitable if you can check the port at determined time intervals. If you need to read the port immediately when some data arrives then you need to use some of the notification mechanism instead of this polling mechanism.
Regards, Idael
|
| Sign In·View Thread·PermaLink | 2.50/5 (2 votes) |
|
|
|
 |
|
|
Hello Idael,
I used your CSerialPort class which works excellent but I found out that although the code I used with SetTimer is quite simple, my program is upto two times slower than the old DOS program it is supposed to replace. Do you have an example of such a notification mechanism or a link to an article giving one? I checked MSDN for overlapped communication and it didn't give me much except for a headache.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The code I proposed in this article is very trivial and could be not suitable for applications that need immediate response when data arrives (real-time applications, for instance). If your application's constraints allow it you can decrease the timer delay in order to query the port more frequently. Another solution is using a worker thread to query the port continually. Those solutions could interfere in the overall performance of your application but sometimes the performance penalty it is not so big. While this code can be easily adapted to supports Win32 Serial Events is better to use a library that was already designed with it in mind. In the article "Serial communication for Win32 with modem support" you can find a library that could helps you. If your problems persist you can contact me to see if I can help you.
Idael.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you, I will try to embed it in a thread that continuously polls the port for data. I have taken a look at the TSerial_Event class but have no idea how to imbed that in an SDI application for example, like where would I declare the event and would I also need to place that in a thread, and how to stop polling if a stop button is pressed. Many of the examples for the serial port seem too complicated for me, I have no idea how to implement them.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In the following code I’ll show how to adapt a MFC SDI application to use a worker thread that pool the serial port continually, when data is arrived it is converted to string (assuming that data read is in ASCII format) and added to a CStringList. The data arrival is signaled using a custom windows message. This method is not so efficient because the working thread is active all the time but can works if this performance penalty isn’t so important for your application. When u create and SDI MFC application using the MFC app wizards a class named CMainFrame is declared in the file MainFrm.h and method defined in MainFrm.cpp, these files are the files where code must be added. In MainFrm.h add the following line:
#define WM_DATAREAD (WM_USER + 1) The previous line defines the custom message that will be used to signal that data was arrived. Within the declaration of CMainFrame add the following:
class CMainFrame : public CFrameWnd { protected: afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnSetFocus(CWnd *pOldWnd); afx_msg void OnDestroy(); afx_msg LRESULT OnDataRead(WPARAM, LPARAM); DECLARE_MESSAGE_MAP() private: CStringList m_PortDataList; CCriticalSection m_CriticalSection; CWinThread* m_pReadPortThread; CSerialPort m_Port; void StartPooling(); void StopPooling(); static UINT ReadPort(LPVOID pParam); }; In MainFrm.cpp, make the following changes:
Add the ON_MESSAGE(WM_DATAREAD, OnDataRead) entry to the message map as following:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_CREATE() ON_WM_SETFOCUS() ON_WM_DESTROY() ON_MESSAGE(WM_DATAREAD, OnDataRead) END_MESSAGE_MAP() Modify the following methods as follows:
CMainFrame::CMainFrame() { m_pReadPortThread = NULL; } void CMainFrame::OnDestroy() { StopPooling(); CFrameWnd::OnDestroy(); }
An finally add the following code:
LRESULT CMainFrame::OnDataRead(WPARAM, LPARAM) { CSingleLock slock(&m_CriticalSection); slock.Lock(); if ( slock.IsLocked() ) { } else { } slock.Unlock(); return 0; } UINT CMainFrame::ReadPort(LPVOID pParam) { CMainFrame* pMainFrame = (CMainFrame*)pParam; char buffer[100]; DWORD read; while( pMainFrame->m_Port.IsOpen() ) { if ( (read = pMainFrame->m_Port.Read(buffer, sizeof(buffer)-1)) > 0 ) { buffer[read] = 0; CString str(buffer); CSingleLock slock(&pMainFrame->m_CriticalSection); slock.Lock(); if ( slock.IsLocked() ) { pMainFrame->m_PortDataList.AddTail(str); } else { } slock.Unlock(); if ( pMainFrame->m_Port.IsOpen() ) { ::PostMessage(pMainFrame->GetSafeHwnd(), WM_DATAREAD, 0, 0); } } } return 0; } void CMainFrame::StartPooling() { StopPooling(); m_Port.Open(_T("COM1:"), 9600, 8, NOPARITY, ONESTOPBIT, GENERIC_READ); CWinThread* m_pReadPortThread = AfxBeginThread( ReadPort, (LPVOID)this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); m_pReadPortThread->m_bAutoDelete = FALSE; m_pReadPortThread->ResumeThread(); } void CMainFrame::StopPooling() { m_Port.Close(); if (m_pReadPortThread != NULL) { if ( WaitForSingleObject(m_pReadPortThread->m_hThread, 3000) != WAIT_OBJECT_0) { m_pReadPortThread->SuspendThread(); } delete m_pReadPortThread; m_pReadPortThread = NULL; } } Finally you must call StartPooling/StopPooling in a handler to buttons or any other method. To use Tserial_event class declare a member variable of its type as following:
class CMainFrame : public CFrameWnd { private: Tserial_event m_Port; void StartPooling(); void StopPooling(); } In the cpp file:
void SerialEventManager(uint32 object, uint32 event) { char *buffer; int size; Tserial_event *com; com = (Tserial_event *) object; if (com!=0) { switch(event) { case SERIAL_DATA_ARRIVAL : size = com->getDataInSize(); buffer = com->getDataInBuffer(); com->dataHasBeenRead(); break; } } } CMainFrame::CMainFrame() { m_Port.setManager(SerialEventManager); } void CMainFrame::OnDestroy() { StopPooling(); CFrameWnd::OnDestroy(); } void CMainFrame::StartPooling() { m_Port. connect("COM1", 19200, SERIAL_PARITY_NONE, 8, true); } void CMainFrame::StopPooling() { m_Port.disconnect(); }
Regards, Idael
|
| Sign In·View Thread·PermaLink | 2.50/5 (2 votes) |
|
|
|
 |
|
|
I know U do not like the registry method. But this func is in a piece of industrial code (look at my e-mail address domain) I am not supposed to give away code like this, but it is not unlike other routines on this site and others. This one works as-is, and it works.
// --------------------------- EnumerateSerialPorts ----------------------------- // PURPOSE: Retrieve hardware configuration from registry instead of letting // the user guess what ports he has available. // LONG EnumerateSerialPorts (char *deviceName, DWORD maxLen, DWORD index) { CHAR RegPath[MAX_PATH] = "HARDWARE\\DEVICEMAP\\SERIALCOMM"; HKEY hKey; HKEY hKeyRoot = HKEY_LOCAL_MACHINE; DWORD retCode; CHAR ClassName[MAX_PATH] = ""; // Buffer for class name. DWORD dwcClassLen = MAX_PATH; // Length of class string. DWORD dwcSubKeys; // Number of sub keys. DWORD dwcMaxSubKey; // Longest sub key size. DWORD dwcMaxClass; // Longest class string. DWORD dwcValues; // Number of values for this key. CHAR valueName[MAX_VALUE_NAME] ; DWORD dwcValueName = MAX_VALUE_NAME; DWORD dwcMaxValueName; // Longest Value name. DWORD dwcMaxValueData; // Longest Value data. DWORD dwcSecDesc; // Security descriptor. FILETIME ftLastWriteTime; // Last write time. DWORD dwType; DWORD retValue; DWORD cbData;
// Use RegOpenKeyEx() with the new Registry path to get an open handle // to the child key you want to enumerate. retCode = RegOpenKeyEx (hKeyRoot, RegPath, 0, KEY_ENUMERATE_SUB_KEYS | KEY_EXECUTE | KEY_QUERY_VALUE, &hKey);
if (retCode != ERROR_SUCCESS) return(FAILED);
// Get Class name, Value count. RegQueryInfoKey ( hKey, // Key handle. ClassName, // Buffer for class name. &dwcClassLen, // Length of class string. NULL, // Reserved. &dwcSubKeys, // Number of sub keys. &dwcMaxSubKey, // Longest sub key size. &dwcMaxClass, // Longest class string. &dwcValues, // Number of values for this key. &dwcMaxValueName, // Longest Value name. &dwcMaxValueData, // Longest Value data. &dwcSecDesc, // Security descriptor. &ftLastWriteTime); // Last write time.
// Enumerate the Key Values cbData = maxLen ; dwcValueName = MAX_VALUE_NAME; valueName[0] = '\0';
retValue = RegEnumValue (hKey, index, valueName, &dwcValueName, NULL, &dwType, (BYTE *)&deviceName[0], &cbData);
RegCloseKey (hKey); // Close the key handle. if(dwType == REG_SZ && retValue == (DWORD)ERROR_SUCCESS) return(SUCCESS); else return(FAILED); } // EnumerateSerialports
Just call it in a loop with 0 as the indes, until it fails, then U know that was the index after last port. I jsut call it and fill a combo-box untill it fails.
I like your nice OO method of deriving the scanner class from the serial port class, I did the same thing to develop, a "threaded" serial class so it can fire events, on it's own thread, so there is no GUI interrption while reading/writting characters (personally overlapped I/O SUX for complexity).
I may put my class up someday, but it looks like spaghetti, since I derived 4 different classes from it, and then went on to derive more from that untill I got almost as many "children" as the layers in the ISO 7-layer diagrams. But the technique rocks! To get an old copy fire up your browser to http://www.codeguru.com/network/mod_rssim.html.
Go well
Conrad - conradb@adroit.co.za Always do badly to start off, that way when you get the hang of it suddenly, everyone is surprised.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
BuildCommDCB function is not supported in W/CE so you must parse the string by yourself (to provide an overlapped version of CSerialPort::Open that receives a string as parameters and that parse it could be a good idea, maybe I'll include it in a next version ).
Good luck, Idael.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|