当前位置:文档之家› 51单片机与PC串口通讯

51单片机与PC串口通讯

目录第1章需求分析 ............................................................................................................................ - 1 -1.1课题名称 (1)1.2任务 (1)1.3要求 (1)1.4设计思想 (1)1.5课程设计环境 (1)1.6设备运行环境 (2)1.7我在本实验中完成的任务 (2)第2章概要设计 ............................................................................................................................ - 2 -2.1程序流程图 (2)2.2设计方法及原理 (3)第3章详细设计 ............................................................................................................................ - 3 -3.1电路原理 (3)3.1.1STC89C52芯片 ............................................................................................................. - 3 -3.2串口通信协议 (4)3.3程序设计 (5)3.3.1主程序模块 .................................................................................................................... - 5 -3.3.2串口通讯模块 ................................................................................................................ - 6 -3.3.3控制部分文件 ................................................................................................................ - 8 -3.3.4公共部分模块 .............................................................................................................. - 11 -3.4电路搭建 (12)3.4.1电路原理图 .................................................................................................................. - 12 -第4章上位机关键代码分析 ...................................................................................................... - 12 -4.1打开串口操作 (12)4.2后台线程处理串口程序 (15)4.3程序运行界面 (18)第5章课程设计总结与体会 ...................................................................................................... - 19 -第6章致谢 .................................................................................................................................. - 19 -参考文献........................................................................................................................................... - 19 -第1章需求分析1.1 课题名称故障诊断数据采集通信系统设计与制作。

1.2 任务1、进行协议分析,完成单片机硬件电路原理图设计,在面包板上搭建电路。

2、测试上位机与单片机的通讯状态,实现实验要求部分的功能。

1.3 要求1、单片机能接收并识别上位机的查询请求。

2、单片机能够查询对应接口的状态,并返回接口状态给上位机。

3、能够通过按钮来控制单片机相应接口的状态,相应接口的状态通过LED灯的亮灭状态来表示。

1.4 设计思想根据实验要求,设计数据采集电路,选择合适的元器件,按照原理图,并根据各个元器件的特性以及接口电路结构形式,在面包板上搭接实际电路,搭接完毕之后对电路做优化设计,使电路尽量简洁。

通过与上位机的连接测试,来优化单片机程序的代码,使上位机和下位机能够很协调的工作。

软件编程:使用C语言实现下位机的程序设计。

1.5 课程设计环境1. Windows XP的PC机。

2. Keil uVision3集成开发环境。

3. 串口调试助手3. 单片机最小系统开发板。

4. 面包板和外围器件5. 万用表等辅助工具1.6 设备运行环境由STC89C52单片机和面包板搭建的电路板。

1.7 我在本实验中完成的任务在和小组成员讨论之后,我们小组成员分工合作,我完成的工作是单片机串口的设置及接收上位机的串口数据,并对流数据进行分析。

第2章概要设计2.1 程序流程图2.2 设计方法及原理1、USB转串口模块将USB接口转化为串口后与单片机相连,用来实现单片机与PC机通过串口通讯。

2、初始化单片机串口的设置,使之与上位机的设置相符,具体为通信速率9600B/S,停止位1位,数据位8位。

3、本系统串口数据接收是采用的查询方式,单片机每次循环查询串口是否收到了数据。

4、上位机若连续查询接口状态2次都收不到回复,上位机可以判断与从机失去联系,所有接口状态都置为不正常状态第3章详细设计3.1 电路原理3.1.1STC89C52芯片1、芯片引脚51系列的DIP封装的单片机共有40个外部引脚,其中有P0,P1,P2,P3四组IO口,详细如右图。

2、芯片串口工作原理1、波特率选择波特率(Boud Rate)就是在串口通信中每秒能够发送的位数(bits/second)。

MSC-51串行端口在四种工作模式下有不同的波特率计算方法。

下面以工作模式1为来说明串口通信波特率的选择。

在串行端口工作于模式1,其波特率将由计时/计数器1来产生,通常设置定时器工作于模式2(自动再加模式)。

在此模式下波特率计算公式为:波特率=(1+SMOD)*晶振频率/(384*(256-TH1))其中,SMOD——寄存器PCON的第7位,称为波特率倍增位;TH1——定时器的重载值。

2、SBUF数据缓冲寄存器这是一个可以直接寻址的串行口专用寄存器。

SBUF包含了两个独立的寄存器,一个是发送寄存,另一个是接收寄存器,但它们都共同使用同一个寻址地址-99H。

CPU在读SBUF 时会指到接收寄存器,在写时会指到发送寄存器。

3、SCON串行口控制寄存器通常在芯片或设备中为了监视或控制接口状态,都会引用到接口控制寄存器。

SCON就是51芯片的串行口控制寄存器。

它的寻址地址是98H,是一个可以位寻址的寄存器,作用就是监视和控制51芯片串行口的工作状态。

51芯片的串口可以工作在几个不同的工作模式下,其工作模式的设置就是使用SCON寄存器。

它的各个位的具体定义如下:(MSB)(LSB)串行口控制寄存器SCONSM0、SM1为串行口工作模式设置位,这样两位可以对应进行四种模式的设置。

看表串行口工作模式设置。

3.2 串口通信协议通信协议是通信设备在通信前的约定。

单片机、计算机有了协议这种约定,通信双方才能明白对方的意图,以进行下一步动作。

主机通过轮询方式访问从机,通过发送从机地址呼叫从机,地址相符合的从机把数据发送给主机通信格式:主机发送数据格式:主机每隔5秒钟查询一遍备注: 1从机地址分配:①号:0X01; ②号:0X02; ③号:0x03;④号: 0X04; ⑤号:0X05 ; ⑥号:0X06;⑦号:0X07; ⑧号: 0X08: ⑨号:0X093、数据字节01010101表示检测到故障;01 55 5610101010表示没有检测到故障3.3 程序设计3.3.1主程序模块1、main.c#include <reg52.h>#include <string.h>#include "uart.h"#include "common.h"#include "control.h"UCHAR rstr[TX_PAYLOAD_WIDTH]={0};void main() //接收{uchar t;bool sta;uchar key;InitUart(); //串口初始化InitControl(); //控制端口初始化while(1) //主程序不断的对接收到的数据进行分析{if(RI==1) //检查一下串口中有没有数据(每次一个字节){AppendByte(SBUF); //向协议窗口中增加一个数据RI=0; //清除串口中断t=ProtocolAnalysis(); //进行协议分析if(t!=0){sta=GetSta(t); //查询接口状态SendSta(t,sta); //发送接口状态}UartSend("ENA",3);}//对按钮状态进行查询, 如果有按钮被按下,将对应接口状态改变,然后将对应接口状态发给上位机key=GetKey();if(key!=0) //有键盘按下{ChangeSta(key);// 使用下面两句话接口状态改变之后会立刻通知上位机, 现在上位机采用查询方式来了解接口的状态,故不需要这两句话if(key==3){sta=GetSta(key); //查询接口状态SendSta(key,sta); //发送接口状态}}}}3.3.2串口通讯模块1、Uart.c#include <reg52.h>#include <string.h>#include "uart.h"uint m_nEndIndex; //下一个将要插入数据的下标uchar m_wsBlock[WINDOW_SIZE]; //定义接收缓存区的大小uchar m_wsTemp[WINDOW_SIZE]; //这是一个供数据交换的缓存区void InitUart() //初始化串口工作模式,在使用串口之前必须被调用一次{TMOD = 0x20; // 定时器1工作于8位自动重载模式, 用于产生波特率TH1=(unsigned char)(256 - (XTAL / (32L * 12L * Baudrate)));TL1=(unsigned char)(256 - (XTAL / (32L * 12L * Baudrate))); // 定时器1赋初值SCON = 0x50; //mode1方式,启用接收PCON = 0x00;TR1 = 1; //启用定时器1IE=0x00;//接收数据缓存区初始化部分memset(m_wsBlock,0,WINDOW_SIZE); //将窗口清零m_nEndIndex=0;}//串口接收处理,采用的是中断处理方式,对于发送中断该函数不处理//串口发送函数void UartSend(UCHAR *Data, int Len) //Data为数据指针,len为要发送的数据长度{UCHAR c;int i;for(i=0;i<Len;i++){c=Data[i];SBUF = c; // 要发送的字符放入缓冲区while(TI == 0);TI = 0;}}//第一个参数为接口编号,第二个参数为接口状态,为真接口状态为1,为假接口状态为0 void SendSta(uchar num,bool sta){uchar buf[PROT_LEN]={0};buf[0]=num;if(sta)buf[1]=0xAA; //没有故障elsebuf[1]=0x55; //有故障buf[2]=buf[0]^buf[1]; //按位异或UartSend(buf,3);}// 用于向窗口中插入一个字符,只有这一个函数会修改窗口中的数据void AppendByte(uchar ch){if (WINDOW_SIZE<=0)return;if (m_nEndIndex>=WINDOW_SIZE)//说明窗口已近满了,需要将顶位最前面的字符溢出,后面的所有字符向前移动一位{memcpy(m_wsTemp,m_wsBlock+1,WINDOW_SIZE-1);memcpy(m_wsBlock,m_wsTemp,WINDOW_SIZE-1);m_nEndIndex=WINDOW_SIZE-1;}m_wsBlock[m_nEndIndex]=ch;m_nEndIndex++;}// 用于协议分析,// 返回值为0说明协议分析没有分析到有效请求,不需要状态监测// 为1 说明要对端口1监测// 为2 说明要对端口1监测// 为3 说明要对端口1监测int ProtocolAnalysis(){uchar t;if (m_nEndIndex<PROT_LEN)//接收的长度小于协议长度,直接返回0,不需要分析return 0;if((m_wsBlock[0]=='$')&&(m_wsBlock[2]=='#')){t=m_wsBlock[1];if( (t>0x00)&&(t<0x04) )return t;}return 0;}2.Uart.h#ifndef _UART_H#define _UART_H#include "common.h"#define XTAL 11059200 // CUP 晶振频率#define baudrate 9600#define Baudrate 9600L#define WINDOW_SIZE 3 //定义窗口数据缓存区大小#define TX_PAYLOAD_WIDTH 3 //定义接受数据缓存区的长度#define PROT_LEN 3 //协议长度void InitUart(); //初始化串口工作模式,在使用串口之前必须被调用一次void UartSend(char *Data, int Len);//串口发送.Data为数据指针,len为要发送的数据长度void SendSta(uchar num,bool sta);//第一个参数为接口编号,第二个参数为接口状态,为真接口状态为1,为假接口状态为0 void AppendByte(uchar ch);// 用于向窗口中插入一个字符,只有这一个函数会修改窗口中的数据int ProtocolAnalysis(); // 用于协议分析,#endif3.3.3控制部分文件1、Control.c#include <reg52.h>#include "control.h"sbit key1=P2^0; //按钮状态没有按下时为1sbit key2=P2^1;sbit key3=P2^2;sbit sta1=P2^3; //"!sta0"为1表示正常状态,为0表示异常状态sbit sta2=P2^4;sbit sta3=P2^5;void InitControl() //初始化控制{key1=1;key2=1;key3=1;//开启三盏灯sta1=0;sta2=0;sta3=0;}int GetSta(int t) //得到sta状态{switch (t){case 0x01:return !sta1;;case 0x02:return !sta2;case 0x03:return !sta3;default:return -1;}}int GetKey() //得到按钮的状态{key1=1;if(key1==0){wait(1); //延时10ms消抖if(key1==0) //说明确实被按下{while(key1==0); //等待知道按钮被按下return 1;}}key2=1;if(key2==0){wait(1); //延时10ms消抖if(key2==0) //说明确实被按下{while(key2==0); //等待知道按钮被按下return 2;}}key3=1;if(key3==0){wait(1); //延时10ms消抖if(key3==0) //说明确实被按下{while(key3==0); //等待知道按钮被按下return 3;}}return 0;}void ChangeSta(int t) //改变接口的状态{switch (t){case 0x01:sta1=!sta1;break;case 0x02:sta2=!sta2;break;case 0x03:sta3=!sta3;break;default:break;}}2.Control.h#ifndef _CONTROL_H#define _CONTROL_H#include "common.h"void InitControl(); //初始化控制int GetSta(int t); //得到sta状态int GetKey(); //得到按钮的状态void ChangeSta(int t); //改变接口的状态#endif3.3.4公共部分模块1、Common.c#include <reg52.h>#include <intrins.h>#include "common.h"//用于存放一些公共的函数和宏定义void wait(int n) //延时函数n*10ms;{int i,j;for(i=0;i<n;i++)for(j=0;j<1681;j++);}2、common.h#ifndef _COMMON_H#define _COMMON_H#define UCHAR unsigned char#define uchar unsigned char#define bool unsigned char#define UINT unsigned int#define uint unsigned int#define true 0x01#define false 0x00#define TRUE 0x01#define FALSE 0x00void wait(int n); //延时函数n*10ms #endif3.4 电路搭建3.4.1电路原理图第4章上位机关键代码分析上位机采用的是Windows API编写的串口通讯程序4.1 打开串口操作BOOL CSerialPortEx::InitPort( CWnd* pPortOwner,UINT portnr, // 端口号UINT baud, // 波特率char parity, //检验位UINT databits, //数据位UINT stopbits, //停止位DWORD dwCommEvents,UINT writebuffersize) // {assert(portnr > 0 && portnr < 17);assert(pPortOwner != NULL);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); //用ResetEvent来手动清除事件对象的通知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 prioritym_hEventArray[1] = m_ov.hEvent;m_hEventArray[2] = m_hWriteEvent;//清空list缓存区m_listBuf.clear();// initialize critical section 初始化临界区InitializeCriticalSection(&m_csCommunicationSync); //用来构建一个临界区InitializeCriticalSection(&m_csListSync);// set buffersize for writing and save the ownerm_pOwner = pPortOwner;if (m_szWriteBuffer != NULL)delete [] m_szWriteBuffer; //如果发送缓存区中有数据则释放缓存区m_szWriteBuffer =new BYTE[writebuffersize]; //开辟一个缓存区,长度默认为512个字节m_nPortNr = portnr; //初始化端口号m_nWriteBufferSize = writebuffersize; //初始化数据缓存区大小m_dwCommEvents = dwCommEvents; //初始化串口上待监听的事件BOOL bResult = FALSE;char *szPort = new char[50];char *szBaud = new char[50];EnterCriticalSection(&m_csCommunicationSync);//获得临界区对象的所有权if (m_hComm != NULL){CloseHandle(m_hComm); //如果串口处于打开状态就关闭它m_hComm = NULL;}sprintf(szPort, "COM%d", portnr); //格式化串口号,并存放在szPort中//这里有一个问题,就是1.5个停止位在SetCommState中如何设置sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);//格式化波特率,奇偶检验,数据位,停止位m_hComm = CreateFile(szPort, //串口名称字符串(COM1)GENERIC_READ | GENERIC_WRITE, //读写0, //以独占方式打开NULL, //未设置安全属性OPEN_EXISTING, //串口设备必须设置该值FILE_FLAG_OVERLAPPED, //使用异步IO0); //串口设备这个参数必须设置为0if (m_hComm == INV ALID_HANDLE_V ALUE) //如果打开失败{// port not founddelete [] szPort;delete [] szBaud;return FALSE; //返回FALSE}//打开成功设置下面的值//总超时=时间系数×要求读/写的字符数+ 时间常量m_CommTimeouts.ReadIntervalTimeout = 1000; // 读间隔超时m_CommTimeouts.ReadTotalTimeoutMultiplier = 1000; // 读时间系数m_CommTimeouts.ReadTotalTimeoutConstant = 1000; // 读时间常量m_CommTimeouts.WriteTotalTimeoutMultiplier = 1000; // 写时间系数m_CommTimeouts.WriteTotalTimeoutConstant = 1000; // 写时间常量// configureif (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))//用指定的字符串来填充DCB结构{if (SetCommState(m_hComm, &m_dcb)); // normal operation... continueelseProcessErrorMessage("SetCommState()");}elseProcessErrorMessage("BuildCommDCB()");}elseProcessErrorMessage("GetCommState()");}elseProcessErrorMessage("SetCommMask()");}elseProcessErrorMessage("SetCommTimeouts()");delete [] szPort;delete [] szBaud;PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR |PURGE_RXABORT | PURGE_TXABORT);LeaveCriticalSection(&m_csCommunicationSync); //离开临界区TRACE("Initialisation for communicationport %d completed.\nUse Startmonitor to communicate.\n", portnr);return TRUE;}4.2 后台线程处理串口程序UINT CSerialPortEx::CommThread(LPVOID pParam) //串口监视线程的入口函数{CSerialPortEx *port = (CSerialPortEx*)pParam;port->m_bThreadAlive = TRUE;// Misc. variablesDWORD BytesTransfered = 0;DWORD Event = 0;DWORD CommEvent = 0;DWORD dwError = 0;COMSTAT comstat;BOOL bResult = TRUE;BOOL haveError=FALSE;if (port->m_hComm) // check if the port is opened//清空缓冲区PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR |PURGE_RXABORT | PURGE_TXABORT);for (;;){bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);//使用WaitCommEventif (!bResult){switch (dwError = GetLastError()){case ERROR_IO_PENDING:{if(haveError) //说明可能会进入死循环ResetEvent(port->m_hEventArray[1]);//手动将这个事件置为没有信号,防止死循环发生TRACE("表示查询操作转到后台运行\n");break;}case 87:{break;}default:{port->ProcessErrorMessage("WaitCommEvent()");break;}}}else{bResult = ClearCommError(port->m_hComm, &dwError, &comstat);if (comstat.cbInQue == 0) //确定串口中无数据,重新开始循环continue;} // end if bResult// 主监视函数,该函数将阻塞本线程直至等待的某一事件发生TRACE("串口线程正在等待\n");Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);//使用这个函数监视m_hWriteEvent,m_hShutdownEvent,m_ov.hEvent这三个事件TRACE("串口线程等待结束\n");switch (Event){case 0: //关闭{port->m_bThreadAlive = FALSE;::PostMessage(port->m_pOwner->m_hWnd,WM_COMM_THREADEND,(WPARAM) 0,(LPARAM)port->m_nPortNr);AfxEndThread(100); //这个函数彻底杀死线程break;}case 1: // read event{GetCommMask(port->m_hComm, &CommEvent);if (CommEvent & EV_CTS)::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);if (CommEvent & EV_RXFLAG)::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);if (CommEvent & EV_BREAK)::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);if (CommEvent & EV_ERR)::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);if (CommEvent & EV_RING)::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM) 0, (LPARAM) port->m_nPortNr);if (CommEvent & EV_RXCHAR){ // Receive character event from port.if(!port->m_bBlockRead) //字符读取port->ReceiveChar(port, comstat); //读串口else //块读取{//进行块读取int readlen=0;port->ReadBlock(port,readlen,comstat);if(readlen!=0) //表示串口线肯定没有发生错误haveError=FALSE;else //发生了错误haveError=TRUE;}}break;}case 2: // write event{// Write character event from portport->WriteChar(port); //写串口break;}}} // close forever loopreturn 0;}4.3 程序运行界面第5章课程设计总结与体会本次微机原理与接口的实验课中,我们小组成员紧密的团结在一起,密切讨论,合理的规划好了整个程序流程,合理的分配了任务.在较短的时间内将程序完成了.在与上位机的通讯过程中我们也遇到了很多问题,特别是最开始上位机的告诉发送数据导致下位机没办法及时接收,导致丢失数据.在和上位机组成员的讨论和合作下,通过改变了上位机发送数据的方式,最终我们完成了任务。

相关主题