首页> 单片机教学(C言语教程)  
第八课 语 句(4)-开关语句

   咱们进修了前提语句,用多个前提语句能够完成多标的目的前提分支,可是能够发觉使用过多的前提语句完成多标的目的分支会使前提语句嵌套过多,法式冗长,多么读起来也很不好读。这时使用开关语句同样能够达四处置多分支选择的方针,又能够使法式布局清晰。它的语法为下:

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的晶体振荡器也会因晶体本身所具有的误差使波特率发生误差,但晶体本身的误差对波特率的影响是很是之小的,能够忽略不计。

    这一节借着进修开关语句的机缘,简单申了然串行的一些相关内容,但串口的工作编制设定有好种同时也要涉及到其它的相关寄放器,内容很是多,在此也不能逐个做实例申明,下面的章节也会插手一些硬件方面的东西。

示例法式下载

联系地址:浙江省杭州市西湖科技园西园七路3号4层 邮政编码:310011 Email:hificat@163.com xu169@sina.com
德律:0571-87615070 传真:0571-87615070 手机:13185018567 QQ:420951892 MSN:hificat@hotmail.com
杭州电子&算计机工作室 版权所有 COPYRIGHT2003——2007 HangZhou Electron&Computer Studio. All rights reserved