Windows下C++ 串口编程实例

1. 本例子使用了比较规范的软件设计方法,类的设计具有比较好的可扩展性和移植性、代码的注释采用doxgen支持的javaDoc风格。
2. 为了能方便初学者更快地了解和入门,几乎每一行代码都加上了详细的注释,对于注释中如果依然有不清楚的概念,相信你通过百度和google一定能找到答案。
3. 本例子设计的串口操作类可以直接移植到其他的工程中去,大家也可以根据自己的需要添加其他的接口。
4. 本例子只实现了串口数据的基本收发功能,其实为了保证串口数据传输的正确性,往往需要设计一些串口通信协议,协议的设计有待你自己完成,如果以后有时间,我也会尝试提供一种比较基本的串口通信协议设计案例给大家学习。
5. 关于本程序的验证方法,可以使用虚拟串口软件VSPM和串口调试助手进行程序的测试与验证. 

下面即为例子工程的三个文件,SerialPort.h、SerialPort.cpp、maincpp
 

SerialPort.h

[cpp]  view plain  copy
  1. //////////////////////////////////////////////////////////////////////////    
  2. /// COPYRIGHT NOTICE    
  3. /// Copyright (c) 2009, 华中科技大学tickTick Group  (版权声明)    
  4. /// All rights reserved.    
  5. ///     
  6. /// @file    SerialPort.h      
  7. /// @brief   串口通信类头文件    
  8. ///    
  9. /// 本文件完成串口通信类的声明    
  10. ///    
  11. /// @version 1.0       
  12. /// @author  卢俊     
  13. /// @E-mail:[email protected]    
  14. /// @date    2010/03/19    
  15. ///    
  16. ///  修订说明:    
  17. //////////////////////////////////////////////////////////////////////////    
  18.   
  19. #ifndef SERIALPORT_H_    
  20. #define SERIALPORT_H_    
  21.   
  22. #include <Windows.h>    
  23.   
  24. /** 串口通信类 
  25. * 
  26. *  本类实现了对串口的基本操作 
  27. *  例如监听发到指定串口的数据、发送指定数据到串口 
  28. */  
  29. class CSerialPort  
  30. {  
  31. public:  
  32.     CSerialPort(void);  
  33.     ~CSerialPort(void);  
  34.   
  35. public:  
  36.   
  37.     /** 初始化串口函数 
  38.     * 
  39.     *  @param:  UINT portNo 串口编号,默认值为1,即COM1,注意,尽量不要大于9 
  40.     *  @param:  UINT baud   波特率,默认为9600 
  41.     *  @param:  char parity 是否进行奇偶校验,'Y'表示需要奇偶校验,'N'表示不需要奇偶校验 
  42.     *  @param:  UINT databits 数据位的个数,默认值为8个数据位 
  43.     *  @param:  UINT stopsbits 停止位使用格式,默认值为1 
  44.     *  @param:  DWORD dwCommEvents 默认为EV_RXCHAR,即只要收发任意一个字符,则产生一个事件 
  45.     *  @return: bool  初始化是否成功 
  46.     *  @note:   在使用其他本类提供的函数前,请先调用本函数进行串口的初始化 
  47.     *        /n本函数提供了一些常用的串口参数设置,若需要自行设置详细的DCB参数,可使用重载函数 
  48.     *           /n本串口类析构时会自动关闭串口,无需额外执行关闭串口 
  49.     *  @see: 
  50.     */  
  51.     bool InitPort(UINT  portNo = 1, UINT  baud = CBR_9600, char  parity = 'N'UINT  databits = 8, UINT  stopsbits = 1, DWORD dwCommEvents = EV_RXCHAR);  
  52.   
  53.     /** 串口初始化函数 
  54.     * 
  55.     *  本函数提供直接根据DCB参数设置串口参数 
  56.     *  @param:  UINT portNo 
  57.     *  @param:  const LPDCB & plDCB 
  58.     *  @return: bool  初始化是否成功 
  59.     *  @note:   本函数提供用户自定义地串口初始化参数 
  60.     *  @see: 
  61.     */  
  62.     bool InitPort(UINT  portNo, const LPDCB& plDCB);  
  63.   
  64.     /** 开启监听线程 
  65.     * 
  66.     *  本监听线程完成对串口数据的监听,并将接收到的数据打印到屏幕输出 
  67.     *  @return: bool  操作是否成功 
  68.     *  @note:   当线程已经处于开启状态时,返回flase 
  69.     *  @see: 
  70.     */  
  71.     bool OpenListenThread();  
  72.   
  73.     /** 关闭监听线程 
  74.     * 
  75.     * 
  76.     *  @return: bool  操作是否成功 
  77.     *  @note:   调用本函数后,监听串口的线程将会被关闭 
  78.     *  @see: 
  79.     */  
  80.     bool CloseListenTread();  
  81.   
  82.     /** 向串口写数据 
  83.     * 
  84.     *  将缓冲区中的数据写入到串口 
  85.     *  @param:  unsigned char * pData 指向需要写入串口的数据缓冲区 
  86.     *  @param:  unsigned int length 需要写入的数据长度 
  87.     *  @return: bool  操作是否成功 
  88.     *  @note:   length不要大于pData所指向缓冲区的大小 
  89.     *  @see: 
  90.     */  
  91.     bool WriteData(unsigned char* pData, unsigned int length);  
  92.   
  93.     /** 获取串口缓冲区中的字节数 
  94.     * 
  95.     * 
  96.     *  @return: UINT  操作是否成功 
  97.     *  @note:   当串口缓冲区中无数据时,返回0 
  98.     *  @see: 
  99.     */  
  100.     UINT GetBytesInCOM();  
  101.   
  102.     /** 读取串口接收缓冲区中一个字节的数据 
  103.     * 
  104.     * 
  105.     *  @param:  char & cRecved 存放读取数据的字符变量 
  106.     *  @return: bool  读取是否成功 
  107.     *  @note: 
  108.     *  @see: 
  109.     */  
  110.     bool ReadChar(char &cRecved);  
  111.   
  112. private:  
  113.   
  114.     /** 打开串口 
  115.     * 
  116.     * 
  117.     *  @param:  UINT portNo 串口设备号 
  118.     *  @return: bool  打开是否成功 
  119.     *  @note: 
  120.     *  @see: 
  121.     */  
  122.     bool openPort(UINT  portNo);  
  123.   
  124.     /** 关闭串口 
  125.     * 
  126.     * 
  127.     *  @return: void  操作是否成功 
  128.     *  @note: 
  129.     *  @see: 
  130.     */  
  131.     void ClosePort();  
  132.   
  133.     /** 串口监听线程 
  134.     * 
  135.     *  监听来自串口的数据和信息 
  136.     *  @param:  void * pParam 线程参数 
  137.     *  @return: UINT WINAPI 线程返回值 
  138.     *  @note: 
  139.     *  @see: 
  140.     */  
  141.     static UINT WINAPI ListenThread(void* pParam);  
  142.   
  143. private:  
  144.   
  145.     /** 串口句柄 */  
  146.     HANDLE  m_hComm;  
  147.   
  148.     /** 线程退出标志变量 */  
  149.     static bool s_bExit;  
  150.   
  151.     /** 线程句柄 */  
  152.     volatile HANDLE    m_hListenThread;  
  153.   
  154.     /** 同步互斥,临界区保护 */  
  155.     CRITICAL_SECTION   m_csCommunicationSync;       //!< 互斥操作串口    
  156.   
  157. };  
  158.   
  159. #endif //SERIALPORT_H_   


SerialPort.cpp

[cpp]  view plain  copy
  1. //////////////////////////////////////////////////////////////////////////    
  2. /// COPYRIGHT NOTICE    
  3. /// Copyright (c) 2009, 华中科技大学tickTick Group  (版权声明)    
  4. /// All rights reserved.    
  5. ///     
  6. /// @file    SerialPort.cpp      
  7. /// @brief   串口通信类的实现文件    
  8. ///    
  9. /// 本文件为串口通信类的实现代码    
  10. ///    
  11. /// @version 1.0       
  12. /// @author  卢俊      
  13. /// @E-mail:[email protected]    
  14. /// @date    2010/03/19    
  15. ///     
  16. ///    
  17. ///  修订说明:    
  18. //////////////////////////////////////////////////////////////////////////    
  19.   
  20. #include "StdAfx.h"    
  21. #include "SerialPort.h"    
  22. #include <process.h>    
  23. #include <iostream>    
  24.   
  25. /** 线程退出标志 */  
  26. bool CSerialPort::s_bExit = false;  
  27. /** 当串口无数据时,sleep至下次查询间隔的时间,单位:秒 */  
  28. const UINT SLEEP_TIME_INTERVAL = 5;  
  29.   
  30. CSerialPort::CSerialPort(void)  
  31.     : m_hListenThread(INVALID_HANDLE_VALUE)  
  32. {  
  33.     m_hComm = INVALID_HANDLE_VALUE;  
  34.     m_hListenThread = INVALID_HANDLE_VALUE;  
  35.   
  36.     InitializeCriticalSection(&m_csCommunicationSync);  
  37.   
  38. }  
  39.   
  40. CSerialPort::~CSerialPort(void)  
  41. {  
  42.     CloseListenTread();  
  43.     ClosePort();  
  44.     DeleteCriticalSection(&m_csCommunicationSync);  
  45. }  
  46.   
  47. bool CSerialPort::InitPort(UINT portNo /*= 1*/UINT baud /*= CBR_9600*/char parity /*= 'N'*/,  
  48.     UINT databits /*= 8*/UINT stopsbits /*= 1*/DWORD dwCommEvents /*= EV_RXCHAR*/)  
  49. {  
  50.   
  51.     /** 临时变量,将制定参数转化为字符串形式,以构造DCB结构 */  
  52.     char szDCBparam[50];  
  53.     sprintf_s(szDCBparam, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopsbits);  
  54.   
  55.     /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */  
  56.     if (!openPort(portNo))  
  57.     {  
  58.         return false;  
  59.     }  
  60.   
  61.     /** 进入临界段 */  
  62.     EnterCriticalSection(&m_csCommunicationSync);  
  63.   
  64.     /** 是否有错误发生 */  
  65.     BOOL bIsSuccess = TRUE;  
  66.   
  67.     /** 在此可以设置输入输出的缓冲区大小,如果不设置,则系统会设置默认值. 
  68.     *  自己设置缓冲区大小时,要注意设置稍大一些,避免缓冲区溢出 
  69.     */  
  70.     /*if (bIsSuccess ) 
  71.     { 
  72.     bIsSuccess = SetupComm(m_hComm,10,10); 
  73.     }*/  
  74.   
  75.     /** 设置串口的超时时间,均设为0,表示不使用超时限制 */  
  76.     COMMTIMEOUTS  CommTimeouts;  
  77.     CommTimeouts.ReadIntervalTimeout = 0;  
  78.     CommTimeouts.ReadTotalTimeoutMultiplier = 0;  
  79.     CommTimeouts.ReadTotalTimeoutConstant = 0;  
  80.     CommTimeouts.WriteTotalTimeoutMultiplier = 0;  
  81.     CommTimeouts.WriteTotalTimeoutConstant = 0;  
  82.     if (bIsSuccess)  
  83.     {  
  84.         bIsSuccess = SetCommTimeouts(m_hComm, &CommTimeouts);  
  85.     }  
  86.   
  87.     DCB  dcb;  
  88.     if (bIsSuccess)  
  89.     {  
  90.         // 将ANSI字符串转换为UNICODE字符串    
  91.         DWORD dwNum = MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, NULL, 0);  
  92.         wchar_t *pwText = new wchar_t[dwNum];  
  93.         if (!MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, pwText, dwNum))  
  94.         {  
  95.             bIsSuccess = TRUE;  
  96.         }  
  97.   
  98.         /** 获取当前串口配置参数,并且构造串口DCB参数 */  
  99.         bIsSuccess = GetCommState(m_hComm, &dcb) && BuildCommDCB(pwText, &dcb);  
  100.         /** 开启RTS flow控制 */  
  101.         dcb.fRtsControl = RTS_CONTROL_ENABLE;  
  102.   
  103.         /** 释放内存空间 */  
  104.         delete[] pwText;  
  105.     }  
  106.   
  107.     if (bIsSuccess)  
  108.     {  
  109.         /** 使用DCB参数配置串口状态 */  
  110.         bIsSuccess = SetCommState(m_hComm, &dcb);  
  111.     }  
  112.   
  113.     /**  清空串口缓冲区 */  
  114.     PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);  
  115.   
  116.     /** 离开临界段 */  
  117.     LeaveCriticalSection(&m_csCommunicationSync);  
  118.   
  119.     return bIsSuccess == TRUE;  
  120. }  
  121.   
  122. bool CSerialPort::InitPort(UINT portNo, const LPDCB& plDCB)  
  123. {  
  124.     /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */  
  125.     if (!openPort(portNo))  
  126.     {  
  127.         return false;  
  128.     }  
  129.   
  130.     /** 进入临界段 */  
  131.     EnterCriticalSection(&m_csCommunicationSync);  
  132.   
  133.     /** 配置串口参数 */  
  134.     if (!SetCommState(m_hComm, plDCB))  
  135.     {  
  136.         return false;  
  137.     }  
  138.   
  139.     /**  清空串口缓冲区 */  
  140.     PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);  
  141.   
  142.     /** 离开临界段 */  
  143.     LeaveCriticalSection(&m_csCommunicationSync);  
  144.   
  145.     return true;  
  146. }  
  147.   
  148. void CSerialPort::ClosePort()  
  149. {  
  150.     /** 如果有串口被打开,关闭它 */  
  151.     if (m_hComm != INVALID_HANDLE_VALUE)  
  152.     {  
  153.         CloseHandle(m_hComm);  
  154.         m_hComm = INVALID_HANDLE_VALUE;  
  155.     }  
  156. }  
  157.   
  158. bool CSerialPort::openPort(UINT portNo)  
  159. {  
  160.     /** 进入临界段 */  
  161.     EnterCriticalSection(&m_csCommunicationSync);  
  162.   
  163.     /** 把串口的编号转换为设备名 */  
  164.     char szPort[50];  
  165. /** 经常碰到串口号大于10的情况,如"COM12"、"COM20"这样的,打开串口(createfile)时会出错;追加一段前缀"\\\\.\\" */ 
  166.     sprintf_s(szPort, "\\\\.\\COM%d", portNo);  
  167.   
  168.     /** 打开指定的串口 */  
  169.     m_hComm = CreateFileA(szPort,  /** 设备名,COM1,COM2等 */  
  170.         GENERIC_READ | GENERIC_WRITE, /** 访问模式,可同时读写 */  
  171.         0,                            /** 共享模式,0表示不共享 */  
  172.         NULL,                         /** 安全性设置,一般使用NULL */  
  173.         OPEN_EXISTING,                /** 该参数表示设备必须存在,否则创建失败 */  
  174.         0,  
  175.         0);  
  176.   
  177.     /** 如果打开失败,释放资源并返回 */  
  178.     if (m_hComm == INVALID_HANDLE_VALUE)  
  179.     {  
  180.         LeaveCriticalSection(&m_csCommunicationSync);  
  181.         return false;  
  182.     }  
  183.   
  184.     /** 退出临界区 */  
  185.     LeaveCriticalSection(&m_csCommunicationSync);  
  186.   
  187.     return true;  
  188. }  
  189.   
  190. bool CSerialPort::OpenListenThread()  
  191. {  
  192.     /** 检测线程是否已经开启了 */  
  193.     if (m_hListenThread != INVALID_HANDLE_VALUE)  
  194.     {  
  195.         /** 线程已经开启 */  
  196.         return false;  
  197.     }  
  198.   
  199.     s_bExit = false;  
  200.     /** 线程ID */  
  201.     UINT threadId;  
  202.     /** 开启串口数据监听线程 */  
  203.     m_hListenThread = (HANDLE)_beginthreadex(NULL, 0, ListenThread, this, 0, &threadId);  
  204.     if (!m_hListenThread)  
  205.     {  
  206.         return false;  
  207.     }  
  208.     /** 设置线程的优先级,高于普通线程 */  
  209.     if (!SetThreadPriority(m_hListenThread, THREAD_PRIORITY_ABOVE_NORMAL))  
  210.     {  
  211.         return false;  
  212.     }  
  213.   
  214.     return true;  
  215. }  
  216.   
  217. bool CSerialPort::CloseListenTread()  
  218. {  
  219.     if (m_hListenThread != INVALID_HANDLE_VALUE)  
  220.     {  
  221.         /** 通知线程退出 */  
  222.         s_bExit = true;  
  223.   
  224.         /** 等待线程退出 */  
  225.         Sleep(10);  
  226.   
  227.         /** 置线程句柄无效 */  
  228.         CloseHandle(m_hListenThread);  
  229.         m_hListenThread = INVALID_HANDLE_VALUE;  
  230.     }  
  231.     return true;  
  232. }  
  233.   
  234. UINT CSerialPort::GetBytesInCOM()  
  235. {  
  236.     DWORD dwError = 0;  /** 错误码 */  
  237.     COMSTAT  comstat;   /** COMSTAT结构体,记录通信设备的状态信息 */  
  238.     memset(&comstat, 0, sizeof(COMSTAT));  
  239.   
  240.     UINT BytesInQue = 0;  
  241.     /** 在调用ReadFile和WriteFile之前,通过本函数清除以前遗留的错误标志 */  
  242.     if (ClearCommError(m_hComm, &dwError, &comstat))  
  243.     {  
  244.         BytesInQue = comstat.cbInQue; /** 获取在输入缓冲区中的字节数 */  
  245.     }  
  246.   
  247.     return BytesInQue;  
  248. }  
  249.   
  250. UINT WINAPI CSerialPort::ListenThread(void* pParam)  
  251. {  
  252.     /** 得到本类的指针 */  
  253.     CSerialPort *pSerialPort = reinterpret_cast<CSerialPort*>(pParam);  
  254.   
  255.     // 线程循环,轮询方式读取串口数据    
  256.     while (!pSerialPort->s_bExit)  
  257.     {  
  258.         UINT BytesInQue = pSerialPort->GetBytesInCOM();  
  259.         /** 如果串口输入缓冲区中无数据,则休息一会再查询 */  
  260.         if (BytesInQue == 0)  
  261.         {  
  262.             Sleep(SLEEP_TIME_INTERVAL);  
  263.             continue;  
  264.         }  
  265.   
  266.         /** 读取输入缓冲区中的数据并输出显示 */  
  267.         char cRecved = 0x00;  
  268.         do  
  269.         {  
  270.             cRecved = 0x00;  
  271.             if (pSerialPort->ReadChar(cRecved) == true)  
  272.             {  
  273.                 std::cout << cRecved;  
  274.                 continue;  
  275.             }  
  276.         } while (--BytesInQue);  
  277.     }  
  278.   
  279.     return 0;  
  280. }  
  281.   
  282. bool CSerialPort::ReadChar(char &cRecved)  
  283. {  
  284.     BOOL  bResult = TRUE;  
  285.     DWORD BytesRead = 0;  
  286.     if (m_hComm == INVALID_HANDLE_VALUE)  
  287.     {  
  288.         return false;  
  289.     }  
  290.   
  291.     /** 临界区保护 */  
  292.     EnterCriticalSection(&m_csCommunicationSync);  
  293.   
  294.     /** 从缓冲区读取一个字节的数据 */  
  295.     bResult = ReadFile(m_hComm, &cRecved, 1, &BytesRead, NULL);  
  296.     if ((!bResult))  
  297.     {  
  298.         /** 获取错误码,可以根据该错误码查出错误原因 */  
  299.         DWORD dwError = GetLastError();  
  300.   
  301.         /** 清空串口缓冲区 */  
  302.         PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);  
  303.         LeaveCriticalSection(&m_csCommunicationSync);  
  304.   
  305.         return false;  
  306.     }  
  307.   
  308.     /** 离开临界区 */  
  309.     LeaveCriticalSection(&m_csCommunicationSync);  
  310.   
  311.     return (BytesRead == 1);  
  312.   
  313. }  
  314.   
  315. bool CSerialPort::WriteData(unsigned char* pData, unsigned int length)  
  316. {  
  317.     BOOL   bResult = TRUE;  
  318.     DWORD  BytesToSend = 0;  
  319.     if (m_hComm == INVALID_HANDLE_VALUE)  
  320.     {  
  321.         return false;  
  322.     }  
  323.   
  324.     /** 临界区保护 */  
  325.     EnterCriticalSection(&m_csCommunicationSync);  
  326.   
  327.     /** 向缓冲区写入指定量的数据 */  
  328.     bResult = WriteFile(m_hComm, pData, length, &BytesToSend, NULL);  
  329.     if (!bResult)  
  330.     {  
  331.         DWORD dwError = GetLastError();  
  332.         /** 清空串口缓冲区 */  
  333.         PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);  
  334.         LeaveCriticalSection(&m_csCommunicationSync);  
  335.   
  336.         return false;  
  337.     }  
  338.   
  339.     /** 离开临界区 */  
  340.     LeaveCriticalSection(&m_csCommunicationSync);  
  341.   
  342.     return true;  
  343. }  


main.cpp

[cpp]  view plain  copy
  1. int _tmain(int argc, _TCHAR* argv[])    
  2. {    
  3.    
  4.     CSerialPort mySerialPort;    
  5.    
  6.     if (!mySerialPort.InitPort(2))    
  7.     {    
  8.         std::cout << "initPort fail !" << std::endl;    
  9.     }    
  10.     else   
  11.     {    
  12.         std::cout << "initPort success !" << std::endl;    
  13.     }    
  14.    
  15.     if (!mySerialPort.OpenListenThread())    
  16.     {    
  17.         std::cout << "OpenListenThread fail !" << std::endl;    
  18.     }    
  19.     else   
  20.     {    
  21.         std::cout << "OpenListenThread success !" << std::endl;    
  22.     }    
  23.    
  24.     int temp;    
  25.     std::cin >> temp;    
  26.    
  27.     return 0;    
  28. }    

猜你喜欢

转载自blog.csdn.net/kulala082/article/details/78114108