第一章第第一一章章第一章CCCC语言语语言言语言 第一节第第一一节节第一节为何为为何何为何《《《《实例浅析实实例例浅浅析析实例浅析》》》》选择选选择择选择CCCC语言作为语语言言作作为为语言作为单片机开发语言单单片片机机开开发发语语言言单片机开发语言???? 原因至少包以下三项:(1)用机器语言开发单片机难度很大(2)用汇编语言开发单片机难度不小(3)C编译器越来越强大 第二节第第二二节节第二节打好打打好好打好CCCC语言基础语语言言基基础础语言基础 作为单片机初学者,很有必要把C语言的基础知识掌握牢固。如果您觉得您已经很精通C语言,那么请解释下面两个问题:(1)1个变量a和只有1个元素的数组a[1]有什么区与联系?(2)定义了一个数组a[5],而假如在程序中用到了a[9]或者a[-3],为什么并不是所有的编译器都认为这是错误的,有的编译器甚至不会给出警告? 第三节第第三三节节第三节《《《《CCCC总结总总结结总结》》》》 见附录。 第二章第第二二章章第二章中断中中断断中断 第一第第一一第一节节节节中断识方式有哪两种中中断断识识方方式式有有哪哪两两种种中断识方式有哪两种???? 查询中断和向量中断。 第二节第第二二节节第二节中断控制寄存器中中断断控控制制寄寄存存器器中断控制寄存器IEIIEEIE IE的部分位的义如下:EA:允许/禁止全部中断ES:允许/禁止串行中断响应ET0:允许/禁止Timer0溢出中断响应ET1:允许/禁止Timer1溢出中断响应可见,51单片机的中断响应为两级控制。EA为总的中断响应控制位。 第三章第第三三章章第三章定时定定时时定时////计数器与串行通信接口计计数数器器与与串串行行通通信信接接口口计数器与串行通信接口 第一节第第一一节节第一节定时定定时时定时////计数器工作方式计计数数器器工工作作方方式式计数器工作方式和和和和 工作方式寄存器TMOD_________________________________________________________________________________________GATECTM1M0GATECTM1M0_________________________________________________________________________________________M1M定时/计数方式1计数范围16位2进制数10定时/计数方式2计数范围8位2进制数(常用来产生串行通信波特率)_________________________________________________________________________________________定时/计数器控制寄存器TCONTR0定时/计数器0启/停控制位TR1定时/计数器1启/停控制位TF0定时/计数器0溢出中断请求标志位当主机响应中断进入中断服务程序,由内部硬件自动复位即:TF0=0TF1定时/计数器1溢出中断请求标志位当主机响应中断进入中断服务程序,由内部硬件自动复位即:TF1=0:若令ET0=1;EA=1;则程序中一定要存在Timer0的中断服务程序,否则主机无法找到Timer0的中断服务程序,主机就会跳转到不可预知的位置且内部硬件不能自动执行TF0=0。也就是说若允许某中断源响应中断,则程序中一定要存在相应的中断服务程序。 第二节第第二二节节第二节串行通信工串串行行通通信信工工串行通信工作方式作作方方式式作方式 串行接口控制寄存器SCON_________________________________________________________________________________________SM0SM1SM2RENTB8RB8TIRI_________________________________________________________________________________________REN允许/禁止串行接收控制位TI发送中断请求标志位,必须用软件复位即:TI=0RI接收中断请求标志位,必须用软件复位即:RI=0 电源控制寄存器PCON的最高位SMOD是波特率选择位。 :若允许串行中断源响应中断,则程序中一定要存在串行中断服务程序。:若进入中断服务程序,那么要先判断是RI=1还是TI=1,再进行相关操作。当然,如果整个程序只涉及到发送或只涉及到接收,是不用做上述判断的。但是无论什么情况,做上述判断是个好习惯:《MCS-51系列单片微型计算机及其应用(第三版)》第页说:由于接收通道内设有输入移位寄存器和SBUF缓冲器,主机应在该帧接收结束前从SBUF缓冲器中将数据取走,否则前一帧数据将丢失。SBUF以并行方式送往内部数据总线。 第四章第第四四章章第四章实例解析实实例例解解析析实例解析 第一节第第一一节节第一节要求要要求求要求编写时钟程序编编写写时时钟钟程程序序编写时钟程序,,,,实现下图实实现现下下图图实现下图的仿真的的仿仿真真的仿真效果效效果果效果 第二节第第二二节节第二节如何提高如如何何提提高高如何提高论文被杂志社录取论论文文被被杂杂志志社社录录取取论文被杂志社录取的的的的概率概概率率概率???? 这是来自《虚拟机的设计与实现》([美]BillBlunden)的一段文字(有改动):即使某位教授发现的东西用很简单的话就能解释清楚,他也经常啰里啰嗦地写出一篇很长的论文,里面还会提到很多深奥的技术,使他的发现看起来比实际情况复杂的多。这是一种比较普遍的社会现象,因为人们往往有这样一种思维定势:如果某个想法能够用很简单的道理解释清楚,它的重要性或独创性就会大打折扣。要想给订阅科技刊物的人们留下深刻的印象,就必须把问题地解释隐藏在层层迷雾之中。对想保住自己饭碗的教授来说,这无可厚非,但用C语言编程的单片机初学者可千万不能这样做。简洁比性能更重要。我希望自己编写的代码更便于维护。我希望自己在一年之后还能毫不费力地对现在的代码修改而不用找一群考古学家帮忙。 第三节第第三三节节第三节设计方案设设计计方方案案设计方案 编写程序代码之前要做一些准备工作。这里所谓的准备工作,指的就是设计方案。可以用流程图或其他方式将设计方案得以体现。这就类似于建造一幢大楼之前,设计师要画大楼图纸。作为一名单片机初学者,设计方案时最需要注意的问题是什么呢?本章的第二节似乎已经给出了一个很不错的回答。那么,现在开始设计方案吧!!当然,一定会存在多种方案,这就需要做出选择了!!作者选择的方案用流程图表示为: 现在开始考虑方案中的一些细节了!!(1)定时1秒的时间间隔。这个应该不难做到吧,就等同于让灯1秒钟闪1次。(2)更新时钟。这个就更简单了,知道“时、分、秒”之间的关系就能OK。(3)发送时钟。这个会涉及到ASCII码和字符之间的转换。这是因为单片机通过串口发送的数据是ASCII码,而在本章第一节的图中的“时钟数据”是用字符显示的。 细节OK之后,到了编写程序代码的时候了!!在编写程序代码前,请先看如下一些观点:(1)如果编写程序代码共用10小时,那么设计方案至少用3小时。(2)为变量和函数起个好名字是非常重要却不容易做好的事情。(3)一个函数只做一件事情。(4)每编完一个函数就要对其进行测试,而不是在整个C程序编完之后才进行测试。您同意以上这些观点么?:《C程序设计(第二版)》(谭浩强)第50页说:字符数据以ASCII码存储。:ASCII码与字符的对应关系ASCII码字符48‘0’49‘1’50‘2’51‘3’52‘4’53‘5’54‘6’55‘7’56‘8’57‘9’58‘:’ 换行字符为‘\n’空字符为‘’ 根据ASCII码与字符的对应关系可以看出:‘0’=48;‘1’=49; ‘2’=50;‘3’=51;…………….也可以换一种方式表示,‘1’=1+‘0’;‘2’=2+‘0’;‘3’=3+‘0’;…………….:下一节的程序代码中的“变量和函数的命名规则”局部变量名:首个单词首字母小写,余者首字母大写。如interruptCount(中断次数)全局变量名:每个单词的首字母皆大写,且单词之间用下划线隔开。如Time_Interval_OK(时间间隔到)。无返回值函数名:函数名为动宾词组。每个单词的首字母皆大写,且单词之间用下划线隔开。如Update_Clock(更新时钟)。 第四节第第四四节节第四节编写代码编编写写代代码码编写代码 (1)实现第一个细节:定时1秒的时间间隔。用定时器0工作方式1编程,采用中断方式。晶振22.MHz。 写好代码后,要做什么呢?当时是调试了!看看是不是Time_Interval_OK每隔1秒为“真”一次。(2)实现第二个细节:更新时钟代码(略)(3)实现第三个细节:发送时钟代码(略)(采用查询中断方式) 下面给出实现三个细节(即:实现本章第一节仿真效果)的时钟程序源代码。 //建立头文件USER.H //开始写C源文件吧!! //到这里C源文件结束如果您确保已经彻底明白了上面的代码,那么请试着考虑这个问题——如何“修改时钟数据”?将单片机与PC通过串口线连接,让PC上的串口调试助手发送时钟数据,则单片机接收时钟数据。将上面代码中的子程序“初始化UART”中的表达式ES=0改为ES=1然后再添加“接收数据”子程序就可以了,“接收数据”子程序如下所示: 附录附附录录附录 《C总结》学习C一个月整,对某些内容进行了整理。对于自我理解的内容,都以作为标注。KRxv:标准本身和各个特定的编译器是解释语言的最终权威。ANSI标准的文档首先是写给编译器的编写者看的,因此,对程序员来说不一定最合适。控制流之选择控制if语句(KR55)控制流:控制语句执行次序。(KR55)复合语句:在语法上等价于单条语句,其内部可声明变量。对于表达式:用THQ的赋值表达式、逻辑表达式、关系表达式理论(MISRA20)在C标准中,条件语句需要的是布尔值。(MISRA21)规则13.1赋值表达式不能用在需要布尔值的地方,防止“=”与“==”混淆。eg:INT8Ux;INT8Uy;If(x=y){foo();}应为:INT8Ux;INT8Uy;x=y;if(x!=0){foo();}(MISRA21)规则13.2(推荐)判断一个值是否为0应该是显式的,除非该操作数是一个布尔值。(MISRA23)规则14.9if语句后面必须是一个复合语句,else后面必须是一个复合语句或另一个if语句。(MISRA24)规则14.10if…else结构必须由一个else结束。除非只有一个if语句,else是不必须的。(THQ94)else总是与它上面最近的配对的if配对。Switch语句(THQ99)switch后面括弧内的“表达式”,ANSI标准允许它为任何类型。switch后的括弧内表达式,ANSI标准允许它为任何类型,但是表达式的值是有制的,如:若表达式的值是浮点数,则在VC++6.0环境下验证不通过。KR48说:每一个分支都由一个或多个整数值常量或常量表达式标记。(THQ99)case后面常量表达式的值与switch后括弧表达式相等时,就执行case后面的语句。(THQ99)case常量表达式只起到语句标号作用。(KR48)switch(表达式){case常量表达式:语句序列case常量表达式:语句序列default:语句序列}若没有default分支也没有其他分支与表达式匹配,则switch语句不执行任何动作。各分支及default分支的排列次序是任意的。各分支表达式必须互不相同。(KR49)作为一种良好的程序设计风格,在switch语句最后一个分支(即default分支)的后面也加上一个break语句。(MISRA23)规则15.2所有非空的switch子句都应该用break语句结束。(MISRA23)规则15.3switch的最后一个子句必须是default子句,若default中没有任何语句,那么应注释为什么没有任何操作。(MISRA23)规则15.4switch表达式不能是一个有效的布尔值。下列代码是不允许的: switch(x==0){…..}控制流之循环控制(KR49)while(表达式)语句(KR49)for(表达式1;表达式2;表达式3)语句等价于下列while语句:表达式1;while(表达式2){语句表达式3;}与if一样,while的表达式和for的表达式2要判断的是“真”或“假”,即判断布尔值。(KR49)若for语句省略表达式2,则for(;;){….}是一个无限循环语句。(KR50)牵强地把一些无关的计算放到for语句的初始化和变量递增部分是一种不好的程序设计风格。(MISRA22)规则13.5for语句3个表达式只能和循环控制相关。即第一个表达式只能为循环变量赋初值,第二个表达式只能进行循环条件判断,第三个表达式只能进行循环变量增(减)值。(THQ)while语句的主体必须为复合语句,否则while语句范围只到while后面的第一个分号处。(KR23)规则14.8也如此说。(KR23)规则14.8for语句主体必须是复合语句。否则,for只执行第一条语句(在VC++6.0下验证)。break语句、continue语句、return语句(KR53)break语句能使程序从switch语句或最内层循环中立即跳出。(MISRA24)规则14.5不允许使用continue语句。(MISRA24)规则14.6循环体中最多只能出现一个break语句用于结束循环。(MISRA24)规则13.3不允许对浮点数进行相等或不相等的比较,即使是非直接的比较也是不允许的。(KR18)我们在以后的main函数中包return语句,以提醒大家注意,程序还要向执行环境返回状态。(KR19)函数原型中的参数名是可选的。但是合适的参数名起到很好的说明性作用。(THQ)若调用函数中没有return语句,并不带回一个确定的、用户所希望的函数。但是实际上并不是不带回值,而只是不带回有用的值,带回的是一个不确定的值。(THQ)为明确表示“不带回值”,可以用void定义。(KR59)当return语句后面没有表达式时,函数将不向调用者返回值。当被调用函数执行到最后的右花括号而结束执行,控制同样也会返回给调用者(不返回值)。如果某个函数从一个地方返回时有返回值,而从另一个地方返回时没有返回值,该函数并不非法,但可能是一种出问题的征兆。正像(MISRA24)规则14.7函数只能有一个出口,这个出口必须在函数末尾。预处理之宏定义(MISRA30)规则19.7(推荐)应优先考虑使用函数,而非函数式宏定义。注意宏定义次序。(KR76)仔细考虑max的展开式就会发现它存在一些缺陷。(THQ-)宏名一般用大写字母表示;宏名代替字符串,只是置换,不作正确性检查,不分配内存;#undef作用域到本源文件结束。若定义#undef,则到#undef行终止。“”内的内容不被宏名置换。宏定义应放在程序中替换处之前,正像函数原型声明一样。预处理之文件包与条件编译(THQ)头部文件用“.c”作后缀或没有后缀也是可以的,但是用“.h”作后缀更能表示此文件的性质。(THQ)(2)#ifndef标识符程序段1#else 程序段2#endif要注意文件包次序。数据类型之变量(KR27)由于库例程的名字通常以下划线开头,因此变量名不要以下划线开头。大写字母与小写字母是有区的。(KR27)对于内部变量而言,至少前31个字符是有效的。对于外部名,ANSI标准仅保证前6个字符的唯一性,并且不区分大小写。(THQ40)建议变量名的长度不要超过8个字符。(THQ)(作用域)全局变量:有效范围从定义位置开始到本源文件结束。(作用域)局部变量:只在本函数内有效。形式参数也是局部变量。(THQ)要限制使用全局变量。(KR63)使用外部变量可能对程序结构产生不良影响,而且会导致程序中各个函数之间具有太多的数据联系。(THQ)(生存期)静态存储方式:全局变量、static局部变量(生存期)动态存储方式:函数的形式参数、auto局部变量要在显性初始化和未显性初始化初始值时,注意“编译”的情况。下面2条是关于外部变量的文件范围与内外函数的文件范围的归纳。(THQ)用extern声明外部变量(1)在一个文件内声明外部变量;在多文件程序声明外部变量(2)用static声明外部变量。(THQ)内部函数与外部函数(1)static(2)extern(THQ)请注意用auto、static声明局部变量时,是在定义变量的基础上加上这些关键字的,不能单独使用。上条中“局部”二字为自我添加。变量初始化(THQ53)初始化不是在编译阶段完成的(只有第八章介绍的静态存储变量和外部变量的初始化是在编译阶段完成的),而是在程序运行时执行本函数时赋予初值的。KR72说对于外部变量和静态变量来讲,初始化表达式必须是常量表达式,且只初始化一次(从概念上讲是在程序开始执行前进行初始化)。Keil可以实现外部变量的初始化仿真(且必须满足KR72说的初始化表达式必须是常量表达式),但Keil无法仿真静态变量的初始化。Keil对外部变量和局部变量的编译检查不按一个标准进行。eg:外部变量i;inti;I=3;则Keil编译器出现如下信息:errorc:‘i’redefination这正与KR72说的一致。而在VC++6.0环境下竟然编译通过了。可见,各个编译器的编译标准是不一样的,并不是所有的编译器编写者都完全按照ANSI的标准编写编译器。函数(KR57)编译与加载的具体实现细节在各个编译系统中各不相同。(THQ)在调用函数时将实参的值赋给形参(如果形参是数组名,则传递的是数组的首地址而不是数组的值)。(THQ)实参与形参类型应相同或赋值兼容。(THQ)应做到函数类型与return返回值类型一致。(THQ)调用形式如下,函数名(实参表列);但应说明,如果实参表列包多个实参,对实参求值的顺序并不确定。(THQ)编译是从上到下逐行进行的。 (MISRA24)规则14.7函数只能有一个出口,这个出口必须在函数尾。(MISRA28)规则16.1不允许定义函数数量是不确定的数。(MISRA28)MISRA从系统安全角度考虑,选择了最为安全的做法,不准使用递归调用。(THQ)编译系统根据函数原型对函数调用的合法性进行全面检查。(KR)参考手册:函数可返回算数类型、结构、联合、指针或void类型值,但是那是不能返回函数或数组类型。(KR)参考手册:函数声明中的声明必须是显式指定所声明的标识符具有函数类型。特是,不能通过typedef定义函数类型。(THQ)在内存中,实参单元与形参单元是不同的单元。数据类型之整型数据(THQ41)整形变量整形数据在内存中的存储方式为原码(补码形式表示):整形数据在内存中的存储方式ANSI标准规定的最小取值范围:[signed]int16-~unsignedint~(THQ44)整型数据的溢出一个char型变量最大允许值为,若再加1,会怎么样?0111+1=而-的补码为这就是溢出。就像汽车里程表一样,达到最大值又从最小值开始计数。数据类型之字符型数据(THQ48-50)(字符常量)编译器都规定以一个字节来存放一个字符。(字符常量)C的字符常量是用单撇号括起来的一个字符,如a,X,D,?等。(字符常量)特殊形式的字符常量(字符串常量)用双撇号括起来的字符表列,如“Howareyou”(字符串常量)C规定:每一个字符串尾加一个结束标志以便系统判断字符串是否结束。eg一个字符串“CHINA”则内存中CHINA\0如果想让一个字符串放到变量里,必须用字符数组。(字符变量)字符存储方式:将字符的ASCII码放到内存中,以二进制形式存放。常用的运算符的表达式(THQ48-50)赋值部分赋值符号“=”就是赋值运算符。赋值表达式:赋值表达式的值就是被赋值变量的值。eg:“a=5”这个赋值表达式的值为5。再eg:“a=(b=4)+(c=6)”这个表达式的值为10。(THQ56)算数部分C语言规定了运算符的优先级与结合性。算术运算符的结合性为自左向右。++和--结合方向为自右向左。eg:printf(“%d”,-i++)设i原值为3,则先取出i的值为3,输出–i值为-3,然后i增值为4。(THQ90)在逻辑表达式的求解中,并不是所有的逻辑运算符都被执行。(THQ58)ANSIC并没有具体规定表达式的子表达式的求值顺序,允许各编译系统自己安排。eg:a=f1()+f2()类似问题还有,例如:在调用函数时,对于实参数的求值顺序,C标准并没有统一的规定。eg:printf(”%d,%d”,i,i++) 数组定义之初始化 一维数组:inta[10]={0,1,2,3,4,5,6,7,8,9};二维数组:inta[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};字符数组:charc[]={“Iamhappy”};//数组长度为11。(THQ)需要说明的是:字符数组并不要求它的最后一个字符\0,甚至可以不包括\0。像以下这样写是完全合法的。Charc[5]={C,H,I,N,A};(KR83)声明inta[10];定义了一个由10个对象组成的集合,这10个对象存储在相邻区域中。(THQ)数组中的每一个元素都属于同一个数据类型。指针(THQ)int*pointer_1,*pointer_2;左端int为定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定该指针变量可以指向的变量的类型。(THQ)*a与a等价。(THQ)C语言规定数组名代表数组的首地址。(KR84)数组名所代表的就是该数组最开始的一个元素的地址。(MISRA10)指针类型转换是个高风险的操作,所以应该尽量避免进行这个操作。规则11.4(推荐)指向不同数据类型的指针不能相互转化。(MISRA11)ISOC标准中,仅对数组成员的指针运算(包括算数运算、比较等)做了规范定义,除此之外的指针运算属于定义范围,具体实现有赖于具体编译器,其安全性无法得到保证。规则17.1只有指向数组的指针才允许进行算术运算规则17.2只有指向同一个数组的两个指针才允许用,=,,=等关系运算符进行比较规则17.4只允许用数组索引做指针运算(KR81)由于指针也是变量,所在程序中可以直接使用。eg:int*ip;如果iq是另一个指向整型的指针,那么语句iq=ip将把ip中的值拷贝到iq中。这样指针iq也将指向ip指向的对象。结构体.(MISRA14)规则18.1所有结构和联合体的定义必须保持完整性。(THQ)将有联系的不同类型数据组合成结构体。(THQ)声明结构体类型不分配内存单元;定义结构体变量分配内存单元。(THQ)只能对最低级的成员进行赋值或存取的运算。(MISRA)structdate{intmonth;intday;intyear;}date1,date2;位运算(MISRA75)移位运算并不能改变原变量本身。(KR39)对unsigned类型的无符号数进行右移位时,左边空出的部分将用0填补;当对signed类型的带符号值进行右移时,某些机器将对左边空出的部分用符号位填补(即算数右移,而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。keil有符号位时为算数右移。VC++6.0亦如此。 白癜风专科哪里最好白癜风早期症状转载请注明原文网址:http://www.gzdatangtv.com/bcyyys/5455.html |