Freescale MC9S08单片机原理与应用(第06章).doc_第1页
Freescale MC9S08单片机原理与应用(第06章).doc_第2页
Freescale MC9S08单片机原理与应用(第06章).doc_第3页
Freescale MC9S08单片机原理与应用(第06章).doc_第4页
Freescale MC9S08单片机原理与应用(第06章).doc_第5页
免费预览已结束,剩余13页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第6章 MC9S08的C语言程序设计及调试6.1 使用C语言的好处C程序语言最初是由Dennis Ritchie在1971年为UNIX系统开发并实现的。C 语言最大的优点是与任何特定的系统或硬件无关。这使得用户编写的程序可以不作任何修改就能运行在几乎所有的机器上。C语言将高级语言的要素与汇编语言的功能结合在一起,因此通常被称为中级计算机语言。C语言非常灵活,代码的编写可以让人随心所欲,这种自由赋予C语言非常强大的功能。C 语言比较容易上手,但是同样经久耐用。C语言的位操作编程容易实现,可以与汇编语言实现同样的位运算;C语言是结构化编程语言,可以进行面向对象编程;C语言的流程符合人的逻辑思维,可以创建你脑海中已有的任何任务。当设计一个规模较小的嵌入式系统或者对时钟要求很严格时一般使用汇编语言。使用汇编语言的问题在于它的可读性和可维护性,特别是当程序没有足够详细的注释的时候,代码的可重用性很低。使用C语言就可以很好的解决上述问题。因为C 语言具有结构性和模块化的特点,更容易阅读和维护。由于可模块化,所以用C 语言编写的程序有很好的移植性,即可以编写出跨平台运行的程序。功能化的代码能够很方便地从一个工程移植到另一个工程,从而减少开发时间。用C语言编写程序比汇编语言更符合人们的思考习惯,开发者可以更专心的考虑算法而不是考虑一些细节问题,这样就节约了开发和调试的时间。程序员不必熟悉处理器的构造就可以使用C语言进行编程,这意味着对尚未接触过的处理器编程也能很快上手。C语言的最大特点就是可以使你尽量少地对硬件进行操作,从而使不了解硬件的软件工程师尽快上手。对于9S08单片机,其片内有几十甚至几百KB的存储空间,如果项目规模较大,特别是涉及到算法描述时,用汇编语言则无力应付,而用C语言可以将在PC机上调试好的代码直接移植过来,节省了调试时间。本章着重介绍使用C语言编程的常见问题,以及在9S08单片机上与使用ANSI C在PC机上编程的不同之处。6.2 C语言编程的常见问题6.2.1关于循环次数C语言有众多的循环控制语句,如while语句、do-while语句和for语句等等。即便是有经验的程序员也可能对循环体循环的次数感到迷惑。下面就介绍一下常用的几种循环控制语句是如何对循环体的循环次数进行控制的。1. for语句C语言中for语句的使用非常灵活,可以由程序员自己设置循环次数。比如下面的程序:unsigned char i,j=0;for(i=0;i10;i+)j+; 在上面的程序中,定义了两个无符号字节型变量i和j,变量i用于控制循环次数,变量j在循环体中作自加操作。这段程序执行完后,i和j的值分别等于多少呢?经过调试,可知i和j都等于10,也就是说,i自加了10次,循环体共执行了10次。如果把上面程序中的“”误写成“=”会出现什么情况呢?经过调试,可知i和j都等于11。所以可以得到这样一个结论,当循环控制变量i的初值为0时,在“”右面的数字就是循环次数,表示循环体将要被执行的次数。注意,在阅读程序时会经常遇到“for( ; ; )”这样一条语句,它表示无条件循环,将无休止地执行循环体。2. while语句while语句用来实现“当”型的循环结构,表示当某条件满足时,执行循环体,直到条件不满足时,才退出循环。比如下面的程序:unsigned char i=0,sum=0;while(i10)sum+=i;i+;在上面的程序中,定义了两个无符号字节型变量i和sum,i用于控制循环次数,sum则对i进行求和。经过调试, i最终等于10,表示一共执行循环体10次,sum一共进行了10次加法,最终等于45,表示从0加到9的和。设想一下,如果把循环体内的两条语句调换一下位置会得到什么结果呢?经过调试,i最终等于10,与未调换时相同,sum最终等于55,比未调换时多加10。其实,i作为循环控制变量,无论是调换还是未调换,都表示循环体要执行10次,sum的值之所以不一样,是因为它与i有关。没有调换时,sum表示从0加到9的和;调换后,sum表示从1加到10的和,两者都是循环了10次,只是结果不一样罢了。读者可以自行实验一下把“”改成“=”后程序的执行结果。3. do-while语句do-while语句的特点是先执行循环体,然后再判断循环条件是否成立。比如下面的程序:unsigned char i=0;doi+;while(i10);经过调试,i最终等于10,表示循环了10次。看起来与while语句并没有什么不同。但是,如果把循环条件“i10”改成“0”(0代表false),i最终等于1。也就是说,虽然循环条件永远为假,但是循环体依然被执行了1次。这就是do-while与while的不同。6.2.2 赋值与相等程序员们都知道,C语言与其他编程语言不同,相等和赋值符号是不一样的。相等用两个等号“= =”表示,而赋值用一个等号“=”表示。语句“a=b”表示把变量b的值赋给变量a,而“a= =b”表示逻辑判断,变量a等于变量b时为逻辑真,否则为逻辑假。几乎所有的C程序员都犯过一个错误,那就是把“= =”写成“=”。比如下面的程序:if(a=1)a+;程序员大概希望当变量a等于1时把a作自加操作,使a等于2。但是却误把“= =”写成了“=”,这时无论a为何值,都会等于2,显然与编程目的不同。注意,凡是赋值语句用于逻辑判断时,一律为逻辑真。6.2.3 多文件C语言工程的头文件当代码量非常大时,程序员一般会把代码分成几个部分,然后写入到多个文件中。这些文件一般分为C文件和H文件。那么该把什么内容放到H文件中呢?作为一般规则,以下内容应该放到H文件中:预处理和宏定义结构、联合和枚举声明typedef声明外部函数声明全局变量声明如果定义的变量仅仅被一个C文件所私有,那么不可放到H文件中。6.2.4 运算符的优先级及括号的使用C语言的运算符分为15个优先级,在一条语句中出现多个运算符时,必须按照其优先级的高低来决定先计算谁,后计算谁。同一优先级的运算符优先级别相同,运算次序由结合方向决定。如果在一条语句中出现多个运算符不仅仅难于调试,更影响程序的可读性。比如语句“a *= b+”,即便是有经验的程序员也很难一眼看出a到底等于多少。按照规则,可以把这条语句分成两条写,这样更加容易理解:a*=b;b+;这样,变量a和b的值就非常明了了。再如语句“*p+”,到底是地址p自加,还是p指向的变量自加呢?按照规则,这条语句等价于“*(p+)”,而后者显然更容易理解。所以,程序员在做变量的复杂运算时,最好把一条复杂的语句分成若干条简单的语句,或者把复杂的语句添加括号以利于理解。6.2.5 位运算的左移与右移C语言作为中级语言,具有汇编语言所能完成的一些功能,其中左移和右移运算符应用非常广泛。比如以下两条语句:a*=2;a1;两条语句的结果是一样的,都是把变量a乘以2。显然,后者的执行速度更快。所以当一个变量需要乘以2或者除以2运算时,一般不使用“*”和“/”运算符,而是使用左移运算符“”。6.2.6 有争议的goto语句虽然C语言支持无条件跳转指令goto,但是在程序代码中不准出现goto指令却成了一条不成文的规定,原因是随意使用goto指令会使代码难以维护。然而,要养成良好的程序设计风格,就不可以被僵化的教条所束缚。不经思考地把goto语句禁用,并不意味着一定能编写出良好的程序代码。一个无规划的程序员完全可以不使用goto语句而构造出复杂并且难于理解的代码。所以,虽然并不提倡使用像goto这样破坏程序可维护性的语句,但是有规划地编写程序代码更为重要。6.2.7 内部函数和外部函数内部函数又称静态函数,如果一个函数只能被本文件中的其他函数调用,则称为内部函数。在定义内部函数时,必须在函数名和函数类型前加static。如:static int func(int x,int y);内部函数只局限于本文件函数的调用。如果在其他文件中有同名的函数,则不互相干扰。当一个C语言工程由多个程序员进行编写时,就不用担心自己所定义的函数与别人定义的重名了。如果在函数名和函数类型前加extern,则表示此函数是外部函数,可以被其他文件中的函数调用。如:extern int func(int x,int y);这样定义一个函数,它就可以被其他文件调用了。C语言规定,如果定义时省略extern,则默认为外部函数。6.2.8 空指针null每一种类型的指针都有一个特殊值空指针,它与同类型的其他所有指针值都不相同,也就是说,使用取地址操作符“&”永远也不会得到空指针。空指针表示“未分配”或“尚未指向任何地方”的指针。空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数,而未初始化指针则可能指向任何地方。每种指针类型都有一个空指针,而不同类型的空指针的内部表示可能不尽相同。尽管程序员不必知道内部值,但编译器必须时刻明确需要哪种空指针, 以便在需要的时候加以区分。当在程序代码中需要空指针常数时,用“0”或者“null”表示;如果在函数调用中“0” 或“null”用作参数, 把它转换成被调函数需要的指针类型。6.2.9 程序设计风格1. 缩进格式缩进的大小是为了清楚定义一个块的开始和结束,可以使程序更加容易理解。一般来说,我们都使用4个字符(1个Tab)的缩进。代码的缩进除了可以使程序容易理解外,还会在嵌套层数太多的时候提醒你。如果你的程序代码将要缩进到屏幕右端时,你就需要修改你的程序了。2. 大括号的位置大括号有很多放置的方法。可以把第一个大括号放在第一行的最后,而最后一个大括号放在最后一行的首位,也可以把第一个和最后一个大括号都放在一行的首位。对于大括号的位置规定并不那么严格,程序员完全可以按照自己的喜好来放置。3. 注释注释是一件很好的事情,但是注释也不可过多。你应该将代码写得更好,而不是花费大量的时间去解释那些代码。通常情况下,你的注释是说明你的代码做些什么,而不是怎么做的。在给一个函数添加注释时,最好在函数前写上这个函数将要完成些什么功能和为什么要完成这些功能,而不是逐条去注释每一条语句。6.3 MC9S08的C编程与ANSI C的不同6.3.1 变量变量的类型决定其可带值的类型。也就是说,为变量选择一个类型与我们使用这个变量的方法直接相关。下面介绍一下在CodeWarrior中变量的基本类型、怎样写常量和声明这些变量。1. 基本数据类型ANSI标准并没为本地类型规定尺寸大小,但CodeWarrior规定了。CodeWarrior的一些基本数据类型如下:表5-1 CodeWarrior规定的基本数据类型类型默认格式默认取值范围带参数-T的可用格式最小值最大值unsigned char8位02558位,16位,32位(signed) char8位-128+1278位,16位,32位unsigned int16位0655358位,16位,32位(signed) int16位-32768+327678位,16位,32位unsigned short16位0655358位,16位,32位(signed) short16位-32768+327678位,16位,32位unsigned long32位042949672958位,16位,32位(signed) long32位- 214748364821474836478位,16位,32位unsigned long long32位042949672958位,16位,32位(signed) long long32位- 214748364821474836478位,16位,32位floatIEEE32-1.17549435E-38F3.402823466E+38F8位,16位,32位doubleIEEE32-0.89884656743115E-3081.797693134862316E+3088位,16位,32位long doubleIEEE32-0.89884656743115E-3081.797693134862316E+3088位,16位,32位long long doubleIEEE32-0.89884656743115E-3081.797693134862316E+3088位,16位,32位注意,在上表中带括号的表示默认数据类型,例如“int”和“signed int”的含义是相同的。2. 自定义CodeWarrior数据类型在CodeWarrior环境下按ALT+F7打开工程的通用设置,选择“Compiler for HC08”面板然后点按钮“Type Sizes”。这个窗口显示了CodeWarrior编译器的标准数据类型设置。程序员可以在这里修改类型的设置,不过建议还是不要随意修改这些默认值。3. 数据类型的真相为了缩减代码量并且节约执行时间,就要为变量选择最合适的数据类型。8位微控制器内部的数据长度是8位(1字节),所以首选的数据类型是“char”。当效率非常重要时,双精度和浮点操作效率低,更应当避免。当然对于9S08单片机来说,8位数据类型效率最高,因此在使用中,尽量选择char类型数据。图5-1 CodeWarrior的标准数据类型设置4. 选择数据类型8位微控制器选择数据类型有3个规则:用最可能小的类型来完成工作,大小越小占用存贮空间越少;若可能,用无符号类型;在表达式内声明以将数据类型减到最少需要。使用关键字static、volatile和const声明变量,以指定特定需要或内存中变量存贮的相关条件,这样可以使代码更加紧凑。5. 静态变量使用静态有两个主要功能:第一是定义一个变量,在函数连续调用期间,变量不会消失。第二是限制变量的范围。在模块级定义时,能被整个模块中所有函数访问,不能被其它函数访问。这非常重要,因为当严格限制全局变量能被任何函数语句访问的问题时,它让我们获得所有全局变量执行性能的好处。因此,如果一些数据结构需要被函数频繁访问,就应该把它们和这些函数放入同一模块中,并将结构声明为静态。这样,所有函数能够访问它,同时与数据结构无关的函数不可访问。声明模块级静态变量(与将其设为全局变量相反)能取得一些其他潜在的益处。静态变量由于定义为模块级,只能被一组特定的函数访问。因此,编译器和连接器能够明智地选择变量在存储空间的放置。例如,对于静态变量,编译连接器也许选择将一个模块中所有静态变量放在连续的区域,这样增加了代码的优化机会。相反,全局变量在存贮空间的位置通常取决于优化编译器的哈稀算法,当然这就排除了优化的可能性。须着重指出,这些变量不会存储在堆栈中,因为它们必须保存具体的变量值。下面给出一个使用静态变量的例子:File1.c#include /* 包含文件FILE2.c中的函数 */void main (void)MyFunction(); /* 第一次进入MyFunction之前,myVar=0 */MyFunction(); /* 第二次进入MyFunction之前,myVar=1 */File2.cvoid MyFunction (void)static char myVar = 0; /* 本地变量声明为static */myVar = myVar + 1; /* myVar是本地变量,但它保持了自己的值 */以上程序的结果为,在第一次调用函数MyFunction()后,变量myVar等于1,第二次调用后,变量myVar等于2。6. 关键字“static”的使用在函数体内声明的静态变量,在函数调用期间保持其值不变;在模块内声明的静态变量,能被模块内所有函数访问;在模块内声明的静态函数,只能被模块内函数调用。 7. 可变(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关键字,编译器将其编译为:CLIMOV #0X05,0X00LDA #0X0ASTA 0X0100STA 0X1800使用volatile关键字后,编译器将其编译为:CLIMOV #0X05,0X00MOV #0X05,0X00LDA 0X16LDA #0X0ASTA 0X0100STA 0X1800这段代码实际上不做任何事,但它很好地表达了优化怎样强烈地影响程序的结果。在main()函数中连续两次使用语句“PORTA=5;”,这没有意义,但让我们假设这是正确开发程序所必需的。在这两个语句之后,明显地有一条无意义语句“SCS1;”。让我们看当不使用volatile变量会发生什么。我们得到了优化过的汇编代码。重复的语句“PORTA=5;”消失了,只剩下一句“STAB 0x300”。语句“SCS1;”似乎什么都不做,因此聪明的编译器将它消去了。最后,将10加载到累加器并存储。使用volatile关键字声明PORTA 和SCS1,得到的汇编代码没有优化,连续两次在PORTA写入数值5,然后将SCS1加载到累加器。最后由于累加器被使用,于是用X寄存器存储数据10。加载SCS1到累加器是很有用的,这是串行通信接口SCI需要的,读SCS1寄存器目的是清除任何未决的标志。无意义的语句“SCS1;”被翻译为读寄存器的的汇编语句,这将清除SCI中未决的标志。在嵌入式设备中将所有外设寄存器声明为volatile是一个好习惯。在分开的头文件中定义所有外设的名字,能使所写代码更友好并简化移植。8. Const变量关键字“const”是C语言中命名最差的关键字,它并不表示恒量,而是代表“只读”。在嵌入式系统中,这点就有很大的不同了。const声明可用于任何变量,它告诉编译器将其存贮在ROM区,所以如此声明的变量值是不可以改变的。由于它作为常量工作,必须赋初值。如:const double PI = 3.14159265;const 变量与明显的常数相对,很多原文要求用const变量代替明显的常数。例如:用“const unsigned char channels = 8;”代替“#define CHANNELS 8”。Const的用法:const unsigned short a;unsigned short const a;const unsigned short *a;unsigned short * const a;9. Const volatile 变量现在讨论一个深奥的问题,一个变量既能是常量,又能是可变量吗?如果是这样,这意味什么,怎样使用?答案是“能”。这个修饰符应该用于能出乎意料地改变任何存储器位置,因此需要volatile限定语, const代表变量是只读的。最明显的例子是硬件的状态寄存器,像SCI状态寄存器SCS1。这个寄存器包含信号状态标志,如发送空、发送完成、接收满和其他标志位。这是一个依赖于串口通信状态的可变寄存器,但标志不能被程序直接改写,所以是只读的,它们只对模块的状态作出响应。这个状态寄存器最佳声明方法是:const volatile unsigned char SCS1 0x0016 6.3.2 资源映射1. 访问固定的内存位置嵌入式系统的特点是需要编程者访问一个指定的存储器位置。比如在某个项目中需要将绝对地址0x2FFA处的整型变量值设为0xAA55。完成这个任务的代码是:int * ptr;ptr = (int *)0x2FFA;*ptr = 0xAA55;2. 访问I/O寄存器在嵌入式领域,需要管理和访问微控制器的片上资源,比如I/O寄存器和控制寄存器等。那么怎样访问这些寄存器呢? 一个方法是使用“#define”定义:#define PORTA ( * ( volatile unsigned char * ) 0x0000 )这就构成了I/O寄存器,PORTA为地址0x0000处字符型变量。“#define”实际做的是每次发现PORTA时放置一个构件。也就是说在代码中写“PORTA = 0x3F”,实际做的就是告诉编译器0x0000是一个“volatile-unsigned-char”类型的指针,它的内容为0x3F。 另一个方法是在变量声明中使用符号“”,在地址0x0000处创建一个volatile-unsigned-char型的变量PORTA。volatile unsigned char PORTA 0x0000;这是一个编译器特定的语法,它可读性高,但兼容性不好。如果使用另一个编译器去编译该代码,就发现“”不能够被识别。CodeWarrior支持这个特殊语法。C编译器允许在代码中使用汇编指令,具体形式见如下代码:_asm AssemblyInstuction;asm (AssemblyInstruction);asm AssemblyInstruction AssemblyInstruction 3. 位操作在嵌入系统中,在一个给定的地址,一次能访问和修改数据的一位或几位。C语言有很多方法来完成这个任务。位结构:效率随编译器的不同而改变,不具备可移植性。位类型:可提高代码效率,不具备可移植性。移位效率适中,具备可移植性。如果定义一个结构,但所有变量重叠在同一内存的开始位置,就应该使用联合体。联合体允许引用在联合体中定义的以任何形式描述的数据字节。联合体在内存中的尺寸大小为联合体中所列的最大类型的大小。可以使用点操作符来选择需要的成员。4. 数组C语言允许程序员用几种不同方法存取数组的内容。选择最适合的方法来存取,可以提高代码的效率。数组的访问方法有:1) 硬编码:Array0=12*UNIT_VOLTS;编译时决定地址,执行速度快。2) 变址增加Arrayindex+=12*UNIT_VOLTS;快速,比硬编码灵活。3) 数组指针*(ArrayPtr+)=12*UNIT_VOLTS;执行速度快,可读性差,可和循环一起使用。6.3.3 C语言的中断在嵌入式系统中,C语言怎么处理中断呢?CodeWarrior编译器提供了一个非ANSI标准的变通方法,在源码中直接指定中断向量号。表达式以interrupt关键字开始,接着是中断向量号,最后是函数原型。比如下面的函数,定义了中断向量号为7的实时中断服务程序:interrupt 25 void RTI_ISR (void)/* RTI_ISR Function Codes */通过查9S08单片机的文档得知,9S08所有的中断源共分成32个优先级。处在最高优先级的中断是复位中断,它的中断向量号为0,第二位的是软件中断,它的中断向量号为1,以此类推。6.3.4 标准C库如同stdio.h这样的标准库通常包含在编译器中。getchar()、gets()、printf()、putchar()、puts()、scanf()、sprintf()、sscanf()等,都是这些库中的常用函数。#include void main(void) printf(“Hello World!n”); while(1);当在PC上运行这段程序时,printf()缺省的控制台是显示器,但9S08没有显示器作为外设,那么这段程序还能正常运行吗?答案是肯定的。我们可以为printf()函数指定一个控制台。在嵌入式编程中,printf()通常调用putchar()执行打印,设置默认控制台为片上的异步串行口SCI。如果需要使用这个功能,则需要修改基础库函数putchar()才能正确输出。6.3.5 C语言入口在9S08的C程序中,程序的入口在哪里?程序如何启动呢?在PC上,操作系统负责从磁盘上装入程序并建立环境。而在嵌入式系统中没有操作系统,程序员必须完成使程序启动的任务。在嵌入式系统中,特别是用C或C+写的代码,需要一个启动模块,在启动main()之前配置硬件和代码。通常不可避免地用汇编语言编写,这个模块是处理器离开复位状态后首先执行的代码。C/C+起动代码通常执行下列动作: 1.关中断;2.将初始化数据从ROM复制到RAM;3.将未初始化数据区清零;4.为堆栈定位空间以及初始化堆栈;5.创建并初始化堆栈;6.执行构造函数并初始化所有全局变量(仅C+);7.开中断。最后,启动代码调用main()函数,用户程序得到执行。对于9S08单片机,启动代码相对比较容易。可以只初始化堆栈、硬件然后直接跳转到main()函数,下面是一个启动代码的例子:void _Startup()asmLDHX #StackTopTXSJSR main/call C program 当然程序员可以自己编写启动代码,可以将应用程序中的硬件初始化程序都放在启动代码里面,这样就可以比较专注地在应用程序中做要做的事情。6.3.6 程序的链接与定位对于9S08单片机,由于RAM、EEPROM和寄存器可以被重新映射,而且不同型号的单片机内部FLASH大小也不一样,因为连接器不知道你用的单片机RAM放在什么地方,ROM放在什么地方,所以我们必须把程序编译后存放在什么位置告诉连接器。CodeWarrior会自动生成一个“.prm”文件来管理程序的定位。但是该文件并不一定与用户的实际需要相符,因此在连接前,必须仔细核对该文件是否与实际的硬件相符。下面是自动生成的“.prm”文件:NAMES END /* CodeWarrior will pass all the needed files to the linker by command line. But here you may add your own files too. */SEGMENTS /* Here all RAM/ROM areas of the device are listed. Used in PLACEMENT below. */ ROM = READ_ONLY 0x182C TO 0xFFAF; Z_RAM = READ_WRITE 0x0080 TO 0x00FF; RAM = READ_WRITE 0x0100 TO 0x107F; ROM1 = READ_ONLY 0x1080 TO 0x17FF; ROM2 = READ_ONLY 0xFFC0 TO 0xFFCB;ENDPLACEMENT /* Here all predefined and user segments are placed into the SEGMENTS defined above. */ DEFAULT_RAM INTO RAM; DEFAULT_ROM, ROM_VAR, STRINGS INTO ROM; /* ROM1,ROM2 In case you want to use ROM1,ROM2 as well, be sure the option -OnB=b is passed to the compiler. */ _DATA_ZEROPAGE, MY_ZEROPAGE INTO Z_RAM;ENDSTACKSIZE 0x50VECTOR 0 _Startup /* Reset vector: this is the default entry point for an application. */6.3.7 程序举例下面介绍几个C语言的经典范例,它们都可以运行在CodeWarrior环境中,读者可以通过阅读这几个例子来体会9S08单片机的C语言与ANSI C的不同之处。1. 打印“Hello world”经典C语言教材中开始的例子,是在显示器上显示“Hello world”。但是9S08的C语言不能在显示器上显示字符,本例把“Hello world”通过异步串行口传送给PC,PC机可以通过超级终端接收到。本例与硬件有关,下面是经典C语言教材给出的例子。#includemain() printf(“hello worldn”);其中printf()这个函数是C语言函数库中的标准函数,它的标准格式是:printf(file*,char *crtl,);由于标准C语言与硬件无关,I/O的输入输出并不是标准C语言的一部分, 在标准C语言中I/O操作是通过操作系统实现的。在9S08单片机开发时,一般不使用操作系统,所以与硬件相关的程序必须由开发人员来编写。由于printf()函数可以将一些信息打印出来,因此在调试程序的过程中,可以用于将程序的运行状态打印出来,所以这是一个非常有用的函数。但是在9S08单片机中不存在标准的输出设备,那么用什么作为标准设备呢?在使用CodeWarrior的过程中可以发现它将串口作为标准输入输出接口。而且直接使用printf()函数生成的代码非常大,因为它支持的输出格式也多。为了避免这些不必要的格式,可以自己来编写代码。这里我们不采用编写简单printf()函数的方式,而是对打印字符串专门写一个函数,这样能够更好的写出简洁的代码。这里的标准输出口为9S08的异步串行口1。void SCI_send_string(char *string) while (*string) / while (!SCI2S1_TDRE); / 等待上一个发送完成 SCI2D = *string; /发送数据 string+; / 指针指向下一个要发送的数据 void main(void) EnableInterrupts; /* enable interrupts */ /* include your code here */ SCI2BD=26; / SCI baud rate = 4MHz/(16*26)=9615 bps SCI2C2=SCI2C2_TE_MASK; /允许发送 SCI_send_string(Hello Word!); / 写串口 for(;) _RESET_WATCHDOG(); /* feeds the dog */ /* loop forever */ /* please make sure that you never leave main */将编译好的程序下载到单片机中,打开PC机的通讯工具超级终端,设置波特率为9600,把单片机的串行口通过串口线跟PC机的COM口连接起来,就可以在电脑上看到打印的Hello world了。2. 对从RAM中0x3800地址开始的10个无符号字节型数进行由小到大排序程序分析:可以使用C语言把一个unsigned char类型的数组存储到从0x3800开始的内存空间中,数组的大小为10个字节,内容由程序员自定义。由小到大的排序使用冒泡法完成。程序源代码:unsigned char data10 0x3800=0x22,i,j,temp;/* 从地址0x3800开始定义数组 */void main(void)/* 这里是系统代码 */for(i=0;i10;i+)/* 初始化数组 */datai=20-i;for

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论