本章详细介绍单片机程序常用编译软件 Keil 的用法,用一个完整的 C51 程序来操作发光二极管的点亮与熄灭,然后调用 C51 库函数来方 便地实现流水灯,从这一章开始我们将手把手地讲解单片机 C 语言编 程。认真学好本章,对于初学者来说将会是一个非常好的开头。
2 元件特性 发光二极管的原理
正极这边为高电平,负极为低电平,发光二极管就行,两边为高电平, 发光二极管就不亮。
4 程序的书写与调试。 我们要书写程序,需要一个 keil 软件。
Keil 的操作步骤。 详细操作步骤:
从图片中不难看出,我们写的程序(子项目)都是围绕总项目转的, 然后从总项目输出我们想要的单片机程序。
当 uv2 项目产生的时候,会自动附带很多其它 uv2 需要的文件,为了 防止我们产生浏览混乱,这个时候我们就需要“新建一个文件夹”, 把 uv2 存在里面,那么附带的文件也会自己存进去这个文件夹里面去 方便我们浏览和操作了。
下面图片指针头指的是,点亮一盏灯的总项目,通过上面的操作,已 经创建完成。
为总项目添加子项目之前,我们需要新建一个文件(程序编辑窗口), 点击软件 file 下面那个文本图标 create a newfile(创建一个新的文件)
#include Main()
{ P1=0xfe;
} 写好后,我们还不知道子项目里的程序有没有错,现在我们需要把这 个子项目添加到“点亮一只灯.uv2”的总项目里,去让软件查错,方 便我们修正,输出我们想要的单片机代码去让单片机工作,那需要如 何做呢?
需要点击如下图的图标 save the active document(保存活动文档),就 是保存当前编辑中的程序编辑窗口(子项目)。
是,记得文件名后缀必须是“.c”,第二次:文件名后缀必须是“.c” 第三次:文件名后缀必须是“.c”,重要是事情说三遍,这样后面的 操作就会万无一失了。
言程序(就是我们刚刚保存的.c 文件) 然后点窗口上的 add(添加),
如果总项目只有一个子项目的时候(就像点亮一只灯.c),我们就点 built target,如果有二个或以上的子项目,就点 built all target files, 就两个按钮就是这样用,这就为什么叫总项目和子项目的原因,因为 总项目中可以包含多个子项目的。
这样在编译没有错误成功后,它就会产生一个 hex 文件(单片机需要 的代码),我们就可以用这个 hex 文件让我们的单片机工作啦!
需要用的时候,我们需要到这个文件夹找出来,这样我们 P1.0 口的 发光二极管就可以点亮了。
如果你是用仿真软件。 先双击 89C52
再点击 program file 到点亮一盏灯文件夹里面,把点亮一只灯.hex 文 件,找出来就行。(如下图)
你可以复制下面程序去编程软件,熟悉上面的功能,如果想要靠自己 记忆去写,也可以尝试一下。
{ P1=0xff;
单片机里面的地址命名字,方便我们直接用名字使用(每个程序的开 头肯定有头文件的)
Main()这个是主函数入口,程序在花括号{ }里面执行,之于 main 旁边 的括号是什么东西,我们以后用到再讲解,现在明白它是固定格式 main(),mian 括号就行。
需要注意的是,这里的 P1 不可随意写,P 是大写,若写成 p,编译程 序时将报错,因为编译器并不认识 p1,它只认识 P1,这是因为我们 在头文件中定义的是“sfr P1= 0x90;”。
(1)// 两个斜扛后面跟着的为注释语句。这种写法只能注释一行, 当换行时,又必须在新行上重新写两个斜扛。
(2)/*…*/ 斜扛与星号结合使用,这种写法可以注释任意行,即斜 扛星号与星号斜扛之间的所有文字都作为注释。
释的目的是为了我们读程序方便,一般在编写较大的程序时,分段加 入注释,这样当我们回过头来再次读程序时,因为有了注释,其代码 的意义便一目了然了。若无注释,我们不得不特别费力地将程序 重新阅读一遍方可知道代码含义。养成良好的书写代码格式的习惯, 经常为自己编写的代码加入注释,以后定能方便许多。
while语句 通过上面一节的学习,想必大家已经对点亮实验板上的任意发光二极 管非常熟悉了,但是,先不要高兴得太早,上面的程序并不完善,任 何一个程序都要有头有尾才对,而上面我们写的程序似乎只有头而无 尾。我们分析一下看,当程序运行时,首先进入主函数,顺序执行里 面的所有语句,因为主函数中只有一条语句,当执行完这条语句后,
因为我们没有给单片机明确指示下一步该做什么,所以单片机在运行 时就很有可能会出错。
根据经验,当 Keil 编译器遇到这种情况时,它会自动从主函数开始处 重新执行语句,所以单片机在运行上面两个程序时,实际上是在不断 地重复点亮发光二极管的操作,而我们的意图是让单片机点亮二极管 后就结束,也就是让程序停止在某处,这样一个有头有尾的程序才完
是 while(1),while(2),while(3 或以上),即为真,那么执行 while 花括 号的内部语句,如果是 while(0)即为假,跳过while,不执行 while 花 括号的内部语句。
1 如果语句只有一条。 直接用表达式+执行语句+分号结束就行。 如:
While(1)是表达式,用来判断是真是假。 因为这里是真,所以语句就无限循环于 P1=0xfe,后面再加上一个分 号表示这是一条结束。
2 如果有两条以上表达式。 如:
While(1) P1=0xfe; P1=0xfa;
样的程序格式显然是满足不了多少功能,没有什么意义的,那我们要 怎么写才能让 while 执行多些语句呢?
从上面可以观察到,我们还有花括号可用。 是的,
{ P1=0xfe; P1=0xfa;
果只有一条语句,直接在这条语句加分号就行,如果有两条语句或以 上就需要加{}花括号。
如果 while(0) P1=0xfe; P1=0xfa;
因为现在 while 为假,所以它不会执行 P1=0xfe,而往下执行 P1=0xfa, 因为 P1=0xfa;是不属于 while 的内部语句。
{ P1=0xfe; P1=0xfa;
} P1=0x0f;
通过这些认识 我们来编写一个完整的点亮第一个发光二极管的程序。
然后到 P1=0xfe,再停止在 while(1);这里,while 里面,如果有一条语 句就执行完这个语句,停止到分号,如果没有语句就直接在分号这里 无限循环,相当于停止标记,所以以后一看到 while(1);,就知道是停 止标记了。
知识点:for语句 格式:
{ P1=0xfe;
从上面图片可以看到 第一轮:
第 1 步:初始化 i=0(i 赋值等于 0)。 第 2 步:判断 i 是否少于 3。
第 5 步:因为上面 i 从 0 加了一次,现在 i=1。 第 6 步:判断 i 是否少于 3。
第 9 步:因为上面 i 从 1 加了一次,现在 i=2。 第 10 步:判断 i 是否少于 3。
第 11 步:现在 i=1 是少于 3,就执行 for 花括号的内部语句一次。 第 12 步:当执行完一次内部语句后,i++(i++的意思就是 i 自己加 1)。
第 13 步:因为上面 i 从 2 加了一次,现在 i=3。 第 14 步:判断 i 是否少于 3。
第 15 步:现在 i=3 不少于 3,就不执行 for 内部语句了,退出 for 语 句继续往下面执行。
如果上面的理解。 那么
{ P1=0xfe;
也是一样的原理,这里是 i 首先等于 3,如果 i>0,就执行内部语句, 然后 i--,这里是 i 自减 1 次。这个也是共执行 3 次,执行完后就退出 for 语句
很多初学者容易犯的错误是,想用 for 语句写一个延时比较长的语句, 那么他可能会这样写:
unsigned char i;(unsigned char 无符号字符型,这是是定义 i 为无 符号字符型,数值范围是 0~255)
但是结果却发现这样写并不能达到延长时间的效果,因为在这里 i 是 一个字符型变量,它的最大值为 255,当你给它赋一个比最大值都大 的数时,这里 i 赋值是 3000,程序自然就出错误了。
因此我们尤其要注意,每次给变量赋初值时,都要首先考虑变量类型, 然后根据变量类型赋一个合理的值,我们所指的变量类型,就是字符
如果我们想用 for 语句做一个秒的延时,我们该怎么写呢? 秒是我们日常用的时间单位,如果单片机也用秒的时间来一句句执行 程序语句,这样就非常没有效率,还不如直接用人手操作,我们创造 出单片机代替人手的根本原因,就是让它自动化,而且快速。
我们用 s(秒),单片机用的时间是 us(微秒) 而且
1 秒=1000 毫秒=1 百万微秒。 很明显,单片机用的时间是我们的时间的 1 百万倍。
如果我们知道单片机执行一条语句需要多少时间,就可以用 for 语句 编写出一个一秒的延时程序了。
我们来看看执行一个分号需要多少时间。因为晶振是决定时间的,我 们单片机常用的是 11.0592M,想计算准确的时间,先在软件里面设 置一下。
然后通过 keil 软件——start/stop debug 可以监测到程序执行流程,进 入 debug(调试)后(看下图),按键盘F10 或者 F11 一步步执行, F10 是跨越式执行,F11 是细节语句执行,说的多不如动手试一试, 就会明白很多了。
从上图可以看到,执行一个分号之前的时间是,0.00044162 s(左边 的 sec 那里看到),0.00044162 s=0.00044162s*100 万=441.62us(小数 点向右移六位就行)
分号之前的时间=441.62us 分号之后的时间=445.96us 分号用的时间=445.96us - 441.62us=4.34us。 所以执行一次分号的时间是 4.34 微秒。
这个 for 语法是执行了 1ms(1 毫秒)的时间,但是这还没有达到我 们想要的 1S 的时间,或许有些朋友很聪明可以想出,把这个 1ms 执 行多 1000 次不就是 1S 的时间了?
很明显,C 语言是可以用于内嵌语句执行的,从上面图片不难看到, 执行完 230 次分号后(花 1ms),还要重复执行 1000 次 1ms 就是 1s 了。
2 元件特性 发光二极管的原理
正极这边为高电平,负极为低电平,发光二极管就行,两边为高电平, 发光二极管就不亮。
从上面图片可以看到,让一只灯亮灭的原理还是比如容易的,首先是 点亮一只灯,然后一秒延时,再灭灯,再一秒延时,再回到点亮一只 灯这样无限循环的重复。
#define uint unsigned int //把 unsigned int 命名字为 uchar uint i; //整型 i 变量
#include //头文件 头文件每个程序开头肯定有的。
而我们觉得太长不好写,就有了#define uchar unsigned char ,把 unsigned char(无符号字符型)命名字为uchar,#define uint unsigned int 也是同样的道理,uint(无符号整型)
还记得我们上面学过的 8 位,16 位吗?8 个位的二进制最大的数是十 进制 255,当超过 255 后,它就又会回归到 0重新开始,而我们的 uchar 就是用于表明是 8 个位的水杯(最大十进制数是 255)。
声明完后,I, j 这些变量位置哪里来?当你定义好,单片机 RAM(动 态存储器)自动分配的,这不用你操心。
sbit led=P1^0; 也很好理解的,声名特殊功能寄存器的位,把 P1.0 这 个位命名字为 led,现在看到^这个符号了吧?很常用。
命名字方式,我们学过四种。 特殊功能寄存器的位命名:sbit 特殊功能寄存器 8 位地址命名:sfr P1=0x90;
特殊功能寄存器 16 位地址命名:sfr16 TC=0x91;(连续用 0x91,0x92 这两个地址,只声名第一个地址就可以)Define:把什么名字命什么名字(多数用于英文字母的定义,方便我 们记忆运用)
2 Define:把什么名字命什么名字(多数用于英文字母的定义,方便 我们记忆运用)
如何区分特殊功能寄存器和普通寄存器?原理很简单的,当你这个寄 存器是特殊功能的,比如 P1(厂家告诉你这个寄存器有什么用,就
如果厂家没有告诉你这个寄存器具体有什么用,就是普通寄存器,比 如 uchar i, uint j,等等,当你声明好后,单片机会自动分配寄存器的, 如果你想用普通的位,直接用 bit 就行,如 bit flag,为 flag 这个名字 定义为位功能,这个时候 flag 也是只能在 0~1,这两个数内变化,现 在稍微了解就行,以后用就的时候你就明白了。
同样的代码放在别处,需要的时候才调用出来,这样就可以减少很多 代码重写,不用浪费那么多单片机内部资源。
有两种方式: 第一种是在主程序(main)上面书写。 第二种是在主程序(main)下面书写。
从上面图片可以看到,当定义好头文件后和其它相应的名字后,子程 序就可以开始书写了。
Void 是空的意思,相当于从这个子程序中,没有东西(数值)返回的 意思吧,现在不理解,你可以暂时忽略它,直接书写就行,以后你见 到有东西(数值)返回的,对比一下,就一目了然了。
del_ms 是子程序名字, del 是我们把英文单词 delay(延时)的缩写, 加_符号,再加 ms(毫秒),这样从字面上就可以直接理解到是延时 1
叫做 ms 的原因是现在我们没有像上面那样直接赋值 1000 次的一毫 秒延时,而是把这个 1000 变成可变的数字,如下图所示的 uint k:
可以看到,现在我们上面的 1000 改变成可变的变量整型 k,uint(无符 号整型)可在 0~65535 之间随意赋值,定义完后,k 就可以在 0~65535 随意赋值。
如果是少于 255 的话,用无符号字符型(uchar)就行,这里大于 255, 就用了整型,而且这个 k,只能在延时子程序花括号范围内使用,就 是
你在主程序(main)内定义,就是主程序(main)花括号内可以使用,其 它范围无效,如果你是一开头就定义了,子程序和主程序都可以使用。
就是下面的 uint i 和 ichar j 可以在子程序和主程序的花括号里随意使 用,理解没?
记得后面不要漏了分号,这样做只要是告诉软件,你已经写完一条语 句,不然的话,程序会出错。
序外,其它一样,如果你在主程序下面写子程序而没有首先告诉单片 机,程序编译中会出错的,你可以自己试试,试过就知道了。
欢迎光临 (http://www.51hei.com/bbs/) | Powered by Discuz! X3.1 |