这次我们来试着一步步的去掌握PC与单片机通过RS-232进行通讯和控制。
先说说我硬件的情况。我用的PC是个二手的IBM240小本本,十寸屏,赛扬400,机子很老了。但也有它的优点:1、串口,并口,PS鼠标口、USB口、PCM插槽全有。 调试硬件电路最好还是用真实串、并口好些,因为用USB转换的串、并口有时会出现兼容性上的问题,就会增加你调试上的复杂性。本文引用地址:/article/12/325233.htm
下图为本人的IBM 240及各种接口图:
下图是PC的大小对比图
单片机还是我一步步做出来的那个了,USB-ISP编程线也是我前面秀过,好!现在我放上PC与单片机连接图:
用本本的好处就是调整方便,接口、器件都在旁边,假若是用台式机,你还得钻到桌子底下去插拔那些接口,而现在本本却又没有串、并口了。
言归正传,单片机的RS-232串口通过9针串口线接到本本的串口上,单片机的ISP编程口通过USB-ISP编程器接到本本的USB口。
另外本本要接电源,单片机也要接5V电源,还有千万千要记得,本本是要插上鼠标才玩得转哦!
我将这个一步一步掌握串口的通讯与控制分为五步:
1、测试单片机与PC的串口连接是否正确好用。
2、用VB自己编写的程序替换掉串口调试器软件来接收单片机发送的数据。
3、掌握单片机端如何发送字符和数值数据。
4、掌握PC端程序如何接收发送字符和数值型数据。
5、做一个A/D转换(ADC0809)获取数据发送到PC,并在PC上显示实时趋势的例子。
我们要用到的四个软件:
1、USB接口编程软件:是PC机给单片机进行烧写编程用的
2、串口调试软件:用来测试单片机内串口电路、程序工作是否正常。
3、单片机程序的编程软件KEIL:用于编写单片机内的程序并生成HEX文件。
4、VB6.0:用于编写PC机上的应用程序
先进行第一步工作:
在前面一篇《板子上最一个部件——RS232串口》讲过如何对单片机上的串口进行调试,我们还是先对这个连接进行测试,我们首先得确认连接正确,电路正常,才能进行后面程序编写和调试工作。
依旧是那个最简单的程序,AT89S52从串口不停地发送“hello world!”
#include
#includevoid main(void)
{
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
TI=1;
TR1=1; //启动定时器
while(1)
{
printf("hello world!\n");
}
}
把原先生成好的这个HEX文件用上面讲到的Progisp软件写到AT89S52里。
然后打开sscom32串口调试器,设定好串口号、波特率和数据位,按下“打开串口”钮,应该就能收到那个一行行的“hello world!”了。能看到这一行行的文字,说明电路、连接和程序都正常了。
如果是乱码,则要将单片机上的复位钮按一下。如果还是乱码,那一般就是波特率不对,晶振应为11.0592MHZ或串口接触不良,线过长等原因。如果跟本就收不到任何字符,就说明电路或连接有故障,或者程序有问题。那就要好好查查了。
好!现在电路、连接、程序都可以正常工作。但在串口调试器接收框里看到接收到的一行行“hello world!”显示太快,不容易看出它一次一次发送的过程,不便于分析问题,我们得给它加点延时。
#include
#include
void delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
TI=1;
TR1=1; //启动定时器
while(1)
{
printf("hello world!\n"); //向串口送出数据
delay(); //调用延时
delay();
}
}
程序写好了,在KEIL里添加上延时语句后,重新生成HEX文件。再用Progisp将它写进AT89S52里,这时,你就可以看到串口调试器已经接收到大约一秒一次的“Hello world!”了。
在这里说明一下,只要像最上面我图上给出的那样把单片机和PC连接好后,无论你往AT89S52里烧写程序,还是单片机连接到PC并向PC串口发送数据,都不用再插拔器件了,只需要在这几个程序间切换工作便可以了。(是不是很方便呢
)
这样第一项工作就完成了,确认电路的连接及单片机程序都工作正常。
下面要进行第二项工作:
目的:用我自己的PC程序把串口调试器软件替换掉。因为我最终要的是接收单片机上的数据,并将接收的数据在PC上进行处理、存储,而串口调试器只能接收固定的内容,你也不无法把收到的数据接管过来,仅仅能做连接测试而已。
我用的是最简单易用的Visual Basic 6.0。具体如何操作运用,网上有很多教程,也很容易上手。
打开VB6.0,新建一个工程,也就是要建立一个新的程序。
这是个标准的VB6.0界面,我们要进行串口的操作需要添加一个串口控件MSCOMM32.OCX,或许你的机子上有,也许没有,没有的在网上搜了下一个装在你c:\winnt\system32\ 。然后你用鼠标右键点击VB界面左侧的工具箱,弹出菜单后选部件,或者在上部的主菜单上点“工程”--“部件”,就会弹出如下界面:
在列出的项里找到Microsoft Comm Control 6.0。在前面的小方框里点上钩。注意看下面的提示栏里就告诉你这个控件的文件名和所在的目录。点“确定”钮,这时在VB主界面的工具箱里就会多出个小电话的控件图标了:
接下来点击这个控件图标,然后在Form1的窗口上拉出个框(或者双击小电话图标)把图标放到Form1窗口上去。如下图:
如果你的图标放不上去并弹出如下提示框:
就说明你的VB6.0是简化版,不是正式安装的。解决方法如下:
首先把MSComm32.OCX拷进C:\WINNT\SYSTEM32\ (我的机器一开始并没有这个控件,我去网上下了一个,机器里面有此控件的此步不做!)(注:路径以我机器的winXP系统为例)
然后点击 开始>运行>regsvr32 c:\winnt\system32\mscomm32.ocx 成功后,开始>运行>regedit,进入注册表,找到HKEY_CLASSES_ROOT\Licenses,然后新建一个项,命名为:“4250E830-6AC2-11cf-8ADB-00AA00C00905”,值为:“kjljvjjjoquqmjjjvpqqkqmqykypoqjquoun”。一切就OK了。
VB6.0设置正常后,我由简入繁地进行程序的编写。在窗体上先放上一个串口控件,一个文本框,一个按钮,一个定时器。如下图:
串口控件是单片机串口和PC串口进行通讯的桥梁;文本框用来显示我们收到的数据,按钮用来启动这个接收,定时器用来定时检查每一小段时间检查是否有串口数据收到。
我们先对串口控件进行属性设置,Commport是串口号设置,一般设置为1,Settings是对串口的波特率、有无奇偶校验,数据位数,停止位数进行设置,因为我的单片机程序上用的波特率是19200,所以在这儿我只对波特率进行调整,其它都是都用默认值。如下图:
对按钮控键进行设置:只是将“Caption”标题属性改为“接收数据”。
对定时器进行设置,将“Interval”间歇时间改为400。这样就是每400毫秒检查一次有无数据收到。
对文本框进行设置:将“MultiLine”多行显示设为“True”允许。
控件的属性设置完了,下面我们为程序写代码,先双击“接收数据”按钮。会弹出代码窗口,我了如下代码,如图:
上面就是我们编写的按钮事件代码。写完后我们在键盘上按“Shift”+“F7”,回到对象窗口。再双击那个定时器控件,界面就切换到定时器的代码窗口,我们写程序如下图:
现在我们的这个VB程序就写好了。接着就试着运行一下这个程序,且慢!我们还是要先启动串口调试器看看单片机是否还在不断的发送着“hello world!”,确认它还是在不停地显示着那行“hello world!”,就可以关了串口调试器。然后在VB6.0的主界面点击那个小三角的播放钮。我的程序就运行如下了:
第二项任务完成!我自己接管了接收的数据。点菜单的保存这项工程。
第三项任务:掌握单片机端如何发送字符和数值数据。
接下来我要做的是对AT89S52内串行数据的发送进行解和掌握,以便我们能随心所欲地将单片机获得的数值数据或字符数据发送给PC机进行处理或存储。
先来看看原来我写的AT89S52不断发送“hello world!”的那段程序。
#include
#includevoid delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
TI=1;
TR1=1; //启动定时器
while(1)
{
printf("hello world!\n"); //向串口送出数据
delay(); //调用延时
delay();
}
}
上面的程序中除了设置串口的语句和延时语句外,负责向串口发送的语句只有一行,即“printf("helleo world!\n")”。学习过编程的一般都知道,print语句的作用是输出字符串的,但我们如果从单片机的A/D模块上获得了数据想发送到PC,应该怎么做呢?虽然你也可以先将这些数据转换成数字字符串,例如我们从一个8位的A/D模块上获得的数据是个数值从0-255的8位的数值,如果数值是1,那你得先将1这个数值转换成“1”字符对应的代码49(二进制110001,十六进制31H),再用printf语句发送出去。如果值是255,那你得先把它转换成3个字符“2”、“5”、“5”,再用printf发送出去。但这样既复杂又不规范,“1”是一个字符,“2”、“5”、“5”是三个字符,随着数值的不同,发送的数据的字节数据也不同,这样可不行。
我们还是先蹲蹲马步,了解一下单片机串口发送数据的实质:
上图是串口的发送时序示意图,最上面的TX表示的是单片机串口的发送线,,第二根CLK是内部时钟线,最下面的是发送标志信号TI。
我们以最常用的串行方式1,即10位异步通信方式来简单分析一下。它规定了1位起始位、8位数据位和1位停止位。其中第一位(起始位)和最后一位(停止位)是在你设定好串口的方式,打开串口后由芯片内串口模块自动插入的,不用人为加。
当你想通过串口发送数据时,只需要向AT89S52内的一个8位的特殊功能寄存器SBUF(99H)送入一个字节你想要发送给PC的数据,它就会自动连同起始位,数据位,停止位一起产生10位串行的电位信号送出。在第10个脉冲后将TX线的电位拉高,同时将标志位TI置1,告诉自己的程序发送结束。
接收方也是以规定好的相同的波特率时钟脉冲为基准,当某一个脉冲到来后检测到RX线上的电位被拉低,就知道对方开始发送数据了,然后从下一个脉冲起计数并在每个脉冲后检查RX线上的电位,若是高电位便记做1,低电位便记做0,如此得到8个位的数据,然后在第10个脉冲后,检测到RX线上的电位为1就知道这帧数据传送完毕。(注意:单片机的串行发送口(TX)和PC的接收口(RX)是通过串行线直接连接的,所以这两点的信号是相同的)
例如要发送“1”这个字符,代码是49(二进制110001,十六进制31H),串口发送时低位在前,如下图
归纳起来,若想发送数据只要向SBUF送一个字节的数据,然后等TI变为1后,就再发第二个字节依,此类推。
再说说字符和数值的关系,对于电路来说,它不知道什么是字符,什么是数值,只是按高低电位发送一帧帧的电信号。例如00000000代表0(00H),10101010代表170(AAH),11111111代表255(FF),但对于接收方PC就有不同了,大家都知道,电脑下载文本比下载一幅图像的数据量要小得多,原因就是文本只是用一个代码来代表一个将要显示的文字图像,而这个文字的图像数据就预先存在自己的电脑里,就是所说的字库。而你下载一幅图像,则需要每一个阵点的数据都得传送,所以数据量很大。西文也是一样的,也是用代码来代表一个需要显示的西文字符图像,这就是ACSII码。这样用一个字节的数(0-255)的范围就能代表所有的西文字符和常用符号了,例如用数值65(十六进制为41H)代表“A”。用数值49(十六进制为31H)代表“1”,我只要向SBUF里输入值65,PC只要以字符方式接收,就会显示“A”字。如果以数值方式接收,变量的值就是65。这里面也包括有一些非字符的功能控制符号。例如13代表回车,10代表换行。
下面我们就来试试改写一下发送程序:试着用送数值和送字符两种方式发送。同样是“A”“B”“C”“D”四个字符。
#include
#includevoid delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void) //主程序
{
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
TI=1;
TR1=1; //启动定时器
while(1)
{
SBUF=65; //向SBUF内写入65的数值,也就是字符“A”的代码
while(TI==0); //检测TI,当TI=0时,说明还没发送完,就循环等待。
TI=0; //当TI=1时,就把TI的值置0,以便下一组发送。
SBUF=66; //向SBUF送数值66.即字符“B”的代码
while(TI==0);
TI=0;
SBUF=C; //向SBUF送字符“C”
while(TI==0);
TI=0;
SBUF=D"; //向SBUF送字符“D”
while(TI==0);
TI=0;
delay(); //调用延时
delay();
}
}
将程序编译生成HEX文件后写入AT89S52。打开我上面用VB6编好那个程序,点击“接收数据”钮。如下图:
它的确按我预想的执行了。只要是节字数据,无论是数值还是字符代友都是可以进行发送的。这是C语言的优点。
另外我们顺便看一下原先用printf函数发送生成HEX和直接写SBUF来成HEX的差别:
上图的提示显示了生成的代码共用了1120个字节。这是用printf函数发送的。
下图是直接写SBUF后的编译提示信息:
哈!直接操作串口缓冲寄存器只用了89个字节。这是直接进行底层操作的优势。
上面的程序发送“A”“B”“C”“D”四个数据,因为没有发送回车符,所以一次次的字符都是连续显示的。我们再修改一下,把要发送的ABCD这四个数据再加两个代表回车的控制字符数据定义到一个字节数组中变量中,再改用循环的方式来发送,程序如下:
#include
#includevoid delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void)
{
unsigned char buf[ ]={65,66,C,D,13,10}; //定义一个单字节数组最后两个数值13和10是回车符。
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
//TI=1;
TR1=1; //启动定时器
while(1)
{
for(i=0;i<6;i++)
{
SBUF=buf[i]; //向串口送出数据
while(TI==0);
TI=0;
}
delay(); //调用延时
delay();
}
}
下面是运行结果:
这样就和原先printf函数输出的效果一样了。
从上面程序我们知道了,如果我们想发送测量的数值数据,可以把用A/D模块获得的测量数据赋给buf[ ]数组里的变量,然后就可以进行发送处理了。字符直接用上面的发送方法就行了。
这样第三项工作也完成!(啊!累了,要歇歇,歇歇!
)
第四步:掌握PC端程序如何接收发送字符和数值型数据。
接下来我们来看看PC端的程序如何正常接收并处理收到的数据。字符没有问题,因为刚才就是显示的字符,但我主要是想看看采用字符方式接收对于0-255范围内那些非字符数值能否正常接收和处理。
打开VB6,调出我们原先编写的程序,在串口控件的属性中有个InputMode属性,如果是0就是以字符方式接收,如果是1就是以二进制方式接收。
原先我们就是用了缺省的字符方式。为了便于分析收到的数据,我们得分别修改一下单片机和PC里的程序,首先不能让单片机不停的发送,而是从PC机先向AT89S52发送一个字符“s”,AT89S52收到并确认是“s”字符后再发送一组数据,发完后停下,等待PC的下次请求。这样我们可以准确和稳定地看到这组数据的情况。
VB程序的修改如下:
我们在Command1+_Click事件的代码里添加了一行MSComm1.Output="s",也就是每当我按下“接收数据”钮时向AT89S52发送了一个"s"字符,然后清空文本框内容,然后启动定时器子程序Timer1每100毫秒检查一次有无收到数据,收到数据便显示出来。
对AT89S52内的程序做修改,程序循环检查有无收到数据,当的RI=1时便有数据收到,确认收到的数据为字符“s”时,便送出数组内数据。修改程序如下:
#include
void delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=65535;i>0;i--);
}
void main(void)
{
unsigned char buf[]={65,66,C,D,E,F};//定义一个单字节数组
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
//TI=1;
TR1=1; //启动定时器
while(1)
{ if(RI==1) //如果接收到数据则进入以下操作
{if(SBUF==s) //如果收到的数据为“s”字符则进入发送操作
{
for(i=0;i<6;i++)//循环发送出数组buf[ ]的6个数据
{
SBUF=buf[i]; //向串口送出数据
while(TI==0);
TI=0;
delay(); //调用延时子程序
}
RI=0;//上面的发送操作完毕后将标志RI清0等待PC下次请求
}
}
}
}
编译、生成HEX文件然后将文件写进AT89S52。我们运行VB6编写的Scom1程序。
现在我点击“接收数据”钮后,文本框内一个字符一个字符的依次显示出“ABCDEF”如下图:
当我再次点击“接收数据”钮时,文本框先清空,然后再重复上面的显示。达到我们的要求。PC机每请求一次,单片机就发送一次数据。
接下来我要做的是让文本框里不再显示“ABCDEF”这几个字符,而是要显示AT89S52发过来的数值,例如我发送65的值,文本框里就显示65,我发送255,文本框就显示255,这样我就能测试出用字符接收的方法能否将一个字节的值(0-255)都能正确接收和显示。我们把那句Text1.Text = Text1.Text + MSComm1.Input 。改成Text1.Text = Text1.Text + str(asc(MSComm1.Input )),就是把原先字符串变量MSComm1.Input先转换成ASCII值,再把这个值显示出来。改完了。我们运行下试试。
上图显示的确可以正确显示出数值,但这仅仅只是有相对应字符几个代码数值,我再来试试,非字符的ASCII值,看是否都能正确收到并显示。先修改AT89S52里我们原先发送的那些数组buf[ ]里的值,让它们部分不在字符范围里,看能不能正确接收到显示。我们将数组unsigned char buf[]={65,66,C,D,E,F};改写为既有字符也有非字符范围的数组unsigned char buf[]={0,1,2,D,254,255};
从上图来看,后两个数据显示不对,254没有显示出来,255显示为63,经过修改AT89S52程序,把0-255的数值都发送一遍,发现大于128的数值几乎都不能正确接收。所以,得出结论:以字符方式接收数值数据是不可行的!
如下图:
接下来我改变串口控件的InputMode属性,将它的值改为1,即用二进制读取来试试。但二进制方式怎么读取呢,看了很多资料,也试了很多次,终于弄明白了,原来先要定义一个字节型的可变数组,这样当接收到数据时把接收到的一个或多个数值的首地址变量Mscomm1.input赋给这个字节数组名。于是你就可以运用这个字节数组里的变量了。
我先在VB里修改串口控件的InputMode属性,如下图:
然后我要在VB程序里先定义一个单字节Byte类型的数组,将收到的数据变量(Mscomm1.Input)赋给inbuff这个数组名,程序修改如下图:
AT89S52里程序,基本不动,只是等待PC机发来请求字符“s”。收到请求后,发送0-255的全部数值。
修改的程序如下:
#include
void delay(void) //定义一个延时子程序
{
unsigned int i;
for (i=15535;i>0;i--);
}
void main(void)
{
unsigned char buf[]={3,4,5,D,255,253};//定义一个单字节数组
unsigned char i;
SCON=0x50; //串口方式1
TMOD=0x20; //定时器1,定时方式为2
PCON=0x80; //设定串口工作方式为1
TCON=0x40; //设定时器1开始计数
TH1=0xfd; //设定波特率为19200
TL1=0xfd; //
//TI=1;
TR1=1; //启动定时器
while(1)
{ if(RI==1) //如果接收到数据则进入以下操作
{if(SBUF==s) //如果收到的数据为“s”字符则进入发送操作
{for(i=0;i<255;i++)//循环发送出数,这里做为调试,我们先发0-255的数值。
{
SBUF=i; //向串口送出数据,这里不发buf [ ]数组的数据,而是直接发送循环体里的i 值(0-254)。
while(TI==0);
TI=0;
delay(); //调用延时
}
SBUF=255; //上面循环里没有包括255这个值,这里补发送一次
while(TI==0);
TI=0;
RI=0;//发送完毕将收到请求标志清0等待PC下次请求
}
}
}
}
显示结果如下:
这样,所有的数值都是可以正确接收并显示了。
结论:要接收数值数据,串口控件必须修改为二进制的接收属性。即:InputMode=1