[置顶] 基于WIN32 API的串口通讯软件的设计

串口调试助手源代码:http://download.csdn.net/user/kissyfish

1、串口的应用

随着计算机技术的发展及工业自动化水平的提高,在许多场合采用单机控制已不能满足现场要求,因而必须采用多机控制的形式。串行通信作为计算机之间常用的通信方法之一,由于其通信编程灵活、硬件简洁并遵循统一的标准,而在工业控制领域得到了广泛的应用。

2、串口的属性

2.1波特率

波特率即数据传送速率,表示每秒钟传送二进制代码的位数,他的单位是bit/s。波特率对于CPU与外界的通信是很重要的。通讯过程中,必须保证上位机和下位机的波特率一致。如果不一致的话,就有可能出现乱码甚至出现丢包。这个就好如一个蓄水池的进水管道和出水管道,如果进水管道流入水的速度太快而出水管道的出水太慢,这样时间长了水就会溢出,反映在串口通讯上就是丢包。

2.2数据帧

在异步通信中,数据是一帧一帧(包括一个字符或一个字节数据)传送的,每一帧数据的格式如下表所示。

起始位 数据位 奇偶校验位 停止位
0 5-8位 可省 1

在帧格式中,一个字符由四个部分组成:起始位、数据位、奇偶校验位和停止位。首先是一个起始位(0),然后是5-8位数据(规定低位在前,高位在后),接下来是奇偶校验位(可省略),最后是停止位(1)。

起始位(0)信号只占用一位,用来通知设备一个待接收的数据准备到达。线路上在不传送字符时应保持为1。接收端不断检测线路的状态,若连续为以后又测得一个0,就知道后来一个新字符,应该马上接收。字符的起始位还被用作同步接收端的时钟,以保证以后的接收能正确进行。奇偶校验位只占一位,但在字符中也可以规定不用奇偶校验位,这一位可以省去。也可以用这一位来确定这一帧中的字符所代表信息的性质(地址数据等)。停止位用来表示数据的结束,它一定四高电位(1)。停止位可以是1位、1.5位或2位。接收端收到停止位后,知道上一字符已传送完毕,同时为准备接收下字符作好准备。只要接收到0,就是新的字符的起始位。若停止位以后不是紧接着传送下一个字符,则使电路电平保持高电平(1)。存在空闲位,正是异步通信的特征之一。

2.3通讯协议

要想保证通讯成功,通讯双方必须有一系列的约定。作为发送方,必须知道应该什么时候发送,发什么,对方是否接收到,收到的内容有没有错,要不要重发,怎样通知对方结束等等;作为接收方,必须知道对方是否发送了信息,发的是什么,收到的信息有没有错,如果有错,怎样通知对方重发。

这种约定就叫做通信规程或协议,它必须在编程之前确定下来。然后双方必须严格按照预先规定的协议,进行通讯。

比如通讯的起始头、地址位、数据长度、数据段、数据校验位、结束尾等。数据校验算法有CRC算法、异或算法。代码如下:

  1. /*
  2. 将16进制的字符转化为对应的十进制整数
  3. */
  4. HRESULT CCrc:: strHexToInt(BYTE byRecv, int& nData)
  5. {
  6.     nData = 0;
  7.     if(byRecv>=48 && byRecv<=57)
  8.     {
  9.         nData = byRecv - 48; 
  10.     }
  11.     else if(byRecv>=65 && byRecv<=70)
  12.     {
  13.         nData = byRecv - 55; 
  14.     }
  15.     else
  16.     {
  17.             return -1;
  18.     }
  19.     return S_OK;
  20. }
  21. /*
  22. 对字符串所对应的ASCII码进行CRC校验
  23. */
  24. HRESULT CCrc::CRCCheck(BYTE ucChar[], int commandLength, int nCRCData)
  25. {
  26.     WORD chCRC=0XFFFF;//初始化CRC校验    
  27.     for(int i=3;i<commandLength-3;i++)
  28.     {
  29.         chCRC=chCRC^ucChar[i];
  30.         for(int bits=0;bits<8;bits++)
  31.         {
  32.             if(chCRC&0X0001)
  33.             {   
  34.                 chCRC=chCRC>>1;
  35.                 chCRC=chCRC^nCRCData;
  36.             }
  37.             else
  38.                 chCRC=chCRC>>1;
  39.         }
  40.     }
  41.     WORD CRC,CRC2;//原校验码
  42.     CRC2=chCRC>>8;
  43.     CRC2+=chCRC<<8;
  44.     CRC=ucChar[commandLength-3];
  45.     CRC=CRC<<8;
  46.     CRC=CRC+ucChar[commandLength-2];
  47.     if(CRC==CRC2)
  48.         return S_OK;
  49.     else
  50.     return -1;
  51. }

3串口通讯例程序

有了上面的基础知识,我们来开发一个类似串口调试助手的小工具,以便对于WIN32 API的有一个直观认识,现在让我们开始我们的旅程吧!

打开串口,串口在WINDOWS 32位操作系统下也被认识是一种文件资源,但是这个文件资源不允许共享。代码如下:

  1. BOOL CSerialPort::OpenComm(CString strComm)
  2. {
  3.     if(m_hComm == NULL)
  4.     {
  5.         m_hComm = CreateFile((char*)(LPCSTR)strComm, GENERIC_READ | GENERIC_WRITE, 0, NULL,  OPEN_EXISTING, 
  6.             FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); 
  7.         if(m_hComm == INVALID_HANDLE_VALUE)
  8.         {
  9.             int nError = GetLastError();
  10.             m_hComm = NULL;
  11.             AfxMessageBox("打开串口失败!");
  12.             return FALSE;
  13.         }
  14.     }
  15.     return TRUE;
  16. }

设置串口的属性,包括波特率、帧格式、超时属性等,代码如下:

  1. BOOL CSerialPort::SetCommState(DWORD dwBaudrate, BYTE byParity, BYTE byByteSize, BYTE byStopBits)
  2. {
  3.     DCB dcbOld;
  4.     int ret = ::GetCommState(m_hComm, &dcbOld);
  5.     if(ret == 0)
  6.     {
  7.         CloseHandle(m_hComm);
  8.         m_hComm = NULL;
  9.         return FALSE;   
  10.     }
  11.     dcbOld.BaudRate = dwBaudrate;
  12.     dcbOld.ByteSize = byByteSize;
  13.     dcbOld.Parity = byParity;
  14.     dcbOld.StopBits = byStopBits;
  15.     ret = ::SetCommState(m_hComm, &dcbOld);
  16.     if(ret == 0)
  17.     {
  18.         CloseHandle(m_hComm);
  19.         m_hComm = NULL;
  20.         return FALSE;   
  21.     }
  22.     return TRUE;
  23. }

设置缓冲大小,代码如下:

  1. BOOL CSerialPort::SetupComm(DWORD dwInQueue, DWORD dwOutQueue)
  2. {
  3.     return ::SetupComm(m_hComm, dwInQueue, dwOutQueue);
  4. }

清楚错误状态后,设置监视事件,代码如下:

  1. BOOL CSerialPort::PurgeComm(DWORD dwFlags)
  2. {
  3.     return ::PurgeComm(m_hComm, dwFlags);
  4. }
  5. BOOL CSerialPort::SetCommMask(DWORD dwEvtMask)
  6. {
  7.     return ::SetCommMask(m_hComm, dwEvtMask);
  8. }

串口设置完后,可以进行读写操作了,代码如下:

  1. BOOL CSerialPort::WriteFile( LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
  2. {
  3.     return ::WriteFile(m_hComm, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
  4. }
  5. BOOL CSerialPort::ReadFile(LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
  6. {
  7.     return ::ReadFile(m_hComm, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
  8. }

总体的调用代码如下:

  1. // 打开串口,并设置属性
  2. void CCommDemoDlg::OnBnClickedBtnCommcontrol()
  3. {       
  4.     CButton* pBtnCommControl = (CButton*)GetDlgItem(IDC_BTN_COMMCONTROL);
  5.     if(!m_bIsOpen)
  6.     {
  7.         CComboBox* pComboComm = (CComboBox*)GetDlgItem(IDC_COMBO_COMM);
  8.         int nSel = pComboComm->GetCurSel();
  9.         CString strComm;
  10.         pComboComm->GetLBText(nSel, strComm);
  11.         m_bIsOpen = m_serialPort.OpenComm(strComm); 
  12.         if(m_bIsOpen)
  13.         {
  14.             pBtnCommControl->SetWindowText("关闭串口");
  15.             CComboBox* pComboBaudrate = (CComboBox*)GetDlgItem(IDC_COMBO_BAUDRATE);
  16.             int nSel = pComboBaudrate->GetCurSel();
  17.             DWORD dwBaudrate = pComboBaudrate->GetItemData(nSel);
  18.             CComboBox* pComboCheckbit = (CComboBox*)GetDlgItem(IDC_COMBO_CHECKBIT);
  19.             nSel = pComboCheckbit->GetCurSel();
  20.             BYTE byParity = (BYTE)pComboCheckbit->GetItemData(nSel);
  21.             CComboBox* pComboDatabit = (CComboBox*)GetDlgItem(IDC_COMBO_DATABIT);
  22.             nSel = pComboDatabit->GetCurSel();
  23.             BYTE byDataSize = (BYTE)pComboDatabit->GetItemData(nSel);
  24.             CComboBox* pComboStopbit = (CComboBox*)GetDlgItem(IDC_COMBO_STOPBIT);
  25.             nSel = pComboStopbit->GetCurSel();
  26.             BYTE byStopBits = (BYTE)pComboStopbit->GetItemData(nSel);
  27.             BOOL bRet = m_serialPort.SetCommState(dwBaudrate, byParity, byDataSize, byStopBits);
  28.             if(!bRet)
  29.             {
  30.                 m_serialPort.CloseComm();
  31.                 AfxMessageBox("设置COMM属性出错!");
  32.                 return;
  33.             }
  34.             bRet = m_serialPort.SetupComm(1024, 1024);
  35.             if(!bRet)
  36.             {
  37.                 m_serialPort.CloseComm();
  38.                 AfxMessageBox("设置COMM输入输出缓冲区出错!");
  39.                 return;
  40.             }
  41.             bRet = m_serialPort.PurgeComm(PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
  42.             if(!bRet)
  43.             {
  44.                 m_serialPort.CloseComm();
  45.                 AfxMessageBox("无法清除COMM的错误状态!");
  46.                 return;
  47.             }
  48.             bRet = m_serialPort.SetCommMask(EV_RXCHAR);
  49.             if(!bRet)
  50.             {
  51.                 m_serialPort.CloseComm();
  52.                 AfxMessageBox("设置COMM的事件出错!");
  53.                 return;
  54.             }
  55.         }
  56.         else
  57.         {
  58.             pBtnCommControl->SetWindowText("打开串口");
  59.         }
  60.     }
  61.     else
  62.     {
  63.         m_bIsOpen = FALSE;
  64.         AfxMessageBox("已经有一个串口正在运行中,请关闭该串口!");
  65.         m_serialPort.CloseComm();
  66.         pBtnCommControl->SetWindowText("打开串口");
  67.     }
  68. }
  69. // 发送数据
  70. void CCommDemoDlg::OnBnClickedBtnSend()
  71. {
  72.     if(m_serialPort.m_hComm==NULL)
  73.     {
  74.         AfxMessageBox("请打开串口后发送数据!");
  75.         return;
  76.     }
  77.     CEdit* pEditSend = (CEdit*)GetDlgItem(IDC_EDIT_SEND);   
  78.     CEdit* pEditRecv = (CEdit*)GetDlgItem(IDC_EDIT_RECV);
  79.     DWORD dwWrite =0;
  80.     CString strSend, strRecv;
  81.     pEditSend->GetWindowText(strSend);
  82.     if(strSend.IsEmpty())
  83.     {
  84.         return;
  85.     }
  86.     OVERLAPPED m_OverlappedWrite;
  87.     ZeroMemory(&m_OverlappedWrite, sizeof(OVERLAPPED));
  88.     m_OverlappedWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
  89.     m_serialPort.WriteFile((LPVOID)strSend.GetBuffer(strSend.GetLength()+1), strSend.GetLength()+1, &dwWrite, &m_OverlappedWrite);
  90.     pEditSend->SetWindowText("");
  91.     pEditRecv->GetWindowText(strRecv);
  92.     strRecv += strSend;
  93.     pEditRecv->SetWindowText(strRecv);
  94.     Sleep(10);
  95. }
  96. // 读线程
  97. UINT  CThreadSvr::ReadProc(LPVOID lpParameter)
  98. {
  99.     Sleep(100);
  100.     CThreadSvr* pThreadSvr = (CThreadSvr*)lpParameter;
  101.     int nLength = 0;
  102.     while(!pThreadSvr->bMainThreadExit)
  103.     {
  104.         Sleep(100);
  105.         EnterCriticalSection(&pThreadSvr->cs);
  106.         CCommDemoDlg* pCommDemoDlg = (CCommDemoDlg*)AfxGetApp()->m_pMainWnd;
  107.         if(pCommDemoDlg==NULL)
  108.         {
  109.             continue// 对话框还没有创建成功,须等待其创建成功。
  110.         }
  111.         if(pCommDemoDlg->m_serialPort.m_hComm==NULL)
  112.         {
  113.             continue;
  114.         }
  115.         COMSTAT comStat ;
  116.         DWORD dwError = 0;
  117.         BOOL ret = TRUE;
  118.         DWORD dwRead = 0;
  119.         char recvTemp[512];
  120.         ZeroMemory(recvTemp, 512);
  121.         char recvBuf[4096];
  122.         ZeroMemory(recvBuf, 4096);
  123.         pCommDemoDlg->m_serialPort.ClearCommError(&dwError, &comStat);
  124.         if(comStat.cbInQue>0)
  125.         {
  126.             if(comStat.cbInQue<512)
  127.             {
  128.                 ret = pCommDemoDlg->m_serialPort.ReadFile(recvTemp, comStat.cbInQue, &dwRead, &pThreadSvr->m_OverlappedRead);
  129.             }
  130.             else
  131.             {
  132.                 ret = pCommDemoDlg->m_serialPort.ReadFile(recvTemp, 500, &dwRead, &pThreadSvr->m_OverlappedRead);
  133.             }
  134.             if(comStat.cbInQue>=dwRead)
  135.             {
  136.                 memcpy(recvBuf+nLength, recvTemp, dwRead);
  137.                 nLength += dwRead;
  138.             }
  139.             if(comStat.cbInQue>=dwRead)
  140.             {
  141.                 nLength = 0;
  142.                 CEdit* pEditRecv = (CEdit*)pCommDemoDlg->GetDlgItem(IDC_EDIT_RECV);
  143.                 CString strRecv;
  144.                 pEditRecv->GetWindowText(strRecv);
  145.                 strRecv += recvBuf;
  146.                 pEditRecv->SetWindowText(strRecv);
  147.             }
  148.             if(!ret)
  149.             {
  150.                 if(ERROR_IO_PENDING == GetLastError())
  151.                 {
  152.                     while ( !ret )
  153.                     {
  154.                         ret = pCommDemoDlg->m_serialPort.GetOverlappedResult(&pThreadSvr->m_OverlappedRead, &dwRead, TRUE);
  155.                         if ( GetLastError() != ERROR_IO_INCOMPLETE )
  156.                         {
  157.                             pCommDemoDlg->m_serialPort.ClearCommError(&dwError, &comStat);
  158.                             break;
  159.                         }
  160.                     }
  161.                 }
  162.             }
  163.         }
  164.         LeaveCriticalSection(&pThreadSvr->cs);
  165.     }
  166.     return TRUE;
  167. }

最后调试助手的效果如下:

你可能感兴趣的:([置顶] 基于WIN32 API的串口通讯软件的设计)