独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
收起左侧

[原创] 从业十年,教你单片机入门基础。(连载)

[复制链接]
 楼主| 发表于 2015-11-9 00:27:49 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-11-9 00:31 编辑

第四十五节:二维数组。
       上一节讲一维数组时漏讲了一个知识点,一维数组在定义时,如果预先给它填写若干个初始化的数据,也可以省略中括号[N]里面的元素个数N,这样编译器在编译时会根据初始化的个数来自动识别和定义此一维数组实际元素个数。比如:
unsigned char  y[3]={10,11,12};
        跟
unsigned char  y[]={10,11,12};
       的意义是一样的。注意,省略元素个数时必须要有初始化的数据,否则编译器不知道此数组的长度可能导致编译出错。
       继续回到本节的内容,二维数组。一维数组只有一个下标,像由很多点连成的一条直线,而二维数组有两个下标,布下了一行行的点像一张矩形的网,它的两个下标分别代表了行和列,行和列又像我们所学的y轴和x轴坐标,通过y轴和x轴坐标就可以找到所需的点,也就是数组的某个元素。
       上述是对二维数组的感性描述,二维数组是由一维数组发展而来,所以具备了很多一维数组的特点。二维数组的所有”网点”元素的地址都是挨个相临的,先第0行,再第1行,再第2行…再第N行,上一行尾元素跟下一行头元素的地址是相临连续的。
        二维数组未带初始化时的通用定义格式如下:
类型 数组名[行数Y][列数X];
比如:
unsigned char  a[2][3]; //此处的2代表有2行,3代表有3列。
分析:此二维数组定义了6个变量,跟一维数组一样,下标都是从0开始,到(N-1)时结束,此处的N代表行数或者列数。所以a[2][3]数组的元素挨个分别是a[0][0],a[0][1], a[0][2], a[1][0], a[1][1], a[1][2]这6个变量。
         二维数组有两种常用初始化格式,一种是逐行初始化,一种是整体初始化。
         第一种逐行初始化:
unsigned char   a[2][3]=
{
   {0,1,2},
   {3,4,5}
};
        在逐行初始化定义二维数组时,只要有初始化的数据,也可以省略行下标,但是列下标不能省略,比如:
unsigned char   a[][3]=
{
   {0,1,2},
   {3,4,5}
};
此时编译器会根据元素的个数来确定行数是多少。

        第二种整体初始化,跟一维数组一样,内部数据元素不需要额外增加大括号来分行。
unsigned char   a[2][3]=
{
   0,1,2,3,4,5
};
或者
unsigned char   a[2][3]=
{
   0,1,2,
   3,4,5
};
都行。
       C语言是很自由很丰富的语言,比如二维数组还允许不完全初始化的一些情况,我就不再深入讲解,我讲解的都是挑选一些针对以后单片机项目中可能会经常用到的语法。
       二维数组我在很多项目上还是经常用到的,比如用在一些需要把所得的信息进行查表判断的项目,在每一行里放一条关键词字符串信息,利用循环语句进行逐行查找匹配。至于二维数组如何存放字符串的知识点以后再讲。这节的重点是让大家对二维数组有个初步的认识。
       现在编写一个程序来熟悉一下二维数组的书写和使用格式。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         

  5.    unsigned char  a[2][3]=  //定义和初始化一个二维数组
  6.    {
  7.       {0,1,2},
  8.       {3,4,5}
  9.    };

  10.         
  11.   GuiWdData0=a[0][0];   //把a[0][0]这个元素变量放到窗口变量0里面显示
  12.   GuiWdData1=a[0][1];   //把a[0][1]这个元素变量放到窗口变量1里面显示
  13.   GuiWdData2=a[0][2];   //把a[0][2]这个元素变量放到窗口变量2里面显示
  14.   GuiWdData3=a[1][0];   //把a[1][0]这个元素变量放到窗口变量3里面显示
  15.   GuiWdData4=a[1][1];   //把a[1][1]这个元素变量放到窗口变量4里面显示
  16.   GuiWdData5=a[1][2];   //把a[1][2]这个元素变量放到窗口变量5里面显示
  17.         
  18. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  19.    while(1)  
  20.    {
  21.       initial();
  22.       key_service();
  23.       display_service();
  24.    }

  25. }
复制代码


    查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
    变量元素a[0][0]为0。
    变量元素a[0][1]为1。
    变量元素a[0][2]为2。
    变量元素a[1][0]为3。
    变量元素a[1][1]为4。
    变量元素a[1][2]为5。
    下节预告:while循环语句。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-11-14 02:22:00 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-11-14 02:25 编辑

第四十六节:while循环语句。

       46.1   程序的跑道
       “程序跑起来了吗?”同行交流经常用的一句话。程序在哪里跑?有跑道吗?有的。循环语句就像一条椭圆的跑道,程序在跑道上不停的跑,永无止境,一秒几百万圈的速度。单片机main主函数内往往有一条while(1)语句,这就是单片机的“循环跑道”,称之为主循环,主循环内还可以继续嵌套多层while循环语句。

       46.2   while循环的常见格式
while(条件)
{
    语句1;   
    语句2;
     ……   
    语句N;
}

       分析:
(1)先判断条件是否为真。如果为假,就不执行花括号循环体内的语句1至语句N。如果为真,才执行花括号循环体内的语句1至语句N,当执行完花括号循环体内最后的“语句N“时,单片机继续返回while(条件)处,继续判断条件是否为真,如果为假就跳过花括号循环体,如果为真就继续从“语句1“执行到“语句N“,然后再返回while(条件)处,依次循环下去,直到条件为假时才罢休,否则一直循环下去。
(2)while(条件)语句中,条件判断真假的规则跟if语句一模一样,有两种类型:一种是纯常量或者变量类型的,只要此数值不等于0就认为是真,所以while(1)也称死循环语句,因为里面的条件永远不为0,如果不遇到break,return,goto这些语句,那么就永远也别想跳出这个循环;另外一种是关系判断,以及关系语句之间的“与或”判断,跟if语句规则一样,不多讲。break,return,goto这些语句后面章节会讲到。

       46.3   while省略花括号,后面第一条语句前没带分号
while(条件)
         语句1;   
        语句2;
        ……   
        语句N;

        分析:
        跟if语句一样,此时循环体默认只包含“语句1”,相当于:  
while(条件)
{
         语句1;   
}
        语句2;
        ……   
         语句N;


         46.4   while省略花括号,后面第一条语句前带分号
        while(条件);
         语句1;   
        语句2;
        ……   
        语句N;

        分析:
        注意,此时循环体默认不包含“语句1”,而是相当于:   
while(条件)
          ;   //一个分号代表一条空语句,语法上要求加上分号
         语句1;
         语句2;
         ……   
         语句N;
         此时如果条件一直为真,单片机就一直在空循环耗着,执行不到语句1的语句。直到条件为假才罢休。


          46.5   编写程序
          编写一个程序来熟悉一下while语句的书写和使用格式。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:

  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化

  7.   unsigned char i;  //控制循环体的条件判断变量


  8.   i=3;   
  9.   while(i)  //i不断变为0时才跳出此循环体
  10.   {
  11.      a=a+1;
  12.      i=i-1; //循环的条件不断发生变化,不断减小
  13.   }


  14.   i=0;
  15.   while(i<3)  //i大于等于3时才跳出此循环体
  16.   {
  17.      b=b+2;
  18.      i=i+1;   //循环的条件不断发生变化,不断增加
  19.   }

  20.      
  21.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  22.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示

  23.         
  24. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  25.    while(1)  
  26.    {
  27.       initial();
  28.       key_service();
  29.       display_service();
  30.    }

  31. }
复制代码


           查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
           变量a为3。
            变量b为6。


            下节预告:while循环语句的嵌套与break语句。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-11-22 08:40:42 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-11-22 08:45 编辑

第四十七节:循环语句do while和for。
       47.1   do while语句的常见格式
do{
       语句1;   
       语句2;
       ……   
       语句N;
} while(条件);
      分析:
      单片机从上往下执行语句,先从do那里无条件进来,从语句1开始往下执行,一直执行到语句N,然后开始判断while(条件)的条件是否为真,如果为真继续返回到do的入口处,继续从语句1开始往下执行,依次循环。大家留意到了吗,do while和while语句有什么差别?差别是,dowhile是先无条件进来执行一次循环体(花括号里所有的程序代码),执行到循环体最底部才判断while(条件)的条件是否为真来决定是否继续循环,先上车再买票。而while语句是先判断条件是否为真再决定是否需要进入循环体,先买票再上车。


      47.2   for语句的简介
      for语句也是循环语句,任何for语句实现的功能都可以用while语句来编程实现同样的功能,那for语句和while语句有什么差别呢?for语句把变量初始化,变量的条件判断,变量在执行循环体后的步进变化这三个常见要素集成在语句内部,以标准的格式书写出来。在很多场合下,for在书写和表达方面比while语句显得更加简洁和直观。


      47.3   for语句的自加格式。
for(变量的初始化语句; 变量的条件判断;变量在执行一次循环体后自加的步进变化)
{
       语句1;
       语句2;
       ……
       语句N;
}
在把上述变成具体一点如下:
for(i=0; i<3;i++)
{
      语句1;
      语句2;
      ……
      语句N;
}
       分析:
       单片机从上往下,在进入循环体前,先把变量i初始化赋值0,然后判断i是否小于3这个条件,如果此条件为真,就开始从语句1往下执行到语句N,执行完一次循环体后,i因为“i++”语句就自加1,此时i从原来初始化的0变成了1,接着再返回来到for语句的第二句条件判断”i<3”那里,判断i是否继续小于3这个条件,如果此条件为真就继续往下执行,否则就跳过循环体。因此上述for语句的功能如果用while语句来写,相当于以下代码:

i=0;  //进入循环体之前先初始化给予初值
while(i<3)
{
     语句1;
     语句2;
     ……
     语句N;
     i++;   //执行一次循环体之后此变量自加发生变化
}
上述的循环语句只执行了3次循环体。


      47.4   for语句的自减格式
       刚才讲的for(i=0; i<3;i++)格式,它的变量i是不断自加的,还有一种比较常见的格式是i不断自减的。如下:
for(i=3; i>0;i--)
{
     语句1;
     语句2;
     ……
     语句N;
}
      上述自减的for语句功能如果用while语句来写,相当于以下代码:

i=3;  //进入循环体之前先初始化给予初值
while(i>0)
{
     语句1;
     语句2;
     ……
     语句N;
     i--;   //执行一次循环体之后此变量自减发生变化
}
     上述的循环语句也是只执行了3次循环体。

     47.5   for省略花括号,后面第一条语句前没带分号
for(i=0; i<3;i++)
         语句1;  
         语句2;
         ……   
         语句N;

       分析:
       跟if语句一样,此时循环体默认只包含“语句1”,相当于:  
for(i=0; i<3;i++)
{
         语句1;  
}
         语句2;
         ……   
          语句N;

       47.6   for省略花括号,后面第一条语句前带分号
for(i=0; i<3;i++);
         语句1;  
         语句2;
         ……   
         语句N;

    分析:
    注意,此时循环体默认不包含“语句1”,而是相当于:   
for(i=0; i<3;i++)
           ;  //一个分号代表一条空语句,语法上要求加上分号
       语句1;
       语句2;
       ……   
       语句N;
       此时循环体内先循环执行三次空语句,然后才会结束for循环,接着从语句1开始往下执行。

       47.7   编写程序
       编写一个程序来熟悉一下do while和for语句的使用。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化


  8.   unsigned char i;  //控制循环体的条件判断变量

  9.   i=3;   
  10.   do
  11.   {
  12.      a=a+1;  //每执行一次循环体a就增加1,此行代码被循环执行了3次
  13.          i=i-1;  //i不断变小
  14.   }while(i); //i不断变小,当i变为0时才跳出此循环体



  15.   for(i=0;i<3;i++)
  16.   {
  17.      b=b+2;  //此行代码被循环执行了3次
  18.   }


  19.   for(i=3;i>0;i--)
  20.   {
  21.      c=c+3;  //此行代码被循环执行了3次
  22.   }



  23.      
  24.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  25.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  26.   GuiWdData2=c;   //把c这个变量放到窗口变量1里面显示
  27.         
  28. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  29.    while(1)  
  30.    {
  31.       initial();
  32.       key_service();
  33.       display_service();
  34.    }

  35. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为3。
        变量b为6。
        变量c为9。
         下节预告:循环体内的continue和break语句。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-11-30 09:34:25 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-11-30 09:39 编辑

第四十八节:循环体内的continue和break语句。

48.1   continue语句
        通常情况下,单片机在循环体里从上往下执行,一直执行到最底部的花括号才会返回到循环体入口处准备第二次循环,但是,如果在循环体中途遇到continue语句,单片机虽然没有执行到最底部的花括号,也会马上返回到循环体入口处准备第二次循环,在continue语句之后至最底部花括号之间的语句没有被执行到。比如:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    continue;
    语句3;

    ……   
    语句N;
}    //循环体结束

       分析:
       上述语句中,单片机从语句1开始执行到continue语句,然后马上就返回到循环体的条件判断入口处,也就是语句3至语句N是不可能被执行到的,上述语句简化后相当于:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
}    //循环体结束

       既然可以如此简化,还要continue语句有什么用呢?我们一般应用continue的时候不会像上面那样单独使用,而是配合这if条件判断语句来用,这样continue才有它存在价值,比如:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    if(某条件)
    {
         continue;
    }
    语句3;

    ……   
    语句N;
}    //循环体结束


48.2    break语句
         continue语句是提前结束当前这一次的循环,准备进入下一次的新循环,是某次结束的概念。而break语句是直接跳出当前循环体,结束当前循环体,是整体结束的概念。
while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    break;
    语句3;

    ……   
    语句N;
}    //循环体结束
语句(N+1);  //循环体之外语句

      分析:
      上述语句中,单片机从语句1开始执行到break语句,无需判断while或者for的条件,直接马上跳出循环体,执行到循环体之外的语句(N+1)。在实际项目中,break往往也是会配合if条件判断语句一起使用,比如:

while(…)或者for(…)   //循环体的条件判断入口处
{    //循环体开始
    语句1;   
    语句2;
    if(某条件)
    {
         break;
    }
    语句3;

    ……   
    语句N;
}    //循环体结束
语句(N+1);  //循环体之外语句


48.3    break语句能跳多远
         break语句能跳多远?预知答案请先分析以下这个例子:

while(…)
{  
      语句1;   
      语句2;
        while(…)
      {   
           语句3;   
           break;
           语句4;
     }   
     语句5;
}   
语句6;

       上述例子中,在while循环里面有藏着第二个while循环,这种循环之中还有循环,通常称之为循环嵌套。单片机从上往下执行语句,当遇到break后,它会跳到语句5那里呢,还是会跳到语句6那里?正确答案是语句5那里。说明了break语句跳远范围仅仅刚好能跳出当前的循环体。在上述循环嵌套的例子中,最内层的break只能跳出最内层的循环体,不能跳到最外层的语句6那里,如果需要继续跳出最外层的语句6那里,可以继续在外层的循环体内再增加一个break语句。


48.4    还有哪些语句可以无条件退出循环体
         还有return和goto语句可以跳出循环体。这部分的内容大家只需大概了解一下即可。return语句比break语句还厉害,它不仅仅跳出当前循环体,还是跳出了当前函数,也就是提前结束了当前函数,这部分的内容后面章节会讲到,暂时不用管。而goto语句在C语言中大家都公认不建议用,因为它很容易扰乱大家常用的C语言编程结构,我本人也从来没有用过goto语句,所以这条语句我也不再深入讲解它。


48.5   编写程序
         编写一个程序来熟悉一下continue和break语句的使用。最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化
  8.   unsigned char d=0;  //观察这个数最后的变化

  9.   unsigned char i;  //控制循环体的条件判断变量


  10. //i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
  11.   for(i=0;i<6;i++)  
  12.   {
  13.      a=a+1;    //被执行了6次,分别是第0,1,2,3,4,5次
  14.      if(i>=3)
  15.      {
  16.         continue;  //提前结束本次循环,准备进入下一次循环
  17.      }
  18.      b=b+1;  //被执行了3次,分别是第0,1,2次
  19.   }


  20. //i<6的条件判断是在进入循环体之前判断,而i的自加1是在执行完一次循环体之后才自加的。
  21.   for(i=0;i<6;i++)  
  22.   {
  23.      c=c+1;   //被执行了4次,分别是第0,1,2,3次
  24.      if(i>=3)
  25.      {
  26.         break; //马上跳出当前循环体
  27.      }
  28.      d=d+1;   //被执行了3次,分别是第0,1,2次
  29.   }

  30.      
  31.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  32.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  33.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示
  34.   GuiWdData3=d;   //把d这个变量放到窗口变量3里面显示
  35.         
  36. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  37.    while(1)  
  38.    {
  39.       initial();
  40.       key_service();
  41.       display_service();
  42.    }

  43. }
复制代码


       查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
       变量a为6。
       变量b为3。
       变量c为4。     
       变量d为3。
       下节预告:for和while的循环嵌套。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-12-5 23:32:59 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-12-6 11:15 编辑

第四十九节:for和while循环体的嵌套。

        49.1      循环体嵌套的执行顺序
         for循环体内又包含了for循环体可以称为循环体嵌套。比如:
for(i=0;i<2;i++)  //大循环
{
       语句1;
       for(k=0;k<3;k++) //内嵌的小循环
       {
               语句2;
       }
       语句3;
}
         上述例子中,带i的for称为大循环,带k的for称为小循环,单片机从大循环入口进来,由上往下执行,执行第1次大循环,先执行1次“语句1”,接着进入小循环,小循环要连续循环执行3次“语句2”才跳出小循环,之后执行1次“语句3”,然后再返回到大循环入口判断i条件是否满足,此时条件满足,继续执行第2次大循环,1次“语句1”,3次“语句2”,1次“语句3”,第2次循环结束后又返回到大循环入口判断i条件,此时i已经等于2不再小于2了,因此条件不满足,结束整个循环嵌套。上述执行的语句顺序如下:
         语句1;   //第1次大循环开始
         语句2;   
         语句2;
         语句2;
         语句3;
         语句1;   //第2次大循环开始
         语句2;
         语句2;
         语句2;
         语句3;
         根据此顺序,再看一个具体的程序例子:
a=0;  
b=0;
for(i=0;i<2;i++)  //大循环
{
     a=a+1;  //被执行了2次
     for(k=0;k<3;k++) //内嵌的小循环
     {
           b= b+1;  //被执行了6次
     }
}
         上述例子中,执行完程序后,a的值变成了2,b的值变成了6。重点分析b的变化,“b=b+1”在内嵌循环体里被执行了6次,6次从何而来?就是i乘以k等于6。这个乘法次数是循环嵌套一个很重要的特性。上述程序如果用while语句来实现如下:
a=0;  
b=0;
i=0;  //控制大循环的变量初始化
while(i<2)  //大循环
{
     a=a+1;  //被执行了2次
     k=0;    //控制小循环的变量初始化
     while(k<3) //内嵌的小循环
     {
         b= b+1;  //被执行了6次
         k=k+1;
    }
     i=i+1;
}


        49.2   循环嵌套的常见用途---二维数组的应用
         前面章节讲到了二维数组a[2][3],它有6个变量,在没有学for语句之前,要依次把每个元素单独赋值清零真不容易,要写6次赋值语句如下:
a[0][0]=0;
a[0][1]=0;
a[0][2]=0;
a[1][0]=0;
a[1][1]=0;
a[1][2]=0;
         自从懂了for嵌套语句之后,直接写代码如下,简洁了许多:
  1. for(i=0;i<2;i++)  //大循环
  2. {
  3.      for(k=0;k<3;k++) //内嵌的小循环
  4.      {
  5.           a[i][k]=0;
  6.       }
  7. }
复制代码


        49.3     循环嵌套的常见用途---大延时
        以后接触单片机项目后,会经常会用到delay这个延时函数,大部分都是利用for循环来实现,小延时函数往往不用嵌套,直接如下编写:
for(k=0;k<N;k++);  
      上述的N是控制循环次数,每次循环都要消耗单片机一点时间,如果N越大需要消耗的时间就越多,起到延时的作用。但是N所能取的最大值受它所定义的类型所限制,比如是unsigned char类型时最大范围是255,是unsigned int类型时最大范围是65535,是unsigned long类型时最大范围是4294967295。如果要实现更大的延时怎么办?就可以用for的循环嵌套,利用循环嵌套次数的乘法翻倍特性,很容易编写大的延时函数。比如:
for(i=0;i<M;i++)  //大循环
{
      for(k=0;k<N;k++); //内嵌的小循环
}
      此时循环的次数是N乘以M的乘积。
      现在编写一个循环嵌套的练习程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:
  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4.         
  5.   unsigned char a=0;  //观察这个数最后的变化
  6.   unsigned char b=0;  //观察这个数最后的变化
  7.   unsigned char c=0;  //观察这个数最后的变化


  8.   unsigned char i;  //控制大循环体的条件判断变量
  9.   unsigned char k;  //控制内嵌小循环体的条件判断变量

  10.   for(i=0;i<2;i++)  //大循环
  11.   {
  12.      a=a+1;    //被执行了2次
  13.      for(k=0;k<3;k++)  //内嵌小循环
  14.      {  
  15.           b=b+1;  //被执行了6次,也就是i乘以k,2乘以3等于6.
  16.      }
  17.      c=c+1;    //被执行了2次
  18.   }

  19.      
  20.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  21.   GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  22.   GuiWdData2=c;   //把c这个变量放到窗口变量2里面显示

  23.         
  24. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  25.    while(1)  
  26.    {
  27.       initial();
  28.       key_service();
  29.       display_service();
  30.    }

  31. }
复制代码


          查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
         变量a为2。
          变量b为6。
          变量c为2。
          下节预告:switch语句。
(未完待续)


乐于分享,勇于质疑!
 楼主| 发表于 2015-12-14 23:11:17 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-12-14 23:16 编辑

第五十节:switch语句。
         switch是非常重要的语句,我几乎所有的单片机项目都是用switch搭建程序主框架。如果说while和for是一对孪生兄弟,那么if-elseif和switch也是一对孪生兄弟,凡是用switch语句能实现的功能都可以用if-elseif实现。switch有条件分支的功能,在条件超过3个以上的条件分支功能里,switch书写会比if-else if更加直观清晰。
         50.1      switch语句的语法
         以下是switch语句常见的格式:

switch(变量)    //根据变量的数值大小从对应的case入口进来
{
     case 0:   //入口0
         语句0;
         break; //switch程序体的出口之一
     case 1:  //入口1
         语句1;
         break; //switch程序体的出口之一
     case 2: //入口2
         语句2;
         break; //switch程序体的出口之一
}    //最下面的花括号也是一个switch程序体的出口之一

        执行顺序分析:
        单片机从switch(变量)进来,根据变量的数值大小,进入对应的case入口里。假如变量等于1,就直接进入到case 1入口,执行“语句1”后,遇到break语句就跳出整个switch程序体。假如变量等于3,单片机从switch(变量)进来,因为没有发现case 3,所以直接跳出switch程序体没有执行任何语句。多补充一句,在case 2选项中,语句2最后的break可以省略,因为case 2是最后一个case,即使没有遇到break也会遇到最下面的花括号而跳出switch程序体。上述程序功能如果用if-elseif语句来实现如下:

if(变量==0)
{
    语句0;
}
else if(变量==1)
{
    语句1;
}
else if(变量==2)
{
    语句2;
}


        50.2      switch语句的break
        大家从50.1的例子中看到了三个关键字分别是:switch,case,break。其实并不是每个case都必须要break配套,break只是起到一个出口的功能。假如没有遇到break,程序会一直往下执行,直到遇到break或者switch最下面的花括号为止。比如:

switch(变量)    //根据变量的数值大小从对应的case入口进来
{
     case 0:   //入口0
         语句0;
         break;
     case 1:  //入口1
         语句1;
     case 2: //入口2
         语句2;
         break;
     case 3: //入口3
         语句3;
         break;
}   //最下面的花括号也是一个switch程序体的出口之一

        执行顺序分析:
        假如此时switch(变量)的变量等于1,就直接进入到case 1入口,执行“语句1”后,没有遇到break语句,继续执行“语句2”,紧接着遇到break语句,才跳出整个switch程序体。本例子中case 1没有break语句,就继续往下执行下面case2里面的语句,直到遇到break或者最下面的花括号为止。

        50.3      switch语句的case有规定顺序吗?必须连贯吗?
switch程序体内部可以写很多case入口,这些case入口是不是必须按从小到大的顺序?是不是规定必须case数字连贯?答:没有规定顺序,也没有规定case数字连贯,case的数值只是代表入口。比如以下两种写法都是合法的:

        50.3.1    case不按从小到大的顺序:

switch(变量)   
{
     case 2:
         语句2;
         break;
     case 0:  
         语句0;
         break;
     case 1:  
         语句1;
         break;
}   

         50.3.2    case的数字不连贯
switch(变量)   
{
    case 0:  
         语句0;
         break;
     case 3:
         语句3;
         break;
     case 9:  
         语句9;
         break;
}   


         50.4      switch语句的default。
         default和break一样,也不是必须的语句,应根据程序需要来选择。default相当于if-else if-else 语句中的else,也就是当switch(变量)的变量没有匹配的case入口时,就会默认进入default入口,就像if-else if-else 语句中当前面所有的条件不满足时,就进入else语句的程序体,比如:

switch(变量)    //根据变量的数值大小从对应的case入口进来
{
     case 0:   //入口0
         语句0;
         break; //switch程序体的出口之一
     case 1:  //入口1
         语句1;
         break; //switch程序体的出口之一
     case 2: //入口2
         语句2;
         break; //switch程序体的出口之一
     default:  //当所有的case不满足,就进入default入口
         语句3;
         break;
}    //最下面的花括号也是一个switch程序体的出口之一

        执行顺序分析:
        假如switch(变量)的变量等于35,因为没有找到case 35,所以就直接从默认的default入口进来执行” 语句3”,然后遇到break语句就跳出switch程序体。上述程序功能如果用if-elseif-else语句来实现如下:

if(变量==0)
{
    语句0;
}
else if(变量==1)
{
    语句1;
}
else if(变量==2)
{
    语句2;
}
else   //相当于switch中的default
{
    语句3;
}


         50.5      switch语句中内嵌switch
         if语句也可以内嵌if语句,while语句也可以内嵌while语句,switch语句当然也可以内嵌switch。比如

switch(a)
{
   case 1:
        switch(b)
        {
             case 1:
                  Break;
             case 2:
                  Break;
        }
        Break;
   case 2:
        Break;
}

       这种switch内嵌switch语句也是合法的,而且在实际项目中也很常用,大家目前先有个大概的了解即可,暂时不深入讲解。

        50.6      switch练习程序
       现在编写一个switch的练习程序,最后把程序编译后下载到坚鸿51学习板观察结果。请直接复制第十节模板程序,修改的main程序代码如下:


  1. void main() //主程序
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/



  4.   unsigned char k;   //此变量为switch的入口判断变量

  5.   unsigned char a; //观察此变量的变化来理解switch的执行顺序

  6.   a=0;
  7.   k=2;

  8.   switch(k)
  9.   {
  10.      case 0:  //入口0
  11.        a++;
  12.        break; //跳出switch
  13.      case 1:  //入口1
  14.        a++;
  15.      case 2:  //入口2,上述k等于2所以从这里进来
  16.        a++;
  17.      case 3:  //入口3
  18.        a++;
  19.      case 4:  //入口4
  20.        a++;
  21.        break;  //跳出switch
  22.      case 5:  //入口5
  23.        a++;
  24.        break;  //跳出switch
  25.      default: //入口default,相当于else。当前面没有遇到对应的case入口时,就默认进入此default入口
  26.        a++;
  27.        break;  //跳出switch
  28.   }            //最后一个switch的花括号也是跳出switch
  29.       
  30.   GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示

  31.   
  32.         
  33. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  34.    while(1)  
  35.    {
  36.       initial();
  37.       key_service();
  38.       display_service();
  39.    }

  40. }
复制代码


        查看运算结果的方法。如何在坚鸿51学习板上观察变量?按下S1或者S5按键即可切换显示不同的窗口,从而显示不同的变量。按下S9按键不松手就可以切换到十六进制的显示界面,松开手后会自动切换到十进制的界面。16个LED灯显示的就是当前变量的二进制数,亮代表1,灭代表0。上坚鸿51学习板观察程序执行的结果如下:
        变量a为3。
        下节预告:空返回空形参的函数。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-12-20 01:20:41 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-12-20 01:25 编辑

第五十一节:函数的三要素和执行顺序。

        51.1   增加子函数内容后如何运用第10节模板程序调试
        前面章节讲的内容因为没有涉及到子函数,范例程序的编辑调试只需要在一个main主函数里操作就可以,原来的调试方式如下:
  1. void main() //主函数
  2. {
  3. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  4. unsigned char  a;  //普通局部变量的定义     
  5.   unsigned char  b;  //普通局部变量的定义   
  6.   ……     //一些程序代码
  7.   ……
  8.   ……
  9. GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  10. GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  11.   ……      
  12. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  13.   while(1)  
  14.   {
  15.      initial();
  16.      key_service();
  17.      display_service();
  18.   }
  19. }
复制代码

         本节开始增加子函数内容,所以以后的调试方式如下:

  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);  //子函数的声明
  3. unsigned char  x;  //全局变量的定义     
  4. unsigned char  y;  //全局变量的定义   
  5. void hanshu(void)  //子函数的定义
  6. {
  7.    unsigned char  c;  //普通局部变量的定义子函数的语句;
  8. ……
  9. }
  10. void main() //主函数
  11. {
  12. unsigned char  a;  //普通局部变量的定义     
  13.   unsigned char  b;  //普通局部变量的定义   
  14. ……     //其它一些程序代码
  15. hanshu() ; //子函数的调用
  16.   ……     //其它一些程序代码
  17. GuiWdData0=a;   //把a这个变量放到窗口变量0里面显示
  18. GuiWdData1=b;   //把b这个变量放到窗口变量1里面显示
  19.   ……      
  20. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  21.   while(1)  
  22.   {
  23.      initial();
  24.      key_service();
  25.      display_service();
  26.   }
  27. }
复制代码

        对比原来,现在的“C语言学习区域的开始”分割线往上移动增大了范围,其它部分内容不用变,还是直接在第10节的模板程序里更改和添加这部分的内容就可以了。


        51.2   函数的三要素。
        有的人习惯把函数称为程序,比如主程序,子程序,这时的主程序对应主函数,子程序对应子函数,是一回事,只是每个人的表达习惯不一样而已。函数的三要素是声明,定义,调用。新构造一个函数时,尽量遵守这个三个要素来做就可以减少一些差错。什么叫函数的声明,定义,调用?先让大家有一个感性的认识,请先看下面这个例程:

  1.   /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);   //子函数的声明
  3. void hanshu(void)    //子函数的定义
  4. {
  5. ……     //子函数的代码语句
  6. }
  7. void main() //主函数
  8. {
  9. hanshu() ;      //子函数的调用
  10. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  11.   while(1)  
  12.   {
  13.       ……
  14.   }
  15. }
复制代码



         51.3   子函数在主函数里的执行顺序
        子函数被其它函数调用时,子函数的名字就相当于一个跳转地址,而子函数的定义就是要跳转的实际地址,单片机在主函数里遇到子函数名称,就直接跳转到子函数定义那里执行子函数定义部分的代码,执行完子函数后再返回到主函数,继续执行子函数名称后面部分的代码,请看下面这个代码的执行顺序,就可以一目了然:

  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void);   //子函数的声明
  3. void hanshu(void)    //子函数的定义
  4. {
  5. 语句1;
  6. 语句2;
  7. }
  8. void main() //主函数
  9. {
  10. 语句3;
  11. hanshu() ;      //子函数的调用
  12. 语句4;
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.   while(1)  
  15.   {
  16.       ……
  17.   }
  18. }
复制代码

        执行顺序分析:
        单片机从主函数main那里进来往下执行,先执行“语句3”,接着遇到hanshu名称的跳转地址,然后马上跳转到hanshu的定义部分,执行“语句1”,“语句2”,执行完子函数hanshu的定义部分,就马上返回到主函数,继续执行hanshu名称后面的“语句4”。整个执行语句的先后顺序如下:
        语句3;
        语句1;
        语句2;
        语句4;

        下节预告:普通局部变量和全局变量在函数里的作用域和优先级。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2015-12-27 02:04:03 | 显示全部楼层
本帖最后由 jianhong_wu 于 2015-12-27 02:07 编辑

第五十二节:从局部变量和全局变量中感悟“栈”为何物。


        52.1  什么是局部变量,什么是全局变量。
        局部变量就是在函数内部定义的变量,全局变量就是在函数外面定义的变量。下面举的例子能很清晰的说明局部变量和全局变量的特点:

  1. unsigned char a;     //在函数外面定义的,所以是全局变量。
  2.      void main()
  3. {
  4.           unsigned char b; //在函数内部定义的,所以是局部变量。
  5. }
复制代码


        52.2  局部变量和全局变量的内存模型。
        我在第三节里讲到,单片机内存包括ROM和RAM两部分,ROM存储的是单片机程序中的指令和一些不可更改的常量数据,而RAM存放的是可以被更改的数据,这些可以被程序更改的数据就叫做变量。变量包括局部变量和全局变量,但是局部变量和全局变量在RAM中的数据结构有本质不同。欲知不同在哪,首先要从RAM说起。对于大部分的单片机,RAM都至少分成两部分,一部分是给全局变量,一部分是给局部变量。给全局变量那部分的RAM叫全局数据区,给局部变量那部分的RAM叫做“栈”,因为我后面会用宾馆来比喻“栈”,所以为了方便记忆,可以把“栈”想象成 “客栈”来记忆。 全局数据区就像你自己家的房间,是唯一的,一个房间的地址只能你一个人住,而且是永久的,所以说每个全局变量都有唯一对应的RAM地址,不可能重复的。而“栈”就像宾馆客栈,每天晚上住的人不一样,每个人在里面居住的时间是有期限的,不是长久的,一个房间的地址每天可能住进不同的人,不是唯一的。全局区的全局变量拥有永久产权,“栈”区的局部变量只能临时居住在宾馆客栈,地址不是唯一的,有期限的。全局变量像私人区,局部变量像公共区。


         52.3  借用宾馆客栈比喻局部变量的期限。
  1. void hanshu(void);   //子函数的声明
  2. void hanshu(void)    //子函数的定义
  3. {
  4.      unsigned char a;   //局部变量
  5.      a=1;
  6. }
  7. void main() //主函数
  8. {
  9. hanshu() ;      //子函数的调用
  10. }
复制代码

          请看上面的程序例子,单片机从主函数main往下执行,首先遇到hanshu子函数的调用,所以就跳到hanshu函数的定义那里开始执行,此时的局部变量a开始被分配在RAM的栈区的某个地址,相当于你入住宾馆被分配到某个房间。单片机执行完子函数hanshu后,局部变量a在RAM的栈区所分配的地址被收回,局部变量a消失,被收回的RAM地址可能会被系统重新分配给其它被调用的函数的局部变量,此时相当于你离开宾馆,原来在宾馆居住那个房间被收回,你离开宾馆,从此你跟那个宾馆的房间没有啥关系,它可能会被宾馆老板重新分配给其他的客人入住。结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性不受范围限制的。


          52.4  局部变量和全局变量的优先级。
          上面最后一段话中得到一个结论:局部变量的作用域就是它所在函数的内部范围,而全局变量的作用域是永久性并且不受范围限制的。那么问题来,假如局部变量和全局变量的名字重名了,此时函数内部执行的变量到底是局部变量还是全局变量?这个问题就涉及到优先级。注意,当面对同名的局部变量和全局变量时,函数内部执行的变量是局部变量,也就是局部变量在函数内部要比全局变量的优先级高。

           52.4.1   请看第一个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. unsigned char a=5;    //全局变量的定义
  3. void main() //主程序
  4. {
  5.     unsigned char a=2;  //局部变量的定义
  6.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2,而不是5

  7. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  8.    while(1)
  9.    {
  10.       initial();
  11.       key_service();
  12.       display_service();
  13.    }

  14. }
复制代码

           此时输出显示2。


           52.4.2   请看第二个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/
  2. void hanshu(void); //函数声明
  3. unsigned char a=5;    //全局变量的定义
  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }
  8. void main() //主程序
  9. {
  10.     unsigned char a=2;   //局部变量的定义
  11.     hanshu();  //子函数被调用
  12.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是局部变量2
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

           此时输出显示2。
           分析:虽然子函数hanshu内部也有一个a=3,但是当hanshu被调用完结束后,这个a就消失不起作用了,所以此时显示的是main函数内部的局部变量。


           52.4.3   请看第三个例子:
  1. /*---C语言学习区域的开始---------------------------------------------------------------------------*/

  2. void hanshu(void); //函数声明

  3. unsigned char a=5;    //全局变量的定义

  4. void hanshu(void)   //函数定义
  5. {
  6.     unsigned char a=3;  //局部变量的定义
  7. }


  8. void main() //主程序
  9. {

  10.     hanshu();  //子函数被调用

  11.     GuiWdData0=a;   //把a这个数值放到窗口变量0里面显示,此时的a是全部变量5

  12.       
  13. /*---C语言学习区域的结束---------------------------------------------------------------------------*/
  14.    while(1)
  15.    {
  16.       initial();
  17.       key_service();
  18.       display_service();
  19.    }

  20. }
复制代码

          此时输出显示5。
          分析:子函数hanshu 里面的a的作用域只是它本身,因此子函数hanshu里面的a没办法影响到其它函数的a。而main函数内部没有了局部变量,那么此时起作用的只能是全局变量,因此显示5。假如此时连全局变量也没有了怎么办?这种情况是语法错误不能编译通过,因为任何变量的调用都必须要被先定义,定义有两种,要么是在函数内被定义成局部变量,要么是在函数外被定义成全局变量。


          52.5   局部变量和全局变量的总结归纳.
         (1)每定义一个新的全局变量,就意味着多开销一个新的RAM内存。而每定义一个局部变量,只要在函数内部所定义的局部变量总数不超过单片机的“栈”区,此时的局部变量不开销新的RAM内存,因为局部变量是临时借用“栈”区的,使用后就还给“栈”,“栈”是公共区,可以重复利用,可以服务若干个不同的函数内部局部变量。
         (2)单片机每次进入执行函数时,局部变量都会被初始化改变,而全局变量则不会被初始化,全局变量是一直保存之前最后一次更改的值。


下节预告:空返回空形参的函数。
(未完待续)

乐于分享,勇于质疑!
 楼主| 发表于 2016-1-4 05:05:31 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-10 01:36 编辑

第五十三节:函数的作用和四种常见书写类型。
第五十三节_pdf文件.pdf (109.18 KB, 下载次数: 2464)
乐于分享,勇于质疑!
 楼主| 发表于 2016-1-10 01:19:32 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-10 01:35 编辑

第五十四节:return语句在函数中的作用以及容易被忽略的四个功能
第五十四节_pdf文件.pdf (117.79 KB, 下载次数: 2383)
乐于分享,勇于质疑!
 楼主| 发表于 2016-1-17 01:58:36 | 显示全部楼层
本帖最后由 jianhong_wu 于 2016-1-17 11:40 编辑

第五十五节:static静态局部变量在函数中的重要作用。
第五十五节_pdf文件.pdf (144.04 KB, 下载次数: 2405)
乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-4-19 09:27 , Processed in 0.301448 second(s), 15 queries .

快速回复 返回顶部 返回列表