当前位置:   article > 正文

Windows上位机C++串口通信 原理+库代码编写+库运用(机甲大师逐曦战队算法组主线教程1)_c++串口库

c++串口库

C++中的串口通信没有Python方便的sarial库,须自己定义完成库调用。

本文主要教学简单的串口库编写及调用,并附全部代码及库文件。

建议自行编写库体会串口通信,并完成作业。

一、串口通信理论

(一)基本概念

1、数电基础

省流版:通信中串口发送的数据内容与格式编写需一定数电基础,稍作了解即可

(1)数制

指多位数码中每一位的构成方法以及从低位到高位的进位规则。

常用数制:十进制、二进制、八进制、十六进制等

 

 

 (2)码制

用数字技术来处理和传输的以二进制形式表示数字、字母或特殊符号的系统。

用文字、符号或数码表示特定对象的过程称为编码。数字电路中常用的是二进制编码。N位二进制代码有2的N次方个状态,可表示2的N次方个对象。

2、通信基础

(1)并行串行

(2)异步同步

异步串行通信:异步串行通信是指通信双方以一个字符(包括特定附加位)作为数据传输单位且发送方传送字符的间隔时间不一定,具有不规则数据段传送特性的串行数据传输。

同步串行通信:同步串行通信是指在约定的通信速率下,发送端和接收端的时钟信号频率和相位始终保持一致(同步),这就保证了通信双方在发送和接收数据时具有完全一致的定时关系。

(3)单工双工

(二)串口概述

1、UART协议

(1)UART    

Universal Asynchronous Receiver Transmitter 即     通用异步收发器,是一种通用的串行、异步通信总线     该总线有两条数据线,可以实现全双工的发送和接收     在嵌入式系统中常用于主机与辅助设备之间的通信

(2)UART帧格式

(3)硬件连接

 

TXD为发送端,RXD为接收端 

(4)参数 波特率

用于描述UART通信时的通信速度,其单位为     bps(bit per second)即每秒钟传送的bit的数量

2、个人计算机中的串口

(1)USB串口

Universal Serial Bus(通用串行总线) 简称USB,是目前电脑上应用较广泛的接口规范,由Intel、Microsoft、Compaq、IBM、NEC、Northern Telcom等几家大厂商发起的新型外设接口标准。USB接口是电脑主板上的一种四针接口,其中中间两个针传输数据,两边两个针给外设供电。USB接口速度快、连接简单、不需要外接电源,传输速度12Mbps,最新USB2.0可达480Mbps;电缆最大长度5米,USB电缆有4条线,2条信号线,2条电源线,可提供5伏特电源,USB通过串联方式最多可串接127个设备;支持热插拔。

(2)虚拟串口

虚拟串口一般用来调试PC上位机软件和串口的通讯。在没有电控队友在身边时,我们需要知道我们发送过去了什么数据,即数据是否完整,数据传输的状态等,这时候我们就需要虚拟串口 即在mcu中直接虚拟一个串口,这样我们就可以使用串口助手直接查看该串口数据的收发情况。

虚拟串口及串口助手的下载使用可参考以下文章

http://t.csdnimg.cn/fq1ax

二、代码_串口库

先编写串口库文件,

(一)windows主要函数

完成代码须引入windows库,部分常用代码如下。

  1. 1.CreateFile - 打开串口;
  2. 2.SetupComm-初始化一个指定的通信设备的通信参数
  3. 3.ReadFile - 读数据;
  4. 4.WriteFile - 写数据;
  5. 5.CloseHandle - 关闭串口;
  6. 6.GetCommState - 取得串口当前状态;
  7. 7.SetCommState - 设置串口状态;
  8. 8.PurgeComm - 清除串口缓冲区 ;
  9. 9.ClearCommError - 清除串口错误或者读取串口现在的状态;
  10. 10.SetCommMask - 设置串口通信事件;
  11. 11.WaitCommEvent - 用来判断用SetCommMask()函数设置的串口通

(二)类的完成(结尾有各个文件的完整代码)

1、创建类

在头文件中创建一个串口通信类(先创建.h头文件)

整体结构如图,public处函数声明待补充 。

创建这个头文件对应的cpp文件,编写函数。

2、函数编写

(1)头文件及线程退出

 (2)构造函数与析构函数

类的建立及清除

 (3)初始化串口函数

  • 初始化串口并配置其参数,以便与设备进行通信。
  • 在打开和关闭串口时处理临界区,以确保串口操作的线程安全性。
  • 清空串口缓冲区,以防止旧数据干扰新的通信。

(4)打开关闭串口

 (5)打开、关闭和运行串口监听线程

监听线程从串口接收传入的数据字节,并将其存储在缓冲区中。

(6)从串口接收缓冲区中读取数据、将数据从缓冲区写入串口

(7)完善.h头文件

3、完整代码 

ZhuXiSerial.h

  1. #ifndef __ZhuXiSerial_H_
  2. #define __ZhuXiSerial_H_
  3. #include <Windows.h>
  4. #include <string>
  5. #include <process.h>
  6. using namespace std;
  7. typedef enum AxisType //枚举类型表达坐标 方便视觉传输信息
  8. {
  9. AXIS_XX = 2,
  10. AXIS_YY = 3,
  11. AXIS_OO = 4,
  12. }AXIS_TYPE;
  13. class ZhuXiSerial
  14. {
  15. public:
  16. ZhuXiSerial(void);
  17. ~ZhuXiSerial(void);
  18. public:
  19. bool InitPort(UINT portNo, UINT baud, char parity, UINT databits, UINT stopsbits, DWORD dwCommEvents); //初始化
  20. bool InitPort(UINT portNo, const LPDCB& plDCB); //初始化
  21. bool OpenListenThread(); //打开监听
  22. bool CloseListenTread(); //关闭监听
  23. bool WriteData(unsigned char* pData, int length); //向串口写数据
  24. UINT GetBytesInCOM(); //获取串口缓冲区中的字节数
  25. bool ReadChar(unsigned char& cRecved); //读取串口接收缓冲区中一个字节的数据
  26. unsigned char* MotorMoveXY(unsigned char x, unsigned char y);//xy相对移动
  27. unsigned char* StopMotor(unsigned char sm1);
  28. unsigned char* SetSpeed(AXIS_TYPE enAxisType, int speed);
  29. unsigned char* SetRunSpeed(int TY, int speed);
  30. public:
  31. bool openPort(UINT portNo); //打开串口
  32. void ClosePort(); //关闭串口
  33. static UINT WINAPI ListenThread(void* pParam); //串口监听
  34. private:
  35. //一些退出
  36. HANDLE m_hComm;
  37. static bool s_bExit;
  38. volatile HANDLE m_hListenThread;
  39. CRITICAL_SECTION m_csCommunicationSync;
  40. };
  41. #endif

 ZhuXiSerial.cpp

  1. #include <iostream>
  2. #include <sstream>
  3. #include <iomanip>
  4. #include <algorithm>
  5. #include "ZhuXiSerial.h"
  6. using namespace std;
  7. /** 线程退出标志 */
  8. bool ZhuXiSerial::s_bExit = false;
  9. /** 当串口无数据时,sleep至下次查询间隔的时间,单位:秒 */
  10. const UINT SLEEP_TIME_INTERVAL = 5;
  11. //构造函数 析构函数
  12. ZhuXiSerial::ZhuXiSerial(void)
  13. : m_hListenThread(INVALID_HANDLE_VALUE)
  14. {
  15. m_hComm = INVALID_HANDLE_VALUE; //存储串口句柄
  16. m_hListenThread = INVALID_HANDLE_VALUE; //存储监听句柄
  17. InitializeCriticalSection(&m_csCommunicationSync); //初始化临界区对象。临界区用于同步对串口通信操作的访问。
  18. }
  19. ZhuXiSerial::~ZhuXiSerial(void)
  20. {
  21. CloseListenTread();
  22. ClosePort();
  23. DeleteCriticalSection(&m_csCommunicationSync);
  24. }
  25. //初始化串口函数
  26. bool ZhuXiSerial::InitPort(UINT portNo /*= 1*/, UINT baud /*= CBR_9600*/, char parity /*= 'N'*/,
  27. UINT databits /*= 8*/, UINT stopsbits /*= 1*/, DWORD dwCommEvents /*= EV_RXCHAR*/)
  28. {
  29. /** 临时变量,将制定参数转化为字符串形式,以构造DCB结构 */
  30. char szDCBparam[50];
  31. sprintf_s(szDCBparam, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopsbits);
  32. /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */
  33. if (!openPort(portNo))
  34. {
  35. return false;
  36. }
  37. /** 进入临界段 */
  38. EnterCriticalSection(&m_csCommunicationSync);
  39. /** 是否有错误发生 */
  40. BOOL bIsSuccess = TRUE;
  41. /** 在此可以设置输入输出的缓冲区大小,如果不设置,则系统会设置默认值.
  42. 自己设置缓冲区大小时,要注意设置稍大一些,避免缓冲区溢出 */
  43. /*if (bIsSuccess )
  44. {
  45. bIsSuccess = SetupComm(m_hComm,10,10);
  46. }*/
  47. /** 设置串口的超时时间,均设为0,表示不使用超时限制 */
  48. COMMTIMEOUTS CommTimeouts;
  49. CommTimeouts.ReadIntervalTimeout = 0;
  50. CommTimeouts.ReadTotalTimeoutMultiplier = 0;
  51. CommTimeouts.ReadTotalTimeoutConstant = 0;
  52. CommTimeouts.WriteTotalTimeoutMultiplier = 0;
  53. CommTimeouts.WriteTotalTimeoutConstant = 0;
  54. if (bIsSuccess)
  55. {
  56. bIsSuccess = SetCommTimeouts(m_hComm, &CommTimeouts);
  57. }
  58. DCB dcb;
  59. if (bIsSuccess)
  60. {
  61. //DWORD dwNum = MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, NULL, 0);
  62. //wchar_t *pwText = new wchar_t[dwNum];
  63. //if (!MultiByteToWideChar(CP_ACP, 0, szDCBparam, -1, pwText, dwNum))
  64. //{
  65. // bIsSuccess = TRUE;
  66. //}
  67. /** 获取当前串口配置参数,并且构造串口DCB参数 */
  68. /* bIsSuccess = GetCommState(m_hComm, &dcb) && BuildCommDCB(szDCBparam, &dcb);*/
  69. bIsSuccess = GetCommState(m_hComm, &dcb); //&& BuildCommDCB((LPCWSTR)szDCBparam, &dcb)
  70. /** 开启RTS flow控制 */
  71. dcb.fRtsControl = RTS_CONTROL_ENABLE;
  72. /** 释放内存空间 */
  73. /*delete[] pwText;*/
  74. }
  75. if (bIsSuccess)
  76. {
  77. /** 使用DCB参数配置串口状态 */
  78. bIsSuccess = SetCommState(m_hComm, &dcb);
  79. }
  80. /** 清空串口缓冲区 */
  81. PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
  82. /** 离开临界段 */
  83. LeaveCriticalSection(&m_csCommunicationSync);
  84. return bIsSuccess == TRUE;
  85. }
  86. //初始化串口函数
  87. bool ZhuXiSerial::InitPort(UINT portNo, const LPDCB& plDCB)
  88. {
  89. /** 打开指定串口,该函数内部已经有临界区保护,上面请不要加保护 */
  90. if (!openPort(portNo))
  91. {
  92. return false;
  93. }
  94. /** 进入临界段 */
  95. EnterCriticalSection(&m_csCommunicationSync);
  96. /** 配置串口参数 */
  97. if (!SetCommState(m_hComm, plDCB))
  98. {
  99. return false;
  100. }
  101. /** 清空串口缓冲区 */
  102. PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
  103. /** 离开临界段 */
  104. LeaveCriticalSection(&m_csCommunicationSync);
  105. return true;
  106. }
  107. void ZhuXiSerial::ClosePort() //关闭串口
  108. {
  109. /** 如果有串口被打开,关闭它 */
  110. if (m_hComm != INVALID_HANDLE_VALUE)
  111. {
  112. CloseHandle(m_hComm);
  113. m_hComm = INVALID_HANDLE_VALUE;
  114. }
  115. }
  116. bool ZhuXiSerial::openPort(UINT portNo) //打开串口
  117. {
  118. EnterCriticalSection(&m_csCommunicationSync); /** 进入临界段 */
  119. /** 把串口的编号转换为设备名 */
  120. char szPort[50];
  121. sprintf_s(szPort, "COM%d", portNo);
  122. /** 打开指定的串口 */
  123. m_hComm = CreateFileA(szPort, /** 设备名,COM1,COM2*/
  124. GENERIC_READ | GENERIC_WRITE, /** 访问模式,可同时读写 */
  125. 0, /** 共享模式,0表示不共享 */
  126. NULL, /** 安全性设置,一般使用NULL */
  127. OPEN_EXISTING, /** 该参数表示设备必须存在,否则创建失败 */
  128. 0,
  129. 0);
  130. /** 如果打开失败,释放资源并返回 */
  131. if (m_hComm == INVALID_HANDLE_VALUE)
  132. {
  133. cout << "打开失败!" << endl;
  134. LeaveCriticalSection(&m_csCommunicationSync);
  135. return false;
  136. }
  137. /** 退出临界区 */
  138. LeaveCriticalSection(&m_csCommunicationSync);
  139. return true;
  140. }
  141. //打开监听线程
  142. bool ZhuXiSerial::OpenListenThread()
  143. {
  144. /** 检测线程是否已经开启了 */
  145. if (m_hListenThread != INVALID_HANDLE_VALUE)
  146. {
  147. return false; /** 线程已经开启 */
  148. }
  149. s_bExit = false;
  150. /** 线程ID */
  151. UINT threadId;
  152. /** 开启串口数据监听线程 */
  153. m_hListenThread = (HANDLE)_beginthreadex(NULL, 0, ListenThread, this, 0, &threadId);
  154. if (!m_hListenThread)
  155. {
  156. return false;
  157. }
  158. /** 设置线程的优先级,高于普通线程 */
  159. if (!SetThreadPriority(m_hListenThread, THREAD_PRIORITY_ABOVE_NORMAL))
  160. {
  161. return false;
  162. }
  163. return true;
  164. }
  165. //关闭监听线程
  166. bool ZhuXiSerial::CloseListenTread()
  167. {
  168. if (m_hListenThread != INVALID_HANDLE_VALUE)
  169. {
  170. s_bExit = true; /** 通知线程退出 */
  171. Sleep(10); /** 等待线程退出 */
  172. CloseHandle(m_hListenThread); /** 置线程句柄无效 */
  173. m_hListenThread = INVALID_HANDLE_VALUE;
  174. }
  175. return true;
  176. }
  177. //获取串口缓冲区的字节数
  178. UINT ZhuXiSerial::GetBytesInCOM()
  179. {
  180. DWORD dwError = 0; /** 错误码 */
  181. COMSTAT comstat; /** COMSTAT结构体,记录通信设备的状态信息 */
  182. memset(&comstat, 0, sizeof(COMSTAT));
  183. UINT BytesInQue = 0;
  184. /** 在调用ReadFile和WriteFile之前,通过本函数清除以前遗留的错误标志 */
  185. if (ClearCommError(m_hComm, &dwError, &comstat))
  186. {
  187. BytesInQue = comstat.cbInQue; /** 获取在输入缓冲区中的字节数 */
  188. }
  189. return BytesInQue;
  190. }
  191. //串口监听线程
  192. UINT WINAPI ZhuXiSerial::ListenThread(void* pParam)
  193. {
  194. /** 得到本类的指针 */
  195. ZhuXiSerial* pSerialPort = reinterpret_cast<ZhuXiSerial*>(pParam);
  196. string str_input = "";
  197. // 线程循环,轮询方式读取串口数据
  198. while (!pSerialPort->s_bExit)
  199. {
  200. UINT BytesInQue = pSerialPort->GetBytesInCOM();
  201. /** 如果串口输入缓冲区中无数据,则休息一会再查询 */
  202. if (BytesInQue == 0)
  203. {
  204. Sleep(SLEEP_TIME_INTERVAL);
  205. continue;
  206. }
  207. /** 读取输入缓冲区中的数据并输出显示 */
  208. unsigned char cRecved = 0x00;
  209. do
  210. {
  211. cRecved = 0x00;
  212. if (pSerialPort->ReadChar(cRecved) == true)
  213. {
  214. std::stringstream ss;
  215. int tm = cRecved;
  216. ss << std::hex << std::setw(2) << std::setfill('0') << tm;//std::hex进复行制十六进制百流输出 std::setw :需要填充多少个字符,默认填充的字符为' '空格 std::setfill:设置std::setw将填充什么样的字符,如:std::setfill('*')
  217. ss << " ";
  218. string a = ss.str();//str(): return string copy of character array复制字符数组并且返回
  219. string b;
  220. //transform函数的作用是:将某操作应用于指定范围的每个元素,first是容器的首迭代器,last为容器的末迭代器,result为存放结果的容器,op为要进行操作的一元函数对象或sturct、class
  221. //back_inserter用于在末尾插入元素
  222. transform(a.begin(), a.end(), back_inserter(b), ::toupper);
  223. if (b == "2A ")
  224. {
  225. b = "AA ";
  226. }
  227. //完成:将获取的输入放到我的字符串中,到时候判断输入的是否是我需要的输入
  228. str_input.append(b);
  229. continue;
  230. }
  231. } while (--BytesInQue);
  232. //完成:输入串口信号进行解析
  233. if (BytesInQue == 0)
  234. {
  235. //string str_target="55 AA 07 1E 01 01 E6";//制定的信号
  236. string str_target = "55 aa 07 03 00 01";//制定的信号
  237. //str_target=toupper(str_target.size());
  238. transform(str_target.begin(), str_target.end(), str_target.begin(), ::toupper);
  239. str_input.pop_back();//断点发现,字符串最后会多个字符,所以将其删除
  240. int a = str_input.compare(str_target);//比较输入串口信号和制定的信号
  241. if (a == 0)
  242. {
  243. //TODO:执行动作,因为输入串口信号符合制定信号,所以执行动作
  244. int b = 0;
  245. }
  246. str_input.clear();
  247. }
  248. }
  249. return 0;
  250. }
  251. //读取串口接收缓冲区中一个字节的数据
  252. bool ZhuXiSerial::ReadChar(unsigned char& cRecved)
  253. {
  254. BOOL bResult = TRUE;
  255. DWORD BytesRead = 0;
  256. if (m_hComm == INVALID_HANDLE_VALUE)
  257. {
  258. return false;
  259. }
  260. /** 临界区保护 */
  261. EnterCriticalSection(&m_csCommunicationSync);
  262. /** 从缓冲区读取一个字节的数据 */
  263. bResult = ReadFile(m_hComm, &cRecved, 1, &BytesRead, NULL);
  264. if ((!bResult))
  265. {
  266. /** 获取错误码,可以根据该错误码查出错误原因 */
  267. DWORD dwError = GetLastError();
  268. /** 清空串口缓冲区 */
  269. PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);
  270. LeaveCriticalSection(&m_csCommunicationSync);
  271. return false;
  272. }
  273. /** 离开临界区 */
  274. LeaveCriticalSection(&m_csCommunicationSync);
  275. return BytesRead;
  276. }
  277. // 向串口写数据, 将缓冲区中的数据写入到串口
  278. bool ZhuXiSerial::WriteData(unsigned char* pData, int length)
  279. {
  280. int* pData1 = new int;
  281. BOOL bResult = TRUE;
  282. DWORD BytesToSend = 0;
  283. if (m_hComm == INVALID_HANDLE_VALUE)
  284. {
  285. return false;
  286. }
  287. /** 临界区保护 */
  288. EnterCriticalSection(&m_csCommunicationSync);
  289. /** 向缓冲区写入指定量的数据 */
  290. bResult = WriteFile(m_hComm,/*文件句柄*/pData,/*用于保存读入数据的一个缓冲区*/ length,/*要读入的字符数*/ &BytesToSend,/*指向实际读取字节数的指针*/ NULL);
  291. if (!bResult)
  292. {
  293. DWORD dwError = GetLastError();
  294. /** 清空串口缓冲区 */
  295. PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_RXABORT);
  296. LeaveCriticalSection(&m_csCommunicationSync);
  297. return false;
  298. }
  299. /** 离开临界区 */
  300. LeaveCriticalSection(&m_csCommunicationSync);
  301. return true;
  302. }

三、代码_使用库

可自行使用定义声明的代码完成主程序撰写和功能设计。

以下以装甲板识别发送颜色和数字为例。

 main.cpp

  1. #include "ZhuXiSerial.h"
  2. #include <windows.h>
  3. #include <iostream>
  4. using namespace std;
  5. void sendBtyle(int color,int num)
  6. {
  7. ZhuXiSerial mySerialPort;//首先将之前定义的类实例化
  8. if (!mySerialPort.InitPort(1, CBR_19200, 'N', 8, 1, EV_RXCHAR))//是否打开串口,3就是你外设连接电脑的com口,可以在设备管理器查看,然后更改这个参数
  9. {
  10. std::cout << "initPort fail !" << std::endl;
  11. }
  12. else
  13. {
  14. std::cout << "initPort success !" << std::endl;
  15. }
  16. if (!mySerialPort.OpenListenThread())//是否打开监听线程,开启线程用来传输返回值
  17. {
  18. std::cout << "OpenListenThread fail !" << std::endl;
  19. }
  20. else
  21. {
  22. std::cout << "OpenListenThread success !" << std::endl;
  23. }
  24. if (color == 0)
  25. {
  26. unsigned char temp[] = "红色";//动态创建一个数组
  27. mySerialPort.WriteData(temp, 4);
  28. unsigned char* temp2 = new unsigned char[4];
  29. temp2[0] = ' ';
  30. char str[25];
  31. itoa(num, str, 10);
  32. temp2[1] = str[0];
  33. mySerialPort.WriteData(temp2, 2);
  34. }
  35. else
  36. {
  37. unsigned char temp[] = "蓝色";//动态创建一个数组
  38. mySerialPort.WriteData(temp, 4);
  39. unsigned char* temp2 = new unsigned char[4];
  40. temp2[0] = ' ';
  41. char str[25];
  42. itoa(num, str, 10);
  43. temp2[1] = str[0];
  44. mySerialPort.WriteData(temp2, 2);
  45. }
  46. Sleep(1000);
  47. }
  48. int main()
  49. {
  50. int color,Num;
  51. color = 1;
  52. Num = 2;
  53. sendBtyle(color, Num);
  54. }

四、完整使用

开虚拟串口,开串口助手,COM几就把主程序里的下图数字改成几,第二个参数是波特率,串口助手波特率设置一致,串口助手打开串口,主程序发送数据,在串口助手上观察。

串口的更多玩法自己开拓~

五、作业

感谢SCU的作业,搬搬

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/836994
推荐阅读
相关标签
  

闽ICP备14008679号