1. API description
In Win32 API, the serial port uses the file access method, and its operation API is basically the same as the file operation API.
Open serial port
In Win32, the API function used to open the serial port is createfile, and its prototype is:
HANDLE CreateFile ( Lptstr lpFileName, // name of the serial port to be opened, such as COM1 or COM2 DWORD dwACCESS, // specifies the serial access type, which can be read, write, or parallel DWORD dw1_mode, // specify the sharing attribute. This parameter must be set to 0 because the serial port cannot be shared. LPSECURITY_ATTRIBUTES lpsa, // reference the Security Attribute structure. The default value is NULL. DWORD dwCreate, // create a flag. For serial port operations, this parameter must be set to open existing. DWORD dwAttrsAndFlags, // attribute description, used to specify whether asynchronous operations can be performed on the serial port, // FILE_FLAG_OVERLAPPED: asynchronous I/O can be used. Handle htemplatefile // The handle pointing to the template file. For the serial port, this parameter must be set to null. ); |
For example, the following program is used to open the serial port COM1 in synchronous read/write mode:
Handle hcom; DWORD dwerror; Hcon = createfile ("COM1", generic_read | generic_write, 0, null, open_existing, 0, null ); If (hcom = (handle) 0 xffffffff) { Dwerror = getlasterror (); MessageBox (dwerror ); } |
The dwattrsandflags parameter and the file_flag_overlapped flag are described as follows: Windows file operations are divided into synchronous I/O and overlapping I/O (overlapped I/O) methods, in synchronous I/O mode, the API will be blocked until the operation is complete (in multi-threaded mode, although the main thread will not be blocked, the listener thread will still be blocked ); in the overlapping I/O mode, the API will return immediately and the operation will be performed in the background to avoid thread blocking. Overlapping I/O is flexible, and it can also implement blocking (for example, we can set that we must read a data before proceeding to the next operation ). If the I/O operation is returned when the API does not complete the operation, we can call the getoverlappedresult () function to block the operation and return the result.
Configure serial port
The serial port configuration is implemented by changing the member variable value of the device control block DCB (Device Control Block). The size of the receiving buffer and sending buffer can be set through the setupcomm function.
DCB struct is defined:
Typedef struct _ DCB {// DCB DWORD dcblength; // sizeof (DCB) DWORD baudrate; // current baud rate DWORD fbinary: 1; // binary mode, no EOF check DWORD fparity: 1; // enable parity checking DWORD foutxctsflow: 1; // CTS output flow control DWORD foutxdsrflow: 1; // DSR output flow control DWORD fdtrcontrol: 2; // DTR Flow Control Type DWORD fdsrsensiti.pdf: 1; // DSR sensiti.pdf DWORD fTXContinueOnXoff: 1; // XOFF continues Tx DWORD fOutX: 1; // XON/XOFF out flow control DWORD fInX: 1; // XON/XOFF in flow control DWORD fErrorChar: 1; // enable error replacement DWORD fNull: 1; // enable null stripping DWORD fRtsControl: 2; // RTS flow control DWORD fAbortOnError: 1; // abort reads/writes on error DWORD fDummy2: 17; // reserved WORD wReserved; // not currently used WORD XonLim; // transmit XON threshold WORD XoffLim; // transmit XOFF threshold BYTE ByteSize; // number of bits/byte, 4-8 BYTE Parity; // 0-4 = no, odd, even, mark, space BYTE StopBits; // 0, 1, 2 = 1, 1.5, 2 Char XonChar; // Tx and Rx XON character Char XoffChar; // Tx and Rx XOFF character Char ErrorChar; // error replacement character Char EofChar; // end of input character Char EvtChar; // encoded ed event character WORD wReserved1; // reserved; do not use } DCB; The prototype of the SetupComm function is: BOOL SetupComm ( HANDLE hFile, // handle to communications device DWORD dwInQueue, // size of input buffer DWORD dwOutQueue // size of output buffer ); |
The following Program sets the serial port as follows: the baud rate is 9600, the number of data digits is 7 bits, the Stop bits are 2 bits, the parity check, the size of the receiving buffer and the sending buffer are both 1024 bytes, finally, use the purgecomm function to terminate all background read/write operations and clear the receiving and sending buffers:
DCB dcb; Dcb. BaudRate = 9600; // The baud rate is 9600. Dcb. ByteSize = 7; // The number of data digits is 7 digits. Dcb. Parity = EVENPARITY; // Parity check Dcb. StopBits = 2; // two stop bits Dcb. fBinary = TRUE; Dcb. fParity = TRUE; If (! SetCommState (hCom, & dcb )) { MessageBox ("An error occurred while setting the serial port! "); } SetupComm (hCom, 1024,102 4 ); Purgecomm (hcom, purce_txabort | purge_rxabort | purge_txclear | purge_rxclear ); |
Timeout settings
Timeout settings are implemented by changing the value of the member variable of the commtimeouts struct. The prototype of commtimeouts is:
Typedef struct _ commtimeouts { DWORD readintervaltimeout; // defines the maximum time interval between two characters, in milliseconds // When the value of readintervaltimeout is exceeded after one character is read, it will // Timeout DWORD readtotaltimeoutmultiplier; DWORD readtotaltimeoutconstant; // The relationship between each time is as follows: // Readtotaltimeout = readtotaltimeoutmultiplier * bytestoread + readtotaltimeoutconstant DWORD writetotaltimeoutmultiplier; DWORD writetotaltimeoutconstant; } Commtimeouts, * lpcommtimeouts; |
Set the timeout function to setcommtimeouts. In its prototype, the pointer to receive commtimeouts is a parameter:
Bool setcommtimeouts ( Handle hfile, // handle to communications device Lpcommtimeouts // pointer to comm time-out Structure ); |
The following procedure sets the serial port read operation timeout to 10 milliseconds:
Commtimeouts; Memset (& to, 0, sizeof ()); To. ReadIntervalTimeout = 10; SetCommTimeouts (hCom, & ); |
The prototype of the getcommtimeouts () function corresponding to setcommtimeouts is:
BOOL GetCommTimeouts ( HANDLE hFile, // handle of communications device LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm time-out structure ); |
Event settings
Before reading and writing the serial port, you need to use the setcommmask () function to set the event mask to monitor the events on the specified communication port. Its prototype is:
BOOL SetCommMask ( HANDLE hFile, // The HANDLE that identifies the communication port DWORD dwEvtMask // communication event that can be enabled ); |
With set, get will also be available. The prototype of the getcommmask () function corresponding to setcommmask is:
BOOL GetCommMask ( HANDLE hFile, // The HANDLE that identifies the communication port LPDWORD lpEvtMask // address of variable to get event mask ); |
Events that can occur on the serial port can be one or any combination of the following events: ev_break, ev_cts, ev_dsr, ev_err, ev_ring, ev_rlsd, ev_rxchar, ev_rxflag, and ev_txempty.
We can use the waitcommevent () function to wait for the event we set using the setcommmask () function on the serial port:
BOOL WaitCommEvent ( HANDLE hFile, // The HANDLE that identifies the communication port LPDWORD lpEvtMask, // address of variable for event that occurred LPOVERLAPPED lpOverlapped, // address of overlapped structure ); |
The waitcommevent () function is blocked until a communication event set by the setcommmask () function occurs on the serial port. Generally, when waitcommevent () returns an event, the programmer can analyze * lpevtmask to obtain the event type and then process it accordingly.
Read serial port
The functions used to read the serial port are the same as those used to read the file. The READ function is prototype as follows:
BOOL ReadFile ( HANDLE hFile, // handle of file to read LPVOID lpBuffer, // pointer to buffer that records es data DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read LPOVERLAPPED lpOverlapped // pointer to structure for overlapped I/O ); |
Serial Port writing
The functions used for writing data to the serial port are the same as those used for writing data to the file. The writing function is prototype as follows:
BOOL WriteFile ( HANDLE hFile, // handle to file to write Lpvoid lpBuffer, // pointer to data to write to file DWORD nNumberOfBytesToWrite, // number of bytes to write LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written LPOVERLAPPED lpOverlapped // pointer to structure for overlapped I/O ); |
Close serial port
It is very easy to disable the serial port when using the API function for serial communication. You only need to use the handle returned by the createfile function as the parameter to call closehandle:
BOOL CloseHandle ( HANDLE hObject // handle to object to close ); |
2. Routine
In my comprehensive example of Win32 multi-threaded program design, we have provided an example of Serial Communication Using win api. Here is a similar example, to further deepen understanding.
The content in the resource file (. RC) corresponding to the control on the dialog box is as follows:
BEGIN EDITTEXT IDC_RECV_EDIT, 28,119,256, 46, ES_AUTOHSCROLL GROUPBOX "send data", IDC_STATIC, 19,15, 282,70 GROUPBOX "receive data", IDC_STATIC, 19,100,282, 80 EDITTEXT IDC_SEND_EDIT, 29,33, 214,39, ES_AUTOHSCROLL PUSHBUTTON "clear", IDC_CLEAR_BUTTON, 248-33, 50,14 PUSHBUTTON "send", IDC_SEND_BUTTON, 248,55, 50,14 END |
The message ing of the entire dialog box (describes the message and its corresponding behavior) is as follows:
BEGIN_MESSAGE_MAP (CSerialPortAPIDlg, CDialog) // {AFX_MSG_MAP (CSerialPortAPIDlg) ON_WM_SYSCOMMAND () ON_WM_PAINT () ON_WM_QUERYDRAGICON () ON_BN_CLICKED (IDC_CLEAR_BUTTON, OnClearButton) ON_BN_CLICKED (IDC_SEND_BUTTON, OnSendButton) ON_MESSAGE (COM_RECVDATA, OnRecvData) //} AFX_MSG_MAP END_MESSAGE_MAP () |
We added a CString variable m_recv and m_send for the IDC_SEND_EDIT and IDC_RECV_EDIT edit box controls respectively. The following code describes this line:
Class CSerialPortAPIDlg: public CDialog { // Construction Public: Cserialportapidlg (cwnd * pparent = NULL); // standard Constructor// Dialog data // {Afx_data (cserialportapidlg) Enum {IDD = idd_serialportapi_dialog }; Cstring m_recv; // The variable corresponding to the idc_recv_edit Control Cstring m_send; // variable corresponding to the idc_send_edit Control //} Afx_data // Classwizard generated virtual function overrides // {Afx_virtual (cserialportapidlg) Protected: Virtual void dodataexchange (cdataexchange * PDX); // DDX/DDV support //} Afx_virtual // Implementation Protected: BOOL OpenSerialPort1 (); HICON m_hIcon; // Generated message map functions // {AFX_MSG (CSerialPortAPIDlg) Virtual BOOL OnInitDialog (); Afx_msg void OnSysCommand (UINT nID, LPARAM lParam ); Afx_msg void OnPaint (); Afx_msg HCURSOR OnQueryDragIcon (); Afx_msg void OnClearButton (); Afx_msg void OnSendButton (); Afx_msg void OnRecvData (WPARAM wParam, LPARAM lParam ); //} AFX_MSG DECLARE_MESSAGE_MAP () }; CSerialPortAPIDlg: CSerialPortAPIDlg (CWnd * pParent/* = NULL */) : CDialog (CSerialPortAPIDlg: IDD, pParent) { // {AFX_DATA_INIT (CSerialPortAPIDlg) // Initialize the variable in the constructor M_recv = _ T (""); // initialize the variable in the constructor M_send = _ T (""); //} AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 M_hIcon = AfxGetApp ()-> LoadIcon (IDR_MAINFRAME ); } // Create a ing between the edit box control and the variable Void CSerialPortAPIDlg: DoDataExchange (CDataExchange * pDX) { CDialog: DoDataExchange (pDX ); // {AFX_DATA_MAP (CSerialPortAPIDlg) DDX_Text (pDX, IDC_RECV_EDIT, m_recv ); DDX_Text (pDX, IDC_SEND_EDIT, m_send ); //} AFX_DATA_MAP } |
In the OnInitDialog () function of the dialog box, we start the window listening thread and pass the main window handle to the thread control function:
Bool cserialportapidlg: oninitdialog () { Cdialog: oninitdialog ();// Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range. ASSERT (IDM_ABOUTBOX & 0xFFF0) = IDM_ABOUTBOX ); ASSERT (IDM_ABOUTBOX <0xF000 ); CMenu * pSysMenu = GetSystemMenu (FALSE ); If (pSysMenu! = NULL) { CString strAboutMenu; StrAboutMenu. LoadString (IDS_ABOUTBOX ); If (! StrAboutMenu. IsEmpty ()) { PSysMenu-> AppendMenu (MF_SEPARATOR ); PSysMenu-> AppendMenu (MF_STRING, IDM_ABOUTBOX, strAboutMenu ); } } // Set the icon for this dialog. The framework does this automatically // When the application's main window is not a dialog SetIcon (m_hIcon, TRUE); // Set big icon SetIcon (m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here // Start the serial port monitoring thread DWORD threadID; HCommThread =: CreateThread (LPSECURITY_ATTRIBUTES) NULL, 0, (LPTHREAD_START_ROUTINE) SerialPort1ThreadProcess, Afxgetmainwnd ()-> m_hwnd, 0, & threadid ); If (hcommthread = NULL) { : Afxmessagebox ("failed to create the serial port 1 processing thread "); : Postquitmessage (0 ); } Return true; // return true unless you set the focus to a control } // "Clear" Button Function Void cserialportapidlg: onclearbutton () { // Todo: add your control notification handler code here M_send = ""; Updatedata (false ); } // Data sending function ("send" button function) Void cserialportapidlg: onsendbutton () { // Todo: add your control notification handler code here Updatedata (true ); DWORD wcount = 0; Writefile (hcom, m_send, m_send.getlength (), & wcount, null); // send data } // After receiving data (user-defined messages sent by the listening thread) display Void CSerialPortAPIDlg: OnRecvData (WPARAM wParam, LPARAM lParam) { CString recvStr (char *) wParam ); M_recv + = recvStr; UpdateData (false ); } |
Add the SerialPortControl. h and SerialPortControl. cpp files to the project. The former declares the interface functions and external global variables of the serial port control, and the latter implements the serial port interface functions and the serial port listening thread control functions.
SerialPortControl. h file
# Ifndef _ serial_port_control_h # DEFINE _ serial_port_control_h# Define COM_RECVDATA WM_USER + 1000 // custom message Extern HANDLE hCom; // global variable, serial port HANDLE Extern HANDLE hCommThread; // global variable, serial port thread // Serial port monitoring thread control function Extern dword winapi SerialPort1ThreadProcess (HWND hWnd ); // Open and set PC serial port 1 (COM1) Extern BOOL OpenSerialPort1 (); # Endif SerialPortControl. cpp File # Include "StdAfx. h" # Include "SerialPortControl. h" HANDLE hCom; // global variable, serial port HANDLE HANDLE hCommThread; // global variable, serial thread BOOL OpenSerialPort1 () { // Open and set COM1 HCom = CreateFile ("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); If (hCom = (HANDLE)-1) { AfxMessageBox ("failed to enable COM1 "); Return false; } Else { DCB wdcb; GetCommState (hCom, & wdcb ); Wdcb. BaudRate = 9600; // baud rate: 9600; others: UNCHANGED SetCommState (hCom, & wdcb ); PurgeComm (hCom, PURGE_TXCLEAR ); } Return true; } // Data received by different monitoring serial lines in one thread Dword winapi SerialPort1ThreadProcess (HWND hWnd // Main Window handle) { Char str [101]; DWORD wCount; // number of bytes read While (1) { ReadFile (hCom, str, 100, & wCount, NULL ); If (wCount> 0) // receives data { Str [wCount] = '/0 '; : PostMessage (hWnd, COM_RECVDATA, (unsigned int) str, wCount ); // Send a message to the main window of the dialog box to display the received content } } Return TRUE; } |
To verify the correctness of the program, we use the serial port debugging assistant to work with the program and send and receive each other. The following screenshot shows that the program works correctly and the sending and receiving characters are correct.