`

VC程序与单片机的串口通信

    博客分类:
  • VC++
阅读更多

最近同事要再版多年前出版的《单片机教程》,当年帮助编写了与PC机通信一节。

顺手封装了一个类,整理如下:

本例题要求在PC机与单片机间通过串行通信实现从单片机向PC机的数据发送,具体要求是:首先由PC机向单片机发送一个“S”的ASCII码作为联络信号,单片机收到“S”后,发一个“A”的ASCII码作为应答信号。PC机收到“A”后,向单片机发送一个“F”的ASCII码命令信号,单片机收到“F”后,开始从内存50H取数据,并连续发送10个字节的数据,最后发送这10个数据的累加和校验。PC机在收到发送过来的数据和累加和后,与自己的累加和相比较,相同则发一个“J”的ASCII码作为应答信号,表示本次通信结束;不同则发一个“C”的ASCII码作为应答信号,表示本次通信失败,要求重新发送。单片机发送三次之后如果仍然不对,则作错误处理

在软件设计时一定要注意单片机与PC机之间应该遵守相同的通信协议,其主要包括波特率、传输帧格式、校验位等。除此之外,如果要实现PC机与多个单片机的通信,PC机还应该向单片机发送欲寻单片机的地址编码,而单片机中要编写地址识别程序段。

            本例题的通信协议约定如下:

波特率:2400b/s;

帧格式:1位起始位,8位数据位,1位停止位,无奇偶校验;

传送方式:PC机采用中断方式接收,单片机也采用中断方式接收;

数据长度:一个字节

校验方式:累加和校验;

 握手方式:软件握手

 

1.PC机的通信软件设计

实现PC机串口通信的软件在此采用VC++6.0语言编程,VC提供了一组系统函数用于支持Window平台下的串口通信,在msdn帮助文档中提供了TTY方式通信的例子,我们以这组通信函数为基础,实现了一个用于串口通信的类CCom,此通信类能够与指定串口关联,向串口发送数据,并且可以检测串口,一旦有数据到来,就会以中断方式将数据读入用户缓冲并向主窗口发送消息,利用它可以方便地编制串口应用程序。

源程序清单如下:

通信类头文件Com.h

 

#define BUFLEN 1024						//缓冲长度
#define WM_COMM_READ WM_USER+1000	//用户自定义的数据读入消息
class CCom : public CObject  
{
public:
	BOOL VerifyRbuf();			//校验和检查
	void dowithRbuf();			//通信协议应答
	void Write(char cmd);			//发送命令字
	void WriteFormat(char * sbuf,BYTE length);	//打包发送数据
	void Close();				//关闭串口
	void Read();				//从串口读数据
	void Open(UINT com);			//打开串口
	HANDLE m_hCom;			//串口句柄
	CWinThread * m_hThread;		//线程句柄
	BOOL	m_bRun;			//是否运行标志
	UINT	m_com;			//串口编号
	char	m_rbuf[BUFLEN];		//输入缓冲
	int		m_rbuflen;			//输入数据长度
	char	m_sbuf[BUFLEN];		//输出缓冲
	DWORD dwLength;			//实际读入(发送)的数据长度
    OVERLAPPED  osWrite, osRead;	//读写操作结果
	CCom();					//构造函数
	virtual ~CCom();				//析构函数
};

 

通信类实现Com.cpp

UINT CommWatchProc(VOID* pcom);
CCom::CCom()
{
	m_bRun = FALSE;
	m_hThread = NULL;
	m_rbuflen = 0;
}
CCom::~CCom()
{
}
void CCom::Open(UINT com)	//初始化串口,参数com为打开的串口编号
{
	memset(&osRead,0,sizeof(OVERLAPPED));
	memset(&osWrite,0,sizeof(OVERLAPPED));
	//创建读操作系统事件
    osRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
	//创建写操作系统事件
    osWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
	if(osRead.hEvent == NULL || osWrite.hEvent == NULL)
		return;			//创建事件失败返回
	m_com = com;			//记住串口编号
	CString str;
	str.Format("COM%d",m_com);
	m_hCom =CreateFile((LPCTSTR)str, //打开指定的串口
						GENERIC_READ | GENERIC_WRITE, // 允许读和写 
						0,							// 此项必须为0,即独占方式 
						NULL,						// 默认安全属性
						OPEN_EXISTING,				//仅当串口设备存在,打开该串口 
						FILE_ATTRIBUTE_NORMAL |FILE_FLAG_OVERLAPPED,	
// 使用异步方式读写,
						NULL ); //模板文件句柄,用于串口读写时必须设置为NULL
	ASSERT(m_hCom!=INVALID_HANDLE_VALUE);	//检测打开串口操作是否成功 
	SetCommMask(m_hCom, EV_RXCHAR );			//设置事件驱动的类型 
	SetupComm( m_hCom, 1024,512);				//设置输入、输出缓冲区的大小 
	PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
			| PURGE_RXCLEAR );					//清输入、输出缓冲区 

	DCB dcb;										// 定义数据控制块结构 
	GetCommState(m_hCom, &dcb );						//读串口原来的参数设置 
	dcb.BaudRate =2400;								//设置波特率为2400
	dcb.ByteSize =8; 								//数据位8位
	dcb.Parity = NOPARITY; 							//无奇偶校验位
	dcb.StopBits = ONESTOPBIT; 						//1位停止位
	dcb.fParity = FALSE; 								//不进行奇偶校验
	SetCommState(m_hCom, &dcb );					//串口参数设置
	m_bRun = TRUE; 								//启动串口读检测
	m_hThread = AfxBeginThread(CommWatchProc,this, THREAD_PRIORITY_NORMAL, 0,
								CREATE_SUSPENDED,NULL);
	m_hThread->m_bAutoDelete=FALSE;
	m_hThread->ResumeThread();
}
void CCom::Read()	//从串口读入数据,存放到用户自定义缓冲rbuf,并向主窗口发送消息
{
	DWORD nBytesRead,dwEvent,dwError;
	COMSTAT cs;
	while(m_bRun)			//是否停止检测
	{
		if(WaitCommEvent(m_hCom,&dwEvent,NULL))		//等待数据到来
		{
			if((dwEvent & EV_RXCHAR) == EV_RXCHAR)	//是否为数据到来事件
			{
				ClearCommError(m_hCom,&dwError,&cs);	//获得数据长度
				if(cs.cbInQue != 0)
				{	//读数据
					ReadFile(m_hCom,m_rbuf + m_rbuflen,cs.cbInQue,&nBytesRead,&osRead);
					m_rbuflen += cs.cbInQue;	//设置读入数据长度
				//向主窗口发送接收到数据的消息					::SendMessage(AfxGetMainWnd()->m_hWnd,WM_COMM_READ,(UINT)this,NULL);
				}
			}
		}
	}
	PurgeComm(m_hCom,PURGE_RXCLEAR);		//清输入、输出缓冲区
}
void CCom::Close()				//关闭串口
{
	m_bRun = FALSE;			//置停止检测标志
	if(m_hThread)
	{	
		WaitForSingleObject(m_hThread->m_hThread, 1000);//INFINITE);	// 等待子线程停止
		m_hThread = NULL;
	}
	CloseHandle(m_hCom);		//释放串口句柄
	CloseHandle(osRead.hEvent);	//释放读事件句柄
	CloseHandle(osWrite.hEvent);	//释放写事件句柄
}
void CCom::Write(char cmd)			//发送命令字
{
	WriteFile(m_hCom,&cmd,1,&dwLength,&osWrite);
}
//向串口发送数据,sbuf为数据缓冲,length为数据长度
void CCom::WriteFormat(char * sbuf,BYTE length)	
{
	m_sbuf[0] = length;			//设置数据长度字
	char sum = 0;
	for(int i=1; i<length+1; i++)		//计算累加和
	{
		m_sbuf[i] = sbuf[i];
		sum = sum + sbuf[i];
	}
	m_sbuf[length + 1] = sum;		//设置累加和
	WriteFile(m_hCom,m_sbuf,length + 2,&dwLength,&osWrite);//发送数据
}
void CCom::dowithRbuf()			//处理读入数据,实现通信协议
{
	if(m_rbuflen == 1 && m_rbuf[0] == 'A')		//接收到回应
	{
		Write('F');						//发送命令’F’
		m_rbuflen = 0;
	}
	else
	{
		if(m_rbuflen < m_rbuf[0] + 2 )		//读入数据是否完整
			return;
		if(VerifyRbuf())					//检验和是否正确
		{
			Write(‘J’);					//向单片机发送检验和正确回应
//请在此处加入对数据的处理,其中m_rbuflen为长度,m_rbuf为数据缓冲指针
		}
		else Write(‘C’);				//向单片机发送检验和错误回应
		m_rbuflen = 0;
	}
}
BOOL CCom::VerifyRbuf()
{
	char sum = 0;
	for(int i=1; i<m_rbuflen - 1; i++)
		sum += m_rbuf[i];
	if(sum != m_rbuf[m_rbuflen-1])		//校验和错误
		return FALSE;
	else
		return TRUE;				//校验和正确
}
UINT CommWatchProc(VOID* pcom)		//串口读检测线程
{
	CCom * pCom =(CCom *) pcom;
	pCom->Read();
	return TRUE;
}

   在windows下将上述通信类加入到应用程序中的过程如下:

定义通信类对象,调用open方法初始化串口。

在你的主窗口类中定义通信类对象: CCom m_com;

在主窗口类的初始化函数中打开串口:m_com.Open(1);

向串口发送数据:         m_com.Write('S');

在主窗口消息处理函数中处理接收到的数据;

为主窗口类增加消息响应函数:      

afx_msg void OnCommRead(WPARAM wParam,LPARAM lParam);

在主窗口类的实现中增加消息映射(在END_MESSAGE_MAP()宏之前)

      ON_MESSAGE(WM_COMM_READ,OnCommRead)

该消息响应函数实现如下:(其中CMainWnd为你的主窗口类)

void CMainWnd::OnCommRead(WPARAM wParam, LPARAM lParam)

{

      m_com.dowithRbuf();

}

 

2.单片机的通信软件设计

单片机的通信软件适用于51系列的任何一种型号。单片机的发送和接收采用中断程序。准备发送的数据存放在以内存50H为首地址的连续10个单元中。

 

汇编语言程序清单如下:

ORG   0000H
        LJMP  MAIN
        ORG   0023H     ;串行中断入口
        LJMP  RECEIVE   ;转中断程序 
        ORG   0030H
MAIN:   MOV SP,#70H     ;设置堆栈
        MOV IE,#10010000B;CPU开串行中断
        MOV SCON,#0C0H  ;设置串行口方式3
        MOV TMOD,#21H   ;设置定时器1为方式2
        MOV TH1,#0F4H   ;设置波特率2400HZ
        MOV TL1,#0F4H
        SETB TR1        ;启动定时器1
        SETB REN        ;允许串行接收
        SJMP $          ;等待PC机发送联络信号
  ;串行中断程序
RECEIVE:CLR EA          ;关中断
        PUSH ACC
        PUSH PSW
        MOV PSW,#08H 
        MOV A,SBUF    ;接收一个数据
        CLR RI
        CJNE A,#53H,OUT2;是否收到询问信号“S”
        MOV A,#41H      ;发送回答信号“A”
        LCALL SIOO       
        LJMP OUT1
   OUT2:CJNE A,#46H,OUT1;是否收到联络信号“F”
   SEND:LCALL SENDT     ;发送数据
     L1:JBC RI,L2       ;等待接收PC机信号
        SJMP L1
     L2:MOV A,SBUF   
        CJNE A,#04AH,OUT3 ;是否收到联络信号“J”,收到则跳出中断
        SJMP OUT1
   OUT3:CJNE A,#43H,OUT1;是否收到联络信号“C”,收到则重新发送数据
        SJMP SEND       
   OUT1:POP PSW
        POP ACC
SETB EA         ;开中断
        RETI
;发送50H单元起始10个字节数据子程序	   
SENDT:  MOV R2,#00H
        MOV R4,#10      ;发送数据长度
        MOV A,R4
        LCALL SIOO
        MOV R1,#50H     ;数据首址
   ST:  MOV A,@R1     ;取数据
        LCALL SIOO    ;调发送一个字节数据的子程序
        ADD A,R2
        MOV R2,A
        INC R1
        DJNZ R4,ST
        MOV A,R2        ;发送校验和
        LCALL SIOO	   
        RET 
  ;发送一个字节数据的子程序
SIOO:CLR ES     
	 MOV SBUF,A
	 JNB TI,$        ;判一帧是否发送完
	 CLR TI
	 SETB ES
	 RET 

 C语言程序清单如下:

#include <REG51.H>                  //库文件定义
unsigned char SendBUF[10] _at_ 0x50;   //要发送的数据
unsigned char send_temp;              //发送数据状态
void Send_data(unsigned char send_byte); //发送数据子函数
void main (void)
{
	SP = 0x70;                       //设置堆栈
	IE   = 0x80;                    // CPU开串行中断
	SCON = 0xc0;                   //设置串行口方式3
	TMOD = 0x21;                   //设置定时器1为方式2
	TH1  = 0xF4;                    //设置波特率2400HZ
	TL1  = 0xF4;
	TR1  = 1;                       //启动定时器1
	REN  = 1;                       //启动定时器1
	ES   = 1;                        //串行口开中断
	send_temp = 0;
	while (1);	
}
uart_pro(void) interrupt 5                //串行中断程序
{
unsigned char get_data,i,sum;
	
	EA = 0;
	get_data = SBUF;                   //接收一个数据
	RI = 0;
	switch (send_temp)                 //判断状态
	{
   case 0:                            
	 {
		if(get_data =='S')              //是否收到询问信号“S”
	  {
		Send_data('A');               //发送回答信号“A”
		send_temp ++;
     }else send_temp = 0;
      break;}
   case 1:
	 {
		 if(get_data =='F')            //是否收到联络信号“F”
	  {
		sum = 0; 
		for (i=0;i<10;i++)	
		{
          Send_data(SendBUF[i]);  //发送数据
		  sum += SendBUF[i];    //数据求和
          }
		Send_data(sum);		 //发送和
		send_temp ++;
      }else send_temp = 0;
      break;}
    case 2:
	 {
	  if(get_data =='J')   //是否收到联络信号“J”
	   {	 send_temp =0; }
     if(get_data =='C')		//收到联络信号“C , 则重新发送数据
	  {                
		for (i=0;i<10;i++)	
		{
         Send_data(SendBUF[i]);  //发送数据
		 sum +=	 SendBUF[i];    //数据求和
          }
		Send_data(sum);			//发送和
       }
     break;}  
    default :send_temp = 0;break;		
   }
}
void Send_data(unsigned char send_byte)  //发送一个字节数据的子程序
{
ES = 0;
 SBUF = send_byte;
while(TI ==0);    //判一帧是否发送完
TI = 0;
ES = 1;
}

 

  • 大小: 10.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics