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

      工程文件,需要使用vs2008打开。下载
      http://ticktick.blog.51cto.com/ext/down_att.php?aid=23099&code=6280

    SerialPort.h

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

    SerialPort.cpp

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

    main.cpp

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