论坛数据统计           欢迎到【弱电论坛】来学习和讨论问题!

弱电工程项目管理软件 报价之星软件--智能弱电系统专业软件
★电工电气产品供求、电气展会、人才、电气技术文章、图库、电气技术论坛等相关内容,请跳转至【电气工程网】--- 可直接使用本站会员名和密码登陆!(首次使用需要激活账户)
标王 热搜: 网络监控  楼宇对讲  弱电  机房及机柜内部的理线方法  ar800-hn  CAD  门禁  综合布线  工资  面板 
 
当前位置: 首页 » 技术文章 » 电工电子 » 单片机 » 正文

单片机的c语言教程---第八课:语句(4)-开关语句

放大字体  缩小字体 发布日期:2008-01-24  来源:互联网  作者:manage  浏览次数:2546
核心提示:我们学习了条件语句,用多个条件语句可以实现多方向条件分支,但是可以发现使用过多的条件语句实现多方向分支会使条件语句嵌套过多,程序冗长,这样读起来也很不好读。这时使用开关语句同样可以达到处理多分支选择的目的,又可以使程序结构清晰。它的语法为下:switch (
我们学习了条件语句,用多个条件语句可以实现多方向条件分支,但是可以发现使用过多的条件语句实现多方向分支会使条件语句嵌套过多,程序冗长,这样读起来也很不好读。这时使用开关语句同样可以达到处理多分支选择的目的,又可以使程序结构清晰。它的语法为下:

switch (表达式)
{
case 常量表达式1: 语句1; break;
case 常量表达式2: 语句2; break;
case 常量表达式3: 语句3; break;
case 常量表达式n: 语句n; break;
default: 语句
}

   运行中switch后面的表达式的值将会做为条件,与case后面的各个常量表达式的值相对比,如果相等时则执行后面的语句,再执行break(间断语句)语句,跳出switch语句。如果case没有和条件相等的值时就执行default后的语句。当要求没有符合的条件时不做任何处理,则可以不写default语句。
   在上面的课程中我们一直在用printf这个标准的C输出函数做字符的输出,使用它当然会很方便,但它的功能强大,所占用的存储空间自然也很大,要1K左右字节空间,如果再加上scanf输入函数就要达到2K左右的字节,这样的话如果要求用2K存储空间的芯片时就无法再使用这两个函数,例如AT89C2051。在这些小项目中,通常我们只是要求简单的字符输入输出,这里以笔者发表在《无线电杂志》的一个简单的串口应用实例为例,一来学习使用开关语句的使用,二来简单了解51芯片串口基本编程。这个实例是用PC串口通过上位机程序与由AT89C51组成的下位机相通讯,实现用PC软件控制AT89C51芯片的IO口,这样也就可以再通过相关电路实现对设备的控制(这里是控制继电器)。在笔者的网站http://www.cdle.net还可以查看相关文章。所使用的硬件还是用回我们以上课程中做好的硬件,以串口和PC连接,用LED查看实验的结果。下面是源代码。

/*----------------------------------------
CDLE-J20_Main.c

PC串口控制IO口电路
可以用字符控制和读取IO口
简单版本V2.0
更加好的单片机版本和PC控制软件和DLL动态库
请访问磁动力工作室http://www.cdle.net

Copyright 2003 http://www.cdle.net

All rights reserved.
明浩 E-mail: pnzwzw@163.com
pnzwzw@cdle.net
----------------------------------------*/

#include <AT89X51.h>

static unsigned char data CN[4];
static unsigned char data CT;
unsigned char TS[8] = {254,252,248,240,224,192,128,0};

void main(void)
{
void InitCom(unsigned char BaudRate);
void ComOutChar(unsigned char OutData);
void CSToOut(void);
void CNToOut(void);
unsigned int a;

CT = 0; //接收字符序列
CN[0] = 0;
CN[1] = 51;
CN[2] = 51;
CN[3] = 0;
InitCom(6); //设置波特率为9600 1-8波特率300-57600
EA = 1;
ES = 1; //开串口中断
do
{
for (a=0; a<30000; a++)
P3_6 = 1;
for (a=0; a<30000; a++) //指示灯闪动
P3_6 = 0;
}
while(1);
}


//串口初始化 晶振为11.0592M 方式1 波特率300-57600
void InitCom(unsigned char BaudRate)
{
unsigned char THTL;
switch (BaudRate)
{
case 1: THTL = 64; break; //波特率300
case 2: THTL = 160; break; //600
case 3: THTL = 208; break; //1200
case 4: THTL = 232; break; //2400
case 5: THTL = 244; break; //4800
case 6: THTL = 250; break; //9600
case 7: THTL = 253; break; //19200
case 8: THTL = 255; break; //57600
default: THTL = 208;
}
SCON = 0x50; //串口方式1,允许接收
TMOD = 0x20; //定时器1定时方式2
TCON = 0x40; //设定时器1开始计数
TH1 = THTL;
TL1 = THTL;
PCON = 0x80; //波特率加倍控制,SMOD位
RI = 0; //清收发标志
TI = 0;
TR1 = 1; //启动定时器
}

//向串口输出一个字符(非中断方式)
void ComOutChar(unsigned char OutData)
{
SBUF = OutData; //输出字符
while(!TI); //空语句判断字符是否发完
TI = 0; //清TI
}

//串口接收中断
void ComInINT(void) interrupt 4 using 1
{
if (RI) //判断是不是收完字符
{
if (CT>3)
{
CT = 0; //收完一组数据,序列指针清零
CN[0] = 0;
CN[1] = 51;
CN[2] = 51;
CN[3] = 0;
}
CN[CT] = SBUF;
CT++;
RI = 0; //RI清零
if (CN[0]==0x61 && CN[3]==0x61) //用aXXa的简单方式保证接收的可靠性,可以满足业余的要求
{ //a也可以为板下的ID号,在同一个串行口上可以挂上一块以上的板
CSToOut(); //收到的数据格式正确时,调用控制输出函数
} //要想更为可靠的工作则要用到数据检验和通讯协议
}
}

//根据全局变量输出相应的控制信号
void CSToOut(void)
{
unsigned char data a;
unsigned int data b;
switch(CN[1]) //aXXa的格式定义是第一个X为端口,0为P0,1为P1,2为P2,3为关闭所有(同时要第2个X为3,XX=33)
{ //XX=44为测试用,5为读取端口状态,大于5则为无效数据,
case 0: //第一个X小于3时,第二个X为要输出的数据。
P0 = CN[2];
CNToOut();
break;
case 1:
P1 = CN[2];
CNToOut();
break;
case 2:
P2 = CN[2];
CNToOut();
break;
case 3:
P0 = 0xFF;
P1 = 0xFF;
P2 = 0xFF;
CNToOut();
break;
case 4:
P0 = 0xFF;
P1 = 0xFF;
P2 = 0xFF;
for (a=0; a<8; a++)
{
P0 = TS[a];
for (b=0; b<50000; b++);
}
P0 = 0xFF;
for (a=0; a<8; a++)
{
P1 = TS[a];
for (b=0; b<50000; b++);
}
P1 = 0xFF;
for (a=0; a<4; a++)
{
P2 = TS[a];
for (b=0; b<50000; b++);
}
P2 = 0xFF;
CNToOut();
break;
case 5: //根据CN[2]返回所要读取的端口值
switch(CN[2])
{
case 0:
ComOutChar(CN[0]);
ComOutChar(CN[1]);
ComOutChar(P0);
ComOutChar(CN[3]);
break;
case 1:
ComOutChar(CN[0]);
ComOutChar(CN[1]);
ComOutChar(P1);
ComOutChar(CN[3]);
break;
case 2:
ComOutChar(CN[0]);
ComOutChar(CN[1]);
ComOutChar(P2);
ComOutChar(CN[3]);
break;
case 3:
ComOutChar(CN[0]);
ComOutChar(CN[1]);
ComOutChar(P3);
ComOutChar(CN[3]);
break;
}
break;
}
}

void CNToOut(void)
{
ComOutChar(CN[0]);
ComOutChar(CN[1]);
ComOutChar(CN[2]);
ComOutChar(CN[3]);
}

   代码中有多处使用开关语句的,使用它对不同的条件做不同的处理,如在CSToOut函数中根据CN[1]来选择输出到那个IO口,如CN[1]=0则把CN[2]的值送到P0,CN[1]=1则送到P1,这样的写法比起用if (CN[1]==0)这样的判断语句来的清晰明了。当然它们的效果没有太大的差别(在不考虑编译后的代码执行效率的情况下)。
在这段代码其主要的作用就是通过串口和上位机软件进行通讯,跟据上位机的命令字串,对指定的IO端口进行读写。InitCom函数,原型为void InitCom(unsigned char BaudRate),其作用为初始化串口。它的输入参数为一个字节,程序就是用这个参数做为开关语句的选择参数。如调用InitCom(6),函数就会把波特率设置为9600。当然这段代码只使用了一种波特率,可以用更高效率的语句去编写,这里就不多讨论了。
   看到这里,你也许会问函数中的SCON,TCON,TMOD,SCOM等是代表什么?它们是特殊功能寄存器,在以前也略提到过,51芯片的特殊功能寄存器说明可以参看附录二的'AT89C51特殊功能寄存器列表',在这里简单的说说串口相关的硬件设置。

SBUF 数据缓冲寄存器 这是一个可以直接寻址的串行口专用寄存器。有朋友这样问起过“为何在串行口收发中,都只是使用到同一个寄存器SBUF?而不是收发各用一个寄存器。”实际上SBUF包含了两个独立的寄存器,一个是发送寄存,另一个是接收寄存器,但它们都共同使用同一个寻址地址-99H。CPU在读SBUF时会指到接收寄存器,在写时会指到发送寄存器,而且接收寄存器是双缓冲寄存器,这样可以避免接收中断没有及时的被响应,数据没有被取走,下一帧数据已到来,而造成的数据重叠问题。发送器则不需要用到双缓冲,一般情况下我们在写发送程序时也不必用到发送中断去外理发送数据。操作SBUF寄存器的方法则很简单,只要把这个99H地址用关键字sfr定义为一个变量就可以对其进行读写操作了,sfr SBUF = 0x99;当然你也可以用其它的名称。通常在标准的reg51.h或at89x51.h等头文件中已对其做了定义,只要用#include引用就可以了。

    SCON 串行口控制寄存器 通常在芯片或设备中为了监视或控制接口状态,都会引用到接口控制寄存器。SCON就是51芯片的串行口控制寄存器。它的寻址地址是98H,是一个可以位寻址的寄存器,作用就是监视和控制51芯片串行口的工作状态。51芯片的串口可以工作在几个不同的工作模式下,其工作模式的设置就是使用SCON寄存器。它的各个位的具体定义如下:

MSB)

LSB)

SM0

SM1

SM2

REN

TB8

RB8

TI

RI

8-1 串行口控制寄存器SCON

    SM0、SM1为串行口工作模式设置位,这样两位可以对应进行四种模式的设置。看表8-2串行口工作模式设置。

SM0

SM1

模 式

功 能

波特率

0

0

0

同步移位寄存器

fosc/12

0

1

1

8位UART

可变

1

0

2

9位UART

fosc/32或fosc/64

1

1

3

9位UART

可变

8-2 串行口工作模式设置

    在这里只说明最常用的模式1,其它的模式也就一一略过,有兴趣的朋友可以找相关的硬件资料查看。表中的fosc代表振荡器的频率,也就是晶振的频率。UART为(Universal Asynchronous Receiver的英文缩写。

    SM2在模式2、模式3中为多处理机通信使能位。在模式0中要求该位为0。

    REM为允许接收位,REM置1时串口允许接收,置0时禁止接收。REM是由软件置位或清零。如果在一个电路中接收和发送引脚P3.0,P3.1都和上位机相连,在软件上有串口中断处理程序,当要求在处理某个子程序时不允许串口被上位机来的控制字符产生中断,那么可以在这个子程序的开始处加入REM=0来禁止接收,在子程序结束处加入REM=1再次打开串口接收。大家也可以用上面的实际源码加入REM=0来进行实验。

    TB8发送数据位8,在模式2和3是要发送的第9位。该位可以用软件根据需要置位或清除,通常这位在通信协议中做奇偶位,在多处理机通信中这一位则用于表示是地址还是数据帧。

    RB8接收数据位8,在模式2和3是已接收数据的第9位。该位可能是奇偶位,地址/数据标识位。在模式0中,RB8为保留位没有被使用。在模式1中,当SM2=0,RB8是已接收数据的停止位。

    TI发送中断标识位。在模式0,发送完第8位数据时,由硬件置位。其它模式中则是在发送停止位之初,由硬件置位。TI置位后,申请中断,CPU响应中断后,发送下一帧数据。在任何模式下,TI都必须由软件来清除,也就是说在数据写入到SBUF后,硬件发送数据,中断响应(如中断打开),这时TI=1,表明发送已完成,TI不会由硬件清除,所以这时必须用软件对其清零。

    RI接收中断标识位。在模式0,接收第8位结束时,由硬件置位。其它模式中则是在接收停止位的半中间,由硬件置位。RI=1,申请中断,要求CPU取走数据。但在模式1中,SM2=1时,当未收到有效的停止位,则不会对RI置位。同样RI也必须要靠软件清除。

    常用的串口模式1是传输10个位的,1位起始位为0,8位数据位,低位在先,1位停止位为1。它的波特率是可变的,其速率是取决于定时器1或定时器2的定时值(溢出速率)。AT89C51和AT89C2051等51系列芯片只有两个定时器,定时器0和定时器1,而定时器2是89C52系列芯片才有的。

    波特率 在使用串口做通讯时,一个很重要的参数就是波特率,只有上下位机的波特率一样时才可以进行正常通讯。波特率是指串行端口每秒内可以传输的波特位数。有一些初学的朋友认为波特率是指每秒传输的字节数,如标准9600会被误认为每秒种可以传送9600个字节,而实际上它是指每秒可以传送9600个二进位,而一个字节要8个二进位,如用串口模式1来传输那么加上起始位和停止位,每个数据字节就要占用10个二进位,9600波特率用模式1传输时,每秒传输的字节数是9600÷10=960字节。51芯片的串口工作模式0的波特率是固定的,为fosc/12,以一个12M的晶振来计算,那么它的波特率可以达到1M。模式2的波特率是固定在fosc/64或fosc/32,具体用那一种就取决于PCON寄存器中的SMOD位,如SMOD为0,波特率为focs/64,SMOD为1,波特率为focs/32。模式1和模式3的波特率是可变的,取决于定时器1或2(52芯片)的溢出速率。那么我们怎么去计算这两个模式的波特率设置时相关的寄存器的值呢?可以用以下的公式去计算。

波特率=(2SMOD÷32)×定时器1溢出速率

    上式中如设置了PCON寄存器中的SMOD位为1时就可以把波特率提升2倍。通常会使用定时器1工作在定时器工作模式2下,这时定时值中的TL1做为计数,TH1做为自动重装值 ,这个定时模式下,定时器溢出后,TH1的值会自动装载到TL1,再次开始计数,这样可以不用软件去干预,使得定时更准确。在这个定时模式2下定时器1溢出速率的计算公式如下:

            溢出速率=(计数速率)/(256-TH1)

    上式中的“计数速率”与所使用的晶体振荡器频率有关,在51芯片中定时器启动后会在每一个机器周期使定时寄存器TH的值增加,一个机器周期等于十二个振荡周期,所以可以得知51芯片的计数速率为晶体振荡器频率的1/12,一个12M的晶振用在51芯片上,那么51的计数速率就为1M。通常用11.0592M晶体是为了得到标准的无误差的波特率,那么为何呢?计算一下就知道了。如我们要得到9600的波特率,晶振为11.0592M和12M,定时器1为模式2,SMOD设为1,分别看看那所要求的TH1为何值。代入公式:

            11.0592M

            9600=(2÷32)×((11.0592M/12)/(256-TH1))

            TH1=250 //看看是不是和上面实例中的使用的数值一样?

            12M

            9600=(2÷32)×((12M/12)/(256-TH1))

            TH1≈249.49

           

    上面的计算可以看出使用12M晶体的时候计算出来的TH1不为整数,而TH1的值只能取整数,这样它就会有一定的误差存在不能产生精确的9600波特率。当然一定的误差是可以在使用中被接受的,就算使用11.0592M的晶体振荡器也会因晶体本身所存在的误差使波特率产生误差,但晶体本身的误差对波特率的影响是十分之小的,可以忽略不计。

    这一节借着学习开关语句的机会,简略说明了串行的一些相关内容,但串口的工作方式设定有好种同时也要涉及到其它的相关寄存器,内容十分多,在此也不能一一做实例说明,下面的章节也会加入一些硬件方面的东西。

 
 
[ 技术文章搜索 ]  [ 加入收藏 ]  [ 告诉好友 ]  [ 打印本文 ]  [ 关闭窗口 ]

 
0条 [查看全部]  相关评论

 
推荐图文
推荐技术文章
点击排行
 
 



弱电网、电气网合并

 
弱电基础 | 常用软件 | 信息发布规则 | 积分规则 | VIP会员注册 | 广告投放 | 弱电培训 | 联系我们 | 版权声明 | 帮助中心 | 网站地图 | 排名推广 | 苏ICP备14010056号 | RSS订阅
★本站手机app客户端已上线! 点击下载