最近同事要再版多年前出版的《单片机教程》,当年帮助编写了与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; }
相关推荐
51单片机与电脑通过串口通信的例子,包括单片机程序和电脑主机程序(VC),主机程序使用CSerialPort串口类。数据包格式是自定义的一个简单格式,格式如下: ●下位机返回数据格式:1字节导引头 + 5字节命令 + 1字节...
总结起来,基于VC的单片机串口通信涉及了串口的打开、配置、数据发送和接收等多个环节。通过理解串口通信的原理和VC++提供的API,我们可以构建稳定、高效的单片机与计算机间的通信程序。对于初学者,建议先从理解...
本项目涉及的“vc写的单片机更新程序”是利用Visual C++(简称VC)这一强大的C++集成开发环境,构建的一个专门用于通过串行通信接口更新单片机内程序的工具。这种程序通常被称为固件升级工具或者Bootloader更新工具...
本文将深入探讨“VC_mfc.rar”压缩包中涉及的VC++ MFC(Microsoft Foundation Classes)框架下如何实现串口通信,以及串行通信的基本原理。 串行通信是一种通过串行接口进行数据传输的方式,它将数据一位一位地顺序...
本篇文章提供了串口通信与单片机通讯程序的开发指南,涵盖了串口通信基础、串口通信协议、单片机基础、使用VC6.0编写串口通信程序和单片机通讯程序等内容,旨在帮助开发者快速掌握串口通信与单片机通讯程序的开发。
[264]温度传感器VC源程序,用串口与51单片机通讯.zip上位机开发VC串口学习资料源码下载[264]温度传感器VC源程序,用串口与51单片机通讯.zip上位机开发VC串口学习资料源码下载[264]温度传感器VC源程序,用串口与51...
本主题聚焦于如何使用Microsoft Visual C++ 6.0(简称VC6)实现串口通信,以及如何与单片机进行通信。首先,我们要理解串口通信的基本原理。 串口通信基于RS-232标准,它定义了接口的电气特性、信号线排列以及数据...
[177]VC与单片机之间的串口通信,上面东西很有用,可以帮助初学者编写程序.zip上位机开发VC串口学习资料源码下载[177]VC与单片机之间的串口通信,上面东西很有用,可以帮助初学者编写程序.zip上位机开发VC串口学习...
在电子工程和嵌入式系统开发中,串行通信是一种常用的数据传输方式,尤其是在PC机与微控制器(如51单片机)之间。本文将深入探讨如何利用Visual C++(VC)进行串口通信,实现PC机与51单片机之间的数据交换。 首先,...
总之,使用VC++6.0实现PC机与单片机之间的串行通信涉及对Windows API的熟练掌握,理解串口通信的基本原理,以及单片机的串口配置。通过编写串口类并进行错误处理,可以提高代码的可复用性和稳定性。实际项目中,还...
本文介绍了一种利用VC++和MFC框架实现与单片机串口通信的方法。通过这种方法,可以轻松地实现上位机与多个单片机之间的数据交互,适用于多种应用场景,如证券信息显示、广告屏幕控制等。此外,该方案具有较好的稳定...
标题中的“温度传感器VC源程序,用串口与51单片机通讯”涉及了几个重要的IT知识点,包括温度传感器的应用、单片机编程、以及串行通信技术。以下是这些主题的详细说明: 1. **温度传感器**:温度传感器是能够感知...
网上搜集的实例程序,希望对大家有帮助,欢迎访问个人主页:http://wuzz.hostwq.net
利用Keil C51实现单片机与PC机串口通信 利用VC++实现PC机与单片机串口通信
串行通信是一种数据传输方式,相比并行通信,它只需要较少的线路,因此成本更低,适用于远距离通信。在单片机应用中,串口通信通常采用通用异步收发传输器(UART),实现与计算机的数据交换。 1. **串口通信基础**...
用串口类实现VC与MCS51单片机的通信
在本文中,我们将深入探讨如何使用Microsoft Visual C++ 6.0(简称VC6.0)集成开发环境,通过Mscomm控件来实现与单片机的串口通讯程序。Mscomm控件是Windows API提供的一个组件,它使得在应用程序中实现串行通信变得...