简介:串口通信在嵌入式系统和工业控制领域应用广泛。本项目以VC6.0环境下的“串口_com”示例程序为基础,深入探讨串口通信原理和操作步骤。通过实践任务,学生将掌握串口参数配置、数据收发、错误处理和关闭串口等关键技术。本项目还提供串口调试助手功能,帮助学生测试硬件设备通信并排查问题。
1. 串口通信基础
串口通信是一种通过串行接口进行数据传输的通信方式。它广泛应用于嵌入式系统、工业控制和仪器仪表等领域。串口通信的特点是数据位逐个传输,传输速率较低,但成本低廉,易于实现。
串口通信涉及两个设备:发送设备和接收设备。发送设备将数据转换为串行比特流,通过串行接口发送出去;接收设备接收串行比特流,并将其还原为数据。串口通信需要遵守一定的协议,以确保数据传输的正确性和可靠性。
2. VC6.0串口通信原理
2.1 串口硬件结构
串口,又称串行通信接口,是一种用于计算机与外部设备之间进行数据传输的接口。它通过一根电缆将两台设备连接起来,并以串行的方式传输数据,即一次传输一位数据。
串口硬件结构主要包括以下部分:
- 数据线: 用于传输数据,通常使用RS-232标准。
- 控制线: 用于控制数据传输,包括请求发送(RTS)、清除发送(CTS)、数据终端就绪(DTR)、数据载波检测(DCD)等。
- 时钟线: 用于同步数据传输,确保发送方和接收方使用相同的时钟频率。
- 连接器: 用于连接数据线、控制线和时钟线。
2.2 串口数据传输协议
串口数据传输协议定义了数据传输的格式和规则。常用的串口数据传输协议有以下几种:
- 异步传输协议: 数据传输不使用时钟线,发送方和接收方使用各自的时钟频率。
- 同步传输协议: 数据传输使用时钟线,发送方和接收方使用相同的时钟频率。
- 半双工传输协议: 设备只能在同一时间内发送或接收数据,不能同时进行。
- 全双工传输协议: 设备可以同时发送和接收数据。
2.3 VC6.0串口编程接口
VC6.0提供了丰富的串口编程接口,允许开发者在程序中使用串口进行数据传输。主要接口如下:
// 打开串口
HANDLE CreateFile(
LPCTSTR lpFileName, // 串口设备名称
DWORD dwDesiredAccess, // 访问权限
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性
DWORD dwCreationDisposition, // 创建方式
DWORD dwFlagsAndAttributes, // 标志和属性
HANDLE hTemplateFile // 模板文件句柄
);
// 设置串口参数
BOOL SetCommState(
HANDLE hCommDev, // 串口句柄
LPDCB lpDCB // 设备控制块
);
// 读串口数据
DWORD ReadFile(
HANDLE hFile, // 文件句柄
LPVOID lpBuffer, // 缓冲区地址
DWORD nNumberOfBytesToRead, // 要读取的字节数
LPDWORD lpNumberOfBytesRead, // 实际读取的字节数
LPOVERLAPPED lpOverlapped // 重叠结构
);
// 写串口数据
DWORD WriteFile(
HANDLE hFile, // 文件句柄
LPCVOID lpBuffer, // 缓冲区地址
DWORD nNumberOfBytesToWrite, // 要写入的字节数
LPDWORD lpNumberOfBytesWritten, // 实际写入的字节数
LPOVERLAPPED lpOverlapped // 重叠结构
);
// 关闭串口
BOOL CloseHandle(
HANDLE hObject // 对象句柄
);
代码逻辑分析:
-
CreateFile
函数用于打开串口设备,并返回一个串口句柄。 -
SetCommState
函数用于设置串口参数,包括波特率、数据位、停止位和校验位等。 -
ReadFile
函数用于从串口读取数据。 -
WriteFile
函数用于向串口写入数据。 -
CloseHandle
函数用于关闭串口设备。
参数说明:
-
lpFileName
:串口设备名称,如"COM1"。 -
dwDesiredAccess
:访问权限,如GENERIC_READ
或GENERIC_WRITE
。 -
dwShareMode
:共享模式,如FILE_SHARE_READ
或FILE_SHARE_WRITE
。 -
lpSecurityAttributes
:安全属性,通常为NULL
。 -
dwCreationDisposition
:创建方式,如OPEN_EXISTING
或CREATE_NEW
。 -
dwFlagsAndAttributes
:标志和属性,如FILE_FLAG_OVERLAPPED
或FILE_ATTRIBUTE_NORMAL
。 -
hTemplateFile
:模板文件句柄,通常为NULL
。 -
lpDCB
:设备控制块,用于设置串口参数。 -
lpBuffer
:缓冲区地址,用于存储读写数据。 -
nNumberOfBytesToRead
:要读取的字节数。 -
lpNumberOfBytesRead
:实际读取的字节数。 -
nNumberOfBytesToWrite
:要写入的字节数。 -
lpNumberOfBytesWritten
:实际写入的字节数。 -
lpOverlapped
:重叠结构,用于异步操作。
3. 串口参数配置
3.1 波特率设置
波特率是串口通信中最重要的参数之一,它表示每秒钟传输的比特数。波特率的单位是波特(Baud),1 波特表示每秒传输 1 比特。常见的波特率有:
- 9600 波特
- 19200 波特
- 38400 波特
- 57600 波特
- 115200 波特
波特率的设置需要根据实际通信需求来确定。一般来说,波特率越高,数据传输速度越快,但抗干扰能力越弱;波特率越低,数据传输速度越慢,但抗干扰能力越强。
代码块:
// 设置波特率为 9600 波特
DCB dcb;
GetCommState(hComm, &dcb);
dcb.BaudRate = CBR_9600;
SetCommState(hComm, &dcb);
逻辑分析:
该代码块通过 GetCommState() 函数获取当前串口的通信参数,然后修改波特率为 9600 波特,最后通过 SetCommState() 函数设置新的通信参数。
3.2 数据位设置
数据位是指每个字符所占用的比特数,常见的取值为 5、6、7 和 8。数据位越多,表示每个字符可以传输更多的信息,但传输速度也会变慢。
代码块:
// 设置数据位为 8 位
DCB dcb;
GetCommState(hComm, &dcb);
dcb.ByteSize = 8;
SetCommState(hComm, &dcb);
逻辑分析:
该代码块通过 GetCommState() 函数获取当前串口的通信参数,然后修改数据位为 8 位,最后通过 SetCommState() 函数设置新的通信参数。
3.3 停止位设置
停止位是指每个字符传输结束后,发送方停止发送信号的比特数,常见的取值为 1、1.5 和 2。停止位越多,表示数据传输的可靠性越高,但传输速度也会变慢。
代码块:
// 设置停止位为 1 位
DCB dcb;
GetCommState(hComm, &dcb);
dcb.StopBits = ONESTOPBIT;
SetCommState(hComm, &dcb);
逻辑分析:
该代码块通过 GetCommState() 函数获取当前串口的通信参数,然后修改停止位为 1 位,最后通过 SetCommState() 函数设置新的通信参数。
3.4 校验位设置
校验位是指用于校验数据传输正确性的比特,常见的取值为无校验、奇校验和偶校验。无校验表示不进行校验,奇校验表示校验位为 1,使得所有字符的奇偶校验和为奇数,偶校验表示校验位为 0,使得所有字符的奇偶校验和为偶数。
代码块:
// 设置校验位为无校验
DCB dcb;
GetCommState(hComm, &dcb);
dcb.Parity = NOPARITY;
SetCommState(hComm, &dcb);
逻辑分析:
该代码块通过 GetCommState() 函数获取当前串口的通信参数,然后修改校验位为无校验,最后通过 SetCommState() 函数设置新的通信参数。
表格:串口参数设置
| 参数 | 取值 | 描述 | |---|---|---| | 波特率 | 9600、19200、38400、57600、115200 | 每秒传输的比特数 | | 数据位 | 5、6、7、8 | 每个字符所占用的比特数 | | 停止位 | 1、1.5、2 | 每个字符传输结束后,发送方停止发送信号的比特数 | | 校验位 | 无校验、奇校验、偶校验 | 用于校验数据传输正确性的比特 |
流程图:串口参数设置
[mermaid] graph TD subgraph 串口参数设置 A[波特率设置] --> B[数据位设置] B --> C[停止位设置] C --> D[校验位设置] end [/mermaid]
4. 串口事件设置
4.1 串口事件类型
串口事件是指当串口发生特定事件时,系统会触发相应的事件通知。VC6.0中定义了以下几种串口事件类型:
| 事件类型 | 描述 | |---|---| | EV_RXCHAR | 接收到一个字符 | | EV_RXFLAG | 接收到一个标志字符 | | EV_TXEMPTY | 发送缓冲区为空 | | EV_CTS | 清除发送 (CTS) 信号发生变化 | | EV_DSR | 数据设置就绪 (DSR) 信号发生变化 | | EV_RLSD | 请求发送 (RLSD) 信号发生变化 | | EV_BREAK | 检测到中断信号 | | EV_ERR | 发生错误 |
4.2 串口事件处理机制
当串口发生事件时,系统会调用与该事件关联的事件处理函数。事件处理函数的原型如下:
void CALLBACK EventProc(
_In_ HWND hWnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
其中:
-
hWnd
:窗口句柄。 -
uMsg
:消息标识符。 -
wParam
:消息参数。 -
lParam
:消息参数。
事件处理函数需要根据不同的事件类型执行相应的处理操作。例如,当接收到一个字符时,事件处理函数可以将该字符添加到接收缓冲区中。
4.3 串口事件处理函数
VC6.0提供了以下几个串口事件处理函数:
| 函数 | 描述 | |---|---| | SetCommMask | 设置串口事件掩码 | | WaitCommEvent | 等待串口事件发生 | | ClearCommError | 清除串口错误 | | GetCommModemStatus | 获取串口调制解调器状态 | | GetCommState | 获取串口状态 | | SetCommState | 设置串口状态 | | SetCommTimeouts | 设置串口超时时间 | | EscapeCommFunction | 执行串口转义函数 |
SetCommMask 函数
SetCommMask
函数用于设置串口事件掩码。事件掩码是一个位掩码,它指定了哪些事件将触发事件处理函数。
BOOL SetCommMask(
_In_ HANDLE hFile,
_In_ DWORD dwMask
);
其中:
-
hFile
:串口句柄。 -
dwMask
:事件掩码。
WaitCommEvent 函数
WaitCommEvent
函数用于等待串口事件发生。该函数会阻塞,直到指定的事件发生或超时。
BOOL WaitCommEvent(
_In_ HANDLE hFile,
_Out_ LPOVERLAPPED lpOverlapped,
_In_ DWORD dwMilliseconds
);
其中:
-
hFile
:串口句柄。 -
lpOverlapped
:指向重叠结构的指针。 -
dwMilliseconds
:超时时间(毫秒)。
ClearCommError 函数
ClearCommError
函数用于清除串口错误。
BOOL ClearCommError(
_In_ HANDLE hFile,
_Out_ LPDWORD lpErrors,
_Out_ LPCOMSTAT lpStat
);
其中:
-
hFile
:串口句柄。 -
lpErrors
:指向错误代码的指针。 -
lpStat
:指向通信状态结构的指针。
GetCommModemStatus 函数
GetCommModemStatus
函数用于获取串口调制解调器状态。
BOOL GetCommModemStatus(
_In_ HANDLE hFile,
_Out_ LPDWORD lpModemStat
);
其中:
-
hFile
:串口句柄。 -
lpModemStat
:指向调制解调器状态的指针。
GetCommState 函数
GetCommState
函数用于获取串口状态。
BOOL GetCommState(
_In_ HANDLE hFile,
_Out_ LPDCB lpDCB
);
其中:
-
hFile
:串口句柄。 -
lpDCB
:指向设备控制块的指针。
SetCommState 函数
SetCommState
函数用于设置串口状态。
BOOL SetCommState(
_In_ HANDLE hFile,
_In_ const LPDCB lpDCB
);
其中:
-
hFile
:串口句柄。 -
lpDCB
:指向设备控制块的指针。
SetCommTimeouts 函数
SetCommTimeouts
函数用于设置串口超时时间。
BOOL SetCommTimeouts(
_In_ HANDLE hFile,
_In_ const LPCOMMTIMEOUTS lpCommTimeouts
);
其中:
-
hFile
:串口句柄。 -
lpCommTimeouts
:指向通信超时结构的指针。
EscapeCommFunction 函数
EscapeCommFunction
函数用于执行串口转义函数。
BOOL EscapeCommFunction(
_In_ HANDLE hFile,
_In_ DWORD dwFunc
);
其中:
-
hFile
:串口句柄。 -
dwFunc
:转义函数代码。
5. 串口数据收发
5.1 串口数据发送
串口数据发送操作主要通过 WriteFile
函数实现,其函数原型如下:
BOOL WriteFile(
_In_ HANDLE hFile,
_In_ LPCVOID lpBuffer,
_In_ DWORD nNumberOfBytesToWrite,
_Out_ LPDWORD lpNumberOfBytesWritten,
_Inout_ LPOVERLAPPED lpOverlapped
);
其中,参数说明如下:
-
hFile
:串口句柄 -
lpBuffer
:指向要发送的数据缓冲区的指针 -
nNumberOfBytesToWrite
:要发送的数据字节数 -
lpNumberOfBytesWritten
:指向一个变量的指针,该变量接收实际发送的数据字节数 -
lpOverlapped
:指向一个OVERLAPPED
结构的指针,用于异步操作(可选)
使用 WriteFile
函数发送串口数据时,需要遵循以下步骤:
- 确保串口已打开且处于可写状态。
- 分配一个足够大的缓冲区来存储要发送的数据。
- 将数据复制到缓冲区中。
- 调用
WriteFile
函数发送数据。 - 检查
WriteFile
函数的返回值和lpNumberOfBytesWritten
参数以确定发送操作是否成功。
示例代码:
// 发送数据到串口
DWORD dwBytesWritten;
BOOL bRet = WriteFile(hComm, lpBuffer, dwBytesToWrite, &dwBytesWritten, NULL);
if (!bRet)
{
// 发送数据失败,处理错误
}
5.2 串口数据接收
串口数据接收操作主要通过 ReadFile
函数实现,其函数原型如下:
BOOL ReadFile(
_In_ HANDLE hFile,
_Out_ LPVOID lpBuffer,
_In_ DWORD nNumberOfBytesToRead,
_Out_ LPDWORD lpNumberOfBytesRead,
_Inout_ LPOVERLAPPED lpOverlapped
);
其中,参数说明与 WriteFile
函数类似。
使用 ReadFile
函数接收串口数据时,需要遵循以下步骤:
- 确保串口已打开且处于可读状态。
- 分配一个足够大的缓冲区来存储接收的数据。
- 调用
ReadFile
函数接收数据。 - 检查
ReadFile
函数的返回值和lpNumberOfBytesRead
参数以确定接收操作是否成功。
示例代码:
// 从串口接收数据
DWORD dwBytesRead;
BOOL bRet = ReadFile(hComm, lpBuffer, dwBytesToRead, &dwBytesRead, NULL);
if (!bRet)
{
// 接收数据失败,处理错误
}
5.3 串口数据缓冲区管理
串口数据缓冲区用于存储发送和接收的数据。VC6.0中提供了以下函数来管理串口数据缓冲区:
-
GetCommState
:获取串口通信状态,包括缓冲区大小。 -
SetCommState
:设置串口通信状态,包括缓冲区大小。 -
PurgeComm
:清除串口数据缓冲区。
示例代码:
// 获取串口缓冲区大小
COMMPROP cp;
GetCommProperties(hComm, &cp);
DWORD dwInBufferSize = cp.dwInBufferSize;
DWORD dwOutBufferSize = cp.dwOutBufferSize;
// 设置串口缓冲区大小
COMMPROP cp;
cp.dwInBufferSize = 4096;
cp.dwOutBufferSize = 4096;
SetCommProperties(hComm, &cp);
// 清除串口缓冲区
PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR);
6. 错误处理和串口关闭
6.1 串口错误类型
在串口通信过程中,可能会遇到各种错误,常见错误类型包括:
- 帧错误: 接收到的数据帧中包含错误的校验位。
- 奇偶校验错误: 接收到的数据帧中奇偶校验位与计算的校验位不一致。
- 超时错误: 在指定的时间内没有收到数据或事件。
- 缓冲区溢出错误: 接收缓冲区已满,导致新接收的数据丢失。
- 设备未就绪错误: 串口设备尚未准备好进行通信。
6.2 串口错误处理
当发生串口错误时,可以通过以下步骤进行处理:
- 获取错误代码: 使用
GetCommError
函数获取错误代码。 - 分析错误代码: 根据错误代码确定错误类型。
- 采取适当措施: 根据错误类型采取适当的措施,例如重新发送数据、清除缓冲区或关闭串口。
DWORD dwError;
GetCommError(hComm, &dwError);
switch (dwError) {
case CE_FRAME:
// 处理帧错误
break;
case CE_OVERRUN:
// 处理缓冲区溢出错误
break;
case CE_RXOVER:
// 处理接收缓冲区溢出错误
break;
default:
// 处理其他错误
break;
}
6.3 串口关闭操作
当不再需要使用串口时,应及时关闭串口,以释放系统资源。关闭串口操作包括:
- 关闭串口句柄: 使用
CloseHandle
函数关闭串口句柄。 - 释放串口资源: 使用
FreeComm()
函数释放串口资源。
CloseHandle(hComm);
FreeComm(hComm);
简介:串口通信在嵌入式系统和工业控制领域应用广泛。本项目以VC6.0环境下的“串口_com”示例程序为基础,深入探讨串口通信原理和操作步骤。通过实践任务,学生将掌握串口参数配置、数据收发、错误处理和关闭串口等关键技术。本项目还提供串口调试助手功能,帮助学生测试硬件设备通信并排查问题。