利用Win32API开发串行通信程序

来源:百度文库 编辑:神马文学网 时间:2024/04/29 21:18:17
1、概述
在工业控制中,串口是常用的计算机与外部串行设备之间的数据传输通道。由于串行通信结构简单、可靠性强、实现及使用成本低、通讯标准统一,因此在测控系统和工程中应用十分广泛。目前Windows在工业生产监控管理系统中已成为主流平台 ,Windows环境下的上、下位机之间的串行通信是设计与开发监控管理系统和集散控制系统的重要组成部分。
Microsoft公司的VC++6.0是一种高级编程语言,其基础类库 (MFC)封装了Win32API中的标准通信函数,可方便地支持串口通信。下面就结合实例介绍在Windows环境下,用VC++6.0编写串行通信程序的技术及方法。
2、串行通信工作方式
Windows系统为每个通信设备开辟了用户定义的输入/输出缓冲区,数据进出通信口均由系统后台来完成,应用程序只需完成对输入/输出缓冲区操作就可以了。实际过程是每接收一个字符就产生一个低级硬件中断,Windows系统中的串行驱动程序就取得了控制权,并将接收到的字符放入缓冲区,然后将控制权返还给正在运行的应用程序。如果输入缓冲区数据已满,串行驱动程序用当前定义的流控制机制通知发送方停止发送数据,而队列中的数据按先进先出 (FIFO)的次序处理。
在Windows中,串行通信有两种工作方式:查询方式和事件驱动方式。查询方式占用大量的CPU时间,效率较低,但是数据不易丢失;而Win32操作系统基于事件驱动 (也称为消息驱动)机制的内核,支持基于进程的协同式多任务和基于多线程的抢先式多任务。基于事件驱动的多线程应用程序实际上在其内部实现了多任务扩展,为代码赋予了并行执行的特性,可以使应用程序对CPU的利用率大大提高,从而提高系统的响应能力,加快信息处理速度,提高通信程序的实时性和增大数据吞吐量。
基于以上分析,以事件驱动方式实现Windows下的串行通信更具优势,它能完成较大数据量的实时通信,大大提高了通信的效率,故本程序采用此种方式进行串行通信,至于通信的可靠性可以通过软件设计来保证。
3、系统组成及功能
本系统采用分布式上下位机结构,上位机以Windows和PC机作为软硬件资源,下位机由MOTOROLA单片机MC68HC908GP32控制。上下位机之间采用RS232通讯,实现数据交换。利用串行通信的方式,将现场采集的数据交由计算机进行处理,实现对现场设备的监测和控制。各组成部分功能如下:
3.1上位机
以工业PC机、Windows操作系统及支持串行通讯程序的监控软件作为软硬件环境;
3.2下位机
采用MOTOROLA公司生产的高性能CMOS单片机MC68HC908GP32。MC68HC908GP32IDK在线编程开发系统是基于MC68HC908GP32的开发应用系统,它由核心子板和目标评估母板两部分组成。将目标评估母板作为本系统下位机的硬件接口板,利用目标评估母板上的可编程串行通信接口SCI即可实现MOTOROLA单片机与PC机的串行通信。
在使用串行通信接口SCI之前,应对其进行初始化,主要包括波特率寄存器(BAUD)、串行通信数据寄存器(SCDR)、串行通信控制寄存器(SCCR)、串行通信状态寄存器(SCSR)初始化。其中,串行通信状态寄存器SCSR为产生SCI系统中断逻辑电路提供输入信号。对单片机的编程采用汇编语言,其接收和发送子程序如下:
GETD: BRCLR 5,SCSR,GETD;//接收子程序
LDA SCDR
RTS
SEND: BRCLR 7,SCSR,SEND;//发送子程序
STA SCDR
RTS
4、上位机串行通信程序
在Windows环境下,利用Visual C++6.0实现串行通信主要有两种编程方法:
(1) 使用Windows Visual C++6.0 ActiveX控件;
(2) 调用Windows的API函数。
第一种方法虽然简单易用,但由于必须拿到对话框中使用,在一些需要在线程中实现通信的应用场合,控件的使用显得捉襟见肘。而且这种方法经常发生数据丢失的情况,不能满足实时系统的需求。第二种方法要复杂一些:API是附带在Windows内部的一个极其重要的组成部分。Windows的32位API主要是一系列很复杂的函数、消息的集合,它可以看作是Windows系统为在其下运行的各种开发系统提供的开放式通用功能增强接口。调用API函数,既能保证数据的完整,又可以让我们清楚地掌握串口通信的机制,熟悉各种配置和自由灵活采用不同的流控进行串口通信。所以,在本系统中通过调用API函数来实现串行通信。
4.1程序设计思路
串口通信应用程序设计的总体思路是:首先,确定要打开的串口名、波特率、奇偶校验方式、数据位、停止位,传递给CreateFile()函数打开特定串口;其次,为了保护系统对串口的初始设置,调用GetCommTimeouts()得到串口的原始设置;然后,初始化DCB对象,调用SetCommState()设置DCB,调用SetCommTimeouts()设置串口超时控制;再次,设置串口接发送数据的缓冲区大小,串口的设置就基本完成,之后就可以用ReadCommBlock/WriteCommBlock来读/写串口了。
一般来说,串口的读写由串口读写线程完成,这样可以避免读写阻塞时主程序死锁。本程序是对于全双工的串口读写,所以分别开启读线程和写线程。
4.2编程技术细节
图1为本程序的编程步骤。

4.2.1打开串行口
在Windows下,串行口作为文件处理,使用文件操作对串行口进行处理。调用CreateFlie打开串口,如果成功,返回一个操作句柄,该句柄供随后对串行口的设置、读写等操作用。
if ((COMDEV( npTTYInfo ) = CreateFile( szPort,
GENERIC_READ | GENERIC_WRITE, //对串口可进行读写操作
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, // 采用异步方式读写串口
NULL )) = = (HANDLE) -1 )
return ( FALSE ) ; //若打开串口失败,异常错误
else //打开串口成功,继续下面的操作
4.2.2初始化串行口
1)DCB设置
串行口打开成功后,接着要对串行口初始化,设置串行口的通信参数。修改这些参数将要用到设备控制块DCB(Device Control Block)。DCB结构定义了串口设备的控制设置,它是个复杂的数据结构,有近30个数据成员。许多通信的重要设置诸如波特率、奇偶校验类型、数据位、停止位等都是在DCB结构中设置的,并能通过DCB随时进行修改。
DCB dcb; //定义数据控制块结构
GetCommState (COMDEV( npTTYInfo ),&dcb); //读串口原来的参数据设置
dcb.BaudRate=9600;
dcb.ByreSize=8;
dcb.Parity=NOPARITY ;
dcb.StopBits=ONESTOPBIT ;
SetCommState(COMDEV( npTTYInfo ) ,&dcb); //串口参数设置
2)流控设置
①硬件流控
串口通信的硬件流控有两种:DTE/DSR方式和RTS/CTS方式,这与DCB结构的初始化有关系。本程序对这两种方式都进行了处理,使用者可以根据自己的需要任意选择。
bSet = (BYTE) ((FLOWCTRL( npTTYInfo ) & FC_DTRDSR) != 0) ;
dcb.fOutxDsrFlow = bSet ;
if (bSet)
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE ;
else
dcb.fDtrControl = DTR_CONTROL_ENABLE ;
bSet = (BYTE) ((FLOWCTRL( npTTYInfo ) & FC_RTSCTS) != 0) ;
dcb.fOutxCtsFlow = bSet ;
if (bSet)
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ;
else
dcb.fRtsControl = RTS_CONTROL_ENABLE ;
②软件流控
串口通信中采用特殊字符XON和XOFF控制串口数据的收发。它也是通过对DCB的初始化来完成的,与此相关的DCB成员是:fOutX、fInX、XoffChar、XonChar、XoffLim和XonLim。
bSet = (BYTE) ((FLOWCTRL( npTTYInfo ) & FC_XONXOFF) != 0) ;
dcb.fInX = dcb.fOutX = bSet ;
dcb.XonChar = ASCII_XON ;
dcb.XoffChar = ASCII_XOFF ;
dcb.XonLim = 100 ;
dcb.XoffLim = 100 ;
fRetVal = SetCommState( COMDEV( npTTYInfo ), &dcb ) ;
return ( fRetVal ) ;
4.2.3超时设置
在通信中,超时是个很重要的因素。譬如由于某种原因数据接收过程突然被中断或者数据发送过程突然被停止,这时如果不采取相应的措施,可能会引起输入输出线程挂起或被无限阻塞。Windows对于这类问题提供了超时控制机制,通过超时设置来决定通信是否异常并做出相应处理。
串口通信中的超时设置分为两步:首先设置COMMTIMEOUTS结构的五个变量,然后调用SetCommTimeouts()设置超时值。对于使用异步方式读写的操作,如果操作被挂起后,异步成功完成了读写,GetOverlappedResult()函数将返回TRUE。另外还可以用GetCommTimeouts()得到系统的初始值。
4.2.4串行口读写操作
串口的读写操作有两种方式:同步方式(Nonoverlapped)和异步方式(Overlapped)。同步方式是指必须完成了读写操作,函数才返回,这可能造成程序的死掉。因为如果在读写时发生了错误,永远不返回就会出错,可能线程就永远等待在那儿。而异步方式则灵活得多了。当调用CreateFile()函数打开端口时,就会使用FILE-FLAG-OVERLAPPED标志通知o/s(读写完成标志):将在重叠的格式下使用文件。这种情况意味着对ReadFile()函数和WriteFile()函数调用后立即返回,无需等待它们的操作完成。这时ReadFile()函数和WriteFile()函数使用OVERLAPPED管理重叠输入/输出,把这个结构传递给函数,直到异步输入/输出完成才会释放它,之后再把同一结构传递给GetOverlappedReult()函数,以获得最后的输入/输出操作的数据。一旦读写不成功,就将读写挂起,函数直接返回,可以通过GetLastError()函数得知读写未成功的原因。所以,本程序采用异步方式操作:
fReadStat = ReadFile( COMDEV( npTTYInfo ), lpszBlock,
dwLength, &dwLength, &READ_OS( npTTYInfo ) ) ;
if (!fReadStat)
{
if (GetLastError() == ERROR_IO_PENDING)
{
while(!GetOverlappedResult( COMDEV( npTTYInfo ),
&READ_OS( npTTYInfo ), &dwLength, TRUE ))
{
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
continue; //读写未完成,继续
else
{
ClearCommError( COMDEV( npTTYInfo ),
&dwErrorFlags, &ComStat ) ; //发生错误,清除错误标志
break;
}
}}}
4.2.5关闭串口
串行口是非共享资源,应用程序打开串口后,即独占资源,其它应用程序无法再访问,直到该应用程序释放串口。所以打开串口后,一定要关闭串口。关闭串口只要使用API调用CloseHandle()关闭串口的句柄就可以了。但是值得注意的是在关闭串口之前必须保证读写线程已经退出,在线程退出之前,通知主线程可以关闭串口。
4.3串行通信性能的提高
1)错误处理
在串口通信中,可能会发生很多的错误,造成少读字节数,使用ClearCommError()函数可以检测错误并且清除错误条件。
2)控制命令
本程序可以使用自己的流控:利用 EscapeCommFunction()函数来模拟特殊字符XON和XOFF,将硬件信号设置为"ON"和"OFF"。
EscapeCommFunction( COMDEV( npTTYInfo ), SETDTR ) ; // 发送DTR信号
……
EscapeCommFunction( COMDEV( npTTYInfo ), CLRDTR ) ; //清除DTR信号3)引入Sleep函数
由于Windows操作系统的消息处理机制,使用Timer控件和定时函数来确定的间隔时间,不能适合实际应用。这是因为Windows的多任务处理方式。如果计算机在执行串行通信程序的同时还执行其它程序,那么就要等待较长时间才能响应串行通信程序,定时不准确。因此,采用Sleep函数,可以在定时的时间范围内停止进程的所有动作,定时的精确性有了保障。
另外,引入多线程,为串行通信程序赋予较高的优先级,避免在接收过程中被打断,也能提高串行通信的性能。
5、结论
Windows环境下的应用程序编程是目前的主要趋势。它提供了丰富的资源,VC++还提供了面向对象程序设计的功能。Windows的API技术可以清楚地掌握串口通信的机制,且探制手段灵活,功能强大。用API技术开发的串行通信程序在实际应用中,很大地提高了通讯性能和通信的可靠性。本设计利用 VC++的强大功能, 在 Windows环境下开发了PC机与单片机的串行通信软件,对于国内的中、小型企业具有现实意义。本软件经过在一个实际的工业集散系统中的应用表明,运行状态良好,灵活方便。