只要活着,就要有目标。
2013年(109)
分类: LINUX
2013-06-13 14:49:27
1、变量变量的类型决定其可带值的类型。也就是说,为变量选择一个类型与我们使用这个变量的方法直接相关。我们将学习C的基本类型、怎样写常量和声明这些变量。
1.1 选择一个类型“值集合”是有限的。C的整数类型不能代表所有整数;它的浮点类型也不能代表所有浮点数。当声明一个变量并为它选择一个类型,你应紧记你需要的值和操作。
1.2 C的基本数据类型ANSI标准并没为本地类型规定尺寸大小,但CodeWarrior规定了。C只有一些基本数据类型:
所有数量类型(除了char)缺省都是有符号的,例如:‘int’ = ‘signed int’。
注意:INT型的大小依赖于不同的机器。
1.3 CodeWarrior数据类型例如,按ALT+F7打开工程的通用设置,选择“Compiler for HC08”面板并点击类型尺寸。这个窗口向你显示CodeWarrior 编译器使用的标准类型设置。
所有基本类型可以改变,尽管这可能不是个好主意。
1.4 数据类型的事实代码大小和执行时间的最大节约可通过为变量选择最合适的数据类型得到。
8位微控制器内部的数据的长度是8位(一字节),然而C首选的数据类型是‘int‘。
8位机处理8位数据类型比16位类型效率更高。
“int“和大数据类型只有当所描述的数据的大小需要时才使用。
当效率非常重要时,双精度和浮点操作效率低,应当避免。
1.5 选择数据类型8位微控制器选择数据类型有3个规则:
1)用最可能小的类型来完成工作,大小越小占用存贮空间越少;2)若可能,用无符号类型;3)在表达式内声明以将数据类型减到最少需要。
使用类型定义得到固定大小:
1)根据编译器和系统而改变;2)移植到不同的机器代码不变;3)当值需要固定位时使用。
打开文件:Lab1-Variables.mcpMain函数内定义了三种不同类型的变量定义了一个数据类型的完整集合只写了意义最少的位;寄存器用于此目的每个变量剩余位用:clr ,x清变量在堆栈中有一个地址主函数外定义了三个不同类型的变量。
主函数外定义了三个不同类型的变量。
编译器为作用的变量保留了内存。本例中VarA是唯一所有声明的全局变量均被使用。
在这种情况下,编译器为所有变量保留了内存。
根据变量大小的不同,每个加操作用不同的方法完成。
变量声明的内存区,每个变量有不同的大小(1、2和4字节)2、存贮类修饰符以下关键字用于声明变量,以指定特定需要或内存中变量存贮的相关条件。
static volatile const这三个关键字,一起让我们不仅可写出好的代码,而且可写出紧凑的代码。
2.1 静态变量使用静态有二个主要功能:
第一个最常用的用法是定义一个变量,在函数连续调用期间,变量不会消失。
第二个使用静态的用法是限制变量的范围。在模块级定义时,能被整个模块中所有函数访问,不能被其它函数访问。这非常重要,因为当严格限制全局变量众所周知的问题时,它让我们获得所有全局变量执行性能的好处。因此,如果我们有必须被一些函数频繁访问的数据结构,就应当将函数放入同一模块中,并将结构声明为静态。这样所有函数能够访问而不必通过一个访问函数的上层,同时与数据结构无关的代码禁止访问它。这一技术是一种变通方法,立即可访问变量在小的机器上实质上取得了足够的性能。
声明模块级静态变量(与将其设为全局相反)能取得一些其他潜在的益处。静态变量由于定义,只能被一组特定的函数访问。因此,编译器和连接器能够明智地选择变量在存贮空间的放置。例如,对于静态变量,编译器/连接器也许选择将一个模块中所有静态变量放在连续的区域,这样增加了各种优化机会,例如用简单的增加或减少代替重载。相反,全局变量在存贮空间的位置通常计划于优化编译器的哈稀算法,这排除了可能的优化。
须着重指出, 这些变量不会存贮在堆栈中,因为它们必须保存其值。
下面给出一个静态变量怎样工作的例子:
FILE1.c#include
MyFunction(); //在FILE2.c中//第二次进入MyFunction之前,myVar=1。
MyFunction(); //在FILE2.c中}
FILE2.cvoid MyFunction (void){ //FILE2.C中定义//MyFunction函数static char myVar = 0; //本地变量//声明为staticmyVar = myVar + 1; //尽管myVar是本地变量,但它保持了自己的值。
}
2.2 静态函数一个静态函数只能被其所在模块中的其它函数调用。使用静态函数是结构化编程的好习惯。你也许惊讶地知道静态函数能产生小/快的代码。这是可能的,因为编译器在编译时确切地知道什么函数能调用一个给定的静态函数。因此,函数的相关内存区域能被调整,以致使用调用的一个短版本或跳转指令。潜在的改进甚至更好,编译器足够聪明地用跳转代替调用。
2.3 关键字“static”的使用在函数体声明静态的变量,在函数调用期间保持其质;在模块内声明静态的变量,(但在函数体之外)能被模块内所有函数访问;在模块内声明静态的函数,只能被模块内其它函数调用。
对于嵌入式系统:封装持续生存的数据(包装);模块化编码(数据隐藏);在每个模块中隐藏内部处理。(引自: 网)
2.4 可变(volatile)变量可变变量是其值在正常程序流程以外可能改变的变量。在嵌入式系统中,这种情况通过两种主要途径发生:
通过一个中断服务程序,或作为硬件动作的结果。例如,通过一个串口接收到一个字符,结果串口状态寄存器更新,这完全在程序流程之外发生。很多程序员知道编译器不会试图优化一个volatile寄存器,而宁可每次重载它。
在嵌入式设备中,将所有外设寄存器声明为volatile是一个好习惯。
许多编译器供应商经常炫耀他们的代码优化,它们通常非常好,它们有些根本不明显,但能极大地减少周期和内存。但有时我们不想编译器聪明和优化一个部份,因为我们确实需要代码那样作。
我们怎样才能达到呢?那么,访问定义为volatile的变量从不会被编译器优化。
让我们分析一个例子,看看编译器是怎样处理一个volatile和一个非volatile变量…volatile unsigned char PORTA @0x00;volatile unsigned char SCS1 @0x16;unsigned char value;void main(void){PORTA = 0x05; /* PORTA = 00000101 */PORTA = 0x05; /* PORTA = 00000101 */SCS1;value = 10;}
未使用Volatile关键字,编译器将其编译为:
MOV #5,PORTALDA #10STA @value使用Volatile关键字后,编译器将其编译为:
MOV #5,PORTAMOV #5,PORTALDA SCS1LDX #10STX @value这段代码实际上不做任何事,但它很好地表达了优化怎样强烈地影响程序的结果。在main()中连续两次使用语句:PORTA=5,这没有意义,但让我们假设这是正确开发程序所必须的…在这两个语句之后,明显地有一条无意义语句“SCS1;”。让我们看当不使用volatile变量会发生什么…我们得到了优化过的汇编代码。重复的语句Port A = 5消失了只剩下一句“move #5 to PortA”。语句“SCS1;”似乎什么都不做,因此聪明的编译器将它消去了。最后,将10加载到累加器并作为值存贮。
使用volatile关键字声明PORTA 和SCS1,得到的汇编代码没有优化,连续两次在Port A写入数值5,然后将SCS1加载到累加器。最后由于累加器被使用,于是用X寄存器存贮数值10。
好了,连续两次用数值5写PortA,假设这是需要这样做,但是加载SCS1到累加器有一个很有意义的值。这是串行通信接口SCI需要的,读SCS1寄存器目的是清除任何未决的标志。无意义的语句“SCS1;”被翻译为读寄存器的的汇编语句,这将清除SCI中未决的标志。
前面说过,在嵌入式设备中将所有外设寄存器声明为volatile是一个好习惯。在分开的头文件中定义所有外设的名字,能使所写代码更友好并使迁移简化。下面这个例子用volatile变量声明所有寄存器,这样做较妥当,因为任何这些寄存器能在任何时候在程序流程之外被修改。
/* MC68HC908GP20/32 Official Peripheral Register Names */volatile unsigned char PORTA @0x0000; /* Ports and data direction */volatile unsigned char PORTB @0x0001;volatile unsigned char PORTC @0x0002;volatile unsigned char PORTD @0x0003;volatile unsigned char PORTE @0x0008;volatile unsigned char DDRA @0x0004; /* Data Direction Registers */volatile unsigned char DDRB @0x0005;volatile unsigned char DDRC @0x0006;volatile unsigned char DDRD @0x0007;volatile unsigned char DDRE @0x000C;volatile unsigned char PTAPUE @0x000D; /* Port pull-up enables */volatile unsigned char PTCPUE @0x000E;volatile unsigned char PTDPUE @0x000F;2.5 Const变量关键字“const”,C语言中命名最差的关键字,并不表示恒量,而是代表“只读”。在嵌入式系统中,有很大的不同,这一会应会明白。
Const声明可用于任何变量,它告诉编译器将其存贮在ROM代码。编译器保留了那个位置程序存贮器地址。由于位于ROM中,其值不能改变。
由于它作为常量工作,必须赋一初值。如:const double PI = 3.14159265;Const 变量与明显的常数相对,很多原文要求用const变量代替明显的常数。例如:
用const unsigned char channels = 8;代替#define CHANNELS 8 。
本方法的基本原理是在调试器内部,你能检查一个const变量,然而一个明显的常数不可访问。不幸的是,在很多8位机上你将为这一好处付出极大的代价。这两个主要代价是:
. 一些编译器在RAM中创建一个真实的变量来支持cost变量,这是一个极大的惩罚。
. 一些编译器如CodeWarrior,知道变量为const,将把变量存贮在ROM中。无论怎样,变量仍作为变量处理和访问,典型地用某些变址寻址(16位)的方式。与直接寻址(8位)方式相比,这种方法通常很慢。
Const的用法:
const unsigned short a;unsigned short const a;const unsigned short *a;unsigned short * const a;2.6 Const volatile 变量现在讨论一个深奥的问题,一个变量既能是常量,又能是可变量吗?如果是这样,这意味什么,怎样使用?答案是“能”。
这个修饰符应该用于能出乎意料地改变的任何存贮器位置,因此需要volatile限定语,由于const该变量是只读的。
最明显的例子是硬件状态寄存器,象SCI状态寄存器SCS1。这个寄存器包含信号状态标志,如发送空、发送完成、接收满以及其它。这是一个可变寄存器由于这些标志的改变依赖于串行通信的状态,这也是只读,由于标志不能被程序直接改写,它们只对模块的状态作出响应。这个状态寄存器最佳声明方法是:
const volatile unsigned char SCS1 @0x00163、资源映射3.1 访问固定内存位置嵌入式系统通常的特点是需要编程者访问一个指定的存贮器位置。
练习:在某个项目中需要将绝对地址0xFFA处整型变量的值设为0xAA55(编译器为纯粹的ANSI编译器)。完成这个任务的代码是:
Int * ptr;ptr = (int *)0x2FFA;*ptr = 0xAA55;3.2 怎样访问I/O寄存器在嵌入式领域,设备如微控制器有片上资源,应当被管理和访问。很多I/O和控制寄存器位于直接页,它们应如此声明,因此在可能时编译器能直接寻址。它们有定位的地址,但问题是它们不是存储器,那么怎样访问这些I/O寄存器呢?这是一个非常重要的问题,答案比你想的简单或者复杂。
一个普照通而有用的形式是使用如下的#define指示:
#define PortA ( * ( volatile unsigned char * ) 0x0000 )这构成了I/O寄存器,这种情况下,Port A为地址0x0000处字符型变量。#define实际做的是每次发现PortA时放置一个构件。也就是说在代码中写:PortA = 0x3F,实际做的就是告诉编译器0x0000是一个volatile-unsigned-char类型的指针,它的内容等于0x3F。
糊涂吗?有点…让我们看一些其它选择:
这样做的一个容易的方法是在变量声明中使用符号“@”,创建一个语句读作:在地址0x0000处创建一个volatile-unsigned-char型的变量PortA。
这是一个编译器特定的语法,它可读性高,但失去了兼容性。无论什么时候我们决定使用一个不同的编译器去编译该代码,也许会发现@不被识别。CodeWarrior和Cosmic包含了这个特殊语法。
CPU中的寄存器没有内存映射;指令集包含允许它们自修改的子集;C不提供直接访问寄存器的工具;C编译器允许在C代码中使用汇编指令,如:
1)_asm AssemblyInstuction;2)asm (AssemblyInstruction);3)asm {--------}
修改CPU 中CCR的I位的内容。
使用汇编指示,I位被修改。
3.3 位域在嵌入系统中,在一个给定的地址,一次能访问和修改一位或几位。
$002000001001完成这个任务,在C语言中有不同的方法达到和实现。
*位结构:
效率随编译器的不同而改变;跨编译器和目标不能移植。
*位类型:
不能移植(标准C语言中没有);如当使用时可提高代码的效率。
*移位和掩模可移植,适当的效率;经常优化为位操作。
如果定义一个结构,但所有变量重叠在同一内存的开始位置,你应该使用联合体。联合体允许引用在联合体中定义的以任何形式描述的数据字节。联合体在内存中的尺寸大小为联合体中所列的最大类型的大小。点操作符用于选择需要的成员。
打开文件:Lab2-BitFields.mcp联合体是一个变量,不同的时间持有对象不同的类型和大小,编译器跟踪变联合体提供操作单一存贮区不同类型数据的方法,程序中没有嵌入任何依赖只有PS位被修改一条指令所写3.4 数组C允许程序员用几种不同方法存取数组的内容。
Unsigned char Array[]={0xAA,0xBB,0xCC};依赖于执行,选择最适合于该应用的需要,将产生快而小的代码。数组访问方法:
1)硬编码:
Array[0]=12*UNIT_VOLTS;编译时决定地址,执行速度快。
2)变址增加Array[index++]=12*UNIT_VOLTS;快速,比硬编码灵活。
3)数组指针*(ArrayPtr++)=12*UNIT_VOLTS;执行速度快,可读性差,可和循环一起使用。
如下图所示:
打开文件:Lab3-Arrays.mcp硬编码增量变址数组指针每种访问类型都有各自的优点,使用不同的寄存器完成不同的操作3.5 函数指针函数指针与数据指针一样有用处,原因如下:
当你想要一个额外级别的间接时;当你想用同一段代码依环境的不同调用不同的函数。
下面的代码定义了一个指向函数的指针,带了一个整型参数并返回一整数:
int (*function)(int);(*function)周围的圆括符是必须的,因为定义中的优先关系。没有它们,我们则定义了一个函数返回一个整型指针。
例如:
函数指针初始化函数指针现在指向一个不同的函数下面举一个HC08QL的例子:
SLIC模块仅有一个中断;用户必须读SLIC中断向量寄存器(SLCSV)来核实中断源。
可能的解决方案:
switch 语句;嵌套的if语句;函数指针。
打开文件:Lab4-Pointers.mcp定义了一个函数数组每次调用一个不同的函数调试(1):
1、在函数调用处下断点。
调试(2):
每次将执行一个不同的函数调试(3):
打开Component->Visuallization Tool打开Display.vtl然后运行:
VarA在每个函数中被修什么时候使用指针:
当使用少量函数时,嵌套的IF语句占用空间少;Switch语句可读性好,但占空间大;当很多函数被声明时,指针产生的代码少,但它占用大量的RAM空间。
3.6 栈指针与函数参数栈指针支持C的关键特性:
在汇编程序和C编译器中,堆栈通常用于给子程序传递变量;允许使用递归;是自动变量的基础。
典型地子程序将把需要的操作数放入累加器。堆栈相对寻址允许访问堆栈上的数据,提供直接访问操作数,排除从堆栈压入以及弹出数值所需要的代码和时间。
堆栈指针指令与等份的变址指令相比需要一个额外的字节和一个额外的执行周期。
例如:
typedef struct {unsigned char ID;unsigned short Time;} ObjectType;void foo (unsigned char value) {volatile ObjectType instance;instance.ID = value;}
编译后得到:
foo:
B00B A7FB AIS #-3B00D 9EE701 STA 1,SPB010 A707 AIS #3B012 81 RTS3.6.1 堆栈指针寻址堆栈指针相对寻址进一步增强了C代码的效率。有两种类型:
8位偏移的堆栈指针相对寻址和16位偏移的堆栈指针相对寻址。它们和间址建起方式工作相似,但使用堆栈指针代替H:X变址寄存器。注意当中断不允许时可用堆栈指针作为额外的变址寄存器。
3.6.2 堆栈帧1)帧指针函数通常有一个包含其所有本地数据的堆栈帧。编译器并不设置一个明白的帧指针,但堆栈上的本地数据和参数都根据SP寄存器访问。
2)入口代码通常入口代码是一系列为本地变量保留空间的指令:
PSHA ;仅当有寄存器参数PSHX ;仅当有寄存器参数AIS #(-s) ;为本地变量保留空间S是函数的本地数据的大小(单位:字节)。没有静态链接,动态链接并没有明白地存储。
3)出口代码出口代码从堆栈中移除本地变量,并返回到调用者:
AIS #(t) ;移除本地栈空间,包括最终的寄存器参数RTS ;返回调用者3.6.3 HC08返回值除函数返回一对象大于二字节,函数结果都返回到寄存器中。依据返回类型,使用不同的寄存器。如下表所示:
返回类型寄存器Char(signed或unsigned)Aint(signed或unsigned)X:A指针/数组X:A函数指针X:A返回大对象:函数返回大于二字节的对象均与一个附加的参数一起调用,它被传到H:X。
这个参数是对象应复制到的地址。
打开文件:Lab5-Arguments.mcp声明了四个不同类型的函数,每个函数返回一不同类型的变每个函数有一个不同类型的参数(void,byte,word被调用的函数跳转到其源码所在的内存位置全局变量赋值。
函数以RTS返→ A->变量结果存贮在变量里堆栈中进行的操作和返回值存贮在A中参数在A寄存器中A和X用作参数和返回寄存器H:X用于返回指针.
A用作参数寄存器,也用作直接3.7 中断好, 最后一个棘手的问题深深地困扰嵌入式世界:怎样处理中断?
答案是……简单!
CodeWarrior编译器提供了一个非ANSI的变通的方法,在源码中直接指定中断向量号t。表达式以interrupt关键字开始, 接着是中断向量号,最后是函数原型。
interrupt 17 void TBM_ISR (void){/* Timebase Module Handler*/}
你应查HC08手册,RESET和软中断向处在最高和相同的优先级。这两个是HC08的向量0,但CodeWarrior不得不给每个向量不同的号,因此第一个,RESET,将是向量0,然后SWI,向量1,依次类推……直到最后TBM为向量17,以GP32为例。
中断向量表定位:
向量号向量地址向量地址大小00xFFFE~0xFFFF210xFFFC~0xFFFD220xFFFA~0xFFFB2………n0xFFFF-(n*2)2打开文件Lab6-Interrupts.mcp:
使用中断修饰符声明一个中断服务程序。
使用RTI代替RTS函数指针中断向量存贮了ISR开始的地址3.8 叠代、跳转、循环执行无限循环:
While(1);For(;;);Loop:
goto Loop;对于嵌入式系统:
循环总是被基于MCU的应用所需要。对于应用程序第2种循环最好,因为它不会导致“always true warning”的警告。
3.9 标准C库标准库如stdio.h通常包含在编译器中。Getchar()、gets()、printf()、putchar()、puts()、scanf()、sprintf()、sscanf()等,都是这些库中的常用函数。
#include
void main(void){printf(“Hello World!\n”);while(1);}
当给PC机写这段代码, printf()缺省的控制台是显示器,但HC08不需要显示器作为片外外设,如果有,哪个端口用于显示?什么时候我们定义它?在哪儿?
在嵌入式编程中,通常printf()调用putchar()执行打印,这假定控制台缺省为片上串行口(SCI)。
在模拟时,printf()访问片上“虚拟”IO调用一个模拟终端通过片上SCI输出。建议修改基础库函数putchar()和getchar() t使用任何用户需要的输出/输入控制台。
参考: