{
serialPort1.PortName = "COM1";
serialPort1.BaudRate = 9600;
serialPort1.Open();
byte[] data = Encoding.Unicode.GetBytes(textBox1.Text);
string str = Convert.ToBase64String(data);
serialPort1.WriteLine(str);
MessageBox.Show("数据发送成功!","系统提示");
}
private void button2_Click(object sender, EventArgs e)
{
byte[] data = Convert.FromBase64String(serialPort1.ReadLine());
textBox2.Text = Encoding.Unicode.GetString(data);
serialPort1.Close();
MessageBox.Show("数据接收成功!","系统提示");
}
CserialPort类的功能及成员函数介绍
CserialPort类是免费提供的串口累,Codeguru是一个非常不错的源代码网站
CserialPort类支持线连接(非MODEM)的串口编程操作。
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_FRAME,CE_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、串口初始化函数InitPort
BOOL CSerialPort::InitPort(CWnd *pPortOwner, // the owner (CWnd) of the port (receives message)
UINT portnr, // portnumber (1..4)
UINT baud, // baudrate
char parity, // parity
UINT databits, // databits
UINT stopbits, // stopbits
DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
UINT writebuffersize) // size to the writebuffer
{
assert(portnr > 0 && portnr < 5);
assert(pPortOwner != NULL);
// if the thread is alive: Kill
if (m_bThreadAlive)
{
do
{
SetEvent(m_hShutdownEvent);
}
while (m_bThreadAlive);
TRACE("Thread ended\n");
}
// create events
if (m_ov.hEvent != NULL)
ResetEvent(m_ov.hEvent);
m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hWriteEvent != NULL)
ResetEvent(m_hWriteEvent);
m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hShutdownEvent != NULL)
ResetEvent(m_hShutdownEvent);
m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// initialize the event objects
m_hEventArray[0] = m_hShutdownEvent; // highest priority
m_hEventArray[1] = m_ov.hEvent;
m_hEventArray[2] = m_hWriteEvent;
// initialize critical section
InitializeCriticalSection(&m_csCommunicationSync);
// set buffersize for writing and save the owner
m_pOwner = pPortOwner;
if (m_szWriteBuffer != NULL)
delete [] m_szWriteBuffer;
m_szWriteBuffer = new char[writebuffersize];
m_nPortNr = portnr;
m_nWriteBufferSize = writebuffersize;
m_dwCommEvents = dwCommEvents;
BOOL bResult = FALSE;
char *szPort = new char[50];
char *szBaud = new char[50];
// now it critical!
EnterCriticalSection(&m_csCommunicationSync);
// if the port is already opened: close it
if (m_hComm != NULL)
{
CloseHandle(m_hComm);
m_hComm = NULL;
}
// prepare port strings
sprintf(szPort, "COM%d", portnr);
sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);
// get a handle to the port
m_hComm = CreateFile(szPort, // communication port string (COMX)
GENERIC_READ | GENERIC_WRITE, // read/write types
0, // comm devices must be opened with exclusive access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
FILE_FLAG_OVERLAPPED, // Async I/O
0); // template must be 0 for comm devices
if (m_hComm == INVALID_HANDLE_VALUE)
{
// port not found
delete [] szPort;
delete [] szBaud;
return FALSE;
}
// set the timeout values
m_CommTimeouts.ReadIntervalTimeout = 1000;
m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000;
m_CommTimeouts.ReadTotalTimeoutConstant = 1000;
m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000;
m_CommTimeouts.WriteTotalTimeoutConstant = 1000;
// configure
if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
{
if (SetCommMask(m_hComm, dwCommEvents))
{
if (GetCommState(m_hComm, &m_dcb))
{
m_dcb.fRtsControl = RTS_CONTROL_ENABLE; // set RTS bit high!
if (BuildCommDCB(szBaud, &m_dcb))
{
if (SetCommState(m_hComm, &m_dcb))
; // normal operation... continue
else
ProcessErrorMessage("SetCommState()");
}
else
ProcessErrorMessage("BuildCommDCB()");
}
else
ProcessErrorMessage("GetCommState()");
}
else
ProcessErrorMessage("SetCommMask()");
}
else
ProcessErrorMessage("SetCommTimeouts()");
delete [] szPort;
delete [] szBaud;
// flush the port
PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
// release critical section
LeaveCriticalSection(&m_csCommunicationSync);
TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr);
return TRUE;
}
这个函数是用来初始化串口的,即设置串口的通信参数:需要打开的串口号、波特率、奇偶校验方式、数据位、停止位,这里还可 以用来进行事件的设定
如果串口初始化成功,就返回TRUE,若串口被其他设备占用、不存在或存在其他股占,就返回FALSE,编程者可以在这儿提示串口操作是否成功
如果在当前主串口调用这个函数,那么pPortOwner可用this指针表示,串口号在函数中做了限制,只能用1,2,3和4四个串口号,而事实上在编程时可能用到更多串口号,可以通过通过注释掉本函数中的“assert(portur>0&&portnr<5)”语句取消对串口号的限制
2、启动串口通信监测线程函数StartMonitoring()
串口初始化成功后,就可以调用BOOL StartMonitoring()来启动串口检测线程,线程启动成功,发挥TRUE
BOOL CSerialPort::StartMonitoring()
{
if (!(m_Thread = AfxBeginThread(CommThread, this)))
return FALSE;
TRACE("Thread started\n");
return TRUE;
}
3、暂停或停止监测线程函数StopMonitoring()
该函数暂停或停止串口检测,要注意的是,调用该函数后,串口资源仍然被占用//
// Suspend the comm thread
//
BOOL CSerialPort::StopMonitoring()
{
TRACE("Thread suspended\n");
m_Thread->SuspendThread();
return TRUE;
}
4、关闭串口函数ClosePort()
该函数功能是关闭串口,释放串口资源,调用该函数后,如果要继续使用串口,还需要调用InitPort()函数
5、通过串口发送字符/写串口函数WriteToPort()
该函数完成写串口功能,即向串口发送字符。
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
以上是常用的函数介绍,熟悉该类的使用后,可以仔细看看其他函数,对上面介绍的函数,在对串口资源的使用上要记住一下三点:
l 打开串口用调用InitPort()和StartMonitoring();关闭串口用StopMonitoring()和ClosePort()而且以上函数的调用顺序不能乱
l 通过串口发送字符调用函数WriteToPort()
l 接受串口收到的字符需要自己编写WM_COMM_RXCHAR消息处理函数,需要手工添加,
操作:
首先,需要操作一个串口,所以只需要定义1个类对象就可以了,如要操作多个串口,则要为每个串口均定一个类对象,这可以通过数据方式来实现,这里定义的类对象为m_SerialPort,再定义一个布尔变量m_bSerialPortOpened用来标志串口是否打开。
在CserialPort类中有多个串口事件可以响应,在一般串口编程中,只需要处理WM_COMM_RXCHAR消息就可以了,该类所有的消息均需要人工添加消息处理函数,将处理函数名定义为OnComm(),首先在SerialPortTestDlg.h(头文件)中添加串口字符接受消息WM_COMM_RXCHAR(串口接受缓冲区内有一个字符)的响应函数声明:
//Generated message map funnctions
//{{AFX_MSG(CSCportTestView)
afx_msg long OnComm(WPARAM ch, LPARAM port);
//}}AFX_MSG
然后在,SerilPortTestDlg.cpp文件中进行WM_COMM_RXCHAR消息映射
BEGIN_MESSAE_MAP(CSerialPortTestDlg, CDialog)s
//{{AFX_MSG_MAP(CSerialPortTestDlg)
ON_MESSAGE(WM_COMM_RXCHAR, OnComm)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
接着,在SerialPortTestDlg.cpp文件中加入函数OnComm()的实现,并在其中完成对节诶受到的字符的处理,将接收到的字符显示在接受编辑框中:
long CSerialPortTestDlg::OnComm(WPARAM ch, LPARAM port)
{
m_strEditReceiveMsg += ch;
UpdateData(FLASE);//将接收到的字符显示在接受编辑框中
returne 0;
}
说明:WPARAM、LPARAM类型是多态数据类型,在WIN32中为32位,支持多种数据类型,根据需要自动适应,这样,程序有很强的适应性,再次,我们可以分贝理解为char和integer类型数据,每当串口接受缓冲区内有一个字符时,就会产生一个WM_COMM_RXCHAR消息,除法OnComm()函数,这时就可以在函数中进行数据处理,所以,这个消息就是整个程序的源头。
对CSerialPort类的改进
虽然CSerialPort类是一个非常好的类,但毕竟只是集中了作者一个人的智慧和经验,他也有许多缺陷,
n 原类只能发送字符(ASCII文本)不能处理二进制发送(也就是不能发送0X00)
n 该类不能很好的释放串口
n 存在内存泄漏
所以,可以进行如下改进
改进一、ASCII文本和二进制数据发送方式兼容
CSerialPort类中只有一个发送函数WriteToPort()
//
// Write a string to the port
//
void CSerialPort::WriteToPort(char *string)
{
assert(m_hComm != 0);
memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
strcpy(m_szWriteBuffer, string);
// set event for write
SetEvent(m_hWriteEvent);
}
调用上面的函数就只能用字符串方式,而c语言中,空字符(NULL,其中ASCII码值为0,即通常所说的十六禁止0x00字符),是串结束符,当检测到NULL字符后,就认为该字符串结束了,所以0x00字符以ASCII文本方式是不能从串口发送出去的,那么解决这一问题的方法就是用二进制发送,其实这里说的二进制,只不过是不以我们通常所说的“可见”或“能显示的字符”发送,比如,要发如下的一组值:
char chSend[5]={0x33,0x96,0x00,0x31,0xf1};
下面来对类做一些改进,解决这个问题,原理就是用字符数据来发送数据,并在发送时指定其长度,这样,数据没有发送完,发送过程就不会停止,CSerialPort类是用API函数编写的在,只要在WriteFile()函数中指定其实际要发送的长度,就可以将数据全部发送出去:
实现步骤如下:
1、在SerialPort.h文件中为CSerialPort类添加一个整形publicdn成员变量,:int m_nWriteSize;用于指定发送字符数据的长度
添加三个发送函数