Visual Studio 2013 串口类

重构串口驱动

前言:

最近调试串口获取机芯日志,要做个上位机工具能随时查看机芯的本地日志。起初想参考合泰源码做一份,但是考虑到通用性。如果未来要做其他项目,又得改串口类的代码,很麻烦。所以想整一份自己的专属串口类,方便后续移植使用。

网上参考了很多资料,基本上都是大同小异。打算自己重构一份,使用双线程,实现读写分离,环形数组收发数据。应用层可以向指定的环形数组,随时随地写入不定长度的数据。或者随时随地从指定的环形数组,读取不定长度的数据。

参考资料如下:

  1. 《MFC图文并茂详解.doc》
  2. 《SCOM_MFC2.rar》
  3. 《serial_src.zip》
  4. 《SerialCom_YiHai_TEST-master.zip》
  5. 《SerialComSoftware_自写详细注释.rar》
  6. 《VC_MFC串口通信编程详解.pdf》
  7. 《VC++串口通信20个经典源码合集.rar》
  8. 《串口通信处女作.zip》
  9. 《用MFC实现串口编程.doc》

关键API接口和结构体描述:

CreateFile 函数打开串口。

HANDLE CreateFile(
 LPCTSTR lpFileName,     // 指向文件名的指针
 DWORD dwDesiredAccess,     // 访问模式(写 / 读)
 DWORD dwShareMode,     // 共享模式
 LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性的指针
 DWORD dwCreationDisposition,   // 如何创建
 DWORD dwFlagsAndAttributes, // 文件属性
 HANDLE hTemplateFile     // 用于复制文件句柄
);
COMMTIMEOUTS  结构体,用来设置串口读写的超时时间

typedef struct _COMMTIMEOUTS {
    DWORD ReadIntervalTimeout; //读---间隔超时  ms  两字符之间的 间隔超时
    DWORD ReadTotalTimeoutMultiplier; //读---时间系数  ms  ReadTotalTimeoutMultiplier * 字节数 = 总超时
    DWORD ReadTotalTimeoutConstant; //读---时间常量  ms  一次读取串口数据的固定超时
    DWORD WriteTotalTimeoutMultiplier; //写---时间系数  ms  WriteTotalTimeoutMultiplier * 字节数 = 总超时
    DWORD WriteTotalTimeoutConstant; //写---时间常量  ms  一次写入串口数据的固定超时
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
DCB 结构体有28个参数,一般用来配置一些串口的主要参数
(1)DWORD BaudRate;      波特率设置。
(2)BYTE ByteSize;         数据位设置。
(3)DWORD fParity: 1; TRUE时, 支持奇偶检验。
(4)BYTE Parity;         奇偶检验位的设置
(5)BYTE StopBits;        停止位的设置
StopBits指定端口当前使用的停止位数,可取值:
取值             意义
ONESTOPBIT      1停止位
ONE5STOPBITS    1.5停止位
TWOSTOPBITS     2停止位
参考资料: https://blog.csdn.net/weixin_42639646/article/details/120319833
ReadFile 函数从串口读取数据

BOOL ReadFile(
    HANDLE hFile,             //文件的句柄
    LPVOID lpBuffer,           //用于保存读入数据的一个缓冲区
    DWORD nNumberOfBytesToRead,    //要读入的字节数
    LPDWORD lpNumberOfBytesRead,    //指向实际读取字节数的指针
    LPOVERLAPPED lpOverlapped //如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
WriteFile 函数写数据到串口

BOOL WriteFile(
    HANDLE  hFile, //文件句柄
    LPCVOID lpBuffer, //数据缓存区指针
    DWORD   nNumberOfBytesToWrite, //你要写的字节数
    LPDWORD lpNumberOfBytesWritten, //用于保存实际写入字节数的存储区域的指针
    LPOVERLAPPED lpOverlapped //如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
CloseHandle 函数用来关闭串口或者线程

BOOL CloseHandle(HANDLE hObject);
参数
hObject :代表一个已打开对象handle。
返回值
TRUE:执行成功;
FALSE:执行失败,可以调用GetLastError()获知失败原因。
CloseHandel(ThreadHandle ); 只是关闭了一个线程句柄对象,表示我不再使用该句柄,即不对这个句柄对应的线程做任何干预了。并没有结束线程。
CloseHandel(CreateThread(NULL,0,.....)); 在线程创建后马上调用CloseHandle()是个良好的做法,这里不会影响线程的执行
参考资料: https://blog.csdn.net/imJaron/article/details/78812726
AfxBeginThread  函数用来创建线程,一般填前两个参数即可

CWinThread* AfxBeginThread(
   AFX_THREADPROC pfnThreadProc, //线程函数地址
   LPVOID pParam, //线程参数
   int nPriority = THREAD_PRIORITY_NORMAL, //线程优先级
   UINT nStackSize = 0, //线程堆栈大小,默认为1M
   DWORD dwCreateFlags = 0, //线程创建标志
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //指向安全描述符的指针
);
ResumeThread 函数用来重启线程
SuspendThread 函数用来挂起线程
OVERLAPPED 结构体,主要是用来做异步调用的时候,事件通知对象。

typedef struct _OVERLAPPED {
    DWORD Internal;   //预留给操做系统使用
    DWORD InternalHigh;   //预留给操做系统使用
    DWORD Offset;         //该文件的位置是从文件起始处的字节偏移量。
    DWORD OffsetHigh;     //指定文件传送的字节偏移量的高位字
    HANDLE hEvent;        //在转移完成时处理一个事件设置为有信号状态
} OVERLAPPED

参考资料:
https://blog.csdn.net/xbmoxia/article/details/22306317?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-22306317-blog-120159802.pc_relevant_default&spm=1001.2101.3001.4242.1&utm_relevant_index=3
COMSTAT  结构体,主要是用来串口通信的状态,一般用的比较多的是 cbInQue 变量

typedef struct _COMSTAT { // cst
    DWORD fCtsHold : 1;   // Tx waiting for CTS signal
    DWORD fDsrHold : 1;   // Tx waiting for DSR signal
    DWORD fRlsdHold : 1;  // Tx waiting for RLSD signal
    DWORD fXoffHold : 1;  // Tx waiting, XOFF char rec''d
    DWORD fXoffSent : 1;  // Tx waiting, XOFF char sent
    DWORD fEof : 1;       // EOF character sent
    DWORD fTxim : 1;      // character waiting for Tx
    DWORD fReserved : 25; // reserved    保留
    DWORD cbInQue;         //该成员变量的值表明输入缓冲区的字节数
    DWORD cbOutQue;        //记录着输出缓冲区中字节数
} COMSTAT, *LPCOMSTAT;
ClearCommError  函数用来清除硬件的通讯错误,以及获取通讯设备的当前状态

BOOL ClearCommError(
    HANDLE hFile,    //由CreateFile函数返回指向已打开串行口的句柄
    LPDWORD lpErrors,  //指向定义了错误类型的32位变量
    LPCOMSTAT lpStat   //指向一个返回设备状态的控制块COMSTAT
);
WaitCommEvent  函数主要是用来监听串口中断,比如设置掩码 EV_RXCHAR ,则代表串口只要收到任何字符就会触发事件。

BOOL WaitComrnEvent(
    HANDLE hFi1e,  //通信设备的句柄
    LPWORD lpEvtMask,//发生的事件变量的地址
    LPOVERLAPFED lpoverlapped //Overlapped结构的地址

)

GetLastError  函数主要是在操作出错后,查询错误结果。比如读写操作失败的时候,调用改函数,发现错误是 ERROR_IO_PENDING 则表明串口当前正在执行接收或者发送数据

DWORD GetLastError(VOID);
GetOverlappedResult  函数主要是用来读取重叠操作结果

BOOL GetOverlappedResult(
    HANDLE hFile,        // 串口的句柄
    LPOVERLAPPED lpOverlapped,   // 指向重叠操做开始时指定的OVERLAPPED结构
    LPDWORD lpNumberOfBytesTransferred,  // 指向一个32位变量,该变量的值返回实际读写操做传输的字节数。
    BOOL bWait  // 该参数用于指定函数是否一直等到重叠操做结束。
                // 若是该参数为TRUE,函数直到操做结束才返回。
                // 若是该参数为FALSE,函数直接返回,这时若是操做没有完成,
                // 经过调用GetLastError()函数会返回ERROR_IO_INCOMPLETE。

);

EnterCriticalSection   和 LeaveCriticalSection  函数主要是用来做资源保护,避免多线程操作的时候,资源被破坏,保证资源的完整性。

WaitForSingleObject   函数主要用来等待事件发生,避免CPU资源被无效浪费在查询上面。

DWORD WaitForSingleObject(
    HANDLE hHandle, // 串口的句柄
    DWORD dwMilliseconds // 等待时间,单位 ms,永久等待使用 INFINITE
);

SetEvent    函数用来设置事件
ResetEvent  函数用来清除事件
PurgeComm()  函数--清空缓冲区

BOOL PurgeComm(HANDLE hFile,DWORD dwFlags )
    HANDLE hFile //串口句柄
    DWORD dwFlags // 需要完成的操作
};

参数dwFlags指定要完成的操作,可以是下列值的组合:
PURGE_TXABORT 终止所有正在进行的 字符输出操作,完成一个正处于等待状态的重叠 i/o操作,他将产生一个事件,指明完成了写操作
PURGE_RXABORT 终止所有正在进行的字符输入操作,完成一个正在进行中的重叠i/o操作,并带有已设置得适当事件
PURGE_TXCLEAR 这个命令指导设备驱动程序清除输出缓冲区,经常与PURGE_TXABORT 命令标志一起使用
PURGE_RXCLEAR 这个命令用于设备驱动程序清除输入缓冲区,经常与PURGE_RXABORT 命令标志一起使用

代码调试部分:

同步操作(阻塞)

先定义串口类 CSerialPort

再声明串口类 CSerialPort

类的初始化操作,主要是打开串口,创建读写线程

写线程操作。主要是判断头尾指针是否同步,以启动发送数据。这里需要将类里面的头尾指针(port->m_nWriteHead 和 port->m_nWriteTail),转换成线程里面的局部变量(m_nWriteHead 和 m_nWriteTail)来操作。避免在读取数据的时候,由于其他线程操作了该变量导致数据错误。

封装各种输出函数

 EnterCriticalSection(&m_csWriteSync);  //进入临界区 

LeaveCriticalSection(&m_csWriteSync);   //退出临界区

主要是防止多线程同时调用发送函数的时候,破坏数据的完整性

 实测每次都能正常输出内容。而且中间是没有任何停顿,连续发送不同的数据,直接叠加在输出内容后面。

读线程操作。每间隔5ms固定获取一次接收缓存状态,然后根据接收的缓存取数据,再转存到port->m_szReadBuffer给其他线程使用。这里需要将类里面的头尾指针(port->m_nReadHead 和 port->m_nReadTail),转换成线程里面的局部变量(m_nReadHead 和 m_nReadTail)来操作。避免在读取数据的时候,由于其他线程操作了该变量导致数据错误。

实测数据无论什么时候写入,都会保存在m_szReadBuffer里面,供其他线程使用。

异步操作(重叠)

CreateFile 函数打开串口。

HANDLE CreateFile(
 LPCTSTR lpFileName,     // 指向文件名的指针
 DWORD dwDesiredAccess,     // 访问模式(写 / 读)
 DWORD dwShareMode,     // 共享模式
 LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 指向安全属性的指针
 DWORD dwCreationDisposition,   // 如何创建
 DWORD dwFlagsAndAttributes, // 文件属性
 HANDLE hTemplateFile     // 用于复制文件句柄
);

首先打开串口的时候,需要制定文件属性为重叠操作

发送数据的线程,就不在是间隔5ms去轮询是否需要发送数据,而是根据事件触发。

发送数据失败后,通过 GetOverlappedResult函数等待重叠操作完成。

接收数据的时候,一直查询缓存是否有数据,若是读取失败了,通过GetOverlappedResult函数等待重叠操作完成。

实测数据都能接收到指定缓存,供其他线程使用。

资料下载地址:

VisualStudio2013串口类-C++文档类资源-CSDN下载最近调试串口获取机芯日志,要做个上位机工具能随时查看机芯的本地日志。起初想参考合泰源码做一份,但是考更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/l435799304/85698303

Hankin

2022.06.19

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使用Visual Studio编写串口助手,你可以按照以下步骤进行操作: 1. 创建新的Visual Studio项目:打开Visual Studio,选择创建新的Windows Forms应用程序项目。 2. 添加串口控件:在工具箱中找到串口控件,将其拖放到窗体上。 3. 设置串口属性:在属性窗口中,设置串口的端口号、波特率、数据位、停止位和校验位等属性。 4. 编写串口通信代码:在代码编辑器中,编写与串口通信相关的代码。你可以使用SerialPort提供的方法来打开、关闭、发送和接收数据。 例如,你可以使用以下代码打开串口: ```csharp using System.IO.Ports; // 创建一个SerialPort对象 SerialPort serialPort = new SerialPort(); // 设置串口号 serialPort.PortName = "COM1"; // 设置波特率 serialPort.BaudRate = 9600; // 打开串口 serialPort.Open(); ``` 你还可以使用以下代码发送数据: ```csharp // 发送数据 serialPort.Write("Hello World!"); ``` 和接收数据: ```csharp // 接收数据 string data = serialPort.ReadExisting(); ``` 5. 添加事件处理程序:为串口控件的事件添加处理程序,例如接收数据的事件(DataReceived)或错误事件(ErrorReceived)。在事件处理程序中,你可以处理接收到的数据或处理发生的错误。 6. 构建和调试:构建你的项目并进行调试。你可以在Visual Studio的调试器中观察串口通信的过程,以确保代码的正确性。 你可以根据自己的需求和功能来扩展和修改代码。希望这些步骤可以帮助你开始编写串口助手。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值