龙空技术网

串口通信的原理与数据收发的实现过程

呵呵 157

前言:

如今兄弟们对“vbnet读取串口数据”大致比较注重,小伙伴们都想要学习一些“vbnet读取串口数据”的相关资讯。那么小编在网络上网罗了一些有关“vbnet读取串口数据””的相关知识,希望同学们能喜欢,各位老铁们一起来了解一下吧!

串口的全称为串行接口或串行通讯接口(Serial Interface),是采用串行通讯方式的一种扩展接口。所谓串行通信,就是指设备之间通过少量数据信号线,地线及控制信号线,一般不超过8根,按数据位形式一位一位地传输数据的通信方式,像单条车道的公路,同一时刻只能传输一位数据。当然,还有与之相对的并行通信,就是指使用8、16、32及64根或更多的数据线,就像多条车道的公路,可以同时传输多位数据的通信方式。传输方式如下图所示。

并行通信

串行通信

仅从字面理解,并行通信的应该效率高,传输速度快,但在实际工程应用时,需要综合考虑到成本,抗干扰,可施工难度等因素,因此,当今主流应用方式还是以串行通信为主。

有了硬件基础后,下一步就是要建立规则了,就像道路交通规则一样,指导数据的有序流通。

一般根据数据通信的方向,可分为全双工、半双工、及单工通信。

单工通信模式类似全时的单行车道,始终只能支持数据在一个方向上传输,典型应用如“广播信号发射塔”。半双工通信模式允许数据在两个方向上传输,但不能同时进行,类似于近几年为了解决上下班拥堵而设置的潮汐车道,在某一时刻只允许数据在一个方向上传输,它实际上是一种收发双方可以定时切换工作模式的单工通信,典型应用如“对讲机”。全双工通信模式是指允许数据同时在两个方向上传输,可以理解为两个单工通信的结合,因此也需要收发双方都有独立的接收和发送能力典型应用如“电话”。

三种常见的通信方式

在上述硬件和规则的基础上,逐步发展出了RS232,RS485,RS422等标准通讯协议,根据电气标准和协议进一步规范了接口的电气特性,成为行业标准。

RS232也称标准串口,是一种全双工通信,出现时间最早,25芯接口几乎已经淘汰,市面常见的是9芯接口,除了控制线外,最主要的数据线有3根,分别是Rx,Tx和GND,其中GND为公共线,Rx和GND之间的变化的电平信号可解析为接收数据,发送数据可调制成Tx和GND之间的电平信号。由于共模抑制能力差,传送距离最大为约15米,最高速率为20kb/s。

RS422在RS232的基础上发展而来,简单来说,就是为了接解决共模抑制能力差的问题,将GND拆分为两根线,这样就形成了2根Tx数据线,2根Rx数据线,数据线传输的电信号也有高低电平信号转换为差分信号,提高了抗干扰能力,理论传输距离可达1200米,最大传输速率为10Mb/s。

在低速通信的应用场合,双工模式价值不大,反而会增加硬件成本和故障率,因此RS485又在RS422的基础,进一步简化,只保留2根数据线,改为半双工方式通信,传输距离和速度不变。

RS232电平信号

RS485电平信号

RS422电平信号

到此阶段,硬件和基础规则已经搭建完成,收发双方能够实现基本的数据通信,但是只能把高低变化的电平信号转为“0”或“1”的数字信号,需要进一步解析这些二进制数字的含义,就需要双方提前约定好数据格式,将这些数字编译成可以识别的信息,同时还要考虑校验和纠错,从而产生了各种编码方式。

后续为了适应工业应用场合,一些实力雄厚的行业协会和企业,又逐步推出了各种工业总线,如ModbusRTU,Profibus,Canopen等,实现的设备之间的快速互联互通,进一步提升的产品的功能和性能。

到此为止,串口通信的发展和种类基本已经介绍完成了,当然,除了串口以外,还有USB,RJ45,Bluetooth,Wifi,Zigbee等各种有线、无线的通讯方式,都有自己的应用场合。

在早期的计算机中,串口是基本配置,后来由于USB接口和网络接口的流行,逐步淘汰,但是由于串口的应用场合实在太多,因此,市面上又出现了不少USB转串口设备和串口服务器设备等串口类设备,也可实现串口功能。

USB转RS232

串口服务器

一般计算机自带的RS232串口针脚定义如下,其中,主要用于数据传输的是2-RXD,3-TXD,5-GND。

一般将两台计算机的串口交叉连接起来,接线方式如下图所示,即可实现数据通信。当然,除计算机以外,任意两台遵循RS232规范的设备都可以通过这种方式实现数据交换。

串口通讯连接方式

在WinXP及之前的操作系统直接提供了一个“超级终端”工具,可用于串口调试,后续移除。

超级终端

市面上还存在众多的第三方工具软件,比较常见的有“sscom32”,“UartAssist”等,便于计算机与计算机之间或计算机与其他串口设备之间的串口通信功能测试。当然,这些工具软件并不是从最底层直接操作串口,而是依托于Windows操作系统中的API函数来实现的,其中,CreateFile用来打开或创建串口,GetCommState函数用来获取串口的初始配置,ReadFile和WriteFile用来读写串口数据。熟悉WinAPI的读者都知道CreateFile一般都是用来创建或打开磁盘文件的,那么从这里也可以看出,Windows操作系统底层对磁盘文件和IO设备的操作是一致的。

串口调试助手

使用C++设计实现的串口读写功能代码如下

	HANDLE m_Hcom;	DCB dcb;	COMMTIMEOUTS CommTimerOuts;	OVERLAPPED m_overlappedRead; 	OVERLAPPED m_overlappedWrite; //设置超时	void SetTimerOut(uint dwTimerOut= 5000)	{		CommTimerOuts.ReadIntervalTimeout= MAXDWORD;		CommTimerOuts.ReadTotalTimeoutConstant= 0;		CommTimerOuts.ReadTotalTimeoutMultiplier= 0;		CommTimerOuts.WriteTotalTimeoutConstant= dwTimerOut;		CommTimerOuts.WriteTotalTimeoutMultiplier= 0;		SetCommTimeouts(m_Hcom, &CommTimerOuts);	}	//设置DCB参数	bool SetDCBParm(uint xBabd, byte xDataSize, 		byte xParity, byte xStopBit)	{		if (!GetCommState(m_Hcom, &dcb)) //设置通讯口参数失败		{			return FALSE;		}   		//设置通讯参数    		dcb.DCBlength= sizeof(DCB); 		dcb.BaudRate = xBabd;     // set the baud rate		dcb.ByteSize = xDataSize; // data size, xmit, and rcv		dcb.Parity =   xParity;   // no parity bit		dcb.StopBits = xStopBit;  // one stop bit   		if (!SetCommState(m_Hcom, &dcb)) // 设置通讯端口参数失败.\n", GetLastError());		{			return FALSE;		}   		return TRUE;	}	//设置端口缓冲区大小	bool SetPortBuffSize(TU32 InputBuffSize, TU32 OutputBuffSize)	{		if(!SetupComm(m_Hcom, InputBuffSize, OutputBuffSize))// 设置通讯端口缓冲失败		{			return FALSE;		}   		return TRUE;	}
//====打开串口//----返回值: 连接成功返回TRUE,否则返回FALSEbool RTUCom::Connect(void){	//设置事件 	memset(&m_overlappedRead,0,sizeof(OVERLAPPED));	m_overlappedRead.hEvent= CreateEvent(NULL,FALSE,TRUE,(LPCSTR)"portread");	assert(m_overlappedRead.hEvent != INVALID_HANDLE_VALUE);	memset(&m_overlappedWrite,0,sizeof(OVERLAPPED));   	m_overlappedWrite.hEvent= CreateEvent(NULL,FALSE,TRUE,(LPCSTR)"portwrite");  	assert(m_overlappedWrite.hEvent != INVALID_HANDLE_VALUE);	LPCSTR comStr = portName.c_str();  //取得串口字符	//打开通讯端口	m_Hcom =CreateFile(comStr,		GENERIC_READ | GENERIC_WRITE,		0,    		NULL, 		OPEN_EXISTING, 		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,    		NULL  		);	if (m_Hcom == INVALID_HANDLE_VALUE)		//打开通讯口失败	{				return FALSE;	}	SetPortBuffSize(4096,4096);	SetDCBParm(m_BaudRate,m_DataBits,m_Parity,m_StopBits);   	SetTimerOut(1000);    	PurgeComm(m_Hcom, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);	return TRUE;}
//====通过串口读字符//----pBuf (Out): 接收数据缓冲区首地址//----dwBufferLength(In): 缓冲区字节长度//----dwWaitTime(In): 缓冲区接收字节等待时间//----返回值: 实际收到的数据字节长度,如果返回0表示通讯断开int RTUCom::ReadChar(void *pBuf, int dwBufferLength, int dwWaitTime){	COMSTAT Stat;  	if(!Stat.cbInQue)// 缓冲区无数据	{		return 0;	}	uint uReadLength = 0;	dwBufferLength = dwBufferLength - 1 > Stat.cbInQue ? Stat.cbInQue : dwBufferLength - 1;	if(!::ReadFile(m_Hcom, pBuf, dwBufferLength, (LPDWORD)&uReadLength, &m_overlappedRead)) //2000 下 ReadFile 始终返回 True	{		if(::GetLastError() == ERROR_IO_PENDING) 		{			WaitForSingleObject(m_overlappedRead.hEvent, dwWaitTime);   			if(!::GetOverlappedResult(m_Hcom, &m_overlappedRead, (LPDWORD)&uReadLength, false))			{				if(::GetLastError() != ERROR_IO_INCOMPLETE)				{					uReadLength = 0;				}			}		}		else		{			uReadLength = 0;		}	}	return uReadLength;}
//====通过串口写字符//----pBuf (In): 待发送数据首地址//----dwBufferLength(In): 缓冲区字节长度//----返回值: 实际发送的数据字节长度,如果返回0表示通讯断开uint RTUCom::WriteChar( void *pBuf,uint dwBufferLength){	uint uWriteLength = 0;	if(!WriteFile(m_Hcom, pBuf, dwBufferLength, (LPDWORD)&uWriteLength, &m_overlappedWrite)) //串口输出	{		if(GetLastError() == ERROR_IO_PENDING)		{			DWORD m_tmp=0;			m_tmp= WaitForSingleObject(m_overlappedWrite.hEvent, 1000);			if(m_tmp== WAIT_TIMEOUT || m_tmp== WAIT_ABANDONED)			{				return 0;			}			else if(m_tmp== WAIT_OBJECT_0)			{           				if(!GetOverlappedResult(m_Hcom,&m_overlappedWrite,(LPDWORD)&uWriteLength,FALSE))				{					return 0;      				}				else				{					return uWriteLength;        				}			}			uWriteLength = 0;		}	}	return uWriteLength;  }

在开发过程中,直接调用WinAPI函数开发,虽然比较灵活,但稍显复杂,需要花费不少时间去了解函数功能定义,因此,在一些其他高级语言继承开发环境中,提供了封装好的类库,可以直接调用,如VB的MSComm,.NET的SerialPort,QT的QSerialport等。以CSharp调用SerialPort为例:

首先引用命名空间

using System.IO.Ports;

定义变量

private SerialPort mySerialPort = new SerialPort();

关联事件响应

mySerialPort.DataReceived += new SerialDataReceivedEventHandler(mySerialPort_RecvData);

串口接收数据后触发事件

        private void mySerialPort_RecvData(object sender, SerialDataReceivedEventArgs e)        {            Thread.Sleep(100);	            this.Dispatcher.Invoke(new Action(() =>            {                this.txtRecvData.Text += DateTime.Now.ToString() + " --> " + mySerialPort.ReadExisting() + "\n";            }));

初始化并打开串口

                mySerialPort.PortName = cmbPortName.Text;                mySerialPort.BaudRate = (int)cmbBaudRate.SelectedValue;                mySerialPort.DataBits = (int)cmbDataBits.SelectedValue;                mySerialPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cmbStopBits.Text);                mySerialPort.Parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.Text);                mySerialPort.Open();

串口发送数据

mySerialPort.Write(txtSendData.Text);

此外,在单片机或嵌入式系统中,几乎都会提供串口驱动库,一般开发者几乎不需要关注底层实现逻,辑直接调用函数即可,如STM32 中的USART1_Config()等。

头文件 usart1_drive.h

#ifndef __USART1_DRIVE_H__#define __USART1_DRIVE_H__#define USART1_RX_BUFFER_MAX_LENGTH   	250	#define USART1_TX_BUFFER_MAX_LENGTH     250	#include "stm32f10x.h"extern unsigned short usart1_receivecounter ;extern unsigned short usart1_transmitcounter ;extern unsigned char usart1_receivebuffer[USART1_RX_BUFFER_MAX_LENGTH];extern unsigned char usart1_transmitbuffer[USART1_TX_BUFFER_MAX_LENGTH];extern unsigned char usart1_InputCommand[8];extern unsigned char usart1_OutputCommand[9];void USART1_Configuration(void);void usart1_transmitdata(unsigned char *str,unsigned short len);#endif 

源文件 usart1_drive.c

#include "usart1_drive.h"unsigned short usart1_receivecounter ;unsigned short usart1_transmitcounter ;unsigned char usart1_receivebuffer[USART1_RX_BUFFER_MAX_LENGTH];unsigned char usart1_transmitbuffer[USART1_RX_BUFFER_MAX_LENGTH];//初始化串口参数void USART1_Configuration(void){	NVIC_InitTypeDef NVIC_InitStructure;	USART_InitTypeDef USART_InitStructure;  GPIO_InitTypeDef GPIO_InitStructure;	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		GPIO_Init(GPIOA, &GPIO_InitStructure);	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;			GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;			GPIO_Init(GPIOA, &GPIO_InitStructure);		//串口工作模式配置	USART_InitStructure.USART_BaudRate = 9600;	USART_InitStructure.USART_WordLength = USART_WordLength_8b; 				 		USART_InitStructure.USART_StopBits = USART_StopBits_1;								USART_InitStructure.USART_Parity = USART_Parity_No ; 							USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; 	USART_Init(USART1, &USART_InitStructure);			NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	NVIC_Init(&NVIC_InitStructure); 	  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			USART_Cmd(USART1, ENABLE);             }//发送数据void usart1_transmitdata(unsigned char *str,unsigned short len){    unsigned short i;	  for(i=0; i<len; i++){			USART_SendData(USART1,str[i]);			while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);		} }//接收中断extern "C" void USART1_IRQHandler(void){	u8 res;	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)	{		 USART_ClearITPendingBit(USART1,USART_IT_RXNE);		 res=USART_ReceiveData(USART1);		 if((usart1_receivecounter&0x80)==0)		 {			if(usart1_receivecounter&0x40)			{				if(res!=0x0a)usart1_receivecounter=0;				else usart1_receivecounter|=0x80;			}      else 			{					if(res==0x0d)usart1_receivecounter|=0x40;				else				{					usart1_receivebuffer[usart1_receivecounter&0X3F]=res;					usart1_receivecounter++;					if(usart1_receivecounter>63)usart1_receivecounter=0;				}		 			}		 }  				 	}										 } 

在工业应用场合,串口通信更是广泛存在,无论是PLC、仪表、变频器、驱动器还以远程IO模块,物联网模块,基本都是必备的基础接口。

随着通信技术的发展,早年常见的调试解调器、串口打印机等设备逐渐退出日常生活,但是串行接口依然广泛存在,融入到各种底层硬件之中,无论软硬件开发,串口原理都是必修基础。随着计算机技术的发展,各种封装好的框架、类库,给编程开发带来了极大的便捷,一些开发人员,逐渐忽略了这些基础知识,设计出了许多看似合理,实则违背底层硬件基础的代码,以至于造成各类性能问题而无法解决。

这里主要跟大家分享一下在之前学习工作中的心得经验,也是一次不错的总结机会,中间不免有些疏漏浅显,如有错误,希望大家能够及时指正,以免造成误导。

标签: #vbnet读取串口数据 #vb串口接收数据