2 MSP4 3 0单片机原理与言基础
MSP430系列超低功耗单片机有200多种型号,TI公司用3~4位数字表示其型号。其中第一位数字表示大系列,如MSP430F1xx系列、MSP430F2xx系列、MSP430F4xx系列、MSP430F5xx系列等。在每个大系列中,又分若干子系列,单片机型号中的第二位数字表示子系列,一般子系列号数越大,所包含的功能模块越多。最后1~2位数字表示存储容量,数字越大表示RAM和ROM容量越大。MSP430家族中还有针对热门应用而设计的一系列专用单片机,如MSP430FW4xx系列水表专用单片机、MSP430FG4xx系列医疗仪器专用单片机、MSP430FE4xx系列电能计量专用单片机等。这些专用单片机都是在同型号的通用单片机上增加专用模块而构成的。最新的MSP430型号列表可以通过TI公司网站下载。
在开发单片机应用系统时,第一步就是单片机的选型,选择合适的单片机型号往往能事半功倍。单片机选型基本方法是选择功能模块最接近项目需求的系列,然后根据程序复杂程度估算存储器和RAM空间,并留有适当的余量,最终决定选用的单片机型号。
本章以MSP430F249单片机为学习目标,介绍单片机的基本结构和工作原理,读者可以举一反三、触类旁通,而不必每种型号都去学习却无法深入掌握。
2.1 MSP430F249单片机基本结构与原理
2.1.1 MSP430F249的主要结构特点
●供电电压范围1.8 V~3.6 V。
●超低功耗:活动状态为270μA(1 MHz,2.2V);待机模式为0.3μA;关机模式为0.1μA。
●16位RISC精简指令集处理器。
●时钟系统:有多种时钟源,可灵活使用。时钟频率达到16 MHz;具有内部振荡器;可外接32 k Hz低频晶振;外接时钟输入。
●12位A/D转换器,内部参考电压,采用保持电路。
●16位定时器A,3个捕获/比较寄存器。
●16位定时器B,7个捕获/比较寄存器。
●4个通用串口:USCI_A0和USCI_A1、USCI_B0和USCI_B1(I2C、SPI)。
●60kB+256B的Flash程序存储器,2kB的RAM数据存储器。
●64引脚QFP封装。
MSP430F249单片机的芯片封装形式如图2.1所示,各引脚的功能描述如表2-1所列。
图2.1 MSP430F249单片机封装形式
2.1.2 MSP430F249单片机的基本结构
MSP430F24x系列单片机功能结构示意图如图2.2所示。
1.CPU简介
MSP430单片机的CPU为16位RISC精简指令集的处理器,只有27条正交汇编指令和7种寻址方式。RISC处理器基本上是为高级语言所设计的,编译程序对正交指令系统很容易做到最优化,利于产生高效紧凑的代码。MSP430单片机的CPU中集成了16个16位通用寄存器R0~R15,其中R0~R3分别复用为程序指针PC、堆栈指针SP、状态寄存器SR和常数发生器CG1/CG2。这些寄存器之间的操作只需要一个CPU周期。
表2-1 MSP430F249单片机引脚功能
续表
图2.2 MSP430F24x系列单片机功能结构示意图
(1)程序计数器(PC指针)也就是CPU专用寄存器R0,PC指针是一个16位寄存器,可以寻址64kB的空间。MSP430单片机的指令长度以字(16位)为最小单位,而程序存储器单元以字节(8位)为单位,所以PC的值总是偶数。
(2)堆栈指针SP为CPU专用寄存器R1,SP指针为16位寄存器,也总是偶数的。堆栈是在片内RAM中实现的,通常将堆栈指针设置为片内RAM的最高地址加1。使用C语言编程时,集成编译软件IAR会自动设置堆栈指针初始值。对程序员来说无须关心细节,编译结束后在信息窗提示的编译结果会给出RAM使用量的大小,只要不超过RAM区实际容量并稍留余量给堆栈用即可。使用汇编语言编程时必须注意堆栈指针的正确设置,否则堆栈可能会覆盖变量区,导致程序出错。
(3)状态寄存器SR(见表2-2)和常数发生器CG1、CG2(见数据手册)。
表2-2 状态寄存器SR
V 溢出标志,当算术运算结果超出有符号数范围时置位。
SCG1 系统时钟控制位1,该位置位时关闭SMCLK。
SCG0 系统时钟控制位0,如果DCO未用作MCLK或SMCLK,该位置位时关闭DCO。
OSCOFF 晶振控制位,如果LFXT1未用作MCLK或SMCLK,该位置位时关闭LFXT1。
CPUOFF CPU控制位,该位置位时关闭CPU。
GIE 总中断允许位,该位置位时允许可屏蔽中断;复位时禁止所有的可屏蔽中断。
N 负数标志位,当运算结果为负时置位;否则复位。
Z 零标志位,当运算结果为零时置位;否则复位。
C 进位标志位,当运算结果产生进位时置位;否则复位。
2.片内存储器
MSP430单片机采用冯·诺依曼结构,程序存储器Flash、数据存储器RAM、特殊功能寄存器以及中断向量全部映射到64k B内部地址空间。MSP430不同型号单片机地址空间略有不同,MSP430F249的存储器结构如表2-3所示。
表2-3 MSP430F249存储器结构
1)数据存储区
MSP430F249的数据存储区RAM有2kB大小,地址范围0x0200~0x09FF。RAM为堆栈、全局变量和局部变量提供空间。使用C语言来开发项目,注意观察编译结束后在信息窗口中提示的RAM使用量的大小,只有不超过RAM区的实际容量并稍留余量即可。
2)引导区
引导区使得用户可以通过UART串口对MSP430单片机的程序存储器Flash或RAM区实现程序代码的写操作。详细内容参见TI公司的相关技术文档《Features of the MSP430 Bootstrap Loader》。
3)信息存储区
MSP430F249单片机有256B的信息存储区,它分为两段,每段128B。信息存储区用来存放那些掉电后需要保存的变量,一般用来保存项目的设定值或量程转换参数。Flash信息存储区只允许块擦除或写入操作,且有擦除次数的限制。需要频繁(几秒钟一次)擦除写入的变量,这些变量不能存放在信息存储区,这时可以外接铁电存储器EEPROM器件来保存这些变量。
4)程序存储区
MSP430F249单片机的程序存储区位于0x1100~0x FFC0,约60k B,程序存储区用于存放用户程序、常数以及表格等。程序存储区可以通过JTAG、BSL和ISP方式下载得到用户程序。
关于Flash存储器,我们介绍几个基本概念。Flash的结构决定了写操作只能将存储单元中的各比特位从1改写成0,而不能将0改写成1。所以Flash中每个单元可以一次性写入数据,数据一旦写入,在擦除前不能被再次改写。Flash可以被擦除,擦除后所有单元的比特位都恢复为1,但擦除操作只能针对整个段进行。所以在改写某单元之前,必须先擦除整个段。Flash存储器较适合做大批量连续数据存储,而且一般控制器都会提供连续写功能以提高速度。
在Flash中,将每次能擦除的最小区块单位称为“段”(Segment),将每次能连续写入的最大区块单位称为“块”(Block)。
MSP430单片机有五种低功耗模式,一种活动模式,如表2-4所示。任何一种低功耗模式只能与活动模式进行切换。
表2-4 工作模式表
3.单片机工作原理
单片机自动完成赋予它的任务的过程,也就是单片机执行程序的过程,即一条条指令的执行过程。所谓指令就是把要求单片机执行的各种操作,用命令的形式写下来,一条指令对应着一种基本操作。单片机所能执行的全部指令,就是该单片机的指令系统,不同种类的单片机,其指令系统亦不同。为使单片机能自动完成某一特定任务,必须把要解决的问题编成一系列指令(这些指令必须是选定的单片机能识别和执行的指令),这一系列指令的集合就成为程序。程序需要预先存放在具有存储功能的部件——存储器中。存储器由许多存储单元(最小的存储单位)组成,指令就存放在这些单元里。每一个存储单元有唯一的地址号,该地址号称存储单元的地址,这样只要知道了存储单元的地址,就可以找到这个存储单元,其中存储的指令就可以被取出,然后再被执行。
程序的执行通常是顺序的,所以程序中的指令也是一条条顺序存放的。单片机在执行程序时要能够把这些指令一条条取出并加以执行,必须有一个部件能追踪指令所在的地址,这一部件就是程序计数器PC(包含在CPU中)。在开始执行程序时,给PC赋以程序中第一条指令所在的地址,然后取得每一条要执行的命令,PC之中的内容就会自动增加,增加量由本条指令长度决定,以指向下一条指令的起始地址,保证指令顺序执行。
在程序顺序执行时,PC指针的内容自动增加,指向正在执行的指令的下一条指令;当发生中断或调用子程序时,当前的PC值被保存到堆栈,然后PC指针置入新的值(中断向量地址或子程序入口地址),程序的流动发生变化,执行完这些程序后,PC指针的值要恢复为堆栈中保存的旧的PC值,程序从断点处继续顺序执行。
2.2 MSP430单片机的C语言基础
C语言是一种结构化的高级语言,其优点是语言简洁、表达能力强、使用方便灵活、可读性好、可移植性强。C语言程序本身不依赖单片机硬件,如果更改工程项目中的单片机型号,对C语言程序稍加修改就可以进行程序移植,而且移植程序时不一定要求程序开发人员详细掌握新型号单片机的指令系统。
C语言程序的书写格式十分自由。一条语句可以写成一行,也可以写成几行;还可以在一行内写多条语句;但需要注意的是,每条语句都必须以分号“;”作为结束符。为了C语言程序能够书写清晰,便于阅读、理解和维护,在书写C语言程序时最好遵循以下规则。
(1)一个声明或一条语句占一行;
(2)不同结构层次的语句,从不同的起始位置开始,并且缩进相同的字数;
(3)用{}括起来的部分表示程序的某一层次结构。
目前有几种C编译器可以进行MSP430单片机程序开发,这些C编译器基本功能大致相同,但在某些细节上还是有所区别的。因此,当选择了某个C编译器后应该学习掌握相应的C编译器语言用法。本章将以IAR for MSP430编译器为例讲解C430程序设计的C语言基础。
2.2.1 C语言的标识符和关键字
1.关键字的用途
C语言的关键字的用途和说明如表2-5所示。
表2-5 C语言的关键字用途及说明
(1)C语言的标识符是用来标志源程序中某个对象名字的。这些对象可以是函数、变量、常量、数组、数据类型、存储方式、语句等。一个标识符由字符串、数字和下划线等组成,第一个字符必须是字母或下划线,通常以下划线开头的标识符是编译系统专用的,因此在编写C语言源程序时一般不要使用以下划线开头的标识符,而将下划线用作分段符。标识符的长度由系统决定,标识符最长可达255个字符,编写源程序时标识符的长度不要超过32个字符。
(2)关键字是一类具有固定名称和特定含义的特殊标识符,又称为保留字。在编写C语言源程序时一般不允许将关键字另作别用,换句话说就是标识符的命名不要与关键字相同。表2-5所列的C语言关键字由系统保留,不能用作用户标识符。
(3)程序中对于标识符的命名应当简洁明了,含义清晰,便于阅读理解,如用标识符“max”表示最大值,用“TIMER0”表示定时器0等。尽量不要取名“aa”、“bb”等没有特定意义的标识符,这样虽然没有违反C语言的规则,但是在程序里不容易理解。
(4)C语言区分大小写字母,C语言编译器在对程序进行编译时,对于程序中同一个字母的大小写作为不同的变量来处理。例如定义一个延时函数的形式参数time,但是如果程序当中再出现一个由大写字母定义的标识符TIME,那么它们在程序当中是两个不同的标识符,是没有冲突的。
(5)C语言程序中有且只有一个main函数,一个C语言程序,无论main函数的物理位置在哪里,总是从main函数开始执行。
(6)每句程序语句后面一定要加分号,分号是C语言结构的一部分,如果缺少就会语法出错。
(7)注释,在程序中添加注释是为了能更加容易读懂和理解程序,IAR有两种风格的注释方法:“//”和“/*……*/”。“//”的意思是在其后面的全部引导为注释;而“/*……*/”的意思是从“/*”开始,一直到“*/”为止的内容都被认为是注释。
2.变量类型
不同的C语言编译器中变量类型略有差别,表2-6列出了IAR for MSP430支持的变量类型。
表2-6 变量类型
float和double的指数位是按补码的形式来表示的,所以float的指数范围为-128~+127;而double的指数范围为-1024~+1023。float的范围为-2128~+2128,也即-3.4×10-38~+3.4×10-38;double的范围为-21024~+21024,也即-1.79×10-308~+1.79×10-308。
float和double的精度是由尾数的位数来决定的。float:223=8388608,一共7位,这意味着最多能有7位有效数字,float的精度为7位;double:252=4503599627370496,一共16位,double的精度为16位。
IAR for MSP430允许改变某些变量的特性。在打开工程后,选择菜单项Project \Options,在General Option\Target项中可以设置浮点数长度,Floating Point决定了double变量的字节数,默认是32 bit,可以设置为64 bit。在C/C++Compile\Language项中Plain char可以设置char是否等效为unsigned char。
3.变量定义
在变量定义中,增加某些关键字可以给变量赋予某些特殊性质。
const:定义常量。在C430语言中,const关键字定义的常量实际上放在程序存储器flash中,经常用const关键字定义显示表之类的常数数组。
extern:声明外部变量,外部变量是指在函数或文件外部定义的全局变量。使用时,extern置于变量或函数前,表示变量或函数的定义放在别的文件中,提示编译器在遇到此变量和函数时应在其他模块中寻找它的定义。
static:定义静态局部变量或静态函数,静态局部变量或静态函数只有本文件内的代码才能访问它,它的名字在其他文件中不可见。有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下次调用该函数时,该变量保留上一次函数调用结束时的值。这时就应该指定局部变量为静态局部变量。
volatile:定义“挥发性”变量。编译器将认为该变量的值会随时改变,对该变量的任何操作都不会被优化过程删除。volatile用在如下几个地方:中断服务程序中修改的供其他程序检测的变量需要加volatile;多任务环境下任务间共享的标志应该加volatile;存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
全局变量:只要定义在函数体(包括主函数)外,就是全局变量了,编译器为全局变量安排特定的数据区,这些数据区为全局变量专用。全局变量一般定义在C程序的开头部分、主函数之前,在与该程序有关的所有文件中都可以使用该变量。程序开始时分配空间,程序结束时释放空间,默认初始化为0。对于多文件C语言程序,如果全局变量定义在其他文件中,那么别的程序文件里面的函数要访问另一个文件里面的全局变量,须对全局变量进行外部变量声明,使用关键字extern。
局部变量:它是在一个函数内部定义的变量,它只在定义它的那个函数范围以内有效,在此函数之外即失去意义,因而也就不能使用这些变量了。不同的函数可以使用相同的局部变量名,由于它们的作用范围不同,不会相互干扰。函数的形式参数也属于局部变量。局部变量在每次函数调用时分配存储空间,在每次函数返回时释放存储空间。
静态局部变量:静态局部变量在函数内进行定义,但不像其他局部变量在调用时存在、退出函数时消失,静态局部变量始终存在着。静态局部变量的生存期为整个源程序执行期间,但是其作用域仍与局部变量相同,即只能在定义该变量的函数内使用;退出该函数后,尽管静态局部变量还继续存在,但不能使用它。静态局部变量有全局变量的优点,也有局部变量的优势。
全局变量和静态局部变量会在程序刚开始运行时进行初始化,也是唯一的一次初始化,默认初始化值为0。不过和全局变量比起来,static可以控制变量的可见范围。在同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
例如,要同时编译两个源文件,一个是a.c函数,另一个是main.c函数。a.c函数的内容为:
main.c函数的内容为:
程序的运行结果是:
A Hello
为什么在a.c函数中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其他的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其他源文件隐藏。例如在a.c函数中的a和msg的定义前加上static,在main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
2.2.2 C语言的运算符
1.运算符及其说明
C语言运算符的名称及符号如表2-7所示。
表2-7 C语言的运算符
(1)自增、自减运算符说明。
++i:i自增1后再参与运算。
--i:i自减1后再参与运算。
i++:i参与运算后,i的值再自增1。
i--:i参与运算后,i的值再自减1。
(2)复合赋值运算符说明。在赋值运算符“=”的前面加上其他运算符,就构成了所谓复合赋值运算符。
+=加法赋值,>>=右移位赋值,-=减法赋值,&=逻辑与赋值,
*=乘法赋值,|=逻辑或赋值,/=除法赋值,^=逻辑异或赋值,
%=取模赋值,~=逻辑非赋值,<<=左移位赋值。
采用这种复合赋值运算符,可以使程序简化,同时还可以提高程序的编译效率。例如,a+=b表示a=a+b;a-=b表示a=a-b;a*=b表示a=a*b;a/=b表示a=a/b;a%=b表示a=a% b。
2.运算符的优先级与结合性
运算符的运算优先级的操作符、功能及结合性如表2-8所示。
表2-8 运算符的优先级
续表
从表2-8可知,C语言中的运算符的运算优先级共分为15级。1级最高,15级最低。在表达式中,优先级较高的要比优先级较低的先进行运算。而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理。C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。需要时可在算术表达式中采用圆括号来改变运算符的优先级。
C语言中提供了一种用于求取数据类型、变量以及表达式的字节数的运算符为sizeof,该运算符的一般使用形式为:
sizeof(表达式)或sizeof(数据类型)应该注意的是,sizeof是一种特殊的运算符,不要错误地认为它是一个函数。实际上,字节数的计算在程序编译时就完成了,而不是在程序执行的过程中才计算出来的。例如:int a=sizeof(float),执行这条命令的结果是把4赋给了整型变量a,这意味着一个单精度数存储时占有4个字节内存。
3.运算应注意的问题
(1)尽可能避免浮点运算。对于单片机来说,浮点数的运算速度很慢,RAM开销也大,且有效位数有限;在低功耗应用中CPU运算时间直接关系到平均功耗。因此在编程初期就要养成尽量避免使用浮点数的习惯。(www.xing528.com)
(2)防止定点数溢出。定点数运算首先要防止数据溢出。例如:
long x;
int a;
x=a*1000;
虽然x是long变量,但a和常数1000都是int型,相乘结果仍然是int型。在a
>65的情况下,结果就会溢出。程序应该修改为:
x=a*(long)1000;或x=(long)a*1000;
若遇到多个变量相乘,更需要细心检查。所以,在测试每一段软件的时候,一定要取边界条件进行极限测试。
(3)小数的处理。遇到需要保留小数的运算,可以采用浮点数,但是软件开销较大。用定点数也可以处理小数,其原理就是先扩大,再运算。例如,我们需要计算温度并保留1位小数,假设温度计算公式为:
Deg_C=ADC*1.32/1.25-273
为了让小数1.32能被定点运算,先扩大100倍变成132,当然,除数1.25也要随之扩大100倍,公式变为:
Deg_C=(long)ADC*132/125-273
这样运算结果只能保留到整数,为了让结果保留1位小数,需要人为地将所有数值都扩大10倍,得到最终计算公式:
Deg_C=(long)ADC*1320/125-2730
假设温度应该是23.4度,上述公式的运算结果将是234。在显示的时候,将小数点添加在倒数第2位上,即可显示23.4。用定点数处理小数,如需要保留N位小数,就要将数值扩大10 N倍。注意防止溢出,且要记住每个数值所扩大的倍数,在程序中应添加注释。
(4)尽量减少乘除法。MSP430单片机没有乘法/除法指令,乘除操作会被编译器转换成移位和加法来实现。如果乘除的数值刚好是2的幂,那么可以用移位直接替代乘除法,运算速度会提高很多。例如对16次采样数据求平均,程序为:
对于除16写成如下形式,运行速度会提高很多:
若将编译器优化级别设置得比较高,在遇到乘除2的幂表达式时,编译器会自动用移位替代除法(编译器很聪明),从而加快执行速度。
位操作指令大部分存在于早期速度不高的CISC处理器上(以8051为代表),以提高执行效率,弥补CPU运算速度的不足。目前几乎所有的RISC型处理器都取消了位操作指令,MSP430单片机也不例外。在MSP430的C语言中,也不支持位变量,因为位操作完全可以由变量与掩模位(mask bits)之间的逻辑操作来实现。
例如将P2.0置高、将P2.1置低,将P2.2取反,可以写成:
在寄存器头文件中,已经将BIT0~BIT7定义成0x01~0x80,上述程序也可以写成:
对于多位可以同时操作,例如将P1.1、P1.2、P1.3、P1.4全部置高/低可以写成:
实际上,这条语句相当于:
对于读操作,也可以通过寄存器与掩模位(mask bits)之间的“与”操作来实现。例如有通过P1.5、P1.6端口控制位于P2.0端口的LED。下面代码示范读取P1.5端和P1.6的值:
另外还有一种流行的位操作写法,用(1<<x)来替代BITx宏定义:
这种写法的好处是使用纯粹的C语言表达式实现,不依赖于MSP430的头文件中BITx的宏定义,无需改动即可移植到任何其他单片机上,但可读性较差。
2.2.3 函数
1.函数的组成
C语言程序是由若干函数单元组成的,每个函数都是完成某个特殊任务的子程序段。组成一个程序的若干函数可以保存在一个源程序文件中,也可以保存在不同源程序文件中。文件名由程序设计人员根据某种规则自己确定,其扩展名统一为“.C”。一个完整的C语言程序应包含一个主函数main()和若干其他功能的函数。函数之间可以相互调用,但main()函数只能调用其他的功能函数,而不能被其他函数所调用。功能函数可以是C语言编译器提供的库函数,也可以是由用户按实际需要自行编写的函数。不管main()函数处于程序中的什么位置,程序总是从main()函数开始执行。一个函数必须预先定义或声明后才能调用。函数定义或声明位于源程序的预处理命令之后的开始位置。函数定义部分包括函数的存储类型、返回值数据类型、函数名、形式参数说明等,函数名后面必须跟一个圆括号(),形式参数说明在圆括号()内进行。函数也可以没有形式参数。函数的位置比较自由。函数由函数名和一对花括号“{}”组成,在“{}”里面的内容就是函数体,如果一个函数有多个“{}”,则最外面的一对“{}”为函数体的范围。
函数是C语言中的一种基本模块。在进行程序设计的过程中,如果所设计的程序较大,一般应将其分成若干子程序模块,每个子程序模块完成一种特定的功能。在C语言中,子程序是用函数来实现的。对于一些需要经常使用的子程序可以按函数来设计,以供反复调用。此外,EW430编译器还提供了丰富的运行库函数,用户可以根据需要随时调用。这种模块化的程序设计方法,可以大大提高编程效率。标准库函数见IAR for MSP430安装目录文件clib.pdf,路径D:\Program Files\IAR Systems\Embedded Workbench 6.0 Evaluation\430\doc\clib.pdf。
2.自定义函数
从用户的角度来看,有两种函数:标准库函数和用户自定义函数。标准库函数是IAR EW430编译器提供的,不需要用户进行定义,可以直接调用。用户自定义函数是用户根据自己的需要编写的、能实现特定功能的函数,它必须先进行定义然后才能调用。
1)函数的定义
无参数函数定义的语法格式为:
有参数函数定义的语法格式为:
其中,“函数类型”说明了自定义函数返回值的类型。
int compare(int a,int b)//函数头,括号里为形式参数
2)实参与形参的特点
(1)实参与形参在类型﹑数量﹑顺序上应保持一致,否则会在编译的时候出现警告或者程序运行的结果错误。
(2)被调用函数的形参只有被调用的时候才会被分配内存空间,退出了函数之后,所分配的内存单元立即被释放,所以退出了函数之后形参就不能再使用。
(3)实参在调用前一定要有确定的值,因此在函数调用前必须先赋予实参一个确定的值。
3)空函数
如果定义函数时只给出一对花括号{}而不给出其局部变量和函数体语句,则该函数为所谓“空函数”,这种空函数也是合法的。在进行C语言模块化程序设计时,各模块的功能可通过函数来实现。开始时只设计最基本的模块,其他作为扩充功能在以后需要时再加上。编写程序时可在将来准备扩充的地方写上一个空函数,这样可使程序的结构清晰,可读性强,而且易于扩充。
3.函数的调用
C语言程序中函数是可以互相调用的。所谓函数调用就是在一个函数体中引用另外一个已经定义了的函数,前者称为主调用函数,后者称为被调用函数。主调用函数调用被调用函数的一般形式为:
函数名(实际参数表);
其中,“函数名”指出被调用的函数。“实际参数表”中可以包含多个实际参数,各个参数之间用逗号隔开。实际参数的作用是将它的值传递给被调用函数中的形式参数。
1)调用方式
在C语言中可以采用三种方式完成函数的调用。
(1)函数语句。在主调用函数中将函数调用作为一条语句。
(2)函数表达式。在主调用函数中将函数调用作为一个运算对象直接出现在表达式中。
(3)函数参数。在主调用函数中将函数调用作为另一个函数调用的实际参数。
2)传递方式
在进行函数调用时,必须用主调用函数中的实际参数来替换被调用函数中的形式参数,这就是所谓的参数传递。在C语言中,对于不同类型的实际参数,有三种不同的参数传递方式。
(1)基本类型的实际参数传递。
当函数的参数是基本类型的变量时,主调用函数将实际参数的值传递给被调用函数中的形式参数,这种方式称为值传递。前面讲过,函数中的形式参数在未发生函数调用之前是不占用内存单元的,只有在进行函数调用时才为其分配临时存储单元。而函数的实际参数是要占用确定的存储单元的。
值传递方式是将实际参数的值传递到为被调用函数中形式参数分配的临时存储单元中,函数调用结束后,临时存储单元被释放,形式参数的值也就不复存在,但实际参数所占用的存储单元保持原来的值不变。这种参数传递方式在执行被调用函数时,如果形式参数的值发生变化,可以不必担心主调用函数中实际参数的值会受到影响。因此值传递是一种单向传递。
(2)数组类型的实际参数传递。
当函数的参数是数组类型的变量时,主调用函数将实际参数数组的起始地址传递到被调用函数中形式参数的临时存储单元,这种方式称为地址传递。地址传递方式在执行被调用函数时,形式参数通过实际参数传来的地址,直接到主调用函数中去存取相应的数组元素,故形式参数的变化会改变实际参数的值。因此地址传递是一种双向传递。
(3)指针类型的实际参数传递。
当函数的参数是指针类型的变量时,主调用函数将实际参数的地址传递给被调用函数中形式参数的临时存储单元,因此也属于地址传递。在执行被调用函数时,也是直接到主调用函数中去访问实际参数变量,在这种情况下,形式参数的变化会改变实际参数的值。
2.2.4 指针
1.指针的概念
指针是C语言中一个十分重要的概念,也是C语言的一个难点。可以说,只有精通指针的程序员才算真正懂得C语言。只有掌握指针,才能使程序变得更加简洁、紧凑、高效,指针可以说是C语言的全部精华的所在。初学者在开始学习时可能会有一点不习惯,但只要平时多思考、多上机,那么很快就可以掌握它了。
所谓指针就是指内存中的地址,它可能是变量的地址,也可能是函数的入口地址。如果指针变量存储的地址是变量的地址,则称为变量的指针,简称变量指针;如果指针变量存储的地址是函数的入口地址,则称为函数的指针,简称函数指针。
变量的指针就是该变量的地址,可以定义一个指向某个变量的指针变量。为了表示指针变量和它所指向的变量地址之间的关系,C语言提供了两个专门的运算符:用*取内容,用&取地址。
在C语言中,所有的变量在使用之前必须定义。指针变量在使用之前也要先定义说明类型,然后赋予具体的值(指针变量的值只能赋予地址),否则将引起错误。一个指针变量只能指向同一类型的变量。指针变量的定义与赋值程序为:
2.指针的运算
指针变量的算术运算主要有指针变量的自加、自减、加n和减n操作。
1)指针变量自加运算
指令格式:<指针变量>++;
指针变量自加运算并不是将指针变量值加1的运算,而是将指针变量指向下一个元素的运算。当计算机执行<指针变量>++指令后,指针变量实际增加值为指针变量类型字节数,即:<指针变量>=<指针变量>+sizeof(<指针变量类型>)。
假设数组a的首地址为1000,以下程序
第一条语句将数组a的首地址1000赋给指针变量p,使p=1000。第二条语句使p作自加运算:p=p+sizeof(int)=p+4=1004,使p指向下一个元素a[1]。
2)指针变量自减运算
指令格式:<指针变量>--;
指针变量的自减运算是将指针变量指向上一元素的运算。当计算机执行<指针变量>--指令后,指针变量实际减少为指针变量类型字节数,即:<指针变量>=<指针变量>-sizeof(<指针变量类型>)。
自加运算和自减运算既可后置,也可前置。
3)指针变量加n运算
指令格式:<指针变量>=<指针变量>+n;
指针变量的加n运算是将指针变量指向下n个元素的运算。当计算机执行<指针变量>+n指令后,指针变量实际增加值为指针变量类型字节数乘以n,即:
<指针变量>=<指针变量>+sizeof(<指针变量类型>)*n
4)指针变量减n运算
指令格式:<指针变量>=<指针变量>-n;
指针变量的减n运算是将指针变量指向上n个元素的运算。当计算机执行<指针变量>-n指令后,指针变量实际减少值为指针变量类型字节数乘以n,即:<指针变量>=<指针变量>―sizeof(<指针变量类型>)*n
2.2.5 预编译处理命令
1)预处理命令
C语言程序的开始部分通常是预处理命令,如程序中通常遇到的#include命令。这个预处理命令通知编译器在对程序进行编译时,将所需要的头文件读入后再一起进行编译。一般在“头文件”中包含程序在编译时的一些必要的信息,通常C语言编译器都会提供若干不同用途的头文件。头文件的读入是在对程序进行编译时才完成的。
预处理命令通常在程序编译时进行一些符号处理,其并不执行具体的硬件操作。C51语言中的预处理命令主要有宏定义指令、文件包含指令和条件编译指令,还有其他一些调试时使用的指令。本章将详细介绍各种预处理命令以及C51的用户配置文件,并结合一定的程序实例以加深理解。
预处理指令是以#号开头的代码行,#后是指令关键字,整行语句构成了一条预处理指令。该指令将在编译器进行编译之前对源代码进行某些转换。部分预处理指令和用途如表2-9所示。
表2-9 指令和用途
2)宏定义指令
宏定义指令是用一些标识符作为宏名来代替一些符号或者常量的命令。宏定义指令可以带参数,也可以不带参数。
#define命令用于定义一个“宏名”。其中“宏名”是一个标识符,在源程序中遇到该标识符时,均以定义的串的内容替代该标识符。
不带参数的宏定义,其一般形式如下:
#define标识符字符串
其中,#define是宏定义指令,标识符即宏名,字符串是被替换的对象。典型的宏定义指令示例如下:
#include预处理指令的作用是在指令处展开被包含的文件。文件包含指令通常在C程序的开头,将另外一文件的内容引入当前文件。其中被包含的文件通常是头文件、宏定义等,利用文件包含指令可以有助于更好地调试文件。
为了避免那些只能包含一次的头文件被多次包含,可以在头文件中用编译时的条件来进行控制。例如:
3)两种包含格式
在程序中包含头文件有两种格式:
#include<my.h>
#include″my.h″
第一种方法是用尖括号把头文件括起来,这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来,这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,再搜索编译器自带的头文件。
采用两种不同包含格式的理由在于,编译器是安装在公共子目录下的,而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。
4)条件编译指令
条件编译指令用于对程序源代码的各部分有选择地进行编译。采用条件汇编,可以提高程序的适用性,缩小目标代码的大小。在默认情况下,源程序中的所有行都要进行编译,但是有时需要某些语句行在条件满足的情况下,才进行编译,此时便用到条件编译指令。目前商业软件公司广泛应用条件编译来制作某个程序的许多不同用户版本。
#if、#else、#endif为条件编译指令,常数表达式为判断的条件,语句段为条件编译部分。执行过程为如果常量表达式为真,则编译其后面的语句段;如果常量表达式为假,则编译#else后面的语句段;#endif命令是一个条件编译的结束。
#ifdef与#ifndef命令用于判断宏名是否被定义,并根据判断的情况进行条件编译。
#pragma命令用于向编译程序传送控制指令。
定义中断函数,注意关键字#pragma和_interrupt的使用。
思考与练习
1.MSP430单片机有哪些型号?
2.MSP430F249的存储器结构如何分区?
3.MSP430F249单片机的基本结构与特点是什么?
4.复习C语言基础知识。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。