参考链接:
Afx全局函数及MFC常见数据类型
CSerialport类开源地址(国人在github上托管的)
CSerialPort串口类最新修正版及源码简单分析
Remon提供的串口类网址(英文)
[MFC]使用强大的第三方串口类 CSerialPort
相关问题
同步通信如何实现
更新:之前排版问题很严重,抱歉。(2016年11月6日 晚)
串口是计算机与外围设备之间的数据传输通道,由于使用其进行通信非常方便,并且能够实现数据的长距离传输,因此它的使用非常广泛。在 Windows 环境下,串口是系统资源的一部分。应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源,即关闭串口。串口通信最重要的参数是率、数据位、停止位和奇偶校验。
一个字符一个字符地传输,每个字符一位一位地传输,并且传输一个字符时,总是以“起始位”开始,以“停止位”结束,字符之间没有固定的时间间隔要求。
每一个字符的前面都有一位起始位(低电平),字符本身由7位数据位组成,接着字符后面是一位校验位(检验位可以是奇校验、偶校验或无校验位),最后是一位或一位半或二位停止位,停止位后面是不定长的空闲位,停止位和空闲位都规定为高电平。实际传输时每一位的信号宽度与波特率有关,波特率越高,宽度越小,在进行传输之前,双方一定要使用同一个波特率设置。
在标准ASCII码中,其最高位(b7)用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。
停止位是按长度来算的。串行异步通信从计时开始,以单位时间为间隔(一个单位时间就是波特率的倒数),依次接受所规定的数据位和奇偶校验位,并拼装成一个字符的并行字节;此后应接收到规定长度的停止位“1”。所以说,停止位都是“1”,1.5是它的长度,即停止位的高电平保持1.5个单位时间长度。一般来讲,停止位有1,1.5,2个单位时间三种长度。
波特率就是每秒钟传输的数据位数。波特率的单位是每秒比特数(bps),常用的单位还有:每秒千比特数Kbps,每秒兆比特数Mbps。串口典型的传输波特率600bps,1200bps,2400bps,4800bps,9600bps,19200bps,38400bps。
PLC/PC与称重仪表通讯时,最常用的波特率是9600bps,19200bps。PLC/PC或仪表与大屏幕通讯时,最常用的波特率是600bps。
单工模式(Simplex Communication)的数据传输是单向的。通信双方中,一方固定为发送端,一方则固定为接收端。信息只能沿一个方向传输,使用一根传输线。
半双工模式(HalfDuplex)通信使用同一根传输线,既可以发送数据又可以接收数据,但不能同时进行发送和接收。数据传输允许数据在两个方向上传输,但是,在任何时刻只能由其中的一方发送数据,另一方接收数据。因此半双工模式既可以使用一条数据线,也可以使用两条数据线。半双工通信中每端需有一个收发切换电子开关,通过切换来决定数据向哪个方向传输。因为有切换,所以会产生时间延迟,信息传输效率低些。
全双工模式(FullDuplex)通信允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。在全双工模式中,每一端都有发送器和接收器,有两条传输线,信息传输效率高。
显然,在其它参数都一样的情况下,全双工比半双工传输速度要快,效率要高。
①EIA RS232(通常简称“RS232”): 1962年由美国电子工业协会(EIA)制定。
②EIA RS485(通常简称“RS485”): 1983年由美国电子工业协会(EIA)制定。
项目所用的串口是RS232串口。
RS232串口
RS232是计算机与通信工业应用中最广泛一种串行接口。它以全双工方式工作,需要地线、发送线和接收线三条线。RS232只能实现点对点的通信方式。
RS232串口接口定义:
RXD:接收数据;
TXD:发送数据;
GND/SG:信号地。
电脑DB9针接口定义:
RS232串口缺点电脑DB9针接口是常见的RS232串口(如图),其引脚定义如下:
2号脚:RXD(接收数据)
3号脚:TXD(发送数据)
5号脚:SG或GND(信号地)
其它脚:我们不用
RS232串口缺点:
接口信号电平值较高,接口电路芯片容易损坏。
● 传输速率低,最高波特率19200bps。
● 抗干扰能力较差。
● 传输距离有限,一般在15m以内。
● 只能实现点对点的通讯方式。
在 VS2013 下, 实现串口通信的方法有以下几种:
CSerialPort类的简述
CSerialPort类是一名名叫Remon Spekreijse的所写的开源串口类,功能十分强大。
CSerialPort工作流程
首先设置好串口参数,再开启串口检测工作线程,串口检测工作线程检测到串口接收到的数据、流控制事件或其他串口事件后,就以消息方式通知主程序,激发消息处理函数来进行数据处理,这是对接受数据而言的,发送数据可直接向串口发送。
CSerialPort类定义的消息
消息如下表:
消息名称 | 消息号 | 功能说明 |
---|---|---|
WM_COMM_BREAK_DETECTED | WM_USER+1 | 检测到输入中断 |
WM_COMM_CTS_DETECTED | WM_USER+2 | 检测到CTS(清除发送)信号状态改变 |
WM_COMM_DSR_DETECTED | WM_USER+3 | 检测到DSR(数据设备准备就绪)信号状态改变 |
WM_COMM_ERR_DETECTED | WM_USER+4 | 发生线状态错误(包括CE_FRAMECE_OVERRUN,和CE_RXPARITY) |
WM_COMM_RING_DETECTED | WM_USER+5 | 检测到响铃指示信号 |
WM_COMM_RLSD_DETECTED | WM_USER+6 | 检测到RLSD(接收线信号)状态改变 |
WM_COMM_RXCHAR | WM_USER+7 | 接收到一个字符并已放入接受缓冲区 |
WM_COMM_RXFLAG_DETECTED | WM_USER+8 | 检测到接受到字符(该字符已放入接受缓冲区)事件 |
WM_COMM_TXEMPTY_DETECTED | WM_USER+9 | 检测到发送缓冲区最后一个字符已经被发送 |
端口初始化
首先, 设置几个主要参数:
(1) BaudRate(波特率)波特率是模拟线路信号的速率,是以波形每秒的震荡数来衡量,波特率主要有以下几种频率: 2400, 4800, 9600, 14400。
(2) PortName PortName是串口设备所使用的串口名称,在设计串口通信程序时,必须找到这个串口名称, 否则无法和串口设备建立连接进行通信 。
(3) ReceivedBytesThreshold这个参数主要是用来触发数据接收事件 DataReceived,当输入缓冲区中的数据量达到所设的数值时就触发, 否则就一直等待。
这些设置根据具体使用的串口设备, 本文设置为 9600,com1,1, 2048/4096。参数设置完后就调用串口打开函数, SerialPort1.InitPort ()。
串口读写操作
(1)初始化串口
流程:检查参数–>检测线程–>创建事件(监视线程)–>打开端口–>设置异步IO结构参数。
(2)监视线程的控制
即线程控制吧,主要有开启线程,复位和停止。
(3)监视线程
我们把读写串口的操作全部交给监视线程,现在简单看一下监视线程的大致流程:
检查串口–>进入循环{WaitCommEvent(不阻塞询问)询问事件–>如果有事件来到–>到相应处理(关闭\读\写)}
(4)读取数据操作
读取数据是一个异步操作,当有数据发来时,会触发读事件m_ov.hEvent,监视线程捕捉到事件后并获知是读事件,进入相关读处理,这里调用函数ReceiveChar,ReceiveChar中调用ReadFile函数将串口数据读到Buffer缓冲中。
(5)写数据操作
也是由监视线程操作,不过触发事件交给主线程来触发,函数是WriteToPort(),将数据写入缓冲区中,然后由线程调用的函数WriteChar(),把缓冲里的数据写到串口中,期间调用WriteFile().
关闭串口
当使用串口完毕后,关闭串口,该函数为:ClosePort()。
串口通信实例分析
项目基于CSerialPort类进行串口通信,实现下位机PLC与上位机PC机之间进行通信。
1)设计思想
在项目中,上下位机之间通信主要是角度传感器的角度值,故而以下就以角度传输来进行分析。
2)关键代码分析
①数据读取
当有数据发来后,触发读事件m_ov.hEvent,而后满足CserialPort类定义的WM_COMM_RXCHAR事件触发条件后,触发MFC里面的一个自定义消息映射宏ON_MESSAGE ,由此触发函数OnComm()进行缓冲区数据读取。
ON_MESSAGE(WM_COMM_RXCHAR, OnComm)
LONG CSerial_classDlg::OnComm(WPARAM ch, LPARAM port)
{
……
m_recv = ch;
……
}
②数据写入
写数据相对而言比较简单,无须监控事件操作。准备好需要的数据后,直接调用函数WriteToPort()即可
void CSerial_classDlg::OnSend()
{
……
m_SerialPort.WriteToPort(buf); //发送数据
}