新闻中心

EEPW首页>嵌入式系统>设计应用> 51系列单片机学习5—C编程程序语句

51系列单片机学习5—C编程程序语句

作者: 时间:2016-11-25 来源:网络 收藏
曾经在BBS上有朋友问过我{}是什么意思?什么作用?在 C 中是有不少的括号,如{},[],()等,确实会让一些初入门的朋友不解。在 VB 等一些语言中同一个()号会有不一样的作用,它能用于组合若干条语句形成功能块,能用做数组的下标等,而在 C 中括号的分工较为明显,{}号是用于将若干条语句组合在一起形成一种功能块,这种由若干条语句组合而成的语句就叫复合语句。复合语句之间用{}分隔,而它内部的各条语句还是需要以分号“;” 结束。复合语句是允许嵌套的,也是就是在{}中的{}也是复合语句。复合语句在程序运行时,{}中的各行单语句是依次顺序执行的。单片机C语言中能将复合语句视为一条单语句,也就是说在语法上等同于一条单语句。对于一个函数而言,函数体就是一个复合语句,也许大家会因此知道复合语句中不单能用可执行语句组成,还能用变量定义语句组成。要注意的是在复合语句中所定义的变量,称为局部变量,所谓局部变量就是指它的有效范围只在复合语句中,而函数也算是复合语句,所以函数内定义的变量有效范围也只在函数内部。下面用一段简单的例子简单说明复合语句和局部变量的使用。

  #include

本文引用地址://m.amcfsurvey.com/article/201611/321140.htm

  #include

  void main(void)

  {

   unsigned int a,b,c,d; //这个定义会在整个 main 函数中?

   SCON = 0x50; //串行口方式 1,允许接收 TMOD = 0x20; //定时器 1 定时方式 2

   TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;

TI = 1; TR1 = 1; //启动定时器

   a = 5; b = 6; c = 7; d = 8; //这会在整个函数有效

   printf("0: %d,%d,%d,%d",a,b,c,d);

 { //复合语句 1

   unsigned int a,e; //只在复合语句 1 中有效

a = 10,e = 100;

   printf("1: %d,%d,%d,%d,%d",a,b,c,d,e);

   { //复合语句 2

   unsigned int b,f; //只在复合语句 2 中有效

   b = 11,f = 200;

  printf("2: %d,%d,%d,%d,%d,%d",a,b,c,d,e,f);

   }//复合语句 2 结束

   printf("1: %d,%d,%d,%d,%d",a,b,c,d,e);

 }//复合语句 1 结束

   printf("0: %d,%d,%d,%d",a,b,c,d);

   while(1);

}

  运行结果:

  0:5,6,7,8

  1: 10,6,7,8,100

  2: 10,11,7,8,100,200

  1: 10,6,7,8,100

  0:5,6,7,8 结合以上的说明想想为何结果会是这样。

  读完前面的文章大家都会大概对条件语句这个概念有所认识吧?是的,就如学习语文中 的条件语句一样,C 语言也一样是“如果 XX 就 XX”或是“如果 XX 就 XX 不然 XX”。也就是当条件符合时就执行语句。条件语句又被称为分支语句,也有人会称为判断语句,其关键字 是由 if 构成,这大众多的高级语言中都是基本相同的。C 语言供给了 3 种形式的条件语句:

  1: if (条件表达式) 语句 当条件表达式的结果为真时,就执行语句,不然就跳过。 如 if (a==b) a++; 当 a 等于 b 时,a 就加 1

  2: if (条件表达式) 语句 1

  else 语句 2

  当条件表达式成立时,就执行语句 1,不然就执行语句 2 如 if (a==b)

  a++;

  else

  a--;

  当 a 等于 b 时,a 加 1,不然 a-1。

  3:if (条件表达式 1) 语句 1

  else if (条件表达式 2) 语句 2

  else if (条件表达式 3) 语句 3

  else if (条件表达式 m) 语句 n else 语句 m

  这是由 if else 语句组成的嵌套,用来实现多方向条件分支,使用应注意 if 和 else 的配对使用,要是少了一个就会语法出错,记住 else 总是与最临近的 if 相配对。一般条件语句只会用作单一条件或少数量的分支,如果多数量的分支时则更多的会用到下一篇中的开 关语句。如果使用条件语句来编写超过 3 个以上的分支程序的话,会使程序变得不是那么清晰易读。

学习了条件语句,用多个条件语句能实现多方向条件分支,但是能发现使用过多的条件语句实现多方向分支会使条件语句嵌套过多,程序冗长,这样读起来也很不好读。这个时候使用开关语句同样能达到处理多分支选择的目的,又能使程序结构清晰。它的语法为下:

  switch (表达式)

  {

   case 常量表达式 1: 语句 1; break;

case 常量表达式 2: 语句 2; break;

case 常量表达式 3: 语句 3; break;

case 常量表达式 n: 语句 n; break;

default: 语句

  }

  运行中 switch 后面的表达式的值将会做为条件,与 case 后面的各个常量表达式的值相 对比,如果相等时则执行 case 后面的语句,再执行 break(间断语句)语句,跳出 switch 语句。如果 case 后没有和条件相等的值时就执行 default 后的语句。当要求没有符合的条 件时不做任何处理,则能不写 default 语句。

  在上面的章节中我们一直在用 printf 这个标准的 C 输出函数做字符的输出,使用它当然会很方便,但它的功能强大,所占用的存储空间自然也很大,要 1K 左右字节空间,如果 再加上 scanf 输入函数就要达到 2K 左右的字节,这样的话如果要求用 2K 存储空间的芯片时 就无法再使用这两个函数,例如 AT89C2051。在这些小项目中,通常我们只是要求简单的字符输入输出,这里以笔者发表在本人网站的一个简单的串行口应用实例为例,一来学习使用开 关语句的使用,二来简单了解 51 芯片串行口基本编程。这个实例是用 PC 串行口通过上位机程序 与由 AT89c51 组成的下位机相通信,实现用 PC 软件控制 AT89c51 芯片的 IO 口,这样也就可 以再通过相关电路实现对设备的控制。为了方便实验,在此所使用的硬件还是用回以上课程中做好的硬件,以串行口和 PC 连接,用 LED 查看实验的结果。原代码请到在笔者的网站 下载,上面有 单片机c语言 下位机源码、PC 上位机源码、电路图等资料。

  代码中有多处使用开关语句的,使用它对不一样的条件做不一样的处理,如在 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 等是代表什么?它们是特殊 功能寄存器。

  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 串行口工作模式设置。

  SM0SM1模 式功能波特率

  000同步移位寄存器fosc/12

  0118 位 UART可变

  1029 位 UARTfosc/32 或 fosc/64

  1139 位 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 的晶体振荡器也会因晶体本身所存在的误差使波特率产生误差,但晶体本身的误差对波特率的影响是十分之小的,能忽略不计。

==================================================================================================

循环语句是几乎每个程序都会用到的,它的作用就是用来实现需要反复进行多次的操 作。如一个 12M 的 51 芯片应用电路中要求实现 1 毫秒的延时,那么就要执行 1000 次空语句才能达到延时的目的(当然能使用定时器来做,这里就不讨论),如果是写 1000 条空语句那是多么麻烦的事情,再者就是要占用很多的存储空间。我们能知道这 1000 条空语句, 无非就是一条空语句重复执行 1000 次,因此我们就能用循环语句去写,这样不但使程序结构清晰明了,而且使其编译的效率大大的提高。在 C 语言中构成循环控制的语句有while,do-while,for 和 goto 语句。同样都是起到循环作用,但具体的作用和使用方法又大不一 样。我们具体来看看。

goto 语句

这个语句在很多高级语言中都会有,记得小时候用 BASIC 时就很喜欢用这个语句。它是 一个无条件的转向语句,只要执行到这个语句,程序指针就会跳转到 goto 后的标号所在的程序段。它的语法如下:

goto 语句标号; 其中的语句标号为一个带冒号的标识符。示例如下

void main(void)

{

unsigned char a;

start: a++;

if (a==10) goto end;

goto start;

end:;

}

上面一段程序只是说明一下 goto 的使用方法,实际编写很少使用这样的手法。这段程序的意思是在程序开始处用标识符“start:”标识,表示程序这是程序的开始,“end:”标识程序的结束,标识符的定义应遵循前面所讲的标识符定义原则,不能用 C 的关键字也不能和其它变 量和函数名相同,不然就会出错了。程序执行 a++,a 的值加 1,当 a 等于 10 时程序会跳到 end 标识处结束程序,不然跳回到 start 标识处继续 a++,直到 a 等于 10。上面的示例说明 goto 不但能无条件的转向,而且能和 if 语句构成一个循环结构,这些在 C 程序员的程序中都不太常见,常见的 goto 语句使用方法是用它来跳出多重循环,不过它只能从内层循环跳到外层循环,不能从外层循环跳到内层循环。在下面说到 for 循环语句时再略为提一提。 为何大多数 C 程序员都不喜欢用 goto 语句?那是因为过多的使用它时会程序结构不清晰,过多的跳转就使程序又回到了汇编的编程风格,使程序失去了 C 的模块化的优点。

while 语句

while 语句的意思很不难理解,在英语中它的意思是“当…的时候…”,在这里我们可以理解为“当条件为真的时候就执行后面的语句”,它的语法如下:

while (条件表达式) 语句;

使用 while 语句时要注意当条件表达式为真时,它才执行后面的语句,执行完后再次回到 while 执行条件判断,为真时重复执行语句,为假时退出循环体。当条件一开始就为假时, 那么 while 后面的循环体(语句或复合语句)将一次都不执行就退出循环。在调试程序时要注意 while 的判断条件不能为假而造成的死循环,调试时适当的在 while 处加入断点,也许会使你的调试工作更加顺利。当然有时会使用到死循环来等待中断或 IO 信号等,如在第一 篇时我们就用了 while(1)来不停的输出“Hello World!”。下面的例子是显示从 1 到 10 的累 加和,读者能修改一下 while 中的条件看看结果会如果,从而体会一下 while 的使用方法。


上一页 1 2 3 下一页

评论


技术专区

关闭