单片机读出温度后,接下来的工作就是单片机和CH372芯片的通信。CH372已经屏蔽了USB通讯协议,所以只要知道CH372单片机之间的通信过程,就很方便的写出它们的通信流程图。图5.4是CH372和89C51的程序流程图。
(1)单片机向CH372上传数据 (2) 下载数据到单片机
图5.4 单片机和CH372的通信流程图
上传数据是通过查询DS18B2是否转换完,转换完,则上传,否则等待;上位机的应用软件向下位机发送数据,CH372的端点接受器接受来着来自计算机的数据,通过中断来通知单片机,单片机开始接受收据,并进行处理。
5.2.2 上位机软件设计
上位机接受来自下位机的数据,根据数据进行操作,实现各种功能。有一点必须要注意的是,要利用动态连接库的知识,才能打开USB设备,从而进行通信,所以在编写应用软件前,一定要了解C++ builder 动态连接库的使用。由于CH372驱动程序提供的动态连接库是用VC编写的,C++ builder要建立起PC机和CH372的通信,必须要使用VC编写的动态连接库,但C++ builder不能直接使用VC编写的动态连接库。关于这点,在调试部分将做更多说明,这里只介绍上位机软件整体设计方面的内容。
下面给出上位机的程序流程图5.5:
图5.5 上位机流程图
这就是上位机软件的设计思想,具体实现在程序中有将进一步说明。
6 系统调试
前面几章节,已经对整个系统进行了设计,可是对于硬件来说,这只是完成一部分的工作 ,硬件系统真正的挑战在于调试,很多情况下,电路图是在理论是正确的,可以实现的,可是真正的到了实际中,会出现很多情况,但不管怎么样,重要的一点是结合设计理论知识来分析系统,逐步调试系统。
6.1 硬件调试
系统硬件电路并不是很复杂,但调试中必需要注意以下几点,否则将会出现系统工作不稳定情况,严重的话,将CH372芯片烧坏,这是电路特别需要的地方。下面给出硬件调式中碰到的问题,并提出如何解决。
(1)CH372的WR、RD、A0信号线是由单片机89C51的P0口提供的,P0口作为I/O口使用,必须要加上拉电阻,很多资料写着加1K-10K的上拉电阻,但这对于本系统是不正确的。
实际上在本系统中P0口的上拉F范围是30K-125K,最好选择100K的上拉电阻。因为如果只是10K,那么流入CH372的芯片的电流为500uA,远远超过了芯片规定的最高电流160uA,这样肯定会烧坏芯片。所以这个上拉电阻的范围是一定要根据芯片的参数来设计,而不能从其他资料上说的选择1K-10K。这点要特别注意。
(2)在CH372的滤波电容0.1uF中,并联一个5K-10K的电阻。这样的作用是系统在断电时,把电容中的电能及时的释放掉,使VCC及时的下降到0V,确保电路下使用通电时CH372能够可上电复位。
如果不加这个电阻,不少情况下,一旦下位机通电,芯片很久才显示出来,上位机需要较久时间来确认接口芯片,所以应该加5K-10K的电阻,这是系统的一个改进。
(3)共用晶振问题。USB接口芯片CH372和单片机都要需要晶振才能工作,但CH372的晶振必须是12M。单片机可以和CH372共用同一个晶振,但必需是12M。这样的优点是可以节约材料,但是不便于PCB排版,还有如果有芯片坏了,也不容易觉察出是不是CH372坏,还是单片机坏了,所以这里考虑单片机和CH372不能共用晶振。方便硬件调试。
(4)DS18B20的接法。DS18B20连接到单片机的接法很简单,这里提出需要注意的一点是,DS18B20用在P11做通信端口,但必须接4.7K的上拉电阻。P1口是I/O口用驱动外部电路时,一般都不用接上拉电阻的,但这里需要上拉电阻。这一点也就要注意。否则DS18B20工作不稳定,有可能有干扰。
本次系统调试硬件主要出现的问题就是上面所提到的,只要根据理论来分析,综合实际应用,便可以做出正确的硬件,这样为后期软件调试减少很多麻烦,否则,后期软件调试的时候,出现问题有可能是软件问题,或者是硬件问题,混合起来处理会很不方便,需花很多时间。
6.2 软件调试
本系统的软件调试主要有下位机端的软件调试和上位机端的软件调试。
6.2.1 下位机的软件调试
下位机的软件调试主要分两部分:单片机和CH372Z时间的通信、单片机与温度传感器DS18B20的通信。
(1)单片机和CH372之间的通信
这一部分是必须首先要调试出来的, 因为要依靠这一部分通过计算机才能显示出单片机和温度传感器之间的通信。
硬件部分利用具有上拉电阻的P0口作为CH372的WR、RD、A0线,所以通过软件控制P0口信号来达到控制CH372的读写命令和读写数据。只要注意CH372的读写时间要求就可以很快实现。单片机的P2口是作为CH372的数据口,利用它来发送和接受CH372的数据。
这里特别强调的一点是CH372的初始化程序,如果初始化不成功,电脑无法识别硬件,后期将无法进行。所以初始化程序必须注意的一点。下面给出CH372的初始化程序。
#define VID 0x8888 //厂商ID #difine PID 0x9999 //产品ID CH372_Init() //初始化CH372 { int i; CH372_WR_CMD_PORT(CMD_SET_USB_ID); //设置USB设备VID和PID CH372_WR_DATA_PORT(VID&0xff); //写入厂商ID的低字节 CH372_WR_DATA_PORT(VID>>8); //写入厂商ID的高字节 CH372_WR_DATA_PORT(PID&0xff); //写入产品ID的低字节 CH372_WR_DATA_PORT(PID>>8); //写入产品ID的高字节 CH372_WR_CMD_PORT(CMD_SET_USB_MODE); //设置CH372工作模式 CH372_WR_DATA_PORT(2);//工作模式2 for( i=200;i>0;i--) //20us时间复位 if (CH372_RD_DATA_PORT==CMD_RET_SUCCESS) //复位成功 break; }
另外,CH372最大可以传送64个字节,每一个读写函数每一次操作的只能一个字节,要读写多个字节,这里使用的是循环的方法。
CH372初始化成功后,下位机连接到PC机上,安装了CH372驱动程序的PC机就可以识别硬件。完成这部分工作之后先进入上为机和下位机联合的调试,然后再调试DS18B20和DS18B20之间的通信。
(2)单片机和DS18B20之间的通信
单片机要根据DS18B20的时序要求和读写要求来读取温度数据。由于DS18B20是一线式数字温度传感器,对时序要求比较高,延时程序误差大,则不能读出数据。还有要按照访问DS18B20的顺序来操作。这里再次说明访问DS18B20的顺序如下:①初始化;②ROM命令;③DS18B20的函数命令。
总之,这部分编程主要注意的就是延时程序的准确性。并按照DS18B20的操作顺序便可以把温度数字传到单片机。
6.2.2 上位机的软件调试
上位机的编程工具是C++ builder,主要是VCL控件的使用。主要有两个模块组成:通信模块和图像处理模块。其中通信模块负责处理上位机和下位机之间的通信,图像处理模块负责温度采集图像。
对于通信模块,主要是调用CH372的动态连接库,但由于厂家的CH372动态连接库是使用Visual C++制作的,C++ builder 不能直接运用,否则将会有出错信息。所以要经过一定的处理。
处理这一问题主要有两种方法:显式连接法和使用C++Builder中提供的导入库生成工具。由于显示连接对于在系统中多次调用动态连接库的多个函数很不方便,这里选择使用C++Builder中提供的导入库生成工具。步骤如下:
(1)用C++Builder提供的implib.exe工具重新生成该动态库(xxx.dll)的导入库(xxx.lib)。命令如下:
implib ch372.lib ch372.dll。
ch372.dll为已有动态库,ch372.lib为要生成的导入库。由此生成的导入库ch372.lib格式与C++Builder开发平台是相容的;
(2)在动态库的头文件ch372.h中,对其输出函数重新说明,语句如下:
extern _stdcall HANDLE WINAPI CH375OpenDevice(ULON GiIndex );// 指定CH372设备序号,0对应第一个设备
(3)然后采用隐式链接法,将重新生成的导入库(ch372.lib)和重新说明的头文件(ch372.h)加入到C++Builder应用程序的工程项目中,进行编译和连接。
对于图像处理模块,应用软件根据接受到的温度,利用C++ builder在窗体上画出动态连接图,主要采用窗体Canvas属性来实现。
6.2.3 上位机和下位机联机调试
下位机(单片机)对CH372初始化成功之后,上位机就能够识别下位机设备(USB设备),上位机调试部分也初步完成,那就进入系统整体调试。
整体调试主要分三步:
(1)测试单片机和PC机能否正常通信;
这里利用的方法如下:在下位机的程序部分设计一个往上位机发送的字符数组,比如“1214”,然后在PC机上进行操作,看是否能成功接受数组,如果能,则说明单片机可以往上位机发生数据,不行则修改上位机和下位机相关部分的程序。接下来PC往下位机发送数据,如果下位机能够成功返回相同的数据到PC机上,则说明上为机和下位之间的通信已经成功。
(2)测试单片机能否对DS18B20正常的读取温度;
完成上面一步才能进行这一步调试,前面已经提到,DS18B20的对时序要求很高,一定要准确,并且按DS18B20的顺序来进行操作,在硬件电路原理没有错的情况下,如果温度读起不正确,或者无法读取温度,只能是出现两种错误,一是时序问题,没有按照DS18B20的时序精度来对其进行操作,二是单片机访问DS18B20的顺序问题,单片机没有严格按照访问DS18B20的顺序对起发送命令,单片机访问DS18B20的顺序这里再次声明:初始化、ROM命令、DS18B20的函数命令。
这一部分出现的两个问题,大部分情况下出现的是时序问题,所以特别注意单片机对于DS18B20的精确延时。
(3)采集温度;
各部分通信正常后,便可以采集数据并处理。因为系统采集过程是上位机每发送一条采集命令,下位机就上传一次温度数据,所以这一步主要调试的是上位机要间隔多长时间定时向下位机发送采集命令,使系统能快速采集温度并上传,并防止发生读写等冲突。
6.3 系统性能指标
系统性能指标主要是:
(1)测量温度误差小于或者等于0.5℃;
(2)温度显示分辨率为0.0625℃;
(3)测量温度范围在0℃~70℃;
(4)具有控制报警功能。
7 系统的简单操作说明
若用户使用本系统,将可以对其进行以下的简单操作:
(1)首先打开USB设备;在上位机软件上打开USB设备,建立起上位机和下位机的通信机制。使用本系统都首先要打开USB设备。
(2)对计算机端的应用软件进行温度读取操作,将立即得到该环境温度的实时温度;
(3)可以在上位机上输入报警极限温度,上位机把极限温度发送到下位机,并且保留在上位机,一旦超过,则报警;
(4)上位机应用软件提供实时使用帮助功能。如果用户不知道怎么样使用软件,则可以通过帮助,便能快速使用本系统软件。
附 录
1.protel原理图
1PCB图
2本系统部分程序
(1)下位机部分程序图
#include #include #include "ch372.h" #define uchar unsigned char #define uint unsigned int #define VID 0x8888 #define PID 0x9999 #define USBCMD_WR 100 //上传数据 #define USBCMD_RD 101 //设置报警温度 #define DS18B20 102 //检测是否有温度传感器 #define CH375PORT P2 sbit CH375_WR=P0^5; //控制CH372的写数据信号 sbit CH375_RD=P0^6; //控制CH372的读数据信号 sbit CH375_A0=P0^7; //控制CH372的写命令信号 data uchar buffer[64]; data uchar USBCMD; bit UsbRecvOk; sbit LED0=P1^1; sbit LED1=P1^2; sbit DQ=P1^0; static float bwendu; //DS18B20的程序 void delaym(uchar time) //延时为(time*2+3)us { for(;time>0;time--); } uchar read_byte(void) {uchar i,value=0; for(i=0;i<8;i++) {value>>=1; DQ=0; //将总线DQ拉低开始读时序 DQ=1; //释放DQ; delaym(1); if(DQ) value|=0x80; delaym(6); //读取时间间隙,要大于1us } return (value); } void write_byte(uchar value) { uchar i; for(i=0;i<8;i++) {DQ=0; DQ=value&0x01; //每次写1位,通过val右移得到 delaym(5); //延时34us(15~60us采样时间) DQ=1; value>>=1; } delaym(5); //2次写的时间间隙要大于1us } uchar DS18B20_RESET(void) {uchar da; DQ=0; delaym(29); //保持DQ低480us DQ=1; delaym(3); //等待15~60us da=DQ; delaym(25); return (da);//有芯片应答da=0,无则da=1 } unsigned int readtemperature(void) //读温度 { uchar a=0,b=0; // c为温度的小数部分? unsigned int t=0; DS18B20_RESET(); write_byte(0xCC); // 跳过读序号列号的操作 write_byte(0x44); // 启动温度转换 DS18B20_RESET(); write_byte(0xCC); //跳过读序号列号的操作 write_byte(0xBE); //读取温度寄存器等(共可读9个寄存器) 前两个就是温度 a=read_byte(); b=read_byte(); t=b; t<<=8; t=t|a; return(t); } void Delayms(uint m) { uint i; while(m-->0) for (i=0; i<500; i++); } void CH375_CMD(uchar x) //向CH372写命令 { CH375_A0=1; CH375PORT=x; CH375_WR=0; CH375_WR=1; } void CH375_DAT_WR(uchar x) //向CH372写数据 { CH375_A0=0; CH375PORT=x; CH375_WR=0; CH375_WR=1; } uchar CH375_DAT_RD() //从CH372读数据 { uchar x; CH375_A0=0; CH375PORT=0xff; CH375_RD=0; x=CH375PORT; CH375_RD=1; return x; } void CH375_Init( ) //CH372初始化 { CH375_CMD(CMD_SET_USB_ID); //写 VID和PID CH375_DAT_WR(VID&0xff); CH375_DAT_WR(VID>>8); CH375_DAT_WR(PID&0xff); CH375_DAT_WR(PID>>8); CH375_CMD(CMD_SET_USB_MODE); CH375_DAT_WR(2); } void LoadUpData( uchar data *Buf, uchar Len ) //上传数据 { uchar i; CH375_CMD(CMD_WR_USB_DATA7); CH375_DAT_WR(Len); for ( i=0; i<len; i++="" )="" <="" span=""> CH375_DAT_WR(Buf[i]); /* 加载数据 */ } void CH375Interrupt( ) interrupt 0 using 1 { unsigned char Status; unsigned char length, i; EX0 = 0; CH375_CMD(CMD_GET_STATUS); Status = CH375_DAT_RD(); switch(Status) { case USB_INT_EP2_OUT: /* 批量端点下传成功 */ CH375_CMD(CMD_RD_USB_DATA); length = CH375_DAT_RD(); if(length>64) length=64; for(i=0; i<length; i++)="" <="" span=""> buffer[i] = CH375_DAT_RD(); LED1=0; UsbRecvOk=1; USBCMD=buffer[0]; break; case USB_INT_EP2_IN: LED1=0; CH375_CMD(CMD_UNLOCK_USB); break; case USB_INT_EP1_IN: CH375_CMD(CMD_UNLOCK_USB); break; } EX0 = 1; } void main( ) { uint wendu; float tt; uchar a,b; uchar reset; //DS18B20复位成功 uint k; EA=1; EX0 = 1; CH375_CMD(CMD_RESET_ALL); Delayms(50); CH375_Init(); UsbRecvOk=0; LED0=1; LED1=0; while(1) { k++; if(UsbRecvOk) { UsbRecvOk=0; switch(USBCMD) { case USBCMD_RD: wendu=readtemperature(); //读温度 b=wendu&0x0f; a=(wendu>>4)&0xff; buffer[0]=a; //温度整数部分 buffer[1]=b; //温度小数部分 LoadUpData(buffer,3); //上传温度 tt=a+b*0.0625; break; case USBCMD_WR: //设置报警温度 bwendu=buffer[1]+buffer[2]*0.1; break; case DS18B20 : //检测有无温度传感器 reset=DS18B20_RESET(); buffer[0]=reset; LoadUpData(buffer,3); } } if(tt>=bwendu) LED0=0; else LED0=1; } } (2)上位机部分程序: //---------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //---------------------------------------------------------------------- #include #include #include #include #include //---------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components TButton *Button1; TButton *Button2; TButton *Button3; TEdit *Edit1; TEdit *Edit2; TButton *Button4; TLabel *Label1; TLabel *Label2; TShape *Shape1; TShape *Shape2; TShape *Shape3; TButton *Button5; TTimer *Timer1; TTimer *Timer2; TShape *Shape4; TLabel *Label3; TLabel *Label4; TLabel *Label5; TLabel *Label6; TLabel *Label7; TLabel *Label8; TLabel *Label9; TLabel *Label10; void __fastcall Button4Click(TObject *Sender); void __fastcall Button1Click(TObject *Sender); void __fastcall Button3Click(TObject *Sender); void __fastcall Button2Click(TObject *Sender); void __fastcall Button5Click(TObject *Sender); void __fastcall Timer1Timer(TObject *Sender); private: // User declarations public: // User declarations __fastcall TForm1(TComponent* Owner); ULONG index ; float baojing; //存放设置的报警温度 unsigned char sent[64],jieshou[64]; unsigned long length; unsigned int image[64]; unsigned int x1,y1; //坐标 }; //---------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //---------------------------------------------------------------------- #endif //---------------------------------------------------------------------- #include #pragma hdrstop #include "Unit1.h" #include "CH375DLL.H" //---------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //---------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Form1->Timer1->Enabled=false; //关定时器 Form1->Timer2->Enabled=false; baojing=50; } //---------------------------------------------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { Form1->Close(); //关闭窗口 CH375CloseDevice(0);//关闭CH372 } //---------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { length=64; if(LoadLibrary("CH375DLL.DLL")==NULL) ShowMessage("无法加载DLL文件"); if ( CH375OpenDevice(0) == INVALID_HANDLE_VALUE ) ShowMessage("无法打开CH372") ; else ShowMessage("成功打开CH372"); sent[0]=102; CH375WriteData(0,&sent,&length); //发送检测温度传感器的命令 CH375ReadData(0,&jieshou,&length); //接受收据 if(jieshou[0]) ShowMessage("无法检测到温度传感器"); Form1->Timer1->Enabled=false; Form1->Label9->Canvas->Pen->Width=6; //画系统坐标轴 Form1->Label9->Canvas->MoveTo(0,0); Form1->Label9->Canvas->LineTo(0,270); Form1->Label9->Canvas->MoveTo(0,270); Form1->Label9->Canvas->LineTo(350,270); Form1->Label9->Canvas->Pen->Width=1; Form1->Label9->Canvas->Pen->Style=psDashDotDot; Form1->Label9->Canvas->MoveTo(0,220); Form1->Label9->Canvas->LineTo(350,220); Form1->Label9->Canvas->MoveTo(0,170); Form1->Label9->Canvas->LineTo(350,170); Form1->Label9->Canvas->MoveTo(0,120); Form1->Label9->Canvas->LineTo(350,120); Form1->Label9->Canvas->MoveTo(0,70); Form1->Label9->Canvas->LineTo(350,70); Form1->Label9->Canvas->MoveTo(0,20); Form1->Label9->Canvas->LineTo(350,20); x1=0; y1=30; } //---------------------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { Form1->Timer1->Enabled=true; } //---------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { float j; unsigned int t1,t2; unsigned long length=64; unsigned char value,i; unsigned char n,m; i=StrToInt(Edit1->GetTextLen()); if(i==0) ShowMessage("请输入数据"); else j=StrToFloat(Edit1->Text); if(j>50) value=Application->MessageBox(" 你设置的报警温度已经超过上限温度:50", "information", 5); switch(value) { case 4: Edit1->Text=InputBox("温度","请输入报警温度",""); break; case 2: break; } j=StrToFloat(Edit1->Text); baojing=j; t1=(int)(j); t2=(int)(j*10); m=(char) t1; //整数部分 n=(char)(t2%10); //小数部分 sent[0]=101; sent[1]=m; sent[2]=n; if(CH375WriteData(0,&sent,&length)) Form1->Shape1->Brush->Color=clTeal; //下传成功 else ShowMessage("无法设置报警温度"); } //---------------------------------------------------------------------- void __fastcall TForm1::Button5Click(TObject *Sender) { Form1->Timer1->Enabled=false; //打开定时器 } //---------------------------------------------------------------------- void __fastcall TForm1::Timer1Timer(TObject *Sender) { unsigned char a,b,c,i;// a为温度整数部分,b为温度小数部分 unsigned long len=64; float wendu; sent[0]=101; CH375WriteData(0,&sent,&len) ; if(CH375ReadData(0,&jieshou,&len)) { a=jieshou[0]; //a温度的整数部分 b=jieshou[1]; //b为小数部分 wendu=(float)(a+b*0.0625); Edit2->Text=FloatToStr(wendu); Form1->Shape2->Brush->Color=clRed; if(wendu>=baojing) Form1->Shape4->Brush->Color=clRed; else Form1->Shape4->Brush->Color=clWhite; if(c>0.5) a=a+1; //四舍五入; Form1->Label9->Canvas->Pen->Width=2; //画动态图象 Form1->Label9->Canvas->Pen->Color=clRed; Form1->Label9->Canvas->MoveTo(x1,270-y1*5); Form1->Label9->Canvas->LineTo(x1+10,270-a*5); y1=a; x1=x1+10; if(x1>350) { Form1->Label9->Refresh(); x1=0,y1=30; Form1->Label9->Canvas->Pen->Color=clBlack; //重新画坐标 Form1->Label9->Canvas->Pen->Width=6; Form1->Label9->Canvas->MoveTo(0,0); Form1->Label9->Canvas->LineTo(0,270); Form1->Label9->Canvas->MoveTo(0,270); Form1->Label9->Canvas->LineTo(350,270); Form1->Label9->Canvas->Pen->Width=1; Form1->Label9->Canvas->Pen->Style=psDashDotDot; Form1->Label9->Canvas->MoveTo(0,220); Form1->Label9->Canvas->LineTo(350,220); Form1->Label9->Canvas->MoveTo(0,170); Form1->Label9->Canvas->LineTo(350,170); Form1->Label9->Canvas->MoveTo(0,120); Form1->Label9->Canvas->LineTo(350,120); Form1->Label9->Canvas->MoveTo(0,70); Form1->Label9->Canvas->LineTo(350,70); Form1->Label9->Canvas->MoveTo(0,20); Form1->Label9->Canvas->LineTo(350,20);} } else {Form1->Timer1->Enabled=false; ShowMessage("接收不成功"); } } //----------------------------------------------------------------------
3上位机应用软件