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

2019-04-15 15:33发布

最近同事要再版多年前出版的《单片机教程》,当年帮助编写了与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; iRead(); 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 //库文件定义 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; }