打印
[51单片机]

从单片机基础到程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
101
jianhong_wu|  楼主 | 2016-3-17 07:16 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
本帖最后由 jianhong_wu 于 2016-8-1 18:01 编辑

第九节:本节预留。

      本节预留。

使用特权

评论回复
102
f646425684| | 2016-3-18 21:32 | 只看该作者
来占个位置

使用特权

评论回复
103
jianhong_wu|  楼主 | 2016-3-19 11:08 | 只看该作者
第十节:程序从哪里开始,要到哪里去?
第十节_pdf文件.pdf (85.68 KB)

       程序从哪里开始,要到哪里去?为了让初学者了解C语言程序的执行顺序,我把程序分成三个区域:进入主程序前的区域,主程序的初始化区域,主程序的循环区域。当然,这里三个区的分类暂时没有把中断程序的情况考虑进去,中断程序的内容我会在后面相关的章节中再详细介绍,这里暂时不考虑中断。
       进入主程序前的区域。这是上电后,在单片机执行主程序代码之前就已经完成了的工作。包括头文件的包含,宏定义,内存分配这些工作。这部分的内容可以暂时不用去了解,我会在后面的一些章节中陆续深入讲解。
       主程序的初始化区域。这是上电后,单片机进入主程序后马上就要执行的程序代码,这部分区域的代码有一个特点,大家也必须记住的,就是单片机只执行一次。只要单片机不重启,不复位,那么上电后这部分的代码只被执行一次。
       主程序的循环区域。单片机在主程序中执行完了初始化区域的代码,紧接着就进入这片循环区域的代码。单片机一直在逐行循环执行这些代码,执行到末尾时又返回到循环区域的开始处继续开始新一轮的执行,周而复始,往复循环,这就是上电后单片机的最终归宿,一直处在循环的状态。
       下面我跟大家分析一个程序源代码的三个区域和执行顺序,大家先看中文解释部分的内容,暂时不用理解每行指令的语法,有个整体的认识就可以了。此源代码实现的功能是:上电后,蜂鸣器鸣叫一声就停止(初始化区域),然后看到一个LED灯一直在不停的闪烁(循环区域)。

      
                                         图10.1


源代码如下:

#include "REG52.H"             //进入主程序前的区域:头文件包含

sbit beep_dr=P3^4;            //进入主程序前的区域:宏定义
sbit led_dr=P1^6;               //进入主程序前的区域:宏定义

unsigned long i;                  //进入主程序前的区域:内存分配

void main()                          //主程序入口,即将进入初始化区域
{
   beep_dr=0;                     //第一步:初始化区域:蜂鸣器开始鸣叫。
   for(i=0;i<6250;i++);       //第二步:初始化区域:延时0.5秒左右。也就是蜂鸣器鸣叫的持续时间。
   beep_dr=1;                     //第三步:初始化区域:蜂鸣器停止鸣叫。
   while(1)                           //执行完上面的初始化区域,即将进入循环区域
   {
       led_dr=0;                    //第四步:循环区域:LED开始点亮。
       for(i=0;i<6250;i++);   //第五步:循环区域:延时0.5秒左右。也就是LED点亮的持续时间。
       led_dr=1;                    //第六步:循环区域:LED开始熄灭。
       for(i=0;i<6250;i++);   //第七步:循环区域:延时0.5秒左右。也就是LED熄灭的持续时间。
   }                                      //执行完上面第七步后,单片机又马上返回到上面第四步继续往下执行。
}

      上述代码执行顺序分析:
      单片机进入主程序后,从第一步到第三步是属于初始化区域,只被执行一次。然后进入循环区域,从第四步执行到第七步,执行完第七步之后,马上又返回上面第四步继续循环往下执行,单片机一直处于第四步到第七步的往复循环中。可以很清晰的看到,上面的main和while(1)关键词就是三个区域的边界分割线。   
      经过以上的分析,可以看出这三个区域的大概分布如下:

//...进入主程序前的区域
void main()               
{
   //...初始化区域
   while(1)                     
   {
       //...循环区域
   }
}


使用特权

评论回复
104
奥卡姆剃刀| | 2016-3-20 17:12 | 只看该作者
谢谢楼主!

使用特权

评论回复
105
jianhong_wu|  楼主 | 2016-3-27 23:10 | 只看该作者
本帖最后由 jianhong_wu 于 2017-2-12 10:54 编辑

第十一节:一个在单片机上练习C语言的模板程序。
第十一节_pdf文件.pdf (294.23 KB) 第十一节可复制的模板源程序.zip (1.42 KB)

【11.1   一套完整的模板源代码。】

       先给大家附上一套完整的模板源代码,后面章节练习C语言的模板程序就直接复制此完整的源代码,此源代码适合的单片机型号是STC89C52RC,晶振是11.0592MHz,串口波特率是9600,初学者只需修改代码里从“C语言学习区域的开始”到“C语言学习区域的结束”的区域,其它部分不要更改。可复制的源代码请到网上论坛原贴处复制或者下载,搜索本教程名字就可以找到原贴出处。一套完整的模板源代码如下:
#include "REG52.H"
void View(unsigned long u32ViewData);
void to_BufferData(unsigned long u32Data,unsigned char *pu8Buffer,unsigned char u8Type);
void SendString(unsigned char *pu8String);   

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   unsigned char a; //定义一个变量a。
   unsigned int  b; //定义一个变量b。
   unsigned long c; //定义一个变量c。
   a=100;          //给变量a赋值。
   b=10000;        //给变量b赋值。
   c=1000000000;   //给变量c赋值。
   View(a);   //在电脑串口端查看第1个数a。
   View(b);   //在电脑串口端查看第2个数b。
   View(c);   //在电脑串口端查看第3个数c。
   while(1)  
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

void View(unsigned long u32ViewData)
{
    static unsigned char Su8ViewBuffer[43];
            code unsigned char Cu8_0D_0A[]={0x0d,0x0a,0x00};
           code unsigned char Cu8Start[]={"开始..."};
    static unsigned char Su8FirstFlag=0;
    static unsigned int Su16FirstDelay;
    if(0==Su8FirstFlag)
            {
           Su8FirstFlag=1;
           for(Su16FirstDelay=0;Su16FirstDelay<10000;Su16FirstDelay++);
           SendString(Cu8Start);  
           SendString(Cu8_0D_0A);  
           SendString(Cu8_0D_0A);  
   }
    to_BufferData(u32ViewData,Su8ViewBuffer,1);
          SendString(Su8ViewBuffer);  
    to_BufferData(u32ViewData,Su8ViewBuffer,2);
        SendString(Su8ViewBuffer);  
    to_BufferData(u32ViewData,Su8ViewBuffer,3);
        SendString(Su8ViewBuffer);  
    to_BufferData(u32ViewData,Su8ViewBuffer,4);
            SendString(Su8ViewBuffer);  
           SendString(Cu8_0D_0A);  
}
void to_BufferData(unsigned long u32Data,unsigned char *pu8Buffer,unsigned char u8Type)
{
        code unsigned char Cu8Array1[]={0xB5,0xDA,0x4E,0xB8,0xF6,0xCA,0xFD,0x00};
        code unsigned char Cu8Array2[]="十进制:";
            code unsigned char Cu8Array3[]="十六进制:";
        code unsigned char Cu8Array4[]="二进制:";
    static unsigned char Su8SerialNumber=1;
    static unsigned int  Su16BufferCnt;
    static unsigned int  Su16TempCnt;
    static unsigned int  Su16TempSet;
    static unsigned long Su32Temp1;
    static unsigned long Su32Temp2;
    static unsigned long Su32Temp3;
    static unsigned char Su8ViewFlag;
    if(1==u8Type)
          {
        for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                   pu8Buffer[Su16BufferCnt]=Cu8Array1[Su16BufferCnt];
              }
                pu8Buffer[2]=Su8SerialNumber+'0';
               pu8Buffer[Su16BufferCnt]=0x0d;
               pu8Buffer[Su16BufferCnt+1]=0x0a;
                pu8Buffer[Su16BufferCnt+2]=0;
            Su8SerialNumber++;
                return;
           }
    else if(2==u8Type)
            {
                for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                       pu8Buffer[Su16BufferCnt]=Cu8Array2[Su16BufferCnt];
                }
               Su32Temp1=1000000000;
                   Su32Temp2=10;
                   Su16TempSet=10;
          }
    else if(3==u8Type)
        {
                for(Su16BufferCnt=0;Su16BufferCnt<9;Su16BufferCnt++)
                {
                       pu8Buffer[Su16BufferCnt]=Cu8Array3[Su16BufferCnt];
                }
                Su32Temp1=0x10000000;
                    Su32Temp2=0x00000010;
                    Su16TempSet=8;       
    }
    else
    {
                for(Su16BufferCnt=0;Su16BufferCnt<7;Su16BufferCnt++)
                {
                       pu8Buffer[Su16BufferCnt]=Cu8Array4[Su16BufferCnt];
                }
                Su32Temp1=0x80000000;
                    Su32Temp2=0x00000002;
                    Su16TempSet=32;       
           }
    Su8ViewFlag=0;
    for(Su16TempCnt=0;Su16TempCnt<Su16TempSet;Su16TempCnt++)
    {
       Su32Temp3=u32Data/Su32Temp1%Su32Temp2;
               if(Su32Temp3<10)
               {
                pu8Buffer[Su16BufferCnt]=Su32Temp3+'0';
               }
               else
               {
                pu8Buffer[Su16BufferCnt]=Su32Temp3-10+'A';
               }
               if(0==u32Data)
               {
                       Su16BufferCnt++;
                       break;
               }
               else if(0==Su8ViewFlag)
               {
                   if('0'!=pu8Buffer[Su16BufferCnt])
               {
               Su8ViewFlag=1;
                               Su16BufferCnt++;
           }
               }
               else
               {
                   Su16BufferCnt++;
               }

       Su32Temp1=Su32Temp1/Su32Temp2;
    }
    pu8Buffer[Su16BufferCnt]=0x0d;
    pu8Buffer[Su16BufferCnt+1]=0x0a;
    pu8Buffer[Su16BufferCnt+2]=0;
}
void SendString(unsigned char *pu8String)   
{
  static unsigned int Su16SendCnt;
  static unsigned int Su16Delay;
  SCON=0x50;
  TMOD=0X21;
  TH1=TL1=256-(11059200L/12/32/9600);  
  TR1=1;
  ES = 0;
  TI = 0;
  for(Su16SendCnt=0;Su16SendCnt<43;Su16SendCnt++)
  {
     if(0==pu8String[Su16SendCnt])
             {
                 break;
             }
             else
             {
        SBUF =pu8String[Su16SendCnt];
        for(Su16Delay=0;Su16Delay<800;Su16Delay++);
                   TI = 0;
             }
  }
}


【11.2   模板程序的使用说明。】

        
                                 图11.2.1

       大多数初学者在学习C语言的时候,往往是在电脑端安装上VC平台软件来练习C语言,这种方法只要在代码里调用printf语句,编译后就可以看到被printf语句调用的变量,挺方便的。本教程没有用这种方法,既然本教程的C语言主要针对单片机,所以我想出了另外一种方法,这种方法就是直接在单片机上练习C语言,这样会让初学者体验更深刻。这种方法对硬件平台要求不高,只要51学习板上有一个9针的串口就可以,这个串口既可以用来烧录程序,也可以用来观察代码里的某个变量,只要在代码里调用View函数就可以达到类似VC平台软件下printf语句的效果,View函数可以向串口输出某个变量的十进制,十六进制和二进制,大家只要在电脑端的串口助手软件就可以看到某个变量的这些信息,View函数能查看的变量最大数值范围是4个字节的unsigned long变量,十进制的范围是从0到4294967295,也可以查看unsigned int 和unsigned char的类型变量(数据的进制以及long,int,char等知识点大家目前还没接触到,因此不懂也没关系,当前只要有个大概的认识就可以,暂时不用深入理解,后面章节还会详细介绍)。View函数是我整个模板程序的其中一部分,所以要用这种方法就必须先复制我整个模板程序,初学者练习代码的活动范围仅仅局限于模板程序里的“C语言学习区域”,在此区域里有一个main主函数,main主函数内有一个初始化区域,初学者往往在这个初始化区域里练习C语言就够了,初学者最大的活动范围不能超过从“C语言学习区域的开始”到“C语言学习区域的结束”这个范围,这个范围之外其它部分的代码主要用来实现数据处理和串口发送的功能,大家暂时不用读懂它,直接复制过来就可以了。比如:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   //...初始化区域,也就是主要用来给初学者学习C语言的区域。
   while(1)  
   {

   }

}

/*---C语言学习区域的结束。-----------------------------------------------*/


       上述例子中,初学者练习代码只能在从“C语言学习区域的开始”到“C语言学习区域的结束”这个范围,此范围外的代码直接复制过来不要更改。我们再来分析分析下面节选的main函数源代码:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
   unsigned char a; //定义一个变量a。
   unsigned int  b; //定义一个变量b。
   unsigned long c; //定义一个变量c。
   a=100;          //给变量a赋值。
   b=10000;        //给变量b赋值。
   c=1000000000;   //给变量c赋值。
   View(a);   //在电脑串口端查看第1个数a。
   View(b);   //在电脑串口端查看第2个数b。
   View(c);   //在电脑串口端查看第3个数c。
   while(1)  
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/


      上述节选的main函数代码里,比如“a=100;          //给变量a赋值。”这行代码,所谓的“赋值”就是“=”这个语句,它表面上像我们平时用的等于号,实际上不是等于号,而是代表“给”的意思,把“=”符号右边的数复制一份给左边的变量,比如“a=100;”就是代表把100这个数值复制一份给变量a,执行这条指令后,a就等于100了。这里的分号“;”代表一条程序指令的结束。 而双斜线“//”是注释语句,双斜线“//”这行后面的文字或字符都是用来注释用的,编译器会忽略双斜线“//”这一行后面的文字或字符,编译器不把注释文字或字符列入源代码,也就是“//”这一行中后面的文字或字符是不占单片机内存的。当然“//”仅仅局限于当前一行代码。上面除了“//”是注释语句外,上面的“/*”和“*/”之间也是注释语句,跟”//”的作用一样,只不过“/*”是注释开始,“*/”是注释结束,它们的范围不局限于一行,而是从“/*”到“*/”的范围,因此可以用于注释连着的多行文字或者字符。
      接着在分析上述代码中最重要的函数,也是本节最核心最重要的函数View(某个变量)。比如“ View(a); ”这行代码,View(a)就是要把变量a的十进制,十六进制和二进制的数值都发送到串口,我们通过USB转串口线让学习板连接上电脑,在电脑串口助手软件上就能看到被View函数调用的变量a的信息。

【11.3   如何在电脑上使用串口助手软件查看被View函数调用的变量?】

      前面章节在讲烧录程序时提到一个叫“stc-isp-15xx-v6.85I”的上位机软件,这个软件除了用来烧录程序,还集成了串口助手软件的功能。所以本节直接共用烧录程序时的USB转串口线和“stc-isp-15xx-v6.85I”软件就可以了,无需额外再购买新的USB转串口线和下载其它串口助手软件,但是如何设置这个“stc-isp-15xx-v6.85I”上位机软件,还是有一些需要特别注意的地方的,现在把这个详细的步骤介绍给大家。

      第一步:设置烧录软件的选项。
      按前面章节介绍烧录程序时所需的步骤,用USB转串口线连接51学习板和电脑,记录COM号,打开“stc-isp-15xx-v6.85I”软件,选择单片机型号,选择对应的串口号(COM号),设置最低波特率和最高波特率,这部分的内容跟烧录程序时的配置步骤是一样的,唯一必须要特别注意的是最高波特率必须选择9600!最低波特率建议选择2400。否则在烧录完程序后,当上位机集成软件自动切换到串口助手软件窗口时,接收区域显示的一些汉字信息可能会出现乱码。

----------------------------------步骤之间的分割线----------------------------------------

      
                                 图11.3.2

      第二步:设置串口助手软件的选项。
      先点击右上方选中“串口助手”选项切换到串口助手的窗口,接收缓冲区选择“文本模式”,串口选择匹配的COM号(跟烧录软件一致的COM号),波特率必须选择9600,勾选上“编程完成后自动打开串口”选项,最后点击“打开串口”按钮使之切换到显示“关闭串口”的文字状态,至此串口助手软件的设置完毕。接下来就是按烧录程序的流程,打开新的HEX程序文件,程序烧录完成后上位机软件会自动切换到串口助手的串口,就可以观察到View函数从单片机上发送过来的某个变量的十进制,十六进制,二进制的信息了。接收缓冲区的窗口比较小,如果收到的信息比较多,只要在上下方向拖动窗口右边的滑块就可以依次看到全部的信息。如果想让单片机重新发送数据,只要让51学习板断电重启就可以重发一次数据,当串口助手的接收区接收的信息太多影响观察时,大家可以点击“清空接收区”的按钮来清屏,然后断电重启让它再重发一次数据。在电脑的串口助手软件里观察到的数据格式大概是什么样子的呢?比如编译完本章节上述完整的模板源代码程序后,会在串口助手软件里看到a,b,c三个变量的信息如下:

开始...

第1个数
十进制:100
十六进制:64
二进制:1100100

第2个数
十进制:10000
十六进制:2710
二进制:10011100010000

第3个数
十进制:1000000000
十六进制:3B9ACA00
二进制:111011100110101100101000000000


      多说一句,烧录程序后,当软件自动切换到串口助手软件选项的窗口时,串口助手窗口显示单片机返回的信息,这时有可能第一行的文字“开始...”会丢失或者显示不出来,但是后面其它的关键信息不受影响,我猜测可能是串口助手软件本身的某个环节存在的小bug,跟我们没关系,我们不用深究原因,因为不会影响我们的使用。此时如果让单片机断电重启就可以看到第一行的文字“开始...”。

【11.4   如何利用现有的工程编辑编译新的源代码?】

      本教程后面有很多章节的源代码,是不是每个章节都要重新建一个工程?其实不用。我们只要用一个工程就可以编译编辑本教程所有章节的源代码。方法很简单,就是打开一个现有的工程,用快捷组合键“Ctrl+A”把原工程里面的C源代码全部选中,再按“Backspace”清空原来的代码,然后再复制本教程相关章节的代码粘贴到工程的C文档里,重新编译一次就可以得到对应的Hex格式的烧录文件。用这种方法的时候,建议大家做好每个程序代码的备份。每完成一个项目的小进度,都要及时把源代码存储到电脑硬盘里,电脑硬盘里每个项目对应一个项目文件夹,每个项目文件夹里包含很多不同版本编号的源代码文件,每个源代码文件名都有流水编号,方便识别最新版本的程序,每天下班前都要把最新版本的源代码文件上传到自己的网盘里备份,在互联网时代,把源代码存到自己的网盘,可以随时异地存取,即使遇到电脑故障损坏也不担心数据永久丢失。

【11.5   编辑源代码的5个常用快捷键。】

      介绍一下常用的快捷键,好好利用这5个快捷键,会让你在编辑源代码时效率明显提高。
(1)选中整篇所有的内容:组合键Ctrl+A。
(2)把选中的内容复制到临时剪贴板:组合键Ctrl+C。
(3)把临时剪贴板的内容粘贴到光标开始处:组合键Ctrl+V。
(4)把选中的一行或者几行内容整体往右边移动:单键Tab。每按一次就移动几个空格,很实用。
(5)把选中的一行或者几行内容整体往左边移动:组合键Shift+Tab。每按一次就移动几个空格,很实用。




使用特权

评论回复
评论
sailor830112 2022-7-19 18:09 回复TA
小白新手初接触,完全不懂这节代码内容。进制转换能看懂,不知道后面有没有这方面的知识点解析。 
106
mega1702| | 2016-3-28 10:52 | 只看该作者
谢谢楼主分享!!!

使用特权

评论回复
107
jianhong_wu|  楼主 | 2016-4-3 10:38 | 只看该作者
第十二节:变量的定义和赋值。
第十二节_pdf文件.pdf (105.69 KB)
【12.1   学习C语言的建议和方法。】

       先提一些学C语言的建议和方法,帮大家删繁就简,去掉一些初学者常见的思想包袱。现阶段我们的学习是使用单片机,把单片机当做一个成品,把单片机当做一个忠诚的士兵,学习C语言就是学习如何使用单片机,如何命令单片机,如何让单片机听懂我们的话并且听我们指挥。单片机内部太细节的构造原理暂时不用过多去关注,只要知道跟我们使用相关的几个特征就可以,这样初学者的学习包袱就没有那么重,就可以把重点放在使用上的,而不是好奇于根本原理的死磕到底。学C语言跟学习英语的性质是一样的,都是在学习一门外语,只是C语言比英语的语法要简单很多,非常容易上手,词汇量也没有英语那么多,C语言常用单词才几十个而已。学习任何一门语言的秘诀在于练习,学习C语言的秘诀是多在单片机上练习编程。本教程后面几乎每个章节都有例程,这个例程很重要,初学者即使看懂了,我也强烈建议要把“C语言学习区域”的那部分代码亲自上机敲键盘练习一遍,并且看看实验现象是否如你所愿。

【12.2   变量定义和赋值的感性认识。】

       这些年我用过很多单片机,比如51,PIC,LPC17系列,STM8,STM32等单片机。尽管各类单片机有一些差异,但是在选单片机时有3个参数我们一定会关注的,它们分别是:工作频率,数据存储器RAM,程序存储器ROM。工作频率跟晶振和倍频有关,决定了每条指令所要损耗的时间,从而决定了运算速度。RAM跟代码里所定义变量的数量有关。ROM跟程序代码量的大小有关。程序是什么?程序就是由对象和行为两者构成的。对象就是变量,就是变量的定义,就是RAM,RAM的大小决定了一个程序允许的对象数量。行为就是赋值,判断,跳转,运算等语法,就是ROM,ROM的大小决定了一个程序允许的行为程度。本节的标题是“变量的定义和赋值”,其中“定义”就是对象,“赋值”就是行为。

【12.3   变量的定义。】

       变量的定义。一个程序最大允许有多少个对象,是由RAM的字节数决定的(字节是一种单位,后面章节会讲到)。本教程的编译环境是以AT89C52芯片为准,AT89C52这个单片机有256个字节的RAM,但是并不意味着程序就一定要全部占用这些RAM。程序需要占用多少RAM,完全是根据程序的实际情况来决定,需要多少就申请多少。这里的“对象”就是变量,这里的“申请”就是变量的定义。
       定义变量的关键字。常用有3种容量的变量,每种变量的取值范围不一样。第一种是”unsigned char”变量,取值范围从0到255,占用RAM一个字节,比喻成一房一厅。第二种是”unsigned int”变量,取值范围从0到65535,占用RAM两个字节,比喻成两房一厅。第三种是“unsigned long”变量,取值范围从0到4294967295,占用RAM四个字节,比喻成四房一厅。unsigned char,unsigned int和unsigned long都是定义变量的关键字,所谓关键字也可以看成是某门外语的单词,需要大家**的,当然不用死记硬背,只要多上机练习就自然熟记于心,出口成章。多说一句,上述的变量范围是针对本教程所用的单片机,当针对不同的单片机时上述变量的范围可能会有一些小差异,比如在stm32单片机中,unsigned int的字节数就不是两个字节,而是四个字节,这些都是由所选的编译器决定的,大家暂时有个初步了解就可以。
       定义变量的语法格式。定义变量的语法格式由3部分组成:关键字,变量名,分号。比如:
       unsigned char a;

       其中unsigned char就是关键字,a就是变量名,分号”;”就是一条语句的结束符号。
       变量名的命名规则。变量名的第一个字符不能是数字,必须是字母或者下划线,字母或者下划线后面可以带数字,一个变量名之间的字符不能带空格,两个独立变量名之间也不能用空格隔开(但是两个独立变量名之间可以用逗号隔开)。变量名不能跟编译器已征用的关键字重名,不能跟函数名重名,这个现象跟古代要求臣民避讳皇帝的名字有点像。哪些名字是合法的,哪些名字是不合法的?现在举一些例子说明:
        unsigned char 3a; //不合法,第一个字符不能是数字。
        unsigned char char; //不合法,char是编译器已征用的关键字。
        unsigned char a b; //不合法,ab是一个变量名,a与b的中间不能有空格。
        unsigned char a,b; //合法,a和b分别是一个独立的变量名,a与b的中间可以用逗号隔开。
        unsigned char a; //合法。
        unsigned char abc; //合法。
        unsigned char _ab; //合法。
        unsigned char _3ab; //合法。
        unsigned char a123; //合法。
        unsigned char a12ced; //合法。

        定义变量与RAM的内在关系。当我们定义一个变量时,相当于向单片机申请了一个RAM空间。C编译器会自动为这个变量名分配一个RAM空间,每个字节的RAM空间都有一个固定唯一的地址。把每个字节的RAM空间比喻成房间,这个地址就是房号。地址是纯数字编号,不利于我们**,C语言编译器为了降低我们的工作难度,不用我们记每个变量的地址,只需要记住这个变量的名称就可以了。操作某个变量名,就相当于操作某个对应地址的RAM空间。变量名与对应地址RAM空间的映射关系是C编译器暗中悄悄帮我们分配好的。比如:
        unsigned char a;  //a占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
        unsigned char b;  //b占用一个字节的RAM空间,这个空间的地址由C编译自动分配。
        unsigned char c;  //c占用一个字节的RAM空间,这个空间的地址由C编译自动分配。  

        上述a,b,c三个变量各自占用一个字节的RAM空间,同时被C编译器分配了3个不同的RAM空间地址。
        变量定义的初始化。变量定义之后,等于被C编译器分配了一个RAM空间,那么这个空间里面存储的数据是什么?如果没有刻意给它初始化,RAM空间里面存储的数据是不太确定的,是默认的。有些场合,需要在给变量分配RAM空间时就给它一个固定的初始值,这就是变量定义的初始化。变量初始化的语法格式由3部分组成:关键字,变量名赋值,分号。比如:
        unsigned char a=9;

        其中unsigned char就是关键字。
        其中a=9就是变量名赋值。a从被C编译器分配RAM空间那一刻起,就默认是预存了一个9的数据。
        分号“;”就是一条语句的结束符号。

【12.4   变量的赋值。】

       赋值语句的含义。把右边对象的内容复制一份给左边对象。赋值语句有一个很重要的特性,就是覆盖性,左边对象原来的内容会被右边对象复制过来的新内容所覆盖。比如,左边对象是变量a,假设原来a里面存的数据是3,右边对象是数据6,执行赋值语句后,会把右边的6赋值给了对象a,那么a原来的数据3就被覆盖丢失了,变成了6。
       赋值语句的格式。赋值语句的语法格式由4部分组成:左边对象,关键字,右边对象,分号。比如:
       a=b;

       其中a就是左边对象。
       其中“=”就是关键字。写法跟我们平时用的等于号是一样,但是在C语言里不是等于的意思,而是代表赋值的意思,它是代表中文含义的“给”,而不是用于判断的“等于”,跟等于号是两码事(C语言的等于号是“==”,这个后面章节会讲到)。
       其中b就是右边对象。
       其中分号“;”代表一条语句的结束符。
       赋值语句与ROM的关系。赋值语句是行为的一种,所以编译会把赋值这个行为翻译成对应的指令,这些指令在下载程序时最终也是以数据的形式存储在ROM里,指令也是以字节为单位(字节是一种单位,后面章节会讲到)。本教程的编译环境是以AT89C52芯片为准,AT89C52这个单片机有8K的ROM容量,也就是有8192个字节的ROM(8乘以1024等于8192),但是并不意味着程序就一定要全部占用这些ROM。程序需要占用多少ROM,完全是根据程序的行为程度决定,也就是通常所说的你的程序容量有多大,有多少行代码。多说一句,在单片机或者我们常说的计算机领域里,存储容量是以字节为单位,而每K之间的进制不是我们日常所用的1000,而是1024,所以刚才所说的8K不是8000,而是8192,这个是初学者很容易迷惑的地方。刚才提到,赋值语句是行为,凡是程序的行为指令都存储在单片机的ROM区。C编译器会把一条赋值语句翻译成对应的一条或者几条机器码,机器码指令也是以字节为单位的。下载程序的时候,这些机器码就会被下载进单片机的ROM区。比如以下这行赋值语句:
       unsigned char a;
       unsigned char b=3;
       a=b;

       经过C编译器编译后会生成以字节为单位的机器码。这些机器码记录着这些信息:变量a的RAM地址,变量b的RAM地址和初始化时的预存数据3,以及把b变量的内容赋值给a变量的这个行为。所有这些信息,不管是“数据”还是“行为”,本质都是以“数据”(或称数字,数码都可以)的形式存储记录的,单位是字节。

【12.5   例程的分析和练习。】

       接下来练习一个程序实例。直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。本章节在“C语言学习区域”练习的代码如下:


/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a;  //定义的变量a被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char b;   //定义的变量b被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char c;   //定义的变量c被分配了一个字节的RAM空间,保存的数据是不确定的默认值。
       unsigned char d=9; //定义的变量d被分配了一个字节的RAM空间,保存的数据被初始化成9.

       b=3;  //把3赋值给变量b,b由原来不确定的默认数据变成了3。
       c=b;  //把变量b的内容复制一份赋值给左边的变量c,c从不确定的默认值变成了3。
   View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
   View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
   View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
   while(1)  
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/



       在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:255
十六进制:FF
二进制:11111111

第2个数
十进制:3
十六进制:3
二进制:11

第3个数
十进制:3
十六进制:3
二进制:11

第4个数
十进制:9
十六进制:9
二进制:1001



分析:
        第1个数a居然是255,这个255从哪来?因为a我们一直没有给它初始值,也没有给它赋值,所以它是不确定的默认值,这个255就是所谓的不确定的默认值,是编译器在定义变量a时分配的,带有不确定的随机性,不同的编译器可能分配的默认值都会存在差异。根据我的经验,unsigned char类型定义的默认值往往是0或者255(255是十六进制的0xff,十六进制的内容后续章节会讲到)。

使用特权

评论回复

打赏榜单

21ic小能手 打赏了 10.00 元 2016-04-05

108
jackhwang| | 2016-4-6 14:27 | 只看该作者
牛人啊 。佩服,万分佩服

使用特权

评论回复
109
k249| | 2016-4-7 13:11 | 只看该作者
暖一个

使用特权

评论回复
110
洛理小子| | 2016-4-7 13:19 | 只看该作者
前排占座

使用特权

评论回复
111
洛理小子| | 2016-4-7 13:22 | 只看该作者

使用特权

评论回复
112
k249| | 2016-4-7 15:36 | 只看该作者
谢楼主

使用特权

评论回复
113
洛理小子| | 2016-4-9 18:32 | 只看该作者
坐等继续更新。。。。

使用特权

评论回复
114
jianhong_wu|  楼主 | 2016-4-10 10:31 | 只看该作者
第十三节:赋值语句的覆盖性。
第十三节_pdf文件.pdf (69.44 KB)
【13.1   什么是赋值语句的覆盖性?】

        a=b;

       上述代码,执行完这条赋值语句后,会把右边变量b的数值复制一份给左边变量a,a获得了跟b一样的数值,但是a原来自己的数值却丢失了,为什么会丢失?就是因为被b复制过来的新数据给覆盖了,这就是赋值语句的覆盖性。

【13.2   例程的分析和练习。】

       既然赋值语句有覆盖性的特点,那么如何让两个变量相互交换数值?假设a原来的数据是1,b原来的数据是5,交换数据后,a的数据应该变为5,b的数据应该变为1,怎么做?很多初学者刚看到这么简单的题目,会马上根据日常生活的思路,你把你的东西给我,我把我的东西给你,就两个步骤而已,看似很简单,现在按这个思路编写一段程序看看会出什么问题,代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a=1;   //定义的变量a被分配了1个字节的RAM空间,保存的数据被初始化成1。
       unsigned char b=5;   //定义的变量b被分配了1个字节的RAM空间,保存的数据被初始化成5。

       b=a; //第一步:为了交换,先把a的数赋值给b。
       a=b; //第二步:为了交换,再把b的数赋值给a。

       View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)  
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/
            在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:1
十六进制:1
二进制:1

第2个数
十进制:1
十六进制:1
二进制:1

分析:        
         第1个数a和第2个数b居然都是1!这不是我们想要的结果。我们要的交换结果是:交换后,a变为5,b变为1。在哪个环节出了问题?把镜头切换到上述代码的“第一步”和“第二步”,由于b的数据在执行完“第一步”后,b自己原来的数据5被覆盖丢失了变成新的数据1,接着执行“第二步”后,此时相当于把 b的新数据1赋值给a,并没有5!所以a和b的数据都是1,不能达到交换后“a为5,b为1”的目的。其实就是赋值语句的覆盖性在作祟。


       上述交换数据的程序宣告失败!怎么办?既然赋值语句具有覆盖性,那么两变量想交换数据,就必须借助第三方变量来寄存,此时只需要多定义一个第三方变量t。正确的代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
       unsigned char a=1;   //定义的变量a被分配了1个字节的RAM空间,保存的数据被初始化成1。
       unsigned char b=5;   //定义的变量b被分配了1个字节的RAM空间,保存的数据被初始化成5。
       unsigned char t;     //定义一个第三方变量t,用来临时寄存数值。

       t=b; //第一步:为了避免b的数据在赋值后被覆盖丢失,先寄存一份在第三方变量t那里。
       b=a; //第二步:把a的数赋值给b,b原来的数据虽然丢失,但是b在t变量那里有备份。
       a=t; //第三步:再把b在t变量里的备份赋值给a。注意,这里不能用b,因b原数据已被覆盖。

       View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
   View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。

   while(1)  
   {
   }
}

/*---C语言学习区域的结束。-----------------------------------------------*/

        在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:5
十六进制:5
二进制:101

第2个数
十进制:1
十六进制:1
二进制:1

分析:        
        实验结果显示,两变量的数值交换成功。

【13.3   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。


使用特权

评论回复
115
夕林居士| | 2016-4-14 20:52 | 只看该作者
日新月异,顶一顶

使用特权

评论回复
116
pguangchun| | 2016-4-16 08:29 | 只看该作者
为你的无偿付出而点赞

使用特权

评论回复
117
jianhong_wu|  楼主 | 2016-4-17 09:31 | 只看该作者
第十四节:二进制与字节单位,以及常用三种变量的取值范围。
第十四节_pdf文件.pdf (81.21 KB)

【14.1   为什么要二进制?】

       为什么要二进制?我们日常生活明明是十进制的,为何数字电子领域偏要选择二进制?这是由数字硬件电路决定的。人有十个手指头,人可以直接发出十种不同声音来命名0,1,2,3...9这些数字,人可以直接用眼睛识别出十种不同状态的信息,但是数字底层基础硬件电路要直接处理和识别十种状态却很难,相对来说,处理和识别两种状态就轻松多了,所以选择二进制。比如,一颗LED灯的亮或灭,一个IO口的输出高电平或低电平,识别某一个点的电压是高电平或低电平,只需要三极管等基础元器件就可把硬件处理电路搭建起来,二进制广泛应用在数字电路的存储,通讯和运算等领域,想学好单片机就必须掌握它。

【14.2   二进制如何表示成千上万的大数值?】

       二进制如何表示成千上万的数值?现在用LED灯的亮和灭来跟大家讲解。

     (1)1个LED灯:
      灭   第0种状态
      亮   第1种状态

      合计:共2种状态。

    (2)2个LED灯挨着:
      灭灭   第0种状态
      灭亮   第1种状态
      亮灭   第2种状态
      亮亮   第3种状态

      合计:共4种状态。

   (3)3个LED灯挨着:
      灭灭灭   第0种状态
      灭灭亮   第1种状态
      灭亮灭   第2种状态
      灭亮亮   第3种状态
      亮灭灭   第4种状态
      亮灭亮   第5种状态
      亮亮灭   第6种状态
      亮亮亮   第7种状态

      合计:共8种状态。

   (4)8个LED灯挨着:
      灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭亮   第1种状态
      ......                         第N种状态
      亮亮亮亮亮亮亮灭   第254种状态
      亮亮亮亮亮亮亮亮   第255种状态

      合计:共256种状态。

    (5)16个LED灯挨着:
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   第1种状态
      ......                                                     第N种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   第65534种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   第65535种状态

      合计:共65536种状态。

    (6)32个LED灯挨着:
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭   第0种状态
      灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭灭亮   第1种状态
      ......                                                               第N种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮灭   第4294967294种状态
      亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮亮   第4294967295种状态

      合计:共4294967296种状态。

结论:
       连续挨着的LED灯越多,能表达的数值范围就越大。

【14.3   什么是位?】

       什么是位?以上一个LED灯就代表1位,8个LED灯就代表8位。位的英文名是用bit来表示。一个变量的位数越大就意味着这个变量的取值范围越大。一个单片机的位数越大,就说明这个单片机一次处理的数据范围就越大,意味着运算和处理速度就越快。我们日常所说的8位单片机,32位单片机,就是这个位的概念。为什么32位的单片机比8位单片机的处理和运算能力强,就是这个原因。

【14.4   什么是字节?】

       什么是字节?字节是计算机很重要的一个基本单位,一个字节有8位。8个LED灯挨着能代表多少种状态,就意味着一个字节的数据范围有多大。从上面举的例子中,我们知道8个LED灯挨着,能表示从0到255种状态,所以一个字节的取值范围就是从0到255。

【14.5   三种常用变量的取值范围是什么?】

       前面章节曾提到三种常用的变量:unsigned char,unsigned int ,unsigned long。现在有了二进制和字节的基础知识,就可以跟大家讲讲这三种变量的取值范围,而且很重要,这是我们写单片机程序必备的概念。
       unsigned char的变量占用1个字节RAM,共8位,根据前面LED灯的例子,取值范围是从0到255。
       unsigned int的变量占用2个字节RAM,共16位,根据前面LED灯的例子,取值范围是从0到65535。多说一句,对于51内核的单片机,unsigned int的变量是占用2个字节。如果是在32位的stm32单片机,unsigned int的变量是占用4个字节的,所以不同的单片机不同的编译器是会有一些差异的。
       unsigned long的变量占用4个字节RAM,共32位,根据前面LED灯的例子,取值范围是从0到4294967295。

【14.6   例程练习和分析。】

       现在我们编写一个程序来验证unsigned char,unsigned int,unsigned long的取值范围。
       定义两个unsigned char变量a和b,a赋值255,b赋值256,255和256恰好处于unsigned char的取值边界。
       再定义两个unsigned int变量c和d,c赋值65535,d赋值65536,65535和65536恰好处于unsigned int的取值边界。
       最后定义两个unsigned long变量e和f,e赋值4294967295,f赋值4294967296,4294967295和4294967296恰好处于unsigned long的取值边界。
       程序代码如下:

/*---C语言学习区域的开始。-----------------------------------------------*/

void main() //主函数
{
        unsigned char a;   //定义一个变量a,并且分配了1个字节的RAM空间。
        unsigned char b;   //定义一个变量b,并且分配了1个字节的RAM空间。
        unsigned int c;    //定义一个变量c,并且分配了2个字节的RAM空间。
        unsigned int d;    //定义一个变量d,并且分配了2个字节的RAM空间。
        unsigned long e;   //定义一个变量e,并且分配了4个字节的RAM空间。
        unsigned long f;   //定义一个变量f,并且分配了4个字节的RAM空间。

        a=255;         //把255赋值给变量a,a此时会是什么数?会超范围溢出吗?
        b=256;         //把256赋值给变量b,b此时会是什么数?会超范围溢出吗?
        c=65535;       //把65535赋值给变量c,c此时会是什么数?会超范围溢出吗?
        d=65536;       //把65536赋值给变量d,d此时会是什么数?会超范围溢出吗?
        e=4294967295;  //把4294967295赋值给变量e,e此时会是什么数?会超范围溢出吗?
        f=4294967296;  //把4294967296赋值给变量f,f此时会是什么数?会超范围溢出吗?

        View(a);   //把第1个数a发送到电脑端的串口助手软件上观察。
    View(b);   //把第2个数b发送到电脑端的串口助手软件上观察。
        View(c);   //把第3个数c发送到电脑端的串口助手软件上观察。
    View(d);   //把第4个数d发送到电脑端的串口助手软件上观察。
        View(e);   //把第5个数e发送到电脑端的串口助手软件上观察。
    View(f);   //把第6个数f发送到电脑端的串口助手软件上观察。

    while(1)  
    {
    }
}

/*---C语言学习区域的结束。-----------------------------------------------*/


在电脑串口助手软件上观察到的程序执行现象如下:

开始...

第1个数
十进制:255
十六进制:FF
二进制:11111111

第2个数
十进制:0
十六进制:0
二进制:0

第3个数
十进制:65535
十六进制:FFFF
二进制:1111111111111111

第4个数
十进制:0
十六进制:0
二进制:0

第5个数
十进制:4294967295
十六进制:FFFFFFFF
二进制:11111111111111111111111111111111

第6个数
十进制:0
十六进制:0
二进制:0


分析:        
        通过实验结果,我们知道unsigned char变量最大能取值到255,如果非要赋值256就会超出范围溢出后变成了0。unsigned int变量最大能取值到65535,如果非要赋值65536就会超出范围溢出后变成了0。unsigned long变量最大能取值到4294967295,如果非要赋值4294967296就会超出范围溢出后变成了0。

【14.7   如何在单片机上练习本章节C语言程序?】

        直接复制前面章节中第十一节的模板程序,练习代码时只需要更改“C语言学习区域”的代码就可以了,其它部分的代码不要动。编译后,把程序下载进带串口的51学习板,通过电脑端的串口助手软件就可以观察到不同的变量数值,详细方法请看第十一节内容。

使用特权

评论回复
118
XQ2013| | 2016-4-19 13:28 | 只看该作者
ding

使用特权

评论回复
119
whyuansy00983| | 2016-4-20 21:42 | 只看该作者
继续关注

使用特权

评论回复
120
Jordon138| | 2016-4-21 17:02 | 只看该作者
路过了,顶一下,充满正能量的电工,有你中国有希望!!!!!

使用特权

评论回复
发新帖 本帖赏金 72.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则