从c语言写的代码到能烧进单片机c语言的文件,这中间经历了什么?

赞助商链接
当前位置: >>
51单片机C语言编程入门(中科大)
中国科学技术大学业余无线电协会编 目录1§1 前言…………………………………………………………………………………………§2 单片机简介………………………………………………………………………………… 2 2.1 数字电路简介…………………………………………………………………………… 2 2.2 MCS-51 单片机简介……………………………………………………………………… 2 2.3 Easy 51 Kit Pro 简介………………………………………………………………… 5 2.4 Easy 51 Kit Pro 电路功能分析……………………………………………………… 5 §3 MCS-51 单片机的 C 语言编程……………………………………………………………… 8 3.1 汇编语言………………………………………………………………………………… 8 3.2 建立你的第一个 C 项目………………………………………………………………… 8 3.3 生成 hex 文件…………………………………………………………………………… 12 3.4 Keil C 语言……………………………………………………………………………… 14 3.5 单片机 I/O……………………………………………………………………………… 18 3.6 中断……………………………………………………………………………………… 25 3.7 定时器/计数器………………………………………………………………………… 27 3.8 定时器的应用举例……………………………………………………………………… 29 3.9 外部中断………………………………………………………………………………… 34 3.10 串行通信……………………………………………………………………………… 38 3.11 定时器 2……………………………………………………………………………… 43 3.12 看门狗………………………………………………………………………………… 47 3.13 空闲模式和掉电模式………………………………………………………………… 50 §4 MCS-51 单片机 C 语言编程应用进阶…………………………………………………… 4.1 扫描式键盘……………………………………………………………………………… 4.2 EEPROM 芯片 AT93C46 的读写…………………………………………………………… 4.3 Keil C 的高级使用……………………………………………………………………… §5 编写高质量的单片机 C 程序……………………………………………………………… 5.1 文件结构………………………………………………………………………………… 5.2 程序的版式……………………………………………………………………………… 5.3 单片机程序命名规则与变量选择……………………………………………………… 5.4 表达式和基本语句……………………………………………………………………… 5.5 函数设计………………………………………………………………………………… 5.6 单片机程序框架………………………………………………………………………… 附图:Easy 51 Kit Pro 电路图(最小系统板)…………………………………………… 附图:Easy 51 Kit Pro 电路图(学习板)………………………………………………… 51 51 55 63 64 64 66 70 73 77 79 80 81V2.0.2 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料§1前言什么是单片机,目前还没有一个确切的定义。普通认为单片机是将CPU、RAM、ROM、定 时器/计数器以及输入输出(I/O)接口电路等计算机主要部件集成在一块芯片上,这样所 组成的芯片级微型计算机称为单片微型计算机(Single Chip Microcomputer)。简称为单 片微机或单片机。利用单片机程序,可以实现对硬件系统的小型化的智能控制。由于单片机 的硬件结构与指令系统都是按工业控制要求设计的,常用于工业的检测、控制装置中,因而 也称为微控制器(Micro-Controller)或嵌入式控制器(Embedded-Controller)。 单片机的应用十分广泛,我们将以 Easy 51 Kit Pro 单片机学习板为基础,学习 51 单 片机的入门知识。 本学习资料面向掌握基本电路知识和基础 C 语言编程的单片机初学者。 为使读者能迅速 上手, 本资料并不深入介绍单片机的内部体系结构和指令系统, 而是从读者较熟悉的高级语 言开始使读者掌握单片机的 C 语言编程。 通过本资料的大量程序例子, 读者应当可以在较短 的时间内熟悉单片机的入门编程以及单片机基本外围电路的连接, 从而具备基本的单片机开 发能力。然而,本资料的局限性也正在于此,当读者需要更深入地开发单片机或其它控制器 时,可能需要进一步了解其内部体系结构和指令系统,这时读者就应查阅其它资料了。 同时, 本学习资料还致力于引导读者编写高质量的单片机 C 语言程序。 尽管单片机程序 的规模有限, 但高质量的单片机程序除了可以优化运行效率外, 对程序的开发速度和可维护 性也具有重要的影响。中国科学技术大学业余无线电协会1 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料§2 单片机简介2.1 数字电路简介在一个控制系统中, 单片机是电路的一部分, 单片机中的程序是针对其所在的电路编写 的。因此,要对单片机编程并实现一定的功能,必须了解整个系统的电路图。单片机编程是 针对某个特定的电路进行的, 这一点和普通的编程不一样。 编程时很可能要经常参照电路图。 与单片机直接相关的电路大都是数字电路。 数字电路各部分的功能十分明确, 因此相对比较 容易掌握。 简单地说,数字电路就是只有“0”和“1”两种信号的电路。判别信号究竟是“0”还 是“1”是通过电压的大小(常称作“电平” )来判断的。不同的数字器件的电平判断标准是 不一样的。常用的数字器件以高电平(超过某一阈值的电平)作为逻辑“1” ,以低电平(低 于某一阈值的电平)作为逻辑“0” 。其中高电平阈值大于低电平阈值,处于高电平阈值与低 电平阈值之间的电压是无效的。 高电平阈值与低电平阈值的具体值与数字器件的供电电压有 关,如 AT89S51 单片机的高电平阈值为(0.2Vcc+0.9)V,低电平阈值为(0.2Vcc-0.1)V, 其中 Vcc 为单片机的供电电压。 早年常用的数字器件的额定供电电压为 5V, 现在 3.3V、 1.8V 等电压的数字器件已经大量使用了。在 Easy 51 Kit Pro 中,我们仍使用 5V 供电的单片机。 另外,还有一种 RS-232 电平标准,以-12V~-5V 作为逻辑“1” ,以 5V~12V 作为逻辑“0” 。 电脑上的串口都符合 RS-232 标准。 还有一个“地”的概念。在电路中“地”并不是通常意义中的地,而是指电路中的一点, 这一点的电压被人为地规定为 0V。2.2 MCS-51 单片机简介目前生产单片机产品的公司非常多, 当中较有影响力的有 intel 公司推出的 MCS-51 系 列等。很多公司的产品都是与 MCS-51 架构兼容(MCS-51 compatible)的。本资料中采用的 atmel 公司的 AT89C51/52 或 AT89S51/52 单片机,就是兼容 MCS-51 架构的单片机。 Atmel 公司的 AT89C51(以后简称“C51”)、AT89C52(以后简称“C52”)、AT89C2051 (以后简称“C2051”)以及 C51、C52 的换代产品 AT89S51(以后简称“S51”)、AT89S52 (以后简称“S52”)容易上手、价格低廉(不超过 10 元/片)、资料丰富,是初学者入门 时广泛采用的单片机。 C51 拥有 4096 字节(1 字节=8 位)的片内程序存储器、128 字节的 RAM、32 个 I/O 口、 两个定时器、6 个中断源、一个串口等。C52、C2051 的资源与 C51 差别不大,其中 C52 的片 内程序存储器为 8192 字节、RAM 为 256 节、定时器有 3 个,其它与 C51 一样; C2051 的片 内程序存储器为 2048 字节、I/O 口只有 15 个,另比 C51 多了一个模拟比较器,工作电压范 围比较宽,为 2.7V~6V(C51/52、S51/52 为 4.5~5.5V),其它与 C51 一样。 I/O、 定时器、 中断、 串口等资源的用法在后面有详细介绍。 这里只对程序存储器与 RAM 作一下说明。单片机程序代码经过编译(C 程序)或汇编(汇编程序)后,要把编译或汇编 得到的代码文件(一般来说编译得到 hex 格式文件、汇编得到 bin 格式文件)烧写到单片机 内,存放这个程序的地方就是程序存储器。显而易见,单片机的程序存储器越大,我们就可 以把越大、越复杂的程序放进去。如果我们编写的程序太大,那么单片机的程序存储器就有 可能会放不下这个程序。这时,解决办法就只有精简代码、外接程序存储器(前提是所用的 单片机支持使用外部程序存储器)或采用程序存储器容量更大的单片机。RAM 是单片机程序 运行时存放变量的地方,常量也可以放在 RAM 中。C51 中的 RAM 大小为 128 字节,这就是说 单片机程序中最多只能同时存在 128 个 unsigned char 型的变量或 64 个 unsigned int 型的 变量(在 Keil 环境中,int 型变量的长度为 16 位,这与 VC 的 32 位不一样)或者是相应的中国科学技术大学业余无线电协会2 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料各种不同类型变量的组合。 可以看出, 单片机程序所允许的规模比 Windows 或其它操作系统 环境下的程序要小得多。编写单片机程序时一定要注意不要滥用资源。 S51 与 S52 分别是 C51、C52 的换代产品。从用户的角度看,S5x 单片机比相对应的 C5x 单片机多了看门狗与在线编程 (ISP) 功能, 另外最高运行速度有所增加 (C5x 最高支持 24MHz 的时钟频率, S5x 最高支持 33MHz 的时钟频率, 而 但市面上比较容易买到的 S5x 单片机仍只 最高支持 24MHz 的时钟频率)。 看门狗的使用在后面会有详细介绍,我们来看看单片机的在线编程(ISP)功能。要把 程序烧写到 AT89C 系列单片机中, 最常用的做法是把单片机插入专用的编程器中, 通过编程 器把程序烧到单片机里。 这样做的麻烦之处是在调试程序时, 编程者对程序作出的每次修改, 都要把单片机从电路中拔出来,插到编程器,烧好后又要把单片机重新插回电路板。可以想 象,这种工作是吃力不讨好的。利用 S5x 单片机的 ISP 功能,我们就无须来回插拔单片机, 只要在电路中把单片机的 ISP 编程引脚接出来, 并且这几个引脚所接的外围电路对 ISP 没有 影响,就可以用 ISP 编程器对单片机进行烧写了。另外,支持 AT89C 系列单片机的编程器成 本要比 ISP 下载线高最少几倍。一根并口 ISP 下载线的成本仅几元钱。 除了 S 系列、C 系列外,atmel 公司的 MCS-51 兼容产品也有其它系列,它们的主要区 别在于供作电压范围,在此就不作介绍了。 值得注意的是,一片单片机可以反复擦写的次数是有限的,atmel 的 C 系列、S 系列单 片机的声称可重复擦写次数为 1000 次。 下面以 S52 为例,对其引脚功能一一作出说明。C51、C52 以及 S51 的引脚功能与 S52 大致相同,如有遇到不同的地方会用粗斜体字特别说明。图2.2AT89C2051引脚功能图图2.1AT89S52引脚功能图1、电源引脚 Vcc 40 电源引脚 GND 20 接地引脚 工作电压为4V~5.5V。 2、外接晶体引脚中国科学技术大学业余无线电协会3 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图2.3外接晶体引脚XTAL1 19 XTAL2 18 XTAL1是片内振荡器的反相放大器输入端,XTAL2则是输出端,使用外部振荡器时,外部 振荡信号应直接加到XTAL1,而XTAL2悬空。内部方式时,时钟发生器对振荡脉冲二分频,如 晶振为12MHz,时钟频率就为6MHz。晶振的频率最高可以达到33MHz(C5x为24MHz)。电容取 30pF±10pF。 单片机程序指令的执行是以振荡器的振荡来驱动的。在MCS-51架构中,每12个振荡器 周期组成一个指令周期(或称机器周期)。单片机执行指令的时间是以指令周期为单位的。 不同指令的执行时间可能是不同的,一条指令的执行时间最短为一个指令周期。因此,单 片机所接的振荡器频率越高,它执行指令的速度就越快。 型号同样为AT89S52的芯片,在其后面还有频率编号,有24MHz和33MHz等可选。读者在 购买和选用时要注意了。 如AT89S52 24PC就是最高振荡频率为24MHz,40P6封装的普通商用芯 片。单片机芯片后缀的详细含义可见相应单片机数据手册的Ordering Information。 3、复位 RST 9 在振荡器运行时,有两个机器周期(24个振荡周期)以上的高电平出现在此引脚时,将 使单片机复位,只要这个引脚保持高电平,单片机便一直处于复位状态。复位后P0~P3口均 置1,引脚表现为高电平,程序计数器和特殊功能寄存器SFR全部清零。当复位引脚由高电平 变为低电平时,芯片从ROM的00H处开始运行程序。复位操作不会对内部RAM有所影响。常用 的复位电路如图2.4所示。当单片机上电时,由于电容的作用,RST引脚会处于短暂的高电平 状态,直到电容充电到一定程度时,RST引脚的电平会被8.2K的电阻拉低,单片机开始运行 程序。图中的按键为手动复位按键,当按下复位按键后,RST引脚会被1K的电阻上拉至高电 平, 单片机复位, 按键松开后, RST恢复低电平, 单片机重新从程序存储器的00H处运行程序。 手动复位按键在单片机的最小系统中并不是必须的,但对单片机的复位控制会方便些。中国科学技术大学业余无线电协会4 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图2.4 常用复位电路 4、输入输出引脚 (1)P0端口[P0.0~P0.7] P0是一个8位漏极开路型双向I/O端口, 端口置1 对端口写1) ( 时作高阻抗输入端。作为输出口时能驱动8个TTL。P0端口要外接上拉电阻。 (2)P1端口[P1.0~P1.7] P1是一个带有内部上拉电阻的8位双向I/0端口。 输出时可驱 动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。对内部Flash程序存 储器编程时,接收低8位地址信息。除此之外P1端口还用于一些专门功能,具体见表2.1。 P1 引脚 P1.0 P1.1 P1.5 P1.6 P1.7 表2.1 兼用功能 T2(外部计数器)、时钟输出(C51、S51无此功能) T2EX(定时器2捕捉和重载触发及方向控制)(C51、S51无此功能) MOSI(用于在线编程)(C51、C52无此功能) MISO(用于在线编程)(C51、C52无此功能) SCK(用于在线编程)(C51、C52无此功能) P1 端口引脚兼用功能表(3)P2端口[P2.0~P2.7] P2是一个带有内部上拉电阻的8位双向I/0端口。 输出时可驱 动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。对内部Flash程序存 储器编程时,接收高8位地址和控制信息。在访问外部程序和16位外部数据存储器时,P2口 送出高8位地址。而在访问8位地址的外部数据存储器时其引脚上的内容在此期间不会改变。 (4)P3端口[P3.0~P3.7] P2是一个带有内部上拉电阻的8位双向I/0端口。 输出时可驱 动4个TTL。端口置1时,内部上拉电阻将端口拉到高电平,作输入用。对内部Flash程序存 储器编程时,接控制信息。除此之外P3端口还用于一些专门功能,具体见表2.2。 P3 引脚 P3.0 P3.1 P3.2 P3.3 P3.4 P3.5 P3.6 P3.7 表2.2 兼用功能 串行通信输入(RXD) 串行通信输出(TXD) 外部中断0(INT0) 外部中断1(INT1) 定时器0输入(T0) 定时器1输入(T1) 外部数据存储器写选通WR 外部数据存储器写选通RD P3 端口引脚兼用功能表中国科学技术大学业余无线电协会 5 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料5、其它的控制或复用引脚 (1)ALE/-PROG 30 访问外部存储器时,ALE(地址锁存允许)的输出用于锁存地址的 低位字节。 即使不访问外部存储器, ALE端仍以不变的频率输出脉冲信号(此频率是振荡器频 率的1/6)。在访问外部数据存储器时,出现一个ALE脉冲。对Flash存储器编程时,这个引脚 用于输入编程脉冲PROG。 (2)PSEN 29 该引脚是外部程序存储器的选通信号输出端。 当S52由外部程序存储器取 指令或常数时,每个机器周期输出2个脉冲即两次有效。但访问外部数据存储器时,将不会 有脉冲输出。 (3)-EA/Vpp 31 外部访问允许端。当该引脚访问外部程序存储器时,应输入低电平。 要使S52只访问外部程序存储器(地址为0000H-FFFFH),这时该引脚必须保持低电平。当使 用内部的程序存储器时,此引脚应与Vcc相连。对Flash存储器编程时,用于施加Vpp编程电 压。 C2051 的引脚功能与 C51 相仿,在此不另外介绍了,仅给出 C2051 的引脚图,如图 2.2 所示。2.3 Easy 51 Kit Pro 简介Easy 51 Kit Pro 是中国科大业余无线电协会继 Easy 51 Kit 之后设计的一块 51 单片 机学习板,供单片机初学者使用。Easy 51 Kit Pro 包括了四位的数码 LED 显示输出、两个 按键输入、一个 3×4 扫描式键盘和外接 93C46 EEPROM 芯片等。利用这块学习板,初学者可 以学习 51 单片机各模块的编程,熟悉单片机程序的特点,也可以试着写一些功能比较完整 的程序(如闹钟、秒表等) 。如果初学者能够掌握本资料中给出的各个例子,就可以独立开 发单片机程序了。 Easy 51 Kit Pro 由两块电路板组成。其中大的电路板为 MCS-51 最小系统板,小的电 路板为学习板(学习板中没有单片机)。采用两块电路板的原因是考虑到当初学者入门后学 习板部分的电路可能已经没有很大的用处, 而一块设计完善的最小系统板可能在日后仍能派 上用场。在设计上,Easy 51 Kit Pro 支持 USB 接口供电,采用一体化设计,学习板可以直 接插在最小系统板上面,无须数据线连接,两块电路板可共用同一电源。如果单片机采用 AT89S51 或 AT89S52,那么整个 Easy 51 Kit Pro 与并口 ISP 下载线的总成本大约为 50 元, 再加上一根双公头的 A 型 USB 线,就可以轻松学习 MCS-51 单片机了。2.4 Easy 51 Kit Pro电路功能分析现在我们来分析一下Easy 51 Kit Pro的电路。Easy 51 Kit Pro的电路见附图。 最小系统板包含了51单片机的最小系统、 I/O线外接插座以及可断开的232电平转换芯片 及9针串口等。C5x、S5x的最小系统如图2.5。这个最小系统在前面已有详细介绍,在这里就 不罗嗦了。标记为“TX”和“RX”的跳线可以用跳线帽把MAX232与单片机的串口断开或接通。 接通时可以与电脑的串口直接通信,断开时则MAX232与单片机无任何电气上的信号连接。中国科学技术大学业余无线电协会6 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图2.5 MCS-51单片机最小系统 在学习板中的电路我们将会配合后面的例子逐步进行分析。中国科学技术大学业余无线电协会7 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料§3 MCS-51单片机的C语言编程3.1 汇编语言在学习51单片机的C语言编程之前,我们先来了解一下汇编语言。使用汇编语言可以对 单片机进行最直接的控制。每执行一条汇编语句,单片机就会执行一条指令。下面是一些 汇编语句的例子: LD AX,#0CCC2H ADD AX,CX 利用汇编语言对单片机编程, 所编写的代码效率很高, 但用汇编语言写程序尤其是较大 型的程序十分费时,程序的移植也存在问题。所以现在多用C语言对单片机进行编程,再在 必要的地方用汇编语言实现。本资料介绍的是单片机的C语言编程。尽管汇编语言在单片机 程序开发中有着其固有的缺点,但对单片机的指令系统以及汇编语言有一定的了解,对编 写出高质量的单片机C程序是很有帮助的。3.2建立你的第一个C项目使用C语言肯定要使用到C编译器,以便把写好的C程序编译为机器码,这样单片机才能 执行编写好的程序。Keil uVision2是众多单片机应用开发软件中优秀的软件之一,下面我 们用Keil uVision2建立一个小程序项目。 首先是运行Keil软件。运行几秒后,出现如图3.1的屏幕。图3.1 启动时的屏幕 接着按下面的步骤建立第一个项目: (1)点击Project菜单,选择弹出的下拉式菜单中的“New Project”,如图3.2。接着 弹出一个标准Windows文件对话窗口,如图3.3。在“文件名”中输入你的第一个C程序项目 名称,这里我们用“test”。“保存”后的文件扩展名为uv2,这是Keil uVision2项目文件 扩展名,以后我们可以直接点击此文件以打开先前做的项目。中国科学技术大学业余无线电协会8 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图3.2New Project菜单图3.3文件窗口(2)选择所要使用的单片机,这里我们选择Atmel公司的AT89S52。此时屏幕如图3.4 所示。完成上面步骤后,我们就可以编写程序了。中国科学技术大学业余无线电协会9 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图3.4选取芯片(3)首先我们要在项目中创建新的程序文件或加入旧程序文件。如果你没有现成的程 序, 那么就要新建一个程序文件。 在这里我们以一个C程序为例介绍如何新建一个C程序和如 何加到项目中。点击图3.5中1的新建文件的快捷按钮,在2中出现一个新的文字编辑窗口, 这个操作也可以通过菜单“File”->“New”或快捷键Ctrl+N来实现。现在可以编写程序 了,光标已出现在文本编辑窗口中,等待我们的输入。下面是一段程序: #include &AT89x52.h& #include &stdio.h& void main(void) { SCON = 0x50; //串口方式1,允许接收 TMOD = 0x20; //定时器1 定时方式2 TCON = 0x40; //设定时器1 开始计数 TH1 = 0xE8; //22.1184MHz 2400 波特率 TL1 = 0xE8; TI = 1; TR1 = 1; //启动定时器 while(1) { printf(&Hello World!\n&); //显示Hello World } } 值得注意的是,Keil的文本编辑器对中文的支持欠佳,所以读者在写程序时,可以考 虑用英文对程序代码进行注释。本书中的代码多用中文注释仅是为了使读者阅读方便。 (4) 点击图3.5中的3保存新建的程序, 也可以用菜单 “File” “Save” -& 或快捷键Ctrl+S 进行保存。因是新文件所以保存时会弹出类似图3.3的文件操作窗口,我们把第一个程序命 名为“test1.c”,保存在项目所在的目录中,这时你会发现程序单词有了不同的颜色,说中国科学技术大学业余无线电协会10 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料明Keil的C语法检查生效了。如图3.6鼠标在屏幕左边的“Source Group 1”文件夹图标上右 击弹出菜单,在这里可以做在项目中增加减少文件等操作。我们选“Add File to Group ‘Source Group 1’”弹出文件窗口,选择刚刚保存的文件,按“ADD”按钮,关闭文件窗, 程序文件已加到项目中了。这时在“Source Group 1”文件夹图标左边出现了一个小“+” 号,说明文件组中有了文件,点击它可以展开查看。图3.5新建程序文件图3.6把文件加入到项目中(5)C程序文件已被我们加到了项目中了,下面就剩下编译运行了。这个项目我们只是 用做学习新建程序项目和编译运行仿真的基本方法, 所以使用软件默认的编译设置, 它不会 生成用于芯片烧写的hex文件。我们先来看图3.7,图中1、2、3都是编译按钮,不同的是1 是用于编译单个文件。 2是编译当前项目, 如果先前编译过一次之后文件没有作过编辑改动,中国科学技术大学业余无线电协会11 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料这时再点击是不会再次重新编译的。3是重新编译,每点击一次均会再次编译链接一次,不 管程序是否有改动。在3右边的是停止编译按钮,只有点击了前三个中的任一个,停止按钮 才会生效。5是这三个按钮在菜单中相应的命令,效果和按钮是一样的。这个项目只有一个 文件,你按1、2、3中的任一个都可以编译。在4中可以看到编译的错误信息和使用的系统资 源情况等,以后我们要查错就靠它了。6是有一个小放大镜的按钮,这就是开启/关闭调试模 式的按钮,它也在菜单“Debug”-&“Start/Stop Debug Session”中,快捷键为Ctrl+F5。图3.7编译程序(6)进入调试模式,软件窗口样式大致如图3.8所示。图中1为运行,当程序处于停止状 态时才有效,2为停止,程序处于运行状态时才有效。3是复位,模拟芯片的复位,程序回到 最开头处执行。 按4我们可以打开5中的串行调试窗口, 这个窗口我们可以看到从51芯片的串 行口输入输出的字符,这里的第一个项目也正是在这里看运行结果。这些在菜单中也有,这 里不再一一介绍。首先按4打开串行调试窗口,再按运行键,这时就可以看到串行调试窗口 中不断的打印“Hello World!”。这样就完成了第一个C项目。最后我们要停止程序运行回 到文件编辑模式中,就要先按停止按钮再按开启/关闭调试模式按钮。然后我们就可以进行 关闭Keil等相关操作了。到此为止,我们初步学习了一些Keil uVision2的项目文件创建、中国科学技术大学业余无线电协会12 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料编译、运行和软件仿真的基本操作方法。图3.8调试运行程序3.3 生成hex文件在开始学习C语言的主要内容时,我们先来看看如何用Keil uVision2来编译生成用于烧 写芯片的hex文件。Hex文件格式是Intel公司提出的用来保存单片机或其他处理器的目标程 序代码的文件格式。一般的编程器都支持这种格式。我们先来打开刚做的第一个项目,打开 它的所在目录, 找到test.Uv2的文件就可以打开先前的项目了。 然后右击图3.9中的1项目文 件夹,弹出项目功能菜单,选“Options for Target’Target1’”,弹出项目选项设置窗口, 同样先选中项目文件夹图标,这时在Project菜单中也有一样的菜单可选。打开项目选项窗 口,转到“Output”选项页,如图3.10所示,图中1是选择编译输出的路径,2是设置编译输 出生成的文件名,3则是决定是否要创建hex文件,选中它就可以输出hex文件到指定的路径 中。 现在我们重新编译一次, 很快在编译信息窗口中就显示hex文件创建到指定的路径中了, 如图3.11。这样我们就可用自己的编程器所附带的软件去读取并烧到芯片了。中国科学技术大学业余无线电协会13 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图3.9项目功能菜单图3.10项目选项窗口中国科学技术大学业余无线电协会14 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图3.11编译信息窗口3.4 Keil C语言相信读者们对标准C语言(ANSI C)已经十分熟悉。Keil中的C语言与ANSI C语言略有 变化,下面对Keil C语言的介绍将着重介绍Keil C与ANSI C不同的地方。 3.4.1 数据类型 表3.1中列出了Keil uVision2 C51编译器所支持的数据类型。在标准C语言中基本的数 据类型为char、 int、 short、 long、 float和double, 而在C51编译器中int和short相同, float 和double相同,这里就不列出说明了。下面来看看它们的具体定义: 数据类型 unsigned char signed char unsigned int signed int unsigned long signed long float * bit sfr sfr16 sbit 表3.1 长 度 值 域 单字节 单字节 双字节 双字节 四字节 四字节 四字节 1~3 字节 位 单字节 双字节 位 0~255 -128~+127 0~6~+~+ ±1.~±3. 对象的地址 0 或1 0~255 0~65535 0 或1KEIL uVision2 C51 编译器所支持的数据类型1、char 字符类型 char类型的长度是一个字节, 通常用于定义处理字符数据的变量或常量。 分无符号字符 类型unsigned char和有符号字符类型signed char,默认值为signed char类型。unsigned char类型用字节中所有的位来表示数值,所可以表达的数值范围是0~255。signed char类 型用字节中最高位字节表示数据的符号,“0”表示正数,“1”表示负数,负数用补码表示 (正数的补码与原码相同,负二进制数的补码等于它的绝对值按位取反后加1)。所能表示 的数值范围是-128~+127。unsigned char常用于处理ASCII字符或用于处理小于或等于255中国科学技术大学业余无线电协会 15 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料的整型数。在51单片机程序中,unsigned char是最常用的数据类型。 2、int 整型 int整型长度为两个字节,用于存放一个双字节数据。分有符号int整型数signed int 和无符号整型数unsigned int,默认值为signed int类型。signed int表示的数值范围是 -32768~+32767,字节中最高位表示数据的符号, “0”表示正数, “1”表示负数。unsigned int表示的数值范围是0~65535。 3、long 长整型 long长整型长度为四个字节,用于存放一个四字节数据。分有符号long长整型signed long和无符号长整型unsigned long,默认值为signed long类型。signed int表示的数值范 围是-~+,字节中最高位表示数据的符号,“0”表示正数,“1”表 示负数。unsigned long表示的数值范围是0~。 4、float 浮点型 float浮点型在十进制中具有7位有效数字,是符合IEEE-754标准的单精度浮点型数据, 占用四个字节。 5、* 指针型 指针型本身就是一个变量, 在这个变量中存放的指向另一个数据的地址。 这个指针变量 要占据一定的内存单元,对不同的处理器长度也不尽相同,在C51中它的长度一般为1~3个 字节。 6、bit 位标量 bit位标量是C51编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义 位指针,也不能定义位数组。它的值是一个二进制位,不是0就是1,类似一些高级语言中 Boolean类型中的True和False。 7、sfr 特殊功能寄存器 sfr也是一种扩充数据类型,占用一个内存单元,值域为0~255。利用它可以访问51单 片机内部的所有特殊功能寄存器。如用sfr P1 = 0x90 这一句定P1为P1端口在片内的寄存 器,在后面的语句中我们用P1 = 255(对P1 端口的所有引脚置高电平)之类的语句来操作 特殊功能寄存器。 8、sfr16 16 位特殊功能寄存器 sfr16占用两个内存单元, 值域为0~65535。 sfr16和sfr一样用于操作特殊功能寄存器, 所不同的是它用于操作占两个字节的寄存器,如定时器T0和T1。 9、sbit 可寻址位 sbit同样是C51中的一种扩充数据类型,利用它可以访问芯片内部RAM中的可寻址位或 特殊功能寄存器中的可寻址位。如先前我们定义了 sfr P1 = 0x90; //因P1 端口的寄存器是可位寻址的,所以我们可以定义 sbit P1_1 = P1^1; //P1_1 为P1 中的P1.1 引脚 同样我们可以用P1.1的地址去写,如sbit P1_1 = 0x91;中国科学技术大学业余无线电协会16 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料这样我们在以后的程序语句中就可以用P1_1来对P1.1引脚进行读写操作了。 通常这些可 以直接使用系统提供的预处理文件, 里面已定义好各特殊功能寄存器的简单名字, 直接引用 可以省去一点时间。当然你也可以自己写自己的定义文件,用你认为好记的名字。 3.4.2 常量 常量是在程序运行过程中不能改变值的量。变量的定义可以使用所有C51编译器支持的 数据类型,而常量的数据类型只有整型、浮点型、字符型、字符串型和位标量。 常量的数据类型说明是这样的: 1、整型常量可以表示为十进制如123,0,-89等。十六进制则以0x开头如0x34,-0x3B等。长 整型就在数字后面加字母L,如104L,034L,0xF340等。 2、浮点型常量可分为十进制和指数表示形式。十进制由数字和小数点组成,如 0.888,.0等, 整数或小数部分为0, 可以省略但必须有小数点。 指数表示形式为[±] 数字[.数字]e[±]数字,[]中的内容为可选项,其中内容根据具体情况可有可无,但其余部 分必须有,如125e3,7e9,-3.0e-3。 3、字符型常量是单引号内的字符,如‘a’,‘d’等,不可以显示的控制字符,可以在该字 符前面加一个反斜杠“\”组成专用转义字符。常用转义字符表请见表3.2。 4、字符串型常量由双引号内的字符组成,如“test”,“OK”等。当引号内的没有字符时, 为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在C中字符串常量是作为字 符类型数组来处理的, 在存储字符串时系统会在字符串尾部加上\o转义字符以作为该字符串 的结束符。字符串常量“A”和字符常量‘A’是不同的,前者在存储时多占用一个字节的字 间。 5、位标量,它的值是一个二进制。 转义字符 \o \n \r \t \b \f \' \& \\ 含义 空字符(NULL) 换行符(LF) 回车符(CR) 水平制表符(HT) 退格符(BS) 换页符(FF) 单引号 双引号 反斜杠 表3.2 常用转义字符表 常量可用在不必改变值的场合,如固定的数据表,字库等。常量的定义方式有几种,下 面来加以说明。 #define FALSE 0x0; //用预定义语句可以定义常量 #define TRUE 0x1; //这里定义False 为0,True 为1 //在程序中用到False 编译时自动用0 替换,同理True 替换为1 unsigned int code a=100; //这一句用code 把a 定义在程序存储器中并赋值 const unsigned int a=100;//这一句用const关键字把a定义在RAM中并赋值 常量的合理使用可以提高程序的可读性、可维护性。因此,一个非小型的高质量的单 片机C程序必定会用到常量。上面介绍了定义常量的三种方法:宏定义、用code关键字定义 ASCII 码(16/10 进制) 00H/0 0AH/10 0DH/13 09H/9 08H/8 0CH/12 27H/39 22H/34 5CH/92中国科学技术大学业余无线电协会17 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料以及用const关键字定义。通过宏定义的常量并不占用单片机的任何存储空间,而只是告诉 编译器在编译时把标识符替换一下,这在资源受限的单片机程序中显得非常有用。用code 关键字定义的常量放在单片机的程序存储器中; 用const关键字定义的常量放在单片机的RAM 中, 要占用单片机的变量存储空间。 单片机的程序存储器空间毕竟要比RAM大得多 (S51、 C51 只有128字节的RAM空间,S52、C52只有256字节的RAM空间),所以当要定义比较大的常量数 组时,用code关键字定义常量要比用const关键字定义合理一些。3.5 单片机 I/O输入输出(I/O)是单片机的最基本功能。C51、C52、S51、S52 共有 4 个 I/O 端口,合 共 32 根 I/O 引脚。每个引脚都可以分别设置用作输入还是输出。在单片机程序中,只要往 某个 I/O 寄存器写“1” ,那么相对应的引脚就会输出高电平;反之,只要往某个 I/O 寄存器 写“0” ,那么相对应的引脚就会输出低电平。当单片机的 I/O 端口用作输入时,要先往相 对应的 I/O 寄存器写“1” 。这时在单片机程序中,就可以通过读相对应的 I/O 寄存器的值 得知该引脚处于高电平状态( “1” )还是低电平状态( “0”。 ) 3.5.1 显示“0123”的程序 下面我们结合 Easy 51 Kit Pro 电路,详细分析一个简单的输出程序,并对程序进行 仿真。 在实际的硬件中, 我们除了观看程序的效果外还可以用示波器查看单片机各个引脚的 输出波形。在本资料中,所有程序中所用的晶体的频率为 22.1184MHz。 1、 数码管显示原理图 3.12b 共阳极数码管原理图图 3.12a 数码管图 3.12c 共阴极数码管原理图在单片机系统中,常用LED数码管来显示各种数字或符号。由于它具有显示清晰、亮度 高、使用电压低、寿命长的特点,因此使用非常广泛。 LED数码管由8个发光二极管(LED)组成,如图3.12a所示。其中7个长条形的LED排列成 “日”字形,另一个圆点形的LED在右下角作为显示小数点用,能显示各种数字及部分英文 字母。LED数码管按连接方式分为两种:一种是8个LED的阳极都连在一起的,称之为共阳极 数码管,如图3.12b所示;另一种是8个LED的阴极都连在一起的,称之为共阴极数码管,如 图3.12c所示。 共阴和共阳结构的数码管各笔划段名和安排位置是相同的。当LED导通时,相应的笔划 段发亮,由发亮的笔划段组合显示各种字符。如果把8个笔划段dp、g、f、e、d、c、b、a 对应于一个字节(8位)的D7、D6、D5、D4、D3、D2、D1、D0,那么显示字符的字形代码就中国科学技术大学业余无线电协会18 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料可以用8位二进制码表示。例如,对共阴的数码管,当公共阴极接地(为零电平),而阳极 dp、g、f、e、d、c、b、a各段为时,显示“P”字符,即对于共阴极的数码管, “P” 字符的字形码是0x73。如果是共阳的数码管,公共阳极接高电平,显示“P”字符的字形码 应为x8C)。 在 Easy 51 Kit Pro 学习板中,使用了一个 4 位的数码管模块,能同时显示 4 个字母或 数字符号。这个模块的 4 个数码管的笔划引脚是共用的,而公共阴极(或阳极)是各自独立 的。因此,模块中的 4 位显示控制是不能同时进行的,而是通过扫描控制的。现以共阴极的 4 位数码管模块为例,说明如何显示“1234”这一组数字。我们把 4 个共阴极引脚分别记为 DIG1、DIG2、DIG3 和 DIG4。首先我们给模块的笔划引脚加上“1”的共阴字形码电平 0x06 (),然后给 DIG1 引脚送上低电平,DIG2、DIG3、DIG4 引脚送上高电平,这样, 模块的第二、三、四位都不亮,而第一位显示“1”的图案。经过一个较短的时间后,我们 给模块的笔划引脚加上“2”的共阴字形码电平 0x5b,然后给 DIG2 引脚送上低电平,DIG1、 DIG3、DIG4 引脚送上高电平,这样,模块的第一、三、四位都不亮,而第二位显示“2”的 图案。接着,我们给模块的笔划引脚加上“3”的共阴字形码电平 0x4f,然后给 DIG3 引脚 送上低电平,DIG1、DIG2、DIG4 引脚送上高电平,这样,模块的第一、二、四位都不亮, 而第三位显示“3”的图案。最后,给模块的笔划引脚加上“4”的共阴字形码电平 0x66, 然后给 DIG4 引脚送上低电平,DIG1、DIG2、DIG3 引脚送上高电平,这样,模块的第一、二、 三位都不亮,而第四位显示“4”的图案。这个扫描过程循环进行。由于人眼的视觉暂留和 LED 的余辉效应,我们就觉得“1”、“2”、“3”、“4”这四个图案时同时连接出现的了。 2、 电路分析 我们把 Easy 51 Kit Pro 的电路中本程序用到的部分提取出来,重绘于图 3.13。图 3.13显示“0123”的程序的电路图19中国科学技术大学业余无线电协会 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料在图 3.13 中,D3 为 4 位的共阳极数码管模块。DIG1~DIG4 分别是 4 个位的共阳极。单 片机的 P1.0~P1.3 四个端口分别通过 74HC244 后与 4 个公共极相连(电路图标号相同的地 方在电气上都是连通的)。由于点亮数码管需要的电流较大,单片机的 I/O 输出高电平时不 能提供足够的电流,所以电路中用 74HC244 来驱动数码管的公共阳极,在效果上,我们可以 忽略掉 74HC244,认为 P1.0~P1.3 就是直接与模块的四个公共极相连,控制着这四个公共 极。P0.0~P0.7 分别与模块的 a~g 七个笔划引脚以及 dp 相连。 3、 程序分析 程序中所包含的头文件 at89x52.h 里包含了 C52 和 S52 的寄存器的定义。如果用的单片 机是 C51 或 S51,则头文件应为 at89x51.h;如果用的单片机是 C2051,则头文件应为 at89x051.h。 下面我们编写一个程序,在 Easy 51 Kit Pro 的数码管模块(共阳)上固定显示“0123” 4 个数字。4 位数码管模块的原理以及 Easy 51 Kit Pro 的电路图都已经分析过了,实现这个 程序应该很容易。程序中的 P0 与 P1 分别对应单片机的 P0 和 P1 端口。因此只要对 P0 或 P1 赋一个 8 位的值, 就可以控制 P0 或 P1 的 8 个引脚的电平输出了。 比如 P1 = 0x01 语句可 以使 P1.0 输出高电平,而 P1.1~P1.7 输出低电平。 程序中 SEG_CODE 数组分别对应 0~9 这十个数字的共阳字形码,并且 SEG_CODE [i] = i, 这样有利于程序的编写。COMM 数组控制模块的 4 个共阳极的电平。值得注意的是,在这个 程序中,SEG_CODE 数组和 COMM 数组都是常量,因此在这两个数组的声明中都加入了 code 关键字。 “for(j=0; j&200; j++);”这一句是用来提供一定的延时的,适当的延时可以改善显 示的效果。大家可以试试不同的值以达到最佳效果。必须知道的是,这个循环 200 次的循环 体所执行的指令次数并不是 200 次,而是与 j 的数据类型以及编译器有关。因此,这个循环 的执行时间是不能直接确定的。想知道它的执行时间,最好用示波器观察一下时间。 另外可以看到,程序的显示控制部分是在主函数的 while(1)死循环里进行的,这样数 码管的显示就会不断地刷新,直到掉电为止。在以后的例子中读者可以看到,while(1)死循 环是单片机程序必不可少的部分。 【程序 1】程序清单如下: /*I/O 口分配情况*/ /*P0:P0_0...P0_7 用于控制七段数码管笔段码*/ /*P1:P1_0...P1_3--扫描四个数码管的共阳极*/ //晶振频率 22.1184MHz #include&at89x52.h& void main() { unsigned char code COMM[4] = {0x01,0x02,0x04,0x08}; unsigned char i,j; unsigned char code SEG_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //0~9 的共阳字形码 while(1) {中国科学技术大学业余无线电协会20 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料for(i=0; i&4; i++) { P1 = COMM[i]; //选通一位数码管 P0 = SEG_CODE[i]; //送笔段码 for(j=0; j&200; j++); //延时 } } } 4、 仿真 在把程序烧到单片机之前,我们可以先在 Keil 软件中进行仿真。程序仿真是单片机编 程中的重要手段。 通过仿真, 我们可以看到单片机内寄存器值的变化、 输入输出端口的变化、 串口、定时器/计数器的情况以及中断状态等。 单片机程序的软仿真与 VC 中的 Debug Manager 十分相似 (但愿读者用过) 如果在调 。 试单片机程序时,不使用硬件的仿真器,那么 Keil 中的软件仿真就是调试程序的最有力手 段了。因为如果不对编写好的程序进行仿真而直接烧到单片机里,在电路中运行,就像是 在编写普通 C 语言程序时只用 printf 语句在关键的地方打印一些关键信息出来。这样做往 往只能看得出结果是错误的,而并不能很快地定位出出错的地方。更糟糕的是,在编写普 通 C 语言程序时,即使不使用 Debug Manager,你也可以在你所需要的地方加入 printf 语 句,在显示器上打印出足够多的信息供你查错。但在单片机程序中,能够用于显示的资源 往往是少之又少的。例如在 Easy 51 Kit Pro 学习板中,就只有 4 位的数码管用作显示,读 者可以想象它能显示出多少能供你查错的信息。相信用过 printf 与 Debug Manager 调试程 序的读者都可以体会到后者比前者可以带来多大的效率提升。而在调试单片机程序时,仿 真(当然使用仿真器要比纯软件仿真要好,因为仿真器的仿真是在真实的电路中查看程序 运行状态)比不仿真只会带来更大的效率提升。另外,在编写单片机程序时,你可能会遇 到一些你认为是莫名其妙的错误(发现这些错误后,你可能可以很快地采取一些办法避开 它或解决它,但理解这些错误出现的原因可能要花比较长的时间) ,这些错误往往隐蔽性很 强,如果不通过仿真,你可能永远也想象不出这会是个错误。 在仿真中,我们把“for(j=0; j&200; j++);”一句改为: for(j=0; j&10000; j++) { for(k=0; k&100; k++); } 并把变量 j 的定义改为 unsigned int,同时增加一个 unsigned int 类型的变量 k。这样做 的好处时增加延时时间,使我们能在仿真中比较清楚地看到单片机 P0 和 P1 端口的变化。 修改后重新编译,运行“Debug”菜单中的“Start/Stop Debug Session” 。在“Peripherals” 菜单中选中“I/O Ports”中的“Port0”和“Port1” 。这时可以看见 P0 和 P1 端口了,如图 3.14 所示。然后点击“run”快捷按钮,仿真就开始了。这时可以看到 P0 和 P1 端口的值在不断 改变(打上勾的为 1,不打勾的为 0) 。中国科学技术大学业余无线电协会21 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料图 3.14P0 和 P1 的仿真窗口为了更清楚地检查程序运行是否正确,我们可以进入单步运行的模式。运行“Debug” 菜单中的“Step”命令,可以一步一步地检查程序的运行。 5、 用示波器观察程序的运行 用示波器观察程序的运行的单片机程序调试中的重要手段。 我们用示波器观察单片机的 P0.0~P0.7、P1.0~P1.3 引脚,应该可以看到各个引脚的电平是周期变化的。 至此,本资料中的第一个示例程序就分析完毕了。Easy 51 Kit Pro 中还有 4 个 LED,可 以实现走马灯程序。相信看懂显示“0123”的程序后,走马灯程序对读者来说实在是太简单 了。下面仅简单地分析一下电路。程序中用到的电路重绘于图 3.15。4 个 LED D2、D3、D4、 D5 的阴极分别与单片机的 P3.7、 P3.6、 P3.5、 P3.4 相连。 LED 的阳极通过限流电阻接到 Vcc。 当单片机的相应引脚置高电平时,相应的 LED 不亮;当单片机的相应引脚置低电平时,相 应的 LED 亮。如果把 LED 的阳极与单片机的 I/O 口相连,而阴极通过限流电阻接到地,那 么当单片机的相应引脚置低电平时,相应的 LED 不亮;当单片机相应的引脚置高电平时, 相应的 LED 亮。 但这种连接方式会由于单片机的 I/O 端口置高电平时驱动能力不足而使 LED 点亮时亮度不足。图 3.15走马灯程序的电路图中国科学技术大学业余无线电协会22 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料【程序 2】程序清单如下: //晶振频率 22.1184MHz #include &at89x52.h& void main() { P3 |= 0xf0; //P3.4~P3.7 置为高电平,4 个 LED 都灭 while(1) { for(i=4; i&8; i++) { P3 = ~(1&&i); //亮一个 LED for(k=0; k&10000; k++) //延时 { for(j=0; j&10; j++); } } } } 读者应该已经注意到,程序 2 中用到了位运算。位运算是单片机程序中常用到的运算。 在这个程序中,由于循环变量 k 的值要取到 10000,而 unsigned char 型变量的取值只能取 到 255,所以 k 的类型必须为整型。循环变量 j 的取值最大为 10,所以用 unsigned char 型的变量就够了。 读者必须注意, 不同的变量类型除了占用内存的空间不一样外 (比如 char 型占一个字节,int 型占两个字节) ,程序的执行效率也会受到影响。读者不妨把 j 由 unsigned char 型变量改成 unsigned int 型变量,程序的其它地方都不变。重新编译后的 程序走马灯的速度将大大降低。 3.5.2 数按键次数的程序 在这个程序中,我们用一个按键作程序的输入,每按下一次按键,数码管的显示就加 1 (程序开始时显示“0”。为简单起见,这次我们只用到数码管的一位。当数码管的数字从 ) 0 加到 9 后, 下一次按键事件将使数码管的显示归 0。 当按键按未按下时, 单片机相应的 I/O 口引脚呈高电平,程序从单片机读出“1” ;当按键按下时,单片机相应的 I/O 口接地,电压 为 0,程序从单片机读出“0” 。按键按下时,单片机引脚的电平会发生短暂的抖动,在单片 机输入引脚上接上一个 0.01μF 的接地电容可以减小抖动带来的干扰。 我们把 Easy 51 Kit Pro 的电路中本程序用到的部分提取出来,重绘于图 3.16。电路 中按键与单片机的 P3.2 相连。 当单片机发现输入引脚 P3.2 有从高电平“1”到低电平“0” ( ) ( ) 的变化时,认为是可能按键事件。再经过一段短时间的延时(约 10ms) ,如果此时 P3.2 仍 为低电平,则认为的确发生了按键事件。延时的目的是为了消除按键时发生的抖动,称为软 件去抖动。 在程序中我们第一次用到 bit 型变量,这种变量在 ANSI C 中是没有的。在程序中为了中国科学技术大学业余无线电协会23 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料便于区分变量的类型,我们在 bit 型变量前都加了小写的 b,以与 unsigned char 型变量区 别开来。Delay 函数中的延时值 DELAY_10MS 用了宏定义,方便调试时修改。因为用循环的 做法进行延时,这个值最好用实验的办法试出来。图 3.16 【程序 3】程序清单如下: //晶振频率 22.1184MHz #include &at89x52.h&数按键程序的电路图#define INPUT_PIN P3_2 //定义输入引脚,这里用 pin 而不是 port,是因为只有一个引脚 #define SEG_PORT P0 #define DISPLAY_DIG1 P1&=0xf0;P1|=0x01 //选通 4 位数码管中的 1 位,用宏定义的方法使程序更直观 #define PRESSED 0 //定义按键的两个状态 #define UNPRESSED 1 #define DELAY_10MS 10000 //10ms 的延时参数中国科学技术大学业余无线电协会24 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料void Delay(unsigned int n) { for(i=0; i&n; i++); } void main() { unsigned char currentNumber = 0; //数码管显示的当前数值,初始化为 0 bit bNewStatus,bOldS //记录按键的前后两个状态 unsigned char code SEG_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; INPUT_PIN = 1; //把该引脚设置为输入 DISPLAY_DIG1; SEG_PORT = SEG_CODE[currentNumber]; //显示 bOldStatus = INPUT_PIN; //读输入引脚电平 while(1) { bNewStatus = INPUT_PIN; if(bNewStatus==PRESSED && bOldStatus==UNPRESSED) { //检测到电平负跳变 Delay(DELAY_10MS); bNewStatus = INPUT_PIN; //再读一次输入引脚 if(bNewStatus == PRESSED) { //确认为按键事件 if(currentNumber != 9) { //更新当前显示的数字 currentNumber++; } else { currentNumber = 0; } } } bOldStatus = bNewS SEG_PORT = SEG_CODE[currentNumber]; //显示当前数字 } } 程序 3 中大量利用宏定义的方法定义常量、端口以及简单语句。这大大增加了程序的 可读性。后面的程序示例也会沿用这一风格。3.6 中断中断是指在 CPU 处理某事情时,外部发生了某一事件,请求 CPU 马上处理,CPU 暂中国科学技术大学业余无线电协会 25 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料时停止当前的处理工作,转入处理外部事件,处理完再重新返回原来中断的地方,继续执行 原来的工作。 我们把请求 CPU 中断的请求源称为中断源,也就是 CPU 中一些可以引起中断的事件。 C51、S51 中一共有 5 个中断源:INT0 中断、INT1 中断、两个片内定时器中断(TF0、TF1) 、 一个串行口中断。而 C52 和 S52 则还有定时器 2 中断,共 6 个中断源。我们把这些中断分 为外部中断和内部中断。外部中断包括 INT0、INT1 中断,内部中断包括两个或三个片内定 时器中断(TF0、TF1) 、一个串行口中断。其中 INT0、INT1、定时器中断是由控制寄存器 TCON 控制,而串行口中断由串口控制寄存器 SCON 控制。 下面介绍几个与中断控制有关的寄存器。 1、 中断屏蔽寄存器 IE 中断屏蔽寄存器 IE 控制对中断源的开放或者屏蔽,每一个中断源是否被允许中断,都 由它来设置,其内容如表 3.3 所示。 名称 IE D7 EA D6 - 表 3.3 D5 ET2 D4 ES D3 ET1 D2 EX1 D1 ET0 D0 EX0中断屏蔽寄存器 IEEA:CPU 的中断开放标志,EA=1,开放所有中断;EA=0,屏蔽所有的中断。 ET2:定时器/计数器 T2 溢出中断允许位,ET2=1,允许 T2 中断;ET2=0,禁止 T2 中断。在 C51、S51 和 C2051 中没有 T2,所以 C51、S51 和 C2051 中的 IE 寄存器 ET2 位 无定义。 ES:串行口中断允许位。ES=1,允许串行口中断;ES=0,禁止串行口中断。 ET1:定时器/计数器 T1 溢出中断允许位,ET1=1,允许 T1 中断;ET1=0,禁止 T1 中断。 EX1:外部中断 1 中断允许位。EX1=1,允许外部中断 1 中断;EX1=0,禁止外部中 断 1 中断。 ET0:定时器/计数器 T0 溢出中断允许位,ET0=1,允许 T0 中断;ET0=0,禁止 T0 中断。 EX0:外部中断 0 中断允许位。EX0=1,允许外部中断 0 中断;EX0=0,禁止外部中 断 0 中断。 2、中断优先级寄存器 IP 中断优先级寄存器 IP 可以用来设置中断的优先级,只要用程序修改其内容,就可以实 现各中断源级别的设置,其内容如表 3.4 所示。 名称 IP D7 - D6 - D5 PT2 D4 PS D3 PT1 D2 PX1 D1 PT0 D0 PX0表 3.4 中断优先级寄存器 IP IP 中的某位如果为 1,则该位所对应的中断定义为高优先级中断;反之,IP 中的某位如 果为 0,则该位所对应的中断定义为低优先级中断(PT2、PS、PT1、PX1、PT0、PX0 分别 对应定时器 T2 中断、串行口中断、定时器 T1 中断、外部中断 1、定时器 T0 中断、外部中 断 0) 。其中 PT2 位在 C51、S51 和 C2051 中无定义。 3、定时器/计数器控制寄存器 TCON(见下一节) 4、串行口控制寄存器 SCON中国科学技术大学业余无线电协会 26 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料串行口控制寄存器 SCON 控制串行口的工作,其内容如表 3.5 所示。 名称 SCON D7 SM0 D6 SM1 表 3.5 D5 SM2 D4 REN D3 TB8 D2 RB8 D1 TI D0 RI串行口控制寄存器 SCONSM0:串行口工作方式控制位。 SM1:串行口工作方式控制位,和 SM0 一起来选择工作方式。 SM2:多机通信控制位。 REN:允许串行接收控制位。由软件来设置,置 1 时允许接收,清零时禁止接收。 TB8:串行发送数据的第 9 位,由设置 0 或者 1。 RB8:串行接收数据的第 9 位。 TI:发送中断标志位,由片内硬件在方式 0 串行发送第 8 位结束时置位,或在其它方式 串行发送停止位的开始时置位。在中断服务程序中要清零才能发送下一次数据。 RI:接收中断标志位,由片内硬件在方式 0 串行接收到第 8 位结束时置位,或在其它方 式串行接收到停止位的中间时置位。在中断服务程序中要清零才能接收下一次数据。3.7定时器/计数器MCS-51 内含 2 个 16 位的定时器/计数器 T0 和 T1。 它们分别有 4 种工作模式, 其控制字 均在相应的特殊功能寄存器中, 通过对控制寄存器的编程, 用户可以方便地选择行当的工作 模式。对每个定时器/计数器,在特殊功能寄存器 TMOD 中都有一个控制位,它选择 T0/T1 为定时器还是计数器。 当 T0/T1 用于定时器方式时, 定时器的输入来自内部时钟发生电路, 每过一个机器周期, 定时器加 1,而一个机器周期包含有 12 个振荡周期,所以,定时器的计数频率为晶振频率 的 1/12。当 T0/T1 用于计数器方式时,计数器对外部事件计数,计数脉冲来自外部输入引 脚。当外部输入引脚发生“1”到“0”的负跳变时,计数器的值加 1。 MCS-51 单片机定时器 T0 由 TH0、TL0 构成,T1 由 TH1、TL1 构成。T0、T1 的工作控制 是由控制寄存器 TCON 和方式寄存器 TMOD 实现的。 TMOD 用于控制各定时器/计数器的功能和 工作模式,TCON 用于控制定时器/计数器 T0、T1 的启动和停止,同时包含定时器/计数器的 状态。它们属于特殊功能寄存器,需要由软件来设置,在系统复位时,TCON、TMOD 寄存器 的所有位都被清零。 TCON 的内容如表 3.6 所示。 名称 TCON D7 TF1 D6 TR1 表 3.6 D5 D4 D3 IE1 D2 IT1 D1 IE0 D0 IT0 TF0 TR0 TCON 寄存器TF1:定时器 1 溢出标志。当定时器/计数器 1 溢出时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。 TR1:定时器 1 运行控制位,由软件置位/复位来开启或关闭定时器/计数器 1。 TF0:定时器 0 溢出标志。当定时器/计数器 0 溢出时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。 TR0:定时器 0 运行控制位,由软件置位/复位来开启或关闭定时器/计数器 0。 IE1:外部中断 1 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置 位;当主机响应中断,转向中断服务程序时,由硬件清零。 IT1:外部中断 1 触发方式控制位,由软件置位或清零来选择外部中断 1 的跳变/电平触中国科学技术大学业余无线电协会 27 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料发中断请求。IT1=0 时,外部中断 1 为电平触发方式,当 INT1 输入低电平时,置位 IE1。 采用电平触发方式时,外部中断源必须保持低电平有效,直到该中断被 CPU 响应,同时在 该中断服务程序执行完之前,外部中断源必须被清除,否则将产生另一次中断。IT1=1 时, 外部中断 1 为边沿触发方式,在对 INT1 的相邻两次采样中,如果一个周期中为高电平,接 下来的周期为低电平,则置位 IE1,表示外部中断 1 正在向 CPU 申请中断。直到该中断被 CPU 响应时,才被硬件清零。 IE0:外部中断 0 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置 位;当主机响应中断,转向中断服务程序时,由硬件清零。 IT0:外部中断 0 触发方式控制位,应用同 IT1。 TMOD 的内容如表 3.7 所示。 名称 TMOD D7 GATE D6 D5 M1 D4 M0 D3 GATE D2 D1 M1 D0 M0C /T表 3.7C /TTMOD 寄存器GATE:门控制位,当 GATEx=1 时,控制寄存器 TCON 的 TRx=1(x=0 或 1) 。C / T :定时器、计数器方式选择位,该位为 1 时为计数器,为 0 时为定时器。M0:定时器/计数器工作模式选择位。 M1:定时器/计数器工作模式选择位。 定时器/计数器的工作模式由 M0、M1 来选择,共有 4 种工作模式,下面逐一介绍。 1、工作模式 0 M1=0、M0=0 时,工作在工作模式 0,此时 T0、T1 的功能都是相同的,用户可以任 意选用。定时器工作在模式 0 时,其计数器为 13 位,由 TLx 的低 5 位和 THx 的 8 位所构 成,TLx 的高 3 位可以不考虑。定时器/计数器溢出时,置位 TCON 中的溢出标志位 TFx。 2、工作模式 1 M1=0、M0=1 时,工作在工作模式 1,此时 T0、T1 的功能都是相同的,用户可以任 意选用。 工作模式 1 与工作模式 0 的区别是计数器的位数不同。 工作模式 0 是 13 位计数器, 工作模式 1 是 16 位计数器。TLx、THx 作为 16 位寄存器用,计数值从初值开始(初值由软 件设定) ,计数到 0xFFFF 后,再加 1,计数器被溢出复位,并把溢出标志 TFx 置 1。 3、工作模式 2 M1=1、M0=0 时,工作在工作模式 2,此时 T0、T1 的功能都是相同的,用户可以任 意选用。工作模式 2 下,计数器是 8 位的,并且具有自动恢复功能。TLx 作为计数器用,而 THx 作为 8 位寄存器用,存放计数初始值。计数器开始以 TLx 的数值为初始值做增 1 计数, 当溢出时,除了把溢出标志 TFx 置 1 外,还同时把 THx 的计数初值送入 TLx,使 TLx 又重 新从初始值开始计数,THx 中的数值保持不变。 4、工作模式 3 M1=1、M0=1 时,工作在工作模式 3,此时 T0、T1 的功能与前 3 种工作模式大不相 同。在这种工作模式下,T1 仅保留了它原来的计数值,并停止了计数,相当于设置了 TR1 =0, T0 分成了两个独立的 8 位计数器 TH0 和 TL0。 而 其中 TL0 使用 T0 的所有控制位 (GATE、中国科学技术大学业余无线电协会 28 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料,而 TH0 被固定设置为一个 8 位的定时器,对机器周期计数,并 C / T 、TR0、TF0 和 INT1) 借用了 T1 的控制位 TR1 和 TF1。 这种方式适用于要求增加一个额外的定时器/计数器的场合。 因为当 T0 工作在模式 3 时,T1 可以定为模式 0、1、2,作为串口的波特率发生器。3.8定时器的应用举例定时器/计数器的应用十分广泛,下面我们举两个例子说明其使用方法。3.8.1 实现数码管显示的按秒跳变 下面这个程序控制 4 位数码管模块中的第一位实现从 0 到 9 的跳变(跳到 9 后回 0) , 16 。Easy 51 Kit Pro采用 跳变时间为 1 秒。计数器的最大值为 2 -1=0xFFFF(16 位计数器时) 22.1184MHz的晶振,所以计数器计数增 1 的时间为12 秒。使计数器溢出的最 22.1184 × 10 6长时间为12 × 216 ≈ 35.6(ms) 。为实现较准确的计时,我们可以选择计数器的初值, 22.1184 × 10 6使计数器的溢出时间为 25ms。这个初值计算如下: 初值= 2 16 ?0.025 =0x4C00 12 22.1184 × 10 6所以 TH=0x4C,TL=0。 计数器每溢出一次,就产生一次中断,产生 40 次中断就是一秒。 程序电路如图 3.13。 【程序 4】程序清单如下: //晶振频率 22.1184MHz #include&at89x52.h& #define #define #define #define #define #define TIMER0H TIMER0L TIMER0_RUN SECOND_OVERFLOW SEG_PORT DISPLAY_DIG1 0x4c 0x00 TR0=1 40 P0 P1&=0xf0;P1|=0x01 //当前显示的数字unsigned char g_CurrentDigit=0;void timer() interrupt 1 { static unsigned char s_Count = 0; TH0 = TIMER0H; //重置定时器初值 TL0 = TIMER0L;中国科学技术大学业余无线电协会29 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料//每次进入中断服务程序,TH0 和 TL0 的值都已经变为 0,所以要恢复原来的初始值 TIMER0_RUN; //定时器运行,开始下一次计数 if(s_Count != SECOND_OVERFLOW) { //未到整秒,把 sCount 值加 1 s_Count++; } else { //到整秒,s_Count 归 0,更新把当前显示的数字 s_Count = 0; if(g_CurrentDigit != 9) { g_CurrentDigit++; } else { g_CurrentDigit = 0; } } } void Initial(void) //初始化 { IE = 0x82; //仅允许 Timer0 中断 TMOD = 0x01; //Timer0 使用工作方式 1(16 位) ,定时器 TH0 = TIMER0H; //设置定时器初值 TL0 = TIMER0L; TIMER0_RUN; //定时器开始运行 DISPLAY_DIG1; } void main() { unsigned char code SEG_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; Initial(); while(1) { SEG_PORT = SEG_CODE[g_CurrentDigit]; //显示当前的数字 //当 timer0 溢出时,单片机响应 timer0 中断,调用 timer 函数, //每 40 次调用当前显示的数字加 1 } } 程序分析:中国科学技术大学业余无线电协会30 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料程序中主程序做的事只是在死循环中反复显示当前的数字, 每产生一次中断, 程序就跳 转到中断服务函数 timer()中进行相应的更新。 这里中断服务函数 timer()有别于普通 C 函数的地方是在声明中多了“interrupt 1” ,说明 这个函数是中断号为 1 的中断服务函数。各个中断对应的中断号如表 3.8 所示。 这个程序需要初始化的东西比较多, 我们把这些初始化语句都放在了初始化函数 Initial() 中,这也是程序初始化很常见的做法。我们还第一次用到了静态变量和全局变量。全局变量 是中断处理函数与外界程序进行参数传递的唯一途径,因此在单片机程序中全局变量的使 用频率要比普通的 C 语言程序高。尽管如此,由于全局变量的使用会影响程序的结构化, 所以在可以不使用全局变量的地方,还是要避免使用全局变量。在程序中,为了把全局变 量与静态变量跟普通变量区别开来,我们在变量前分别加了小写 g_和小写 s_以示区别。 IE 寄存器中的使能位和C中的中断号 0 1 2 3 4 5 表 3.8 Keil C 中的中断号 中断源 外部中断0 定时器0溢出 外部中断1 定时器1溢出 串行口中断 定时器2溢出(仅在S52、 C52中有此中断源)3.8.2 延时函数 在程序中有时可能需要实现准确的延时,这就需要用到定时器。我们可以屏蔽掉相应 定时器的中断请求,使CPU不响应中断,通过访问TFx来判断计数器是否溢出。下面这个程序 编写了一个Delay(unsigned int)函数,入口参数为以毫秒为单位的延时时间。该程序利用 延时函数实现了和上一程序相同的功能。 Delay()函数通过反复调用延时10ms或1ms的函数来 达到入口参数所要求的延时时间。 最长的延时时间支持为约65秒。 这里比较画蛇添足地同时 编写了延时10ms和1ms两个函数, 是考虑到延时1ms时计算得到的定时器初值不是整数, 四舍 五入后会产生一定的误差。 而延时10ms的函数没有四舍五入误差。 这样做在实用中未必很有 用, 也往往没有必要。 读者可以注意一下这个程序的currentDigit变量由于无须与中断处理 函数交换数据,所以不用全局变量,而是采用了局部变量。【程序5】程序清单如下://实现Delay函数,利用Delay函数而不是中断实现上一个程序的功能 //晶振频率22.1184MHz #include&at89x52.h& #define #define #define #define #define #define #define TIMER10MS_H TIMER10MS_L TIMER1MS_H TIMER1MS_L TIMER0_RUN TIMER0_STOP SEG_PORT 0xb8 0x00 0xf8 0xcd TR0=1 TR0=0 P0中国科学技术大学业余无线电协会31 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料#define DISPLAY_DIG1 P1&=0xf0;P1|=0x01 void Delay10ms(void) //精确延时10ms { TH0 = TIMER10MS_H; TL0 = TIMER10MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay1ms(void) //延时1ms,由于晶振频率的关系误差比10ms的函数大 { TH0 = TIMER1MS_H; TL0 = TIMER1MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay(unsigned int t) //提供以毫秒为单位的延时 { while(t &= 10) //当延时时间大于10ms时,每10ms调用一次Delay10ms函数,比较准确 { Delay10ms(); t -= 10; } while(t) //当延时时间不足10ms时,每1ms调用一次Delay1ms函数,误差会大一点 { Delay1ms(); t--; } } void Initial(void) { IE = 0; //屏蔽所有中断 TMOD = 0x01; //Timer0使用工作方式1(16位),定时器中国科学技术大学业余无线电协会32 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料DISPLAY_DIG1; } void main() { unsigned char code SEG_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; unsigned char currentD Initial(); while(1) { SEG_PORT = SEG_CODE[currentDigit]; //数码管显示当前数字 Delay(1000); //延时1000ms if(currentDigit != 9) { currentDigit++; } else { currentDigit = 0; } } } 3.8.3 重写数按键的程序 前面的数按键程序也有Delay函数,但那时的Delay函数并不能提供准确的延时。下面 的程序采用提供准确延时的Delay函数。【程序6】程序清单如下:#include&at89x52.h& #define #define #define #define #define #define #define #define #define TIMER10MS_H TIMER10MS_L TIMER1MS_H TIMER1MS_L TIMER0_RUN TIMER0_STOP SEG_PORT DISPLAY_DIG1 INPUT_PIN 0xb8 0x00 0xf8 0xcd TR0=1 TR0=0 P0 P1&=0xf0;P1|=0x01 P3_2 0 1#define PRESSED #define UNPRESSED void Delay10ms(void)中国科学技术大学业余无线电协会33 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料{ TH0 = TIMER10MS_H; TL0 = TIMER10MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay1ms(void) { TH0 = TIMER1MS_H; TL0 = TIMER1MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay(unsigned int t) { while(t &= 10) { Delay10ms(); t -= 10; } while(t) { Delay1ms(); t--; } } void Initial(void) { IE = 0; //屏蔽所有中断 TMOD = 0x01; //Timer0使用工作方式1(16位),定时器 DISPLAY_DIG1; INPUT_PIN = 1; }中国科学技术大学业余无线电协会34 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料void main() { unsigned char currentNumber = 0; bit bNewStatus,bOldS unsigned char code SEG_CODE[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; Initial(); bOldStatus = INPUT_PIN; while(1) { SEG_PORT = SEG_CODE[currentNumber]; bNewStatus = INPUT_PIN; if(bNewStatus==PRESSED && bOldStatus==UNPRESSED) { Delay(10); bNewStatus = INPUT_PIN; if(bNewStatus == PRESSED) { if(currentNumber != 9) { currentNumber++; } else { currentNumber = 0; } } } bOldStatus = bNewS } } 上面的两个Delay函数各有好处。用循环延时的Delay函数尽管不能提供非常准确的延 时,但程序简单,更可以省下单片机一个定时器资源,这在很多时候是很有用的。用定时器 的Delay函数要占用一个定时器,在单片机定时器资源够用的情况下用来产生准确延时是个 不错选择。 前面单片机I/O一节中的显示“0123”的程序中,延时也是用循环实现的。这个程序也 可以用定时器(中断或查询)来实现。有兴趣的读者可以自己重写一下。3.9外部中断外部中断的使用可以见前面TCON寄存器的说明。 下面我们再次重写前面的数按键程序。 这次我们利用单片机的外部中断0检测按键按下的事件。【程序7】程序清单如下:#include&at89x52.h&中国科学技术大学业余无线电协会35 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料#define #define #define #define #define #define #define #define #define #define #defineTIMER10MS_H TIMER10MS_L TIMER1MS_H TIMER1MS_L TIMER0_RUN TIMER0_STOP SEG_PORT DISPLAY_DIG1 ENABLE_INT DISABLE_INT INPUT_PIN0xb8 0x00 0xf8 0xcd TR0=1 TR0=0 P0 P1&=0xf0;P1|=0x01 EA=1 EA=0 P3_2 0 1#define PRESSED #define UNPRESSEDunsigned char g_CurrentNumber = 0; void Delay10ms(void) { TH0 = TIMER10MS_H; TL0 = TIMER10MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay1ms(void) { TH0 = TIMER1MS_H; TL0 = TIMER1MS_L; //给timer0写入初始值 TIMER0_RUN; //开始计数 while(!TF0); //等到定时器溢出 TF0 = 0; //清定时器溢出标志 TIMER0_STOP; //停止计数 } void Delay(unsigned int t) { while(t &= 10) { Delay10ms();中国科学技术大学业余无线电协会36 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料t -= 10; } while(t) { Delay1ms(); t--; } } void key() interrupt 0 { bit bS DISABLE_INT; //暂时禁止中断 Delay(10); bStatus = INPUT_PIN; if(bStatus == PRESSED) { if(g_CurrentNumber != 9) { g_CurrentNumber++; } else { g_CurrentNumber = 0; } } ENABLE_INT; //重新开放中断 } void Initial(void) { IE = 0x01; //屏蔽所有中断 TMOD = 0x01; //Timer0使用工作方式1(16位),定时器 DISPLAY_DIG1; INPUT_PIN = 1; IT0 = 1; //外部中断0:边缘触发方式 ENABLE_INT; } void main() { unsigned char code SEG_CODE[] =中国科学技术大学业余无线电协会37 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; Initial(); while(1) { SEG_PORT = SEG_CODE[g_CurrentNumber]; } }3.10 串行通信3.10.1 串行通信介绍 串行通信是指将构成字符的每个二进制数据位, 依据一定的顺序逐位进行传送的通信方 法。串行通信不同于并行通信,串行通信的数据是一位一位顺序地发送或接收,而并行通信 的各数据位是同时传送的。串行通信比并行通信节省传送口线,传送距离远,但传送速度要 慢。串行通信在主机与存储器、主机与打印机、存储器与存储器等方面应用广泛。 单片机的串行通信方式有两种:异步通信和同步通信。 1、异步通信 异步通信规定了字符数据的传送格式,每个数据用起始位表示字符的开始,用停止位 表示字符的结束。在一帧数据中,先是一个起始位0,然后是若干个数据位,规定低位在前, 高位在后,最后一位是奇偶校验位(可以省略),接下来是停止位1。用这种格式表示的字 符,可以一个接一个地传送。 (1)起始位 在通信线上,没有数据传送时处于逻辑“1”状态。当发送设备要发送一个字符数据时, 首先发出一个逻辑“0”信号,这个逻辑低电平就是起始位。起始位通过通信线传向接收设 备,当接收设备检测到这个逻辑低电平后,就开始准备接收数据位信号。因此,起始位所起 的作用就是表示字符传送的开始。 (2)数据位 当接收设备收到起始位后,紧接着就会收到数据位,数据位的个数可以是5、6、7或8 位数据。在字符数据传送的过程中,数据位从最低有效位开始传送。 (3)奇偶校验位 数据位发送完之后,就可以发送奇偶校验位(也可以不发送)。奇偶校验位用于有限 差错检验,通信双方在通信时约定一致的奇偶校验方式。就数据传送而言,奇偶校验位是冗 余位,但它表示数据的一种性质,这种性质用于检错,虽然检错能力有限但很容易实现。 (4)停止位 在奇偶校验位或者数据位(无奇偶校验位时)之后是停止位。它可以是1位、1位半或2 位,停止位是一个字符数据的结束标志。 在异步通信中,字符数据一个接一个地传送。在发送间隙,也就是空闲时,通信总路 线处于逻辑“1”状态(高电平),每个字符数据的传送均以逻辑“0”(低电平)开始。 在异步通信中,CPU与外设之间必须有两项约定,即字符格式和波特率。字符格式的约 定使双方能够在对同一种0和1的串理解成同一种意义。 波特率是数据传送的速率, 即每秒钟 传送的二进制数的位数。例如,数据传送的速率是120字符/秒,而每个字符如上述约定包含 10个信息位,则传送波特率为1200位/秒(波特)。“位/秒”常表示为“bps”。 2、同步通信中国科学技术大学业余无线电协会38 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料在异步通信中,每个字符要用起始位和停止位作为字符开始和结束的标志,发送标志 多占用了时间,所以在数据块传送时,为了提高速度,常去掉这些标志,而采用同步传送。 同步通信不像异步通信那样, 靠起始位在每个字符数据开始发送和接收同步, 而是通过同步 字符在每个数据块传送开始时使收发双方同步。由于数据块传输开始要用同步字符来指示, 同时要求由时钟来实现发送端与接收端之间的同步,所以同步通信的硬件比较复杂。 同步通信的特点如下: (1)以同步字符作为传送的开始,从而使收发双方得到同步。 (2)每位占用的时间都相等。 (3)字符数据之间不留空隙,当线路空闲或没有字符可发送时,发送同步字符。 同步字符可由用户选择一个或者两个特殊的8位二进制码, 与异步通信收发双方使用的 相同的字符格式一样,同步通信的收发双方必须使用相同的同步字符。 作为应用,异步通信常用于传输信息量不大、传输速率比较低的场合。在信息量很大、 传输速率要求较高的场合,常采用同步通信。 3.10.2 MCS-51单片机用于串行通信的寄存器 MCS-51单片机的串行接口是一个可编程的全双工串行通信接口。它可用作异步通信方 式, 与串行传送信息的外部设备相连接, 或用于通信标准异步通信协议进行全双工的MCS-51 多机系统,也可以通过同步方式,使用TTL或CMOS移位寄存器来扩充I/O口。 MCS-51单片机通过引脚RXD(P3.0,串行数据接收端)和引脚TXD(P3.1,串行数据发 送端)与外界通信。SBUF是串行口缓冲寄存器,包括发送寄存器和接收寄存器。它们有相同 名字和地址空间,但不会出现冲突,因为它们两个一个只能被CPU读出数据,一个只能被CPU 写入数据。 1、串行口控制寄存器SCON 串行口控制寄存器SCON用于定义串行口的工作方式及实施接收和发送控制。其各位定 义如表3.9所示,其中串行口工作方式如表3.10所示。 名称 SCON D7 SM0 D6 SM1 表 3.9 SM0 0 0 1 1 SM1 0 1 0 1 D5 SM2 D4 REN D3 TB8 D2 RB8 D1 TI D0 RI串行口控制寄存器 功能描述 8位移位寄存器 10位UART 11位UART 11位UART 串行口工作方式 波特率 Fosc/12 可变 Fosc/64或Fosc/32 可变工作方式 0 1 2 3表 3.10其中: Fosc:晶振频率。 SM2:多机通信控制位。在方式0中,SM2一定要等于0。在方式1中,当SM2为1时则只有 接收到有效停止位时,RI才置1。在方式2或3中,当SM2为1且接收到的第9位数据RB8为0时, RI才置1。 REN:接收允许控制位。由软件置位以允许接收,又由软件清零来禁止接收。 TB8:要发送数据的第9位。在方式2或3中,要发送的第9位数据,根据需要由软件置1 或清零。 例如, 可约定作为奇偶校验位, 或在多机通信中作为区别地址帧或数据帧的标志位。中国科学技术大学业余无线电协会 39 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料RB8:接收到的数据的第9位。在方式0中不使用RB8。在方式1中,若SM2为0,RB8为接 收到的停止位。在方式2或3中,RB8为到的第9位数据。 TI:发送中断标志。在方式0中,第8位发送结束时,由硬件置位。在其它方式的发送 停止位前,由硬件置位。TI置位既表示一帧信息发送结束,同时也向CPU申请中断。可根据 需要,用软件查询的方法获得数据已发送完毕的信息,或用中断的方式来发送下一个数据。 TI必须用软件清零。 RI:接收中断标志位。在方式0中,当接收完第8位数据后,由硬件置位。在其它方式 中,在接收到停止位的中间时刻由硬件置位(例外情况见对SM2的说明)。RI表示一帧数据 接收完毕,可用查询的方法获知或者用中断的方法获知。RI也必须用软件清零。 2、特殊功能寄存器PCON 特殊功能寄存器PCON是为了在CHMOS的80C51单片机上实现电源控制而附加的。其中最 高位是SMOD,用于选定串行口的波特率,详见后面说明。 3.10.3 MCS-51单片机串行口的工作方式 MCS-51单片机的全双工串行口可编程为4种工作方式,以下分别介绍。 1、方式0 方式0为移位寄存器输入/输出方式, 也称为同步方式。 可外接移位寄存器以扩展I/O口, 也可以外接同步输入/输出设备。8位串行数据从RXD输入或输出,TXD用来输出同步脉冲。 数据发送:串行数据从RXD引脚输出,TXD引脚输出移位脉冲。CPU将数据写入发送寄存 器时,立即启动发送,将8位数据以Fosc/12的固定波特率从RXD输出,低位在前,高位在后。 发送完一帧数据后,发送中断标志TI由硬件置位。 数据接收:当串行口以方式0接收时,首先置位允许接收位REN。此时,RXD为串行数据 输入端,TXD仍为同步脉冲移位脉冲输出端。当RI=0和REN=1同时满足时,开始接收。当接 收到第8位数据时,将数据移入接收寄存器,并由硬件置位RI。 2、方式1 方式1为波特率可变的10位异步通信接口方式,是标准的异步通信方式。发送或接收一 帧信息,包括1个起始位0,8个数据位和1个停止位1。 数据发送:当CPU执行一条指令将数据写入发送缓冲SBUF时,就启动发送。串行数据从 TXD引脚输出,发送完一帧数据后,由硬件置位TI。 数据接收:在REN=1时,串行口采样RXD引脚,当采样到有1至0的跳变时,确认是开始 位0,就开始接收一帧数据。只有当RI=0且停止位为1或者SM2=0时,停止位在进行RB8,8 位数据才能进入接收寄存器,并由硬件置位中断标志RI;否则信息丢失。所以在方式1接收 时,应先用软件对RI和SM2标志清零。 3、方式2 方式2为固定波特率的11位UART方式。它比方式1增加了一位可编程为1或0的第9位数 据。 数据发送:发送的串行数据由TXD端输出,一帧信息为11位,附加的第9位来自SCON寄 存器的TB8位,用软件置位或复位。它可作为多机通信中地址/数据信息的标志位,也可以作 为数据的奇偶校验位。当CPU执行一条数据写入SBUF的指令时,就启动发送器发送。发送一 帧信息后,置位中断标志TI。 数据接收:在REN=1时,串行口采样RXD引脚,当采样到有1至0的跳变时,确认是开始中国科学技术大学业余无线电协会40 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料位0,就开始接收一帧数据。在接收到附加的第9位后,扫RI=0或者SM2=0时,第9位数据才 进入RB8,8位数据才能进入接收寄存器,并由硬件置位中断标志RI;否则信息丢失,且不置 位RI。再过一位的时间后,不管上述条件是否满足,接收电路即行复位,并重新检测RXD上 从1到0的跳变。 4、方式3 方式3为波特率可变的11位UART方式。除波特率外,其余与方式2相同。 3.10.4 串行口的波特率选择 在串行通信中,收发双方的数据传送速率即波特率要有一定的约定。在MCS-51串行口 的4种工作方式中,方式0和方式2的波特率是固定的,而方式1和方式3的波特率是可变的, 由定时器T1的溢出率控制。 1、方式0 方式0的波特率固定为晶振频率的1/12。 2、方式2 方式2的波特率由PCON中的选择位PCON.7即SMOD来决定,可由下式表示:波特率 = 2 SMOD × Fosc / 64也就是当SMOD=1时,波特率为1/32Fosc,当SMOD=0时,波特率为1/64Fosc。 3、方式1和方式3 定时器T1作为波特率发生器,其波特率计算公式如下:波特率 = 2 SMOD × 定时器T1溢出率/32T1溢出率=T1计数率/产生溢出所需的周期数式中T1计数率取决于它工作在定时器状态还是计数器状态。当工作于定时器状态时, T1计数率为Fosc/12;当工作于计数器状态时,T1计数率为外部输入频率,此输入频率应小 于Fosc/24。产生溢出所需周期与定时器T1的工作方式、T1的预置值有关。 定时器T1工作于方式0:溢出所需周期数=8192-x 定时器T1工作于方式1:溢出所需周期数=65536-x 定时器T1工作于方式2:溢出所需周期数=256-x 因为方式2为自动重装入初值的8位定时器/计数器模式, 所以用它来做波特率发生器最 恰当。当时钟频率选用22.1184MHz时,很容易获得标准的波特率。表3.11列出了定时器T1 工作于方式2时常用的波特率及初值。 常用波特率
00 Fosc(MHz) 22.4 22.4 22.1184 SMOD 1 0 0 0 0 TH1的初值 0xFD 0xFD 0xFA 0xF4 0xE8表 3.11 常用的波特率及 T1 初值中国科学技术大学业余无线电协会 41 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料3.10.5 串行通信编程示例 下面是串行通信的一个程序,Easy 51 Kit Pro通过DB9串口和电脑串口相接,电脑串 口发送数据,单片机接收后发回电脑串口。运行这个程序前,要把Easy 51 Kit Pro的最小 系统板中标注为“RX”和“TX”的两个跳线分别接上跳线帽。程序比较简单,这里不再详细 分析了,程序中已附有注释。【程序8】程序清单如下://晶振频率22.1184MHz #include &at89x52.h& #define ENABLE_INT EA=1 #define DISABLE_INT EA=0 void inituart(void) //串口初始化 { SCON = 0x50; //方式1, 8位数据位,允许接收 TMOD |= 0x20; //设置TIMER1: timer 1,方式2 PCON |= 0x80; //SMOD=1 TH1 = 0 // TH1: reload value for 38400 baud @ 22.1184MHz TR1 = 1; // TIMER1开始计数 ES = 1; //允许串行口中断 PS = 1; //设置串行口中断为高优先级中断 ENABLE_INT; } void com_isr() interrupt 4 { bit bTI_B //串行口中断服务程序DISABLE_INT; //屏蔽所有中断 if(RI) //如果是接收到数据引起中断 { RI = 0; // RI清零 bTI_Backup = TI; //备份当前TI的值 TI = 0; //TI清零,以发送数据 temp = SBUF; //读接收到的数据到temp SBUF = //把temp的数据发送出去 while(!TI); //等待至发送成功 TI = bTI_B //恢复原来TI的值 } if(TI) //如果是发送完数据引起的中断,则除了TI清零外什么也不干 { TI = 0; //TI清零,以发送数据 }中国科学技术大学业余无线电协会42 51 单片机 C 语言编程入门――Easy 51 Kit Pro 配套学习资料ENABLE_INT; } void main() { inituart(); while(1) { ; } }//开放中断3.11 定时器2除了定时器0和定时器1外,C52和S52单片机还有另外一个定时器――定时器2。下面我 们介绍一下定时器2的用法。 TF2 7 EXF2 6 RCLK 5 TCLK 4 EXEN2 3 TR2 2C /T21CP / RL 20表 3.12 T2CON 寄存器 定时器 2 是一个 16 位的定时器/计数器。 T2CON 中的 C / T 2 位决定定时器 2 是用作定时 器还是计数器。定时器 2 有三种工作模式:捕捉模式、自动重载(增或减计数)模式和波特 率发生器模式。定时器 2 的工作模式由 T2CON 决定,详见表 3.14。定时器 2 有两个 8 位的 寄存器 TH2 和 TL2。在定时器功能中,每个机器周期 TL2 都会增 1。由于一个机器周期由 12 个振荡周期组成,所以定时器 2 的计数速率为振荡频率的 1/12。 与定时器 2 有关的寄存器为 T2CON 和 T2MOD。 T2CON 寄存器的组成见表 3.12。下面是详细说明。 TF2: 定时器 2 溢出标志位。 定时器 2 溢出时置位。 必须由软件清零。 RCLK=1 或 TCLK 当 =1 时,TF2 不会置位。 EXF2:定时器 2 外部标志位。当 EXEN2=1,且 T2EX 引脚发生电平的负跳变引起捕捉或 重载时被置位。当允许定时器 2 中断时,EXF2=1 会使 CPU 跳转到定时器 2 的

我要回帖

更多关于 单片机c语言入门 的文章

 

随机推荐