本帖最后由 吴坚鸿 于 2016-1-17 11:32 编辑
前言:
前言_pdf文件.pdf
(30.44 KB, 下载次数: 729)
2016-1-17 11:30 上传
点击文件名下载附件
大家好,我叫吴坚鸿,以前一直想写两本书,一本讲单片机入门基础,一本讲单片机程序框架。现在发现,单片机基础和程序框架并没有明显的分水岭,基础中有框架,框架中有基础,应该合二为一,读起来才会连贯舒畅。所以我决定中止当前已写到55节的
《从业十年,教你单片机入门基础》连载帖,新开此连载帖。
再提一下我2014年写的
《从业将近十年,手把手教你单片机程序框架》,一方面受到很多网友的好评,另一方面也有一些热心网友提出了宝贵的意见,我今天看来,确实还有一些可待改进的地方。本来计划在2017年重写《……单片机程序框架》那个老帖,现在看来不用那么折腾了,只要把《……单片机程序框架》的内容也整合到此新帖里就可以了,这样对我也比较省事。我的时间计划是,先花4年时间写一个初稿,然后再花2年时间重写一次,最后再花1年时间整理成书,整个过程大概7年时间左右,今年是2016年,估计到2023年左右《从单片机基础到程序框架》的新书就可以出版了。
感谢各位朋友的支持。
第十一节:一个在单片机上练习C语言的模板程序。
【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[]="第N个数";
- 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.3.2.jpg (94.47 KB, 下载次数: 0)
下载附件
2016-3-27 23:44 上传
图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格式的烧录文件。用这种方法的时候,建议大家做好每个程序代码的备份。每完成一个项目的小进度,都要及时把源代码存储到电脑硬盘里,电脑硬盘里每个项目对应一个项目文件夹,每个项目文件夹里包含很多不同版本编号的源代码文件,每个源代码文件名都有流水编号,方便识别最新版本的程序,每天下班前都要把最新版本的源代码文件上传到自己的 {MOD}里备份,在互联网时代,把源代码存到自己的 {MOD},可以随时异地存取,即使遇到电脑故障损坏也不担心数据永久丢失。
【11.5 编辑源代码的5个常用快捷键。】
介绍一下常用的快捷键,好好利用这5个快捷键,会让你在编辑源代码时效率明显提高。
(1)选中整篇所有的内容:组合键Ctrl+A。
(2)把选中的内容复制到临时剪贴板:组合键Ctrl+C。
(3)把临时剪贴板的内容粘贴到光标开始处:组合键Ctrl+V。
(4)把选中的一行或者几行内容整体往右边移动:单键Tab。每按一次就移动几个空格,很实用。
(5)把选中的一行或者几行内容整体往左边移动:组合键Shift+Tab。每按一次就移动几个空格,很实用。
楼主的心性是我最为敬佩的!加油!
第十二节:变量的定义和赋值,本作品的印刷出版权也无偿捐给全社会。
【12.0 本作品的印刷出版权也无偿捐给全社会。】
之前我在第七节里曾说过,要把51学习板的版权捐赠给全社会,但是那时我说要保留印刷出版权,今天我干脆决定把本作品本稿件的印刷出版权也无偿捐给全社会,把它变成公共资源,我不保留我的个人版权,任何出版社都可以不经过我的同意直接印刷出版此书,不用给我版税。版权捐赠给全社会的声明如下:
我决定把本作品本稿件的版权无偿捐给全社会,把它变成公共资源,我不保留我的个人版权,任何出版社都可以不经过我的同意直接印刷出版此书,不用给我版税。本书本连载教程的pdf电子档稿件也无偿捐给全社会,我不保留我个人版权。此书配套的51学习板版权也直接无偿捐给全社会,任何学习板厂家或者个人都可以不经过我的同意直接生产和销售此51学习板,我不追究版权责任。我希望在我有生之年尽我个人的努力为社会为行业为初学者做点小贡献就知足了。
因为本论坛在发帖24小时之后不能再编辑修改内容,所以不得不借此篇幅声明一下我的新决定。
【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,十六进制的内容后续章节会讲到)。
一周热门 更多>