<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
|
| |
|
| |
Etoms Electronics Corp.
联系方式: 深圳市南山区南油商服大厦5楼502 电话:86-755-26406990 传真:86-755-26406200 |
目 录
1.2 益统ET45M052单片机的引脚功能与封装... 2
3.1 益统ET45Mx ISP下载软件基本介绍... 37
3.2 益统ET45Mx ISP下载软件使用指南... 38
第一部分 芯片基础介绍
第一章 新款单片机的介绍
1.1 益统ET<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />45M052单片机概述
益统ET45M052单片机是一种低功耗高性能的CMOS8位微控制器,内置8KB可在线编程闪存。该器件采用高密度非易失性存储技术生产,其指令与工业标准的80C51指令集兼容。片内程序存储器允许重复在线编程,允许程序存储器在系统内通过SPI串行口改写或用同用的非易失性存储器改写。通过把通用的8位CPU与可在线下载的Flash集成在一个芯片上,益统新款单片机便成为一个高效的微型计算机。它的应用范围广,可用于解决复杂的控制问题,且成本较低。
益统ET45M052单片机结构框图
益统ET45M052单片机的主要特性如下:
全静态工作:0Hz ~ 24MHz
4.0V到5.5V的工作电源范围
3级程序存储器加密
3个16位定时器/计数器
8个中断源
8K字节可擦写1000次的在线可编程ISP闪存
32条可编程I/O线
256字节内部RAM
UART串行通道
低功耗空闲方式和掉电方式
通过中断终止掉电方式
看门狗定时器
双数据指针
灵活的在线编程(字节和页模式)
兼容MCS51产品
1.2 益统ET45M052单片机的引脚功能与封装
益统ET45M052单片机引脚功能图如图1-1所示
图1-1 益统ET45M052单片机引脚功能图
1.2.1 多功能I/O口
益统ET45M052共有四个8位的并行I/O口:P0、P1、P2、P3端口,对应的引脚分别是P0.0 ~ P0.7,P1.0 ~ P1.7,P2.0 ~ P2.7,P3.0 ~ P3.7,共32根I/O线。每根线可以单独用作输入或输出。
①P0端口,该口是一个8位漏极开路的双向I/O口。在作为输出口时,每根引脚可以带动8个TTL输入负载。当把“1”写入P0时,则它的引脚可用作高阻抗输入。当对外部程序或数据存储器进行存取时,P0可用作多路复用的低字节地址/数据总线,在该模式,P0口拥有内部上拉电阻。在对Flash存储器进行编程时,P0用于接收代码字节;在校验时,则输出代码字节;此时需要外加上拉电阻。
②P1端口,该口是带有内部上拉电阻的8位双向I/O端口,P1口的输出缓冲器可驱动(吸收或输出电流方式)4个TTL输入。对端口写“1”时,通过内部的上拉电阻把端口拉到高电位,此时可用作输入口。P1口作输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输出一个电流。在对Flash编程和程序校验时,P1口接收低8位地址。
另外,P1.0与P1.1可以配置成定时/计数器2的外部计数输入端(P1.0/T2)与定时/计数器2的触发输入端(P1.0/T2EX),如表1-1所示。
端口引脚 | 复用功能 |
P1.0 | T2(定时器/计算器2的外部输入端) |
P1.1 | T2EX(定时器/计算器2的外部触发端和双向控制) |
P1.5 | MOSI(用于在线编程) |
P1.6 | MISO(用于在线编程) |
P1.7 | SCK(用于在线编程) |
表1-1 P1口管脚复用功能
③ P2端口,该口是带有内部上拉电阻的8位双向I/O端口,P2口的输出缓冲器可驱动(吸收或输出电流方式)4个TTL输入。对端口写“1”时,通过内部的上拉电阻把端口拉到高电位,此时可用作输入口。P2口作输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输出一个电流。
④ P3端口,该口是带有内部上拉电阻的8位双向I/O端口,P3口的输出缓冲器可驱动(吸收或输出电流方式)4个TTL输入。对端口写“1”时,通过内部的上拉电阻把端口拉到高电位,此时可用作输入口。P3口作输入口使用时,因为有内部的上拉电阻,那些被外部信号拉低的引脚会输出一个电流。
在益统ET45M052单片机中,同样P3口还用于一些复用功能,如表1-2所列。在对Flash编程和程序校验期间,P3口还接收一些控制信号。
端口引脚 | 复用功能 |
P3.0 | RXD(串行输入口) |
P3.1 | TXD(串行输出口) |
P3.2 | INT0(外部中断0) |
P3.3 | INT1(外部中断1) |
P3.4 | T0(定时器0的外部输入) |
P3.5 | T1(定时器1的外部输入) |
P3.6 | WR(外部数据存储器写选通) |
P3.7 | RD(外部数据存储器读选通) |
表1-2 P3端口引脚与复用功能表
1.2.2 RST 复位输入端
在振荡器运行时,在此脚上出现两个机器周期的高电平将使其单片机复位。看门狗定时器(Watchdog)溢出后,该引脚会保持98个振荡周期的高电平。在SFR AUXR(地址8EH)寄存器中的DISRTO位可以用于屏蔽这种功能。DISRTO位的默认状态,是复位高电平输出功能使能。
1.2.3 ALE/PROG 地址锁存允许信号
在存取外部存储器时,这个输出信号用于锁存低字节地址。在对Flash存储器编程时,这条引脚用于输入编程脉冲PROG。一般情况下,ALE是振荡器频率的6分频信号,可用于外部定时或时钟。但是,在对外部数据存储器每次存取中,会跳过一个ALE脉冲。在需要时,可以把地址8EH中的SFR寄存器的0位置为“1”,从而屏蔽ALE的工作;而只有在MOVX或MOVC指令执行时ALE才被激活。在单片机处于外部执行方式时,对ALE屏蔽位置“1”并不起作用。
1.2.4 PSEN 程序存储器允许信号
它用于读外部程序存储器。当益统ET45M052单片机在执行来自外部存储器的指令时,每一个机器周期PSEN被激活2次。在对外部数据存储器的每次存取中,PSEN的2次激活会被跳过。
1.2.5 EA/Vpp 外部存取允许信号
为了确保益统ET45M052单片机从地址为0000H~FFFFH的外部程序存储器中读取代码,故要把EA接到GND端,即地端。但是,如果锁定位1被编程,则EA在复位时被锁存。当执行内部程序时,EA应接到Vcc。在对Flash存储器编程时,这条引脚接收12V编程电压Vpp。
1.2.6 XTAL1,XTAL2
XTAL1为振荡器的反相放大器输入,内部时钟工作电路的输入。XTAL2为振荡器的反相放大器输出。
1.3 益统ET45M052单片机存储器结构
益统ET45M052单片机都将程序存储器和数据存储器分为不同的存储空间,典型存储器的结构如图1-2所示。
图1-2 ET45M052存储器结构
程序和数据存储器分为不同的逻辑空间,使得可用8位地址来访问数据存储器。这样可提高8位CPU的存储和处理速度。尽管如此,也可通过数据指针(DPTR)寄存器来产生16位的数据存储器地址。
程序存储器只可读不可写,用于存放编好的程序和表格常数。益统ET45M052单片机可寻址的程序存储器总空间为64KB。外部程序存储器的读选通脉冲为PSEN(程序存储允许信号)。
数据存储器在物理上和逻辑上都分为两个地址空间:一个内部和一个外部数据存储器空间。外部数据存储器的寻址空间可达64KB。访问外部数据存储器时,CPU发出读和写的信号——RD和WR。
将RD和PSEN两个信号加到一个与门的输入端,然后用与门的输出作为外部程序/数据存储器的读选通脉冲。这样就可将外部程序存储器空间和外部数据存储器空间合并在一起。
1.3.1 程序存储器
益统ET45M052单片机可寻址的内部和外部程序存储器总空间为64KB。每个外部程序和数据存储器的可寻址范围高达64KB。它没有采用程序存储器分区的方法,64KB的地址空间是统一的。EA引脚接低电平时,单片机就从外部程序存储器中取指。对于益统新款单片机来说,EA引脚接高电平时,程序直接从单片机内部存储器中的0000H到1FFFH单元执行,2000H到FFFFH单元到外部存储器中执行。
程序存储器中有几个单元专门用来存放特定的程序。这几个单元的配置情况如下图1-3所示。
图1-3程序存储器的中断入口配置
由图1-3可知,0000H~0002H单元用于初始化程序。单片机复位后,CPU总是从0000H单元开始执行程序。另外,每个中断在程序存储器中都分配有一个固定的入口地址。中断响应后,CPU便跳到该单元,在这里开始执行中断服务子程序。例如,外部中断0的入口地址被放在0003H单元,如果使用外部中断0,则它的中断服务子程序必须从0003H单元开始。如果中断没有使用,那么它的服务单元也可作一般用途的程序存储器用。
每个中断入口地址的间隔为8个单元;外部中断0的入口地址为0003H;定时器0的入口地址为000BH;外部中断1的入口地址为0013H;定时器1的入口地址为001BH;以此类推。如果一个中断服务子程序足够短的话,则可全部存放在这8个单元中。对较长的服务子程序,则可利用一条跳转指令跳过后续的中断入口地址。
程序存储器最低端的地址可以在片内Flash中,或在外部存储器中。将外部存取(EA)引脚接Vcc或接地,就可进行这种选择。例如,在带有4KB片内Flash的AT89C51中,如果把EA引脚连到Vcc,当地址为0000H~0FFFH时,则访问内部Flash;当地址为1000H~FFFFH时,则访问外部程序存储器。在益统ET45M052(8KB Flash)中,当EA端保持高电平时,如果地址不超过1FFFH,则访问内部Flash;地址超过1FFFH(即为2000H~FFFFH)时,将自动转向外部程序存储器。如果EA端接地,则只访问外部程序存储器,不管是否有内部Flash存储器。
外部程序存储器读选通信号PSEN用于读取所以的外部程序;读取内部程序时,不产生PSEN信号。
注意,在访问外部程序存储器时,16条I/O线(P0和P2)作为总线使用。P0端口作为地址/数据总线使用。它先输出16位地址的低8位PCL,然后进入悬浮状态,等待程序存储器送出的指令字节。当有效地址PCL出现在P0总线上时,ALE(允许地址锁存)把这个地址锁存到地址锁存器中。同时,P2端口输出地址的高8外PCH。然后PSEN选通外部程序存储器,使指令送到P0总线上,由CPU取入。
即使所用的程序存储器的实际空间可能小于64KB,程序存储器的地址总是为16位的。在访问外部程序存储器时,要用到两个8位端口——P0和P2来产生程序存储器的地址。
1.3.2 数据存储器
内部数据存储器的地址是8位的,也就是说其地址空间只有256字节,但内部RAM的寻址方式实际上可提供384字节。高于7FH的直接地址访问同一个存储空间,高于7FH的间接地址访问另一个存储空间。这样,在图1-4中,虽然高128字节区与专用寄存器,即特殊功能寄存器(SFR)区的地址是重合的(80H~FFH),但实际上它们是分开的。究竟访问哪一区,是通过不同的寻址方式加以区分的。访问高128字节区时,采用间接寻址方式;访问SFR区时,采用直接寻址方式;访问低128字节区时,两种寻址方式都可采用。
<?xml:namespace prefix = w ns = "urn:schemas-microsoft-com:office:word" />
图1-4 内部数据存储器的结构
低128字节区的分配情况如图1-5所示。最低32个单元(00H~1FH)是4个通用工作寄存器组。每个寄存器组含有8个8为寄存器,编号为R0~R7。专用寄存器PSW(程序状态字)中有2位(PS0,RS1)用来确定采用哪一个工作寄存器组。这种结构能够更有效地使用指令空间,因为寄存器指令比直接寻址指令更短。
图1-5内部RAM的低128字节区
工作寄存器组上面的16个单元(20H~2FH)构成了布尔处理机的存储器空间。这16个单元的128位各自都有专门的位地址,如图1-6所示,它们可以被直接寻址,这些位地址是00H~7FH。在89系列单片机的指令系统中,还包括了许多位操作指令,这些位操作指令可直接对这128位寻址。
图1-6内部RAM中可寻址位的地址
低128字节区中的所以单元都既可通过直接寻址方式访问,又可通过间接寻址方式访问,又可通过间接寻址方式访问。而高128字节区则只能通过间接寻址方式来访问。仅在带有256字节RAM的单片机中才有高128字节区。
专用寄存器即特殊功能寄存器(SFR)区的分配情况如图1-7所示。这些专用寄存器包括端口锁存器(P0/P1/P2/P3)、程序状态字(PSW)、定时/计数器方式控制(TMOD)、定时/计数器控制(TCON)、定时/计数器(THx/TLx)、累加器(ACC/B)、栈指针(SP),以及其他控制寄存器等等。专用寄存器只能通过直接寻址方式来访问。通常,在所有ATMEL单片机的专用寄存器(SFR)区中,寄存器的分配情况是相同的。
图1-7 益统ET45M052特殊功能寄存器(SFR)区的分配及其复位值
专用寄存器区中有一些单元是即可字节寻址又可位寻址的(见图1-7)。凡是地址以“0”和“8”结尾(能被8整除)的单元都是可位寻地址的,地址的范围是80H~FFH。
第二章 Keil C51语言的用法介绍
每写一个程序,总离不开数据的应用,在学习 C51语言的过程中掌握理解数据类型也是很关键的。先看表 2-1,表中列出了 KEIL uVision2 C51 编译器所支持的数据类型。在标准 C 语言中基本的数据类型为 char, int, short, long, float 和 double,而在 C51 编译器中int 和short相同,float和 double 相同,这里就不列出说明了。下面来看看它们的具体定义:
数据类型 | 长度 | 值域 |
unsigned char | 单字节 | 0~255 |
signed char | 单字节 | -128~+127 |
unsigned int | 双字节 | 0~65535 |
signed int | 双字节 | -32768~+32767 |
unsigned long | 四字节 | 0~4294967295 |
signed long | 四字节 | -2147483648~+2147483647 |
float | 四字节 | ±1.175494E-38~±3.402823E+38 |
* | 1~3字节 | 对象的地址 |
bit | 位 | 0 或1 |
sfr | 单字节 | 0~255 |
sfr16 | 双字节 | 0~65535 |
sbit | 位 | 0 或1 |
表2-1 KEIL uVision2 C51 编译器所支持的数据类型
2.1.1 char字符类型
char 类型的长度是一个字节,通常用于定义处理字符数据的变量或常量。分无符号字符类型 unsigned char 和有符号字符类型 signed char,默认值为 signed char 类型。unsigned char 类型用字节中所有的位来表示数值,所可以表达的数值范围是 0~255。signed char类型用字节中最高位字节表示数据的符号, “0”表示正数, “1”表示负数,负数用补码表示。 所能表示的数值范围是-128~+127。 unsigned char 常用于处理 ASCII字符或用于处理小于或等于 255 的整型数。
*正数的补码与原码相同,负二进制数的补码等于它的绝对值按位取反后加 1。
2.1.2 int 整型
int 整型长度为两个字节,用于存放一个双字节数据。分有符号 int 整型数 signed int和无符号整型数 unsigned int,默认值为 signed int 类型。signed int 表示的数值范围是-32768~+32767,字节中最高位表示数据的符号, “0”表示正数, “1”表示负数。unsigned int 表示的数值范围是 0~65535。
2.1.3 long 长整型
long 长整型长度为四个字节,用于存放一个四字节数据。分有符号 long 长整型 signed long 和无符号长整型 unsigned long,默认值为 signed long 类型。signed int 表示的数值范围是-2147483648~+2147483647,字节中最高位表示数据的符号, “0” 表示正数, “1” 表示负数。unsigned long表示的数值范围是 0~4294967295。
2.1.4 float 浮点型
float 浮点型在十进制中具有 7 位有效数字,是符合 IEEE-754 标准的单精度浮点型数据,占用四个字节。
2.1.5 *指针类型
指针型本身就是一个变量,在这个变量中存放的指向另一个数据的地址。这个指针变量要占据一定的内存单元, 对不同的处理器长度也不尽相同, 在C51中它的长度一般为1~3 个字节。
2.1.6 bit 位标量
bit 位标量是 C51 编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义位指针,也不能定义位数组。它的值是一个二进制位,不是 0 就是1,类似一些高级语言中的 Boolean 类型中的True 和 False。
2.1.7 sfr 特殊功能寄存器
sfr 也是一种扩充数据类型,点用一个内存单元,值域为 0~255。利用它可以访问 51单片机内部的所有特殊功能寄存器。如用 sfr P1 = 0x90这一句定 P1 为P1 端口在片内的寄存器,在后面的语句中用以用 P1 = 255(对P1 端口的所有引脚置高电平)之类的语句来操作特殊功能寄存器。
2.1.8 sfr16 16位特殊功能寄存器
sfr16 占用两个内存单元,值域为 0~65535。sfr16 和sfr 一样用于操作特殊功能寄存器,所不同的是它用于操作占两个字节的寄存器,如定时器 T0 和 T1。
2.1.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;
这样在以后的程序语句中就可以用 P1_1 来对P1.1 引脚进行读写操作了。 通常这些可以直接使用系统提供的预处理文件,里面已定义好各特殊功能寄存器的简单名字,直接引用可以省去一点时间。
常量就是在程序运行过程中不能改变值的量,而变量是可以在程序运行过程中不断变化的量。 变量的定义可以使用所有 C51 编译器支持的数据类型,而常量的数据类型只有整型、浮点型、字符型、字符串型和位标量。
常量的数据类型说明是这样的:
1.整型常量可以表示为十进制如 123,0,-89 等。十六进制则以 0x 开头如0x34,-0x3B等。长整型就在数字后面加字母 L,如 104L,034L,0xF340 等。
2.浮点型常量可分为十进制和指数表示形式。十进制由数字和小数点组成,如0.888,3345.345,0.0 等,整数或小数部分为 0,可以省略但必须有小数点。指数表示形式为[±]数字[.数字]e[±]数字,[]中的内容为可选项,其中内容根据具体情况可有可无,但其余部分必须有,如125e3,7e9,-3.0e-3。
3.字符型常量是单引号内的字符,如‘a’,‘d’等,不可以显示的控制字符,可以在该字符前面加一个反斜杠“\”组成专用转义字符。常用转义字符表请看表 4-1。
4.字符串型常量由双引号内的字符组成,如“test”,“OK”等。当引号内的没有字符时,为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在 C 中字符串常量是做为字符类型数组来处理的,在存储字符串时系统会在字符串尾部加上\o转义字符以作为该字符串的结束符。字符串常量“A”和字符常量‘A’是不同的,前者在存储时多占用一个字节的字间。
5.位标量,它的值是一个二进制。
转义字符 | 含义 | ASCII(16/10 进制) |
\o | 空字符(NULL) | 00H/0 |
\n | 换行符(LF) | 0AH/10 |
\r | 回车符(CR) | 0DH/13 |
\t | 水平制表符(HT) | 09H/9 |
\b | 退格符(BS) | 08H/8 |
\f | 换页符(FF) | 0CH/12 |
\' | 单引号 | 27H/39 |
\" | 双引号 | 22H/34 |
\\ | 反斜杠 | 5CH/92 |
表2-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 c="100"; //用const 定义 c 为无符号 int常量并赋值 以上两句它们的值都保存在程序存储器中,而程序存储器在运行中是不允许被修改的,所以如果在这两句后面用了类似 a="110",a++这样的赋值语句,编译时将会出错。
变量就是一种在程序执行过程中其值能不断变化的量。 要在程序中使用变量必须先用标识符作为变量名,并指出所用的数据类型和存储模式,这样编译系统才能为变量分配相应的存储空间。定义一个变量的格式如下:
[存储种类] 数据类型 [存储器类型] 变量名表
在定义格式中除了数据类型和变量名表是必要的,其它都是可选项。存储种类有四种:自动(auto) ,外部(extern) ,静态(static)和寄存器(register) ,缺省类型为自动(auto)。
说明了一个变量的数据类型后,还可选择说明该变量的存储器类型。存储器类型的说明就是指定该变量在C51 硬件系统中所使用的存储区域,并在编译时准确的定位。表 2-3 中是 KEIL uVision2所能认别的存储器类型。
存储器类型 | 说 明 |
data | 直接访问内部数据存储器(128字节),访问速度最快 |
bdata | 可位寻址内部数据存储器(16字节),允许位与字节混合访问 |
idata | 间接访问内部数据存储器(256字节),允许访问全部内部地址 |
pdata | 分页访问外部数据存储器(256字节) ,用 MOVX @Ri 指令访问 |
xdata | 外部数据存储器(64KB),用MOVX @DPTR 指令访问 |
code | 程序存储器(64KB),用MOVC@A+DPTR 指令访问 |
表2-3 存储器类型
如果省略存储器类型, 系统则会按编译模式SMALL,COMPACT或LARGE所规定的默认存储器类型去指定变量的存储区域。无论什么存储模式都可以声明变量在任何的8051存储区范围,然而把最常用的命令如循环计数器和队列索引放在内部数据区可以显著的提高系统性能。 还有要指出的就是变量的存储种类与存储器类型是完全无关的。
运算符就是完成某种特定运算的符号。 运算符按其表达式中与运算符的关系可分为单目运算符,双目运算符和三目运算符。单目就是指需要有一个运算对象,双目就要求有两个运算对象,三目则要三个运算对象。表达式则是由运算及运算对象所组成的具有特定含义的式子。C 是一种表达式语言,表达式后面加“;”号就构成了一个表达式语句。
2.4.1 赋值运算符
对于“=”这个符号大家不会陌生的,在 C 中它的功能是给变量赋值,称之为赋值运算符。它的作用就是把数据赋给变量。如,x=10;由此可见利用赋值运算符将一个变量与一个表达式连接起来的式子为赋值表达式,在表达式后面加“;”便构成了赋值语句。使用“=”的赋值语句格式如下:
变量 = 表达式;
示例如下
a = 0xFF; //将常数十六进制数 FF 赋于变量 a
b = c = 33; //同时赋值给变量 b,c
d = e; //将变量 e 的值赋于变量 d
f = a+b; //将变量 a+b的值赋于变量 f
由上面的例子可以知道赋值语句的意义就是先计算出“=”右边的表达式的值,然后将得到的值赋给左边的变量。
2.4.2 算术,增减量运算符
对于 a+b,a/b 这样的表达式大家都很熟悉,用在 C语言中,+,/,就是算术运算符。C51的算术运算符有如下几个,其中只有取正值和取负值运算符是单目运算符,其它则都是双运算符:
+ 加或取正值运算符
- 减或取负值运算符
* 乘运算符
/ 除运算符
% 取余运算符
算术表达式的形式:
表达式 1 算术运算符 表达式 2
如:a+b*(10-a), (x+9)/(y-a)
除法运算符和一般的算术运算规则有所不同,如是两浮点数相除,其结果为浮点数,如10.0/20.0 所得值为 0.5,而两个整数相除时,所得值就是整数,如 7/3,值为 2。像别的语言一样 C 的运算符与有优先级和结合性,同样可用用括号“()”来改变优先级。这些和我们小时候学的数学几乎是一样的,也不必过多的说明了。
++ 增量运算符
-- 减量运算符
这两个运算符是 C 语言中特有的一种运算符。在 VB,PASCAL 等都是没有的。作用就是对运算对象作加 1 和减1 运算。要注意的是运算对象在符号前或后,其含义都是不同的,虽然同是加 1或减 1。如:I++,++I,I--,--I。
I++(或 I--) 是先使用 I 的值,再执行 I+1(或 I-1)
++I(或--I) 是先执行 I+1(或I-1) ,再使用I 的值。
增减量运算符只允许用于变量的运算中,不能用于常数或表达式。
2.4.3 关系运算符
对于关系运算符,在C中有六种关系运算符:
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 等于
计算机的语言也不过是人类语言的一种扩展,这里的运算符同样有着优先级别。前四个具有相同的优先级,后两个也具有相同的优先级,但是前四个的优先级要高于后两个的。
当两个表达式用关系运算符连接起来时,这时就是关系表达式。关系表达式通常是用来判别某个条件是否满足。要注意的是用关系运算符的运算结果只有 0 和1 两种,也就是逻辑的真与假,当指定的条件满足时结果为 1,不满足时结果为 0。
表达式 1 关系运算符 表达式 2
如:I<J,I==J,(I=4)>(J=3),J+I>J
2.4.5 逻辑运算符
关系运算符所能反映的是两个表达式之间的大小等于关系,那逻辑运算符则是用于求条件式的逻辑值,用逻辑运算符将关系表达式或逻辑量连接起来就是逻辑表达式了。也许你会对为什么“逻辑运算符将关系表达式连接起来就是逻辑表达式了”这一个描述有疑惑的地方。
其实之前说过“要注意的是用关系运算符的运算结果只有0和1两种,也就是逻辑的真与假”,换句话说也就是逻辑量,而逻辑运算符就用于对逻辑量运算的表达。逻辑表达式的一般形式为:
逻辑与:条件式 1 && 条件式 2
逻辑或:条件式 1 || 条件式 2
逻辑非:! 条件式 2
逻辑与,说白了就是当条件式 1“与”条件式 2 都为真时结果为真(非0值),否则为假(0值)。也就是说运算会先对条件式1进行判断,如果为真(非0值),则继续对条件式2进行判断,当结果为真时,逻辑运算的结果为真(值为1),如果结果不为真时,逻辑运算的结果为假(0值)。如果在判断条件式1时就不为真的话,就不用再判断条件式2了,而直接给出运算结果为假。
逻辑或,是指只要二个运算条件中有一个为真时,运算结果就为真,只有当条件式都不为真时,逻辑运算结果才为假。
逻辑非则是把逻辑运算结果值取反,也就是说如果两个条件式的运算值为真,进行逻辑非运算后则结果变为假,条件式运算值为假时最后逻辑结果为真。
同样逻辑运算符也有优先级别,!(逻辑非)→&&(逻辑与)→||(逻辑或),逻辑非的优先值最高。
如有!True || False && True
按逻辑运算的优先级别来分析则得到(True代表真,False代表假)
!True || False && True
False || False && True //!Ture先运算得False
False || False //False && True运算得False
False //最终False || False得False
2.4.6 位运算符
位运算符的作用是按位对变量进行运算,但是并不改变参与运算的变量的值。如果要求按位改变变量的值,则要利用相应的赋值运算。还有就是位运算符是不能用来对浮点型数据进行操作的。C51 中共有6种位运算符。位运算一般的表达形式如下:
变量 1 位运算符 变量 2
位运算符也有优先级,从高到低依次是:“~”(按位取反)→“<<”(左移) →“>>”(右移) →“&”(按位与)→“^”(按位异或)→“|”(按位或)。
表2-4是位逻辑运算符的真值表,X表示变量 1,Y 表示变量2:
X | Y | ~X | ~Y | X&Y | X|Y | X^Y |
0 | 0 | 1 | 1 | 0 | 0 | 0 |
0 | 1 | 1 | 0 | 0 | 1 | 1 |
1 | 0 | 0 | 1 | 0 | 1 | 1 |
1 | 1 | 0 | 0 | 1 | 1 | 0 |
表2-4 按位取反,与,或和异或的逻辑真值表
2.4.7 复合赋值运算符
复合赋值运算符就是在赋值运算符“=”的前面加上其他运算符。以下是C语言中的复合赋值运算符:
+= 加法赋值 >>= 右移位赋值
-= 减法赋值 &= 逻辑与赋值
*= 乘法赋值 |= 逻辑或赋值
/= 除法赋值 ^= 逻辑异或赋值
%= 取模赋值 -= 逻辑非赋值
<<= 左移位赋值
复合运算的一般形式为:
变量 复合赋值运算符 表达式
其含义就是变量与表达式先进行运算符所要求的运算, 再把运算结果赋值给参与运算的变量。其实这是C语言中一种简化程序的一种方法,凡是二目运算都可以用复合赋值运算符去简化表达。例如:
a+=56 等价于a=a+56
y/=x+9 等价于 y="y/"(x+9)
很明显采用复合赋值运算符会降低程序的可读性,但这样却可以使程序代码简单化,并能提高编译的效率。对于初学C语言的朋友在编程时最好还是根据自己的理解力和习惯去使用程序表达的方式,不要一味追求程序代码的短小。
2.4.8 逗号运算符
C 语言中逗号还是一种特殊的运算符,也就是逗号运算符,可以用它将两个或多个表达式连接起来,形成逗号表达式。逗号表达式的一般形式为:
表达式 1,表达式 2,表达式 3……表达式 n
这样用逗号运算符组成的表达式在程序运行时,是从左到右计算出各个表达式的值,而整个用逗号运算符组成的表达式的值等于最右边表达式的值,就是“表达式 n”的值。在实际的应用中,大部分情况下,使用逗号表达式的目的只是为了分别得到名个表达式的值,而并不一定要得到和使用整个逗号表达式的值。要注意的还有,并不是在程序的任何位置出现的逗号,都可以认为是逗号运算符。如函数中的参数,同类型变量的定义中的逗号只是用来间隔之用而不是逗号运算符。
2.4.9 条件运算符
C 语言中有一个三目运算符,它就是“?:”条件运算符,它要求有三个运算对象。它可以把三个表达式连接构成一个条件表达式。条件表达式的一般形式如下:
逻辑表达式? 表达式 1 : 表达式 2
条件运算符的作用简单来说就是根据逻辑表达式的值选择使用表达式的值。 当逻辑表达式的值为真时(非 0 值)时,整个表达式的值为表达式 1 的值;当逻辑表达式的值为假(值为 0)时,整个表达式的值为表达式 2 的值。要注意的是条件表达式中逻辑表达式的类型可以与表达式 1 和表达式2的类型不一样。下面是一个逻辑表达式的例子。
如有 a="1",b=2 这时我们要求是取 ab 两数中的较小的值放入 min 变量中,也许你会这样写:
if (a<b)
min = a;
else
min = b; //这一段的意思是当 a<b时 min 的值为 a 的值,否则为 b 的值。
用条件运算符去构成条件表达式就变得简单明了了:
min = (a<b)?a : b
很明显它的结果和含意都和上面的一段程序是一样的,但是代码却比上一段程序少很多,编译的效率也相对要高,但有着和复合赋值表达式一样的缺点就是可读性相对效差。
2.4.10 指针和地址运算符
指针是C语言中一个十分重要的概念,也是学习C语言中的一个难点。在这里我们先来了解一下C语言中提供的两个专门用于指针和地址的运算符:
* 取内容
& 取地址
取内容和地址的一般形式分别为:
变量 = * 指针变量
指针变量 = & 目标变量
取内容运算是将指针变量所指向的目标变量的值赋给左边的变量; 取地址运算是将目标变量的地址赋给左边的变量。要注意的是:指针变量中只能存放地址(也就是指针型数据),一般情况下不要将非指针类型的数据赋值给一个指针变量。
2.4.11 sizeof 运算符
sizeof 是用来求数据类型、变量或是表达式的字节数的一个运算符,但它并不像“=”之类运算符那样在程序执行后才能计算出结果,它是直接在编译时产生结果的。它的语法如下:
sizeof (数据类型)
sizeof (表达式)
下面是两句应用例句:
printf("char 是多少个字节? %bd 字节\n",sizeof(char));
printf("long 是多少个字节? %bd 字节\n",sizeof(long));
结果是:
char 是多少个字节? 1 字节
long 是多少个字节? 4 字节
2.4.12 强制类型转换运算符
1.变量赋值时发生的隐式转换,“=”号右边的表达式的数据类型转换成左边变量的数据类型。如把浮点数赋值给整形变量,小数部分将丢失。
2.所有 char 型的操作数转换成 int型。
3.两个具有不同数据类型的操作数用运算符连接时,隐式转换会按以下次序进行:如有一操作数是 float 类型,则另一个操作数也会转换成float类型;如果一个操作数为long类型,另一个也转换成long;如果一个操作数是unsigned 类型,则另一个操作会被转换成unsigned 类型。
从上面的规则可以大概知道有那几种数据类型是可以进行隐式转换的。在C51中只有char, int, long及float这几种基本的数据类型可以被隐式转换。而其它的数据类型就只能用到显示转换。要使用强制转换运算符应遵循以下的表达形式:
(类型) 表达式
用显示类型转换来处理不同类型的数据间运算和赋值是十分方便和方便的, 特别对指针变量赋值是很有用的。
C语言是一种结构化的程序设计语言。C语言提供了相当丰富的程序控制语句。学习掌握这些语句的用法也是C语言学习中的重点。
表达式语句是最基本的一种语句。不同的程序设计语言都会有不一样的表达式语句,如VB就是在表达式后面加入回车就构成了VB的表达式语句,而在51单片机的C语言中则是加入分号“;”构成表达式语句。举例如下:
b = b * 10;
Count++;
X = A;Y = B;
Page = (a+b)/a-1;
以上的都是合法的表达式语句。
在 C 语言中有一个特殊的表达式语句,称为空语句,它仅仅是由一个分号“;”组成。有时候为了使语法正确,那么就要求有一个语句,但这个语句又没有实际的运行效果那么这时就要有一个空语句。空语句通常用会以下两种用法。
(1)while,for 构成的循环语句后面加一个分号,形成一个不执行其它操作的空循环体。我会常常用它来写等待事件发生的程序。要注意的是“;”号作为空语句使用时,要与语句中有效组成部分的分号相区分,如 for (;a<50000;a++);第一个分号也应该算是空语句,它会使a赋值为0(但要注意的是如程序前有a值,则a的初值为a的当前值),最后一个分号则使整个语句行成一个空循环。
(2)在程序中为有关语句提供标号,标记程序执行的位置,使相关语句能跳转到要执行的位置。这会用在 goto语句中。
在C中括号的分工较为明显,{}号是用于将若干条语句组合在一起形成一种功能块,这种由若干条语句组合而成的语句就叫复合语句。复合语句之间用{}分隔, 而它内部的各条语句还是需要以分号 “;”结束。复合语句是允许嵌套的,也是就是在{}中的{}也是复合语句。复合语句在程序运行时,{}中的各行单语句是依次顺序执行的。C语言中可以将复合语句视为一条单语句,也就是说在语法上等同于一条单语句。对于一个函数而言,函数体就是一个复合语句,也许大家会因此知道复合语句中不单可以用可执行语句组成,还可以用变量定义语句组成。要注意的是在复合语句中所定义的变量,称为局部变量,所谓局部变量就是指它的有效范围只在复合语句中,而函数也算是复合语句,所以函数内定义的变量有效范围也只在函数内部。
条件语句又被称为分支语句,也有人会称为判断语句,其关键字是由if构成,这大众多的高级语言中都是基本相同的。C语言提供了3种形式的条件语句:
1: if (条件表达式) 语句
当条件表达式的结果为真时,就执行语句,否则就跳过。 如 if (a==b) a++; 当a 等于 b 时,a 就加 1
2: if (条件表达式) 语句 1
else 语句 2
当条件表达式成立时,就执行语句 1,否则就执行语句 2 。如
if (a==b)
a++;
else
a--;
当 a 等于b时,a 加1,否则 a-1。
3:if (条件表达式1) 语句1
else if (条件表达式 2) 语句 2
else if (条件表达式 3) 语句 3
else if (条件表达式 m) 语句 n
else 语句m
这是由 if else 语句组成的嵌套,用来实现多方向条件分支,使用应注意 if 和 else的配对使用,要是少了一个就会语法出错,记住 else 总是与最临近的 if 相配对。
用多个条件语句可以实现多方向条件分支,但是可以发现使用过多的条件语句实现多方向分支会使条件语句嵌套过多,程序冗长,这样读起来也很不好读。这时使用开关语句同样可以达到处理多分支选择的目的,又可以使程序结构清晰。它的语法为下:
switch (表达式)
{
case 常量表达式1: 语句1; break;
case 常量表达式2: 语句2; break;
case 常量表达式3: 语句3; break;
case 常量表达式n: 语句n; break;
default: 语句
}
运行中switch后面的表达式的值将会做为条件,与case后面的各个常量表达式的值相对比,如果相等时则执行case后面的语句,再执行break(间断语句)语句,跳出switch语句。如果case后没有和条件相等的值时就执行default 后的语句。当要求没有符合的条件时不做任何处理,则可以不写default 语句。
循环语句是几乎每个程序都会用到的,它的作用就是用来实现需要反复进行多次的操作。如一个12M的51芯片应用电路中要求实现1毫秒的延时,那么就要执行1000 次空语句才可以达到延时的目的(当然可以使用定时器来做,这里就不讨论),如果是写1000条空语句那是多么麻烦的事情,再者就是要占用很多的存储空间。我们可以知道这1000条空语句,无非就是一条空语句重复执行1000次,因此我们就可以用循环语句去写,这样不但使程序结构清晰明了,而且使其编译的效率大大的提高。在C语言中构成循环控制的语句有while, do-while, for和goto语句。同样都是起到循环作用,但具体的作用和用法又大不一样。
goto 语句:
它是一个无条件的转向语句,只要执行到这个语句,程序指针就会跳转到goto后的标号所在的程序段。它的语法如下:
goto 语句标号;
其中的语句标号为一个带冒号的标识符。
while 语句:
while 语句的意思很容易理解,在英语中它的意思是“当…的时候…” ,在这里我们可以理解为“当条件为真的时候就执行后面的语句” ,它的语法如下:
while (条件表达式) 语句;
使用 while语句时要注意当条件表达式为真时,它才执行后面的语句,执行完后再次回到 while 执行条件判断, 为真时重复执行语句, 为假时退出循环体。当条件一开始就为假时,那么 while后面的循环体(语句或复合语句)将一次都不执行就退出循环。
do while 语句:
do while语句可以说是while语句的补充, while是先判断条件是否成立再执行循环体,而do while则是先执行循环体,再根据条件判断是否要退出循环。这样就决定了循环体无论在任何条件下都会至少被执行一次。它的语法如下:
do 语句 while (条件表达式)
for 语句:
在明确循环次数的情况下,for语句比以上说的循环语句都要方便简单。它的语法如下:
for ([初值设定表达式];[循环条件表达式];[条件更新表达式])语句中括号中的表达式是可选的,这样for语句的变化就会很多样了。for语句的执行:先代入初值,再判断条件是否为真,条件满足时执行循环体并更新条件,再判断条件是否为真……直到条件为假时,退出循环。
continue 语句:
continue 语句是用于中断的语句,通常使用在循环中,它的作用是结束本次循环,跳过循环体中没有执行的语句,跳转到下一次循环周期。语法为:
continue;
continue 同时也是一个无条件跳转语句,但功能和前面说到的 break 语句有所不同,continue 执行后不是跳出循环,而是跳到循环的开始并执行下一次的循环。
return 语句:
return 语句是返回语句,是用于结束函数的执行,返回到调用函数时的位置。语法有二种:
return (表达式);
return;
语法中因带有表达式,返回时先计算表达式,再返回表达式的值。不带表达式则返回的值不确定。
2.10 函数
① 函数的定义
通常C语言的编译器会自带标准的函数库,这些都是一些常用的函数,Keil C中也不例外。标准函数已由编译器软件商编写定义,使用者直接调用就可以了,而无需定义。但是标准的函数不足以满足使用者的特殊要求,因此C语言允许使用者根据需要编写特定功能的函数,要调用它必须要先对其进行定义。定义的模式如下:
函数类型 函数名称(形式参数表)
{
函数体
}
函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量类型来定义函数类型就行了。如函数不需要返回值函数类型可以写作“void”表示该函数没有返回值。注意的是函数体返回值的类型一定要和函数类型一致,否则会造成错误。函数名称的定义在遵循C语言变量命名规则的同时,不能在同一程序中定义同名的函数这将会造成编译错误(同一程序中是允许有同名变量的,因为变量有全局和局部变量之分)。形式参数是指调用函数时要传入到函数体内参与运算的变量,它可以有一个、几个或没有,当不需要形式参数也就是无参函数,括号内可以为空或写入“void”表示,但括号不能少。函数体中可以包含有局部变量的定义和程序语句,如函数要返回运算值则要使用return语句进行返回。在函数的{}号中也可以什么也不写,这就成了空函数,在一个程序项目中可以写一些空函数,在以后的修改和升级中可以方便的在这些空函数中进行功能扩充。
② 函数的调用
函数定义好以后,要被其它函数调用了才能被执行。C 语言的函数是可以相互调用的,但在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。标准库函数的说明会被按功能分别写在不同的头文件中,使用时只要在文件最前面用#include 预处理语句引入相应的头文件。调用就是指一个函数体中引用另一个已定义的函数来实现所需要的功能,这时函数体称为主调用函数,函数体中所引用的函数称为被调用函数。一个函数体中可以调用数个其它的函数,这些被调用的函数同样也可以调用其它函数,也可以嵌套调用。调用函数的一般形式如下:
函数名 (实际参数表)
“函数名”就是指被调用的函数。实际参数表可以为零或多个参数,多个参数时要用逗号隔开,每个参数的类型、位置应与函数定义时所的形式参数一一对应,它的作用就是把参数传到被调用函数中的形式参数,如果类型不对应就会产生一些错误。调用的函数是无参函数时不写参数,但不能省后面的括号。
在以前的一些例子我们也可以看不同的调用方式:
1.函数语句
2.函数参数
3.函数表达式
前面说到调用函数前要对被调用的函数进行说明。标准库函数只要用#include引入已写好说明的头文件,在程序就可以直接调用函数了。如调用的是自定义的函数则要用如下形式编写函数类型说明:
类型标识符 函数的名称(形式参数表);
这样的说明方式是用在被调函数定义和主调函数是在同一文件中。 你也可以把这些写到文件名.h 的文件中用#include "文件名.h"引入。如果被调函数的定义和主调函数不是在同一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不同文件之上,其实库函数的头文件也是如此说明库函数的,如果说明的函数也可以称为外部函数。
extern 类型标识符 函数的名称(形式参数表);
函数的定义和说明是完全不同的,在编译的角度上看函数的定义是把函数编译存放在ROM的某一段地址上,而函数说明是告诉编译器要在程序中使用那些函数并确定函数的地址。如果在同一文件中被调函数的定义在主调函数之前,这时可以不用说明函数类型。也就是说在main函数之前定义的函数,在程序中就可以不用写函数类型说明了。可以在一个函数体调用另一个函数(嵌套调用),但不允许在一个函数定义中定义另一个函数。还要注意的是函数定义和说明中的“类型、形参表、名称”等都要相一致。
③ 中断函数
中断服务函数是编写单片机应用程序不可缺少的。中断服务函数只有在中断源请求响应中断时才会被执行,这在处理突发事件和实时控制是十分有效的。例如:电路中一个按键,要求按键后LED点亮,这个按键何时会被按下是不可预知的,为了要捕获这个按键的事件,通常会有三种方法:一是用循环语句不断的对按键进行查询,二是用定时中断在间隔时间内扫描按键,三是用外部中断服务函数对按键进行捕获。在这个应用中只有单一的按键功能,那么第一种方式就可以胜任了,程序也很简单,但是它会不停的在对按键进行查询浪费了CPU的时间。实际应用中一般都会还有其它的功能要求同时实现,这时可以根据需要选用第二或第三种方式,第三种方式占用的 CPU时间最少,只有在有按键事件发生时,中断服务函数才会被执行,其余的时间则是执行其它的任务。
C51语言扩展了函数的定义使它可以直接编写中断服务函数,你可以不必考虑出入堆栈的问题,从而提高了工作的效率。扩展的关键字是interrupt,它是函数定义时的一个选项,只要在一个函数定义后面加上这个选项,那么这个函数就变成了中断服务函数。在后面还可以加上一个选项using,这个选项是指定选用益统单片机内部4组工作寄存器中的哪个组。初学者可以不必去做工作寄存器设定,而由编译器自动选择,避免产生不必要的错误。定义中断服务函数时可以用如下的形式。
函数类型 函数名 (形式参数) interrupt n [using n]
interrupt关键字是不可缺少的,由它告诉编译器该函数是中断服务函数,并由后面的n 指明所使用的中断号。n的取值范围为 0-31,但具体的中断号要取决于芯片的型号,像益统新款单片机实际上就使用0-4号中断。每个中断号都对应一个中断向量,具体地址为8n+3,中断源响应后处理器会跳转到中断向量所处的地址执行程序,编译器会在这地址上产生一个无条件跳转语句,转到中断服务函数所在的地址执行程序。下表2-5是益统芯片的中断向量和中断号。
中断号 | 中断源 | 中断向量 |
0 | 外部中断 | 0003H |
1 | 定时器/计数器 | 000BH |
2 | 外部中断 | 0013H |
3 | 定时器/计数器 | 001BH |
4 | 串行口 | 0023H |
表2-5 益统芯片的中断向量和中断号
使用中断服务函数时应注意:中断函数不能直接调用中断函数;不能通过形参传速参数;在中断函数中调用其它函数,两者所使用的寄存器组应相同。
2.11 数组的使用
数组不过就是同一类型变量的有序集合。形象的可以这样去理解,就像一个学校在操场上排队,每一个级代表一个数据类型,每一个班级为一个数组,每一个学生就是数组中的一个数据。数据中的每个数据都可以用唯一的下标来确定其位置,下标可以是一维或多维的。就如在学校的方队中要找一个学生,这个学生在I年级H班X组Y号的,那么可以把这个学生看做在I类型的H数组中(X,Y)下标位置中。数组和普通变量一样,要求先定义了才可以使用,下面是定义一维或多维数组的方式:
数据类型 数组名 [常量表达式];
数据类型 数组名 [常量表达式 1]...... [常量表达式 N];
“数据类型”是指数组中的各数据单元的类型,每个数组中的数据单元只能是同一数据类型。“数组名”是整个数组的标识,命名方法和变量命名方法是一样的。在编译时系统会根据数组大小和类型为变量分配空间,数组名可以说就是所分配空间的首地址的标识。“常量表达式”是表示数组的长度和维数,它必须用“[]”括起,括号里的数不能是变量只能是常量。
unsigned int xcount [10]; //定义无符号整形数组,有10个数据单元
char inputstring [5]; //定义字符形数组,有5个数据单元
float outnum [10],[10]; //定义浮点型数组,有100个数据单元
在C语言中数组的下标是从0开始的而不是从1开始,如一个具有10个数据单元的数组count,它的下标就是从count[0]到count[9],引用单个元素就是数组名加下标,如count[1]就是引用count 数组中的第2个元素,如果错用了count[10]就会有错误出现了。还有一点要注意的就是在程序中只能逐个引用数组中的元素,不能一次引用整个数组,但是字符型的数组就可以一次引用整个数组。
数组也是可以赋初值的。在上面介绍的定义方式只适用于定义在内存DATA存储器使用的内存,有的时候我们需要把一些数据表存放在数组中,通常这些数据是不用在程序中改变数值的,这时就要把这些数据在程序编写时就赋给数组变量。因为单片机的片内 RAM很有限,通常会把 RAM分给参与运算的变量或数组,而那些程序中不变数据则应存放在片内的CODE存储区,以节省宝贵的RAM。赋初值的方式如下:
数据类型 [存储器类型] 数组名 [常量表达式] = {常量表达式};
数据类型 [存储器类型] 数组名 [常量表达式 1]...... [常量表达式 N]={{常量表达式}...{常量表达式 N}};
在定义并为数组赋初值时,初学的朋友往往会搞错初值个数和数组长度的关系,而致使编译出错。初值个数必须小于或等于数组长度,不指定数组长度则会在编译时由实际的初值个数自动设置。
unsigned char LEDNUM[2]={12,35}; //一维数组赋初值
int Key[2][3]={{1,2,4},{2,2,1}}; //二维数组赋初值
unsigned char IOStr[]={3,5,2,5,3}; //没有指定数组长度,编译器自动设置
unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //数据保存在 code 区
2.12 指针的使用
指针就是指变量或数据所在的存储区地址。如一个字符型的变量STR存放在内存单元DATA区的51H这个地址中,那么DATA区的51H地址就是变量STR的指针。在C语言中指针是一个很重要的概念,正确有效的使用指针类型的数据,可以更有效的表达复杂的数据结构,可以更有效的使用数组或变量,可以方便直接的处理内存或其它存储区。指针之所以可以这么有效的操作数据,是因为无论程序的指令、常量、变量或特殊寄存器都要存放在内存单元或相应的存储区中,这些存储区是按字节来划分的,每一个存储单元都可以用唯一的编号去读或写数据,这个编号就是常说的存储单元的地址,而读写这个编号的动作就叫做寻址,通过寻址就可以访问到存储区中的任一个可以访问的单元,而这个功能是变量或数组等是不可能代替的。C语言也因此引入了指针类型的数据类型,专门用来确定其他类型数据的地址。用一个变量来存放另一个变量的地址,那么用来存放变量地址的变量称为“指针变量”。如用变量STRIP来存放文章开头的STR变量的地址51H,变量STRIP就是指针变量。
变量的指针就是变量的地址,用取地址运算符‘&’取得赋给指针变量。&STR就是把变量STR的地址取得。用语句STRIP=&STR就可以把所取得的STR指针存放在STRIP指针变量中。STRIP的值就变为51H。可见指针变量的内容是另一个变量的地址,地址所属的变量称为指针变量所指向的变量。
要访问变量STR除了可以用‘STR’这个变量名来访问之外,还可以用变量地址来访问。方法是先用&STR 取变量地址并赋于STRIP指针变量,然后就可以用*STRIP 来对STR进行访问了。‘*’是指针运算符,用它可以取得指针变量所指向的地址的值。
使用指针变量之前也和使用其它类型的变量那样要求先定义变量,而且形式也相类似,一般的形式如下:
数据类型 [存储器类型] * 变量名;
unsigned char xdata *pi //指针会占用二字节,指针自身存放在编译器默认存储区,指向 xdata 存储区的 char类型
unsigned char xdata * data pi; //除指针自身指定在data区,其它同上
int * pi; //定义为一般指针,指针自身存放在编译器默认存储区,占三个字节
在定义形式中“数据类型”是指所定义的指针变量所指向的变量的类型。“存储器类型”是编译器编译时的一种扩展标识,它是可选的。在没有“存储器类型”选项时,则定义为一般指针,如有“存储器类型”选项时则定义为基于存储器的指针。
指针变量最大的值为 0xFFFF,这样就决定了一般指针在内存会占用 3 个字节,第一字节存放该指针存储器类型编码,后两个则存放该指针的高低位址。而基于存储器的指针因为不用识别存储器类型所以会占一或二个字节, idata , data, pdata存储器指针占一个字节,code, xdata则会占二个字节。由上可知,明确的定义指针,可以节省存储器的开销,这在严格要求程序体积的项目中很有用处。
2.13 结构、联合和枚举的使用
为了更有效的处理更复杂的数据,C语言引入了构造类型的数据类型。构造类型就是将一批各种类型的数据放在一起形成一种特殊类型的数据。之前讨论过的数组也算是一种构造类型的数据,C51中的构造类型还有结构、枚举和联合。
结构:
结构是一种数据的集合体,它可以按需要将不同类型的变量组合在一起,整个集合体用一个结构变量名表示,组成这个集合体的各个变量称为结构成员。理解结构的概念,可以用班级和学生的关系去理解。班级名称就相当于结构变量名,它代表所有同学的集合,而每个同学就是这个结构中的成员。使用结构变量时,要先定义结构类型。一般定义格式如下:
struct 结构名 {结构元素表};
例子:struct FileInfo
{
unsigned char FileName[4];
unsigned long Date;
unsigned int Size;
}
上面的例子中定义了一个简单的文件信息结构类型,它可用于定义用于简单的单片机文件信息,结构中有三个元素,分别用于操作文件名、日期、大小。因为结构中的每个数据成员可以使用不同的数据类型,所以要对每个数据成员进行数据类型定义。定义好一个结构类型后,可以按下面的格式进行定义结构变量,要注意的是只有结构变量才可以参与程序的执行,结构类型只是用于说明结构变量是属于那一种结构。
struct 结构名 结构变量名1,结构变量名 2……结构变量 N;
例子:struct FileInfo NewFileInfo, OldFileInfo;
通过上面的定义NewFileInfo和OldFileInfo都是FileInfo结构,都具有一个字符型数组一个长整型和一个整形数据。定义结构类型只是给出了这个结构的组织形式,它不会占用存储空间,也就说结构名是不能进行赋值和运算等操作的。结构变量则是结构中的具体成员,会占用空间,可以对每个成员进行操作。
枚举:
在程序中经常要用到一些变量去做程序中的判断标志。 如经常要用一个字符或整型变量去储存1和0做判断条件真假的标志, 但我们也许会疏忽这个变量只有当等于 0或1才是有效的,而将它赋上别的值,而使程序出错或变的混乱。这时可以使用枚举数据类型去定义变量,限制错误赋值。枚举数据类型就是把某些整型常量的集合用一个名字表示,其中的整型常量就是这种枚举类型变量的可取的合法值。枚举类型的二种定义格式如下:
enum 枚举名 {枚举值列表} 变量列表;
例: enum TFFlag {False, True} TFF;
enum 枚举名 {枚举值列表};
emum 枚举名 变量列表;
联合
联合同样是C语言中的构造类型的数据结构。它和结构类型一样可以包含不同类型的数据元素,所不同的是联合的数据元素都是从同一个数据地址开始存放。结构变量占用的内存大小是该结构中数据元素所占内存数的总和, 而联合变量所占用内存大小只是该联合中最长的元素所占用的内存大小。如在结构中定义了一个int和一个char,那么结构变量就会占用3个字节的内存,而在联合中同样定义一个int和一个char,联合变量只会占用2个字节。这种能充分利用内存空间的技术叫‘内存覆盖技术’ ,它可以使不同的变量分时的使用同一个内存空间。使用联合变量时要注意它的数据元素只能是分时使用,而不能同时使用。举个简单的例子,程序先为联合中的int 赋值1000,后来又为char赋值10,那么这时就不能引用int了,否则程序会出错,起作用的是最后一次赋值的元素,而上一次赋值的元素就失效了。使用中还要注意定义联合变量时不能对它的值初始化、可以使用指向联合变量的指针对其操作、联合变量不能作为函数的参数进行传递,数组和结构可以出现在联合中。
联合类型变量的定义方法和结构的定义方法差不多,只要把关键字 struct 换用 union 就可以了。联合变量的引用方法除也是使用‘.’成员运算符。
第三章 益统ET45Mx ISP下载软件
3.1 益统ET45Mx ISP下载软件基本介绍
益统公司提供ET45Mxxx系列芯片的ISP下载工具,用户可在附送的光盘中找到或到益统公司网站上进行下载。以光盘中的文件为例,找到以下名称的文件:
然后复制到用户硬盘的任意位置上,在文件上单击右键,按下图方式解压即可:
解压后如下图所示:
双击打开“ET45MxxxISP”文件夹,在文件夹中找到如下图所示的图标:
双击此图标或右击鼠标再单击打开,提示画面如下所示:
此时,按下任意键后该窗口将消失,ET45Mxxx ISP下载软件就安装到用户的电脑上了。
3.2 益统ET45Mx ISP下载软件使用指南
经过上述步骤后,用户就可以使用益统ET45Mx ISP下载软件来对益统公司的ET45Mxxx系列芯片进行烧录了。
烧录程序前,先将益统公司芯片烧录器插到电脑的任意一个USB接口上,然后在电脑中找到解压后的“ET45MxxxISP”的文件夹,打开该文件夹找到以下图标的执行文件:
双击或右键打开再单击该文件后即可打开ET45Mxxx ISP烧录软件,显示界面如下:
首先点击“SETTING”选项进入如下界面,然后按下蓝色框内的按钮,即自动查找当前使用的端口:
当然,用户也可自己来选择烧录器所在端口,并在“Baud”下拉菜单中选择波特率,完成选择后单击红色框内“OK”按钮即可:
单击“OK”按键后,即可返回主界面,此时可单击蓝色框内的“ISP”按键来与益统烧录器进行连接,连接时黄色框内的红灯将闪烁起来,如下图:
连接成功后黄色框内的红灯变为绿色,红色框内为提示信息,此时可单击“Open”按键,来读入要烧录进单片机内的“.HEX”或“.BIN”文件,如下图:
单击“Open”按键后,在弹出的对话框内,找到想要烧录到单片机的程序,选择好后,可单击蓝色框内的“打开”按键进行确认,如下图:
单击“打开”后,将弹出如下提示框,此时单击蓝色框内的“确定”按键即可,即转换为INTEL的十六进制格式,可支持1M的HEX文件,如下图所示:
单击“确定”后在“Send Code”区内以二进制显示要烧录的文件,此时可单击红色框内的“Erase”按键,清除单片机中原有的程序,如下图:
单击“Erase”按键后,在“Status”栏内蓝下划线处及紫色下划线处均显示状态信息,此时即可单击红色框内的“Write”按键,将之前读入的文件烧录到单片机中:
单击“Write”按键后,蓝下划线处及紫色下划线处均显示当前状态信息,此时可单击红色框内的“Verify”按键来对烧录进去的程序进行校验:
单击“Verify”按键后,程序检验无误如下图红色框内内容所示,即自动使单片机进入到运行状态:
以上操作步骤,在读取要烧录的文件后,可以直接点击下图中红色框内“AutoRun”按键来实现上述步骤:
益统公司ET45Mx ISP下载软件操作介绍如上,用户可按上述步骤对益统公司ET45Mxxx系列芯片进行烧录。
第二部分 入门级程序范例
第四章 LED闪烁程序范例
在益统ET45M052的P1.0端口上接一个发光二极管L1,使L1在不停地一亮一灭,一亮一灭的时间间隔为0.5秒。
当P1.0端口输出高电平,即P1.0=1时,根据发光二极管的单向导电性可知,这时发光二极管L1熄灭;当P1.0端口输出低电平,即P1.0=0时,发光二极管L1亮。
4.5 C语言源程序
#include <ET45M052.H>
sbit L1=P1^0; //在程序中用L1代替P1.0端口
void delay05s(void) //延时0.5秒子程序
{
unsigned char i,j,k;
for(i=20;i>0;i--)
for(j=50;j>0;j--)
for(k=248;k>0;k--);
}
void main(void) //主程序
{
while(1) //总是循环执行
{
L1=0; //点亮LED
delay05s(); //延时0.5S
L1=1; //熄灭LED
delay05s(); //延时0.5S
}
}
第五章 8个LED流水灯控制程序范例
使用益统ET45M052做单一LED的左移右移,八个发光二极管LED分别接在单片机的P1.0-P1.7接口上,输出“0”时,发光二极管亮,开始时P1.0→┅→P1.7→P1.6→┅→P1.0亮,重复循环执行。
我们可以运用输出端口指令,只要给累加器值或常数值,即可达到输出控制的动作。每次送出的数据是不同,具体的数据如下表6-1所示
P1.7 | P1.6 | P1.5 | P1.4 | P1.3 | P1.2 | P1.1 | P1.0 | 说明 |
L8 | L7 | L6 | L5 | L4 | L3 | L2 | L1 |
|
1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | L1亮 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | L2亮 |
1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | L3亮 |
1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | L4亮 |
1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | L5亮 |
1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | L6亮 |
1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | L7亮 |
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | L8亮 |
表6-1
5.5 C语言源程序
#include <ET45M052.H>
unsigned char i;
unsigned char temp;
unsigned char a,b;
void delay(void) //延时0.5S函数
{
unsigned char m,n,s;
for(m=20;m>0;m--)
for(n=50;n>0;n--)
for(s=248;s>0;s--);
}
void main(void) //主程序
{
while(1) //总是循环执行
{
temp=0xfe; //初始数据,最低位点亮
P1=temp; //初始数据送P1端口
delay();
for(i=1;i<8;i++) //低位到高位循环执行8次
{
a=temp<<i;
b=temp>>(8-i);
P1=a|b;
delay();
}
for(i=1;i<8;i++) //高位到低位循环执行8次
{
a=temp>>i;
b=temp<<(8-i);
P1=a|b;
delay();
}
}
}
第六章 驱动七段数码管程序范例
使用益统ET45M052单片机的P0端口的P0.0-P0.7连接到一个共阴数码管的a-g的笔段上,数码管的公共端接地。在数码管上循环显示0-9数字,时间间隔0.5秒。
七段LED显示器内部由七个条形发光二极管和一个小圆点发光二极管组成,根据各管的极管的接线形式,可分成共阴极型和共阳极型。
LED数码管的g~a七个发光二极管因加正电压而发亮,因加零电压而不以发亮,不同亮暗的组合就能形成不同的字形,这种组合称之为字形码,下面给出共阴极的字形码见表7-1:
“0” | 3FH | “8” | 7FH |
“1” | 06H | “9” | 6FH |
“2” | 5BH | “A” | 77H |
“3” | 4FH | “b” | 7CH |
“4” | 66H | “C” | 39H |
“5” | 6DH | “d” | 5EH |
“6” | 7DH | “E” | 79H |
“7” | 07H | “F” | 71H |
表7-1
由于显示的数字0-9的字形码没有规律可循,只能采用查表的方式来完成我们所需的要求了。这样我们按着数字0-9的顺序,把每个数字的笔段代码按顺序排好;建立的表格如下所示:TABLE DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH。
6.5 C语言源程序
#include <ET45M052.H>
unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//七段数码管显示码表
unsigned char dispcount;
void delay02s(void) //延时0.5S的函数
{
unsigned char i,j,k;
for(i=20;i>0;i--)
for(j=50;j>0;j--)
for(k=248;k>0;k--);
}
void main(void) //主程序
{
while(1) //总是循环执行
{
for(dispcount=0;dispcount<10;dispcount++)
{
P0=table[dispcount]; //查显示码表表将数据送到P0口
delay02s(); //延时0.2S
}
}
}
第七章 4*4行列键盘程序范例
用益统ET45M052单片机的并行口P3接4×4矩阵键盘,以P3.0-P3.3作输入线,以P3.4-P3.7作输出线;在数码管上显示每个按键的“0-F”序号。
每个按键有它的行值和列值,行值和列值的组合就是识别这个按键的编码。矩阵的行线和列线分别通过两并行接口和CPU通信。每个按键的状态同样需变成数字量“0”和“1”,开关的一端(列线)通过电阻接VCC,而接地是通过程序输出数字“0”实现的。键盘处理程序的任务是:确定有无键按下,判断哪一个键按下,键的功能是什么;还要消除按键在闭合或断开时的抖动。两个并行口中,一个输出扫描码,使按键逐行动态接地,另一个并行口输入按键状态,由行扫描值和回馈信号共同形成键编码而识别按键,通过软件查表,查出该键的功能。
7.5 C语言源程序
#include <ET45M052.H>
unsigned char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; //按键值码表
unsigned char temp;
unsigned char key;
unsigned char i,j;
void main(void) //主程序
{
while(1) //循环执行
{
P3=0xff;
P3^4=0; //使用0xEF扫描键盘
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f) //如果有键按下
{
for(i=50;i>0;i--)
for(j=200;j>0;j--); //延时10ms
temp=P3;
temp=temp & 0x0f; //再次判断
if (temp!=0x0f) //确认有键按下
{
temp=P3;
temp=temp & 0x0f;
switch(temp) //找出是哪一个键按下
{
case 0x0e:
key=7;
break;
case 0x0d:
key=8;
break;
case 0x0b:
key=9;
break;
case 0x07:
key=10;
break;
}
temp=P3;
P1_0=~P1_0;
P0=table[key]; //将得到的键值查表送P0口显示
temp=temp & 0x0f;
while(temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
}
}
}
P3=0xff;
P3^5=0; //使用0xDF扫描键盘,以下过程同上
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
for(i=50;i>0;i--)
for(j=200;j>0;j--);
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
switch(temp)
{
case 0x0e:
key=4;
break;
case 0x0d:
key=5;
break;
case 0x0b:
key=6;
break;
case 0x07:
key=11;
break;
}
temp=P3;
P1_0=~P1_0;
P0=table[key];
temp=temp & 0x0f;
while(temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
}
}
}
P3=0xff;
P3^6=0; //使用0xBF扫描键盘,以下过程同上
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
for(i=50;i>0;i--)
for(j=200;j>0;j--);
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
switch(temp)
{
case 0x0e:
key=1;
break;
case 0x0d:
key=2;
break;
case 0x0b:
key=3;
break;
case 0x07:
key=12;
break;
}
temp=P3;
P1_0=~P1_0;
P0=table[key];
temp=temp & 0x0f;
while(temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
}
}
}
P3=0xff;
P3^7=0; //使用0x7F扫描键盘,以下过程同上
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
for(i=50;i>0;i--)
for(j=200;j>0;j--);
temp=P3;
temp=temp & 0x0f;
if (temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
switch(temp)
{
case 0x0e:
key=0;
break;
case 0x0d:
key=13;
break;
case 0x0b:
key=14;
break;
case 0x07:
key=15;
break;
}
temp=P3;
P1_0=~P1_0;
P0=table[key];
temp=temp & 0x0f;
while(temp!=0x0f)
{
temp=P3;
temp=temp & 0x0f;
}
}
}
}
}
第八章 独立按键与LED输出结合程序范例
开关SW接在益统ET45M052的P3.7(/RD)管脚上,在益统新款单片机的P1端口接有四个发光二极管,上电的时候,L1接在P1.0管脚上的发光二极管在闪烁,当每一次按下开关SW的时候,L2接在P1.1管脚上的发光二极管在闪烁,再按下开关SP1的时候,L3接在P1.2管脚上的发光二极管在闪烁,再按下开关SP1的时候,L4接在P1.3管脚上的发光二极管在闪烁,再按下开关SP1的时候,又轮到L1在闪烁了,如此轮流下去。
dy><?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
益统ET45M052<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 单片机范例教程 |
Etoms Electronics Corp.
联系方式:
深圳市南山区南油商服大厦5楼502
电话:86-755-26406990
传真:86-755-26406200
目 录
1.2 益统ET45M052单片机的引脚功能与封装... 2
使用益统ET45M052的P2口连接8个LED、INT0脚连接一个10K的上拉电阻,让该引脚保持为高电平,另外再连接一个按钮开关SW。当主程序正常执行时,P2所连接的8个LED将闪烁。若按下INT0按钮开关SW,则进入中断状态,P2所连接的8个LED将变成单灯左移,而左移3圈后,恢复中断前的状态,程序将继续执行8灯闪烁的功能。
根据功能需求可知,首先要声明delay函数,然后依次定义主程序,中断子程序与delay函数。在主程序中,先设定中断,然后进行8灯亮、延时、8灯灭、延时的持续动作。在中断子程序中,以循环嵌套的方式,内循环进行单灯左移8次,即可将亮灯由最右边移至最左边。外循环进行3次,也就是让单灯左移由最右边移至最左边,跑3圈后即可返回主程序。
9.5 C语言源程序
#include<ET45M052.H>
#define LED P2 //定义LED接至P2口
void delay(int); //声明延时函数
void left(int x); //声明左移函数
main() //主程序
{
IE=0x81; //开中断
TCON=0x01; //设中断方式为边缘触发
LED=0x00; //LED灯初值为全亮
WHILE(1) //总是循环执行
{
delay(250); //延时0.25S
LED=~LED; //LED灯取反
}
}
void my_int0(void) interrupt 0 //INT0中断子程序开始
{
unsigned saveLED="LED"; //保存当前LED的状态
left(3); //执行左移函数3次
LED=saveLED; //将保存的LED状态送回LED
}
void delay(int x) //延时1MS函数
{
int i,j;
for(i=0;i<x;i++)
for(j=0;j<120;j++);
}
void left(int x) //左移函数
{
int i,j;
for(i=0;i<x;i++)
{
LED=0xfe;
for(j=0;j<7;j++)
{
delay(250);
LED=(LED<<1)|0x01;
}
delay(250);
}
}
第十章 定时器在数码管上显示00-59程序范例
使用益统ET45M052单片机的定时/计数器T0产生一秒的定时时间,作为秒计数时间,当一秒产生时,秒计数加1,秒计数到59时,自动从0开始。
益统ET45M052单片机的内部16位定时/计数器是一个可编程定时/计数器,它既可以工作在13位定时方式,也可以工作在16位定时方式和8位定时方式。只要通过设置特殊功能寄存器TMOD,即可完成。定时/计数器何时工作也是通过软件来设定TCON特殊功能寄存器来完成的。
现在我们选择16位定时工作方式,对于T0来说,最大定时也只有65536us,即65.536ms,无法达到我们所需要的1秒的定时,因此,我们必须通过软件来处理这个问题,假设我们取T0的最大定时为50ms,即要定时1秒需要经过20次的50ms的定时。对于这20次我们就可以采用软件的方法来统计了。因此,我们设定TMOD=01H,下面我们要给T0定时/计数器的TH0,TL0装入预置初值,通过下面的公式可以计算出:
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
当T0在工作的时候,我们通过检测TCON特殊功能寄存器中的TF0标志位,如果TF0=1表示定时时间已到。
10.5 C语言源程序
#include <ET45M052.H>
unsigned char code dispcode[]= {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00}; //数码管显示码表
unsigned char second;
unsigned char tcount;
void main(void) //主程序
{
TMOD=0x01;
TH0=(65536-50000)/256; //计时初值高位
TL0=(65536-50000)%256; //计时初值低位
TR0=1; //开定时器
tcount=0;
second=0;
P0=dispcode[second/10]; //P0口显示高位
P2=dispcode[second%10]; //P2口显示低位
while(1) //总是执行
{
if(TF0==1) //查询定时是否到来
{
tcount++; //数值递加
if(tcount==20) //满20次执行秒+1
{
tcount=0;
second++;
if(second==60) //到60秒自动归0
{
second=0;
}
P0=dispcode[second/10]; //送P0口显示
P2=dispcode[second%10]; //送P2口显示
}
TF0=0;
TH0=(65536-50000)/256; //重新置初值
TL0=(65536-50000)%256; //重新置初值
}
}
}
第十一章 串口自传自收程序范例
将益统ET45M052单片机的TXD和RXD短路在一起,让串行数据输出连接到串行输入,以达到自己传给自己的目的。另外,第11脚所要传出来的数据是来自P2口所连接的指拨开关DIP SW的状态;而第11脚所接收到的串行数据将反映到P0口所连接的LED上。
在此所要采用的比特率为9600bit/s。首先利用TMOD寄存器将Timer1设定为Mode2,再将PCON寄存器的SMOD位设定为0,然后TH1寄存器加载0xfd(即253),最好启动Timer1,即可产生9600bit/s的波特率。另外,在SCON寄存器里,将串行端口设定为mode1。这样,由P2口所读取的数据,放入SBUF寄存器里(传送至SBUF寄存器),CPU即自动传送。另一方面,CPU也自动接收,当接收的SBUF寄存器满了,即产生RI中断,我们只要将SBUF寄存器里的数据复制到P0即可。
11.5 C语言源程序
#include< ET45M052.H>
sbit SMOD=0x87^7; //SMOD为PCON的bit7
main()
{
P2=0xff; //P2设为输入口
TMOD=0x20; //Timer1设为mode2,做为比特率产生
SMOD=0; //将SMOD设为0
TH1=0xfd; //比特率设定为9600
TR1=1; //启动定时器1
SCON=0x50; //设定为mode0
while(1)
{
SBUF=P2; //将拨指开关状态,放入SBUF
while(RI==0); //检查是否完成输入
RI=0; //RI=1,完成接收,清除RI的标志位
P0=SBUF; //将接收到的数据输出到P0口
TI=0; //清除TI标志位,允许发送数据
}
}
第三部分 进阶级程序范例
第十二章 PWM控制直流电机转动程序范例
使用益统ET45M052单片机采用PWM的方式来对一直流电机进行控制,配合指拨开关使其实现8种不同的速度来驱动直流电机。
使用益统新款单片机的P1.0输出到达林顿管,以控制直流马达。另外,8P的指拨开关连接到P2,由开关的状态决定P1.0所输出脉冲的工作周期,如表13-1所示,其中S8具有最高优先级,随后依次为S7,S6……
拨指开关 | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 |
工作周期 | 12% | 24% | 36% | 48% | 60% | 72% | 84% | 96% |
表13-1
12.5 C语言源程序
#include< ET45M052.H>
sbit motor = P1^0; //设定电机输出控制脚
sbit S8 = P2^7; //设定拨指开关的端口,下同
sbit S7 = P2^6;
sbit S6 = P2^5;
sbit S5 = P2^4;
sbit S4 = P2^3;
sbit S3 = P2^2;
sbit S2 = P2^1;
sbit S1 = P2^0;
void delay1ms(int); //声明1ms延时函数
void output(char); //声明输出函数
main() //主程序
{
motor=0; //关电机
P2=0xff; //P2口设置为输入
while(1) //主程序循环
{
if(S8==0)
output(96); //S8按下,输出96%的工作周期
else if(S7==0)
output(84); // S7按下,输出84%的工作周期
else if(S6==0)
output(72); // S6按下,输出72%的工作周期
else if(S5==0)
output(60); // S5按下,输出60%的工作周期
else if(S4==0)
output(48); // S4按下,输出48%的工作周期
else if(S3==0)
output(36); // S3按下,输出36%的工作周期
else if(S2==0)
output(24); // S2按下,输出24%的工作周期
else if(S1==0)
output(12); // S1按下,输出12%的工作周期
}
}
void output(char on); //输出函数
{
char i;
for(i=0;i<10;i++)
{
motor=1;
delay1ms(on); //开动后,延时指定的时间长度
motor=0;
delay1ms(100-on); //关闭后,延时指定的时间长度
}
delay1ms(500); //停止延时0.5S
}
void delay1ms(int x) //延时1ms函数
{
int x,y;
for(i=0;i<x;i++)
for(j=0;j<120;j++);
}
第十三章 液晶显示程序范例
使用益统的ET45M052单片机配合中文LCM—WG14432B-YYH-N,在显示屏上进行字母与文字的交替显示。
对LCM进行基本的设置后,首先在第一列里显示“LCM test program”,2S后在第二列里显示“Everything is OK”;再经过2S后在第一列里改为“中文LCM测试程序”,第二列里改为“欢迎使用益统产品”。
13.5 C语言源程序
#include< ET45M052.H>
#define LCD P1 //LCD接在P1口上
sbit RS="P3"^2; //RS接P3^2
sbit RW="P3"^1; //RW接P3^1
sbit en="P3"^0; //EN接P3^0
char line1[]=”LCM test program”; //待显示数据1
char line2[]=”Everthing is OK”; //待显示数据2
char line3[]=”中文LCM测试程序”; //待显示数据3
char line4[]=”欢迎使用益统产品”; //待显示数据4
void init_LCM(void); //声明初始化LCM函数
void write_inst(char); //声明写命令函数
void check_BF(void); //声明查忙函数
void delay1ms(int); //声明延时函数
main()
{
char i;
init_LCM(); //初始化LCM
while(1)
{
write_inst(0x80); //在第一列写入数据
for(i=0;i<16;i++)
write_char(line1); //待显示数据1写入
write_inst(0x90); //在第二列写入数据
for(i=0;i<16;i++)
write_char(line2); //待显示数据2写入
write_inst(0x80); //在第一列写入数据
for(i=0;i<16;i++)
write_char(line3); //待显示数据3写入
write_inst(0x90); //在第二列写入数据
for(i=0;i<16;i++)
write_char(line4); //待显示数据4写入
}
}
void init_LCM(void) //LCM初始化函数
{
write_inst(0x30);
write_inst(0x30);
write_inst(0x30);
write_inst(0x38);
write_inst(0x08);
write_inst(0x01);
write_inst(0x06);
write_inst(0x0e);
}
void write_inst(char inst) //向LCM写入命令函数
{
RS=0;
RW=0;
en=1;
LCDP=inst;
en=0;
check_BF();
}
void write_char(char character) //向LCM写入数据函数
{
RS=1;
RW=0;
en=1;
LCD=inst;
en=0;
check_BF();
}
void check_BF(void) //LCM查忙函数
{
char i,x=0x80;
while(x&0x80)
{
RS=0;
RW=1;
en=1;
x=LCD;
en=0;
for(i=0;i<10;i++)
}
}
void delay1ms(int x) //延时1MS函数
{
int i,j;
for(i=1;i<x;i++)
for(j=1;j<120;j++);
}
第十四章 点阵屏显示“0-9”数字程序范例
使用益统ET45M052单片机配合8X8的点阵屏显示0到9的数字。
数字0-9点阵显示代码的形成,如下图15-1所示,假设显示数字“0”
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-1
00 00 3E 41 41 41 3E 00
因此,形成的列代码为 00H,00H,3EH,41H,41H,3EH,00H,00H;只要把这些代码分别送到相应的列线上面,即可实现“0”的数字显示。送显示代码过程如下所示 送第一列线代码到P3端口,同时置第一行线为“0”,其它行线为“1”,延时2ms左右,送第二列线代码到P3端口,同时置第二行线为“0”,其它行线为“1”,延时2ms左右,如此下去,直到送完最后一列代码,又从头开始送。
数字“1”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
|
|
| ● |
|
|
|
|
|
| ● | ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
| ● | ● | ● |
|
图15-2
其显示代码为 00H,00H,00H,00H,21H,7FH,01H,00H
数字“2”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
| ● | ● | ● | ● |
|
|
|
| ● |
|
|
|
|
|
|
| ● | ● | ● | ● | ● |
|
图15-3
00H,00H,27H,45H,45H,45H,39H,00H
数字“3”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
|
|
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-4
00H,00H,22H,49H,49H,49H,36H,00H
数字“4”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
|
|
| ● |
|
|
|
|
|
| ● | ● |
|
|
|
|
| ● |
| ● |
|
|
|
| ● |
|
| ● |
|
|
|
| ● | ● | ● | ● | ● |
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
图15-5
00H,00H,0CH,14H,24H,7FH,04H,00H
数字“5”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
| ● | ● | ● | ● | ● |
|
|
| ● |
|
|
|
|
|
|
| ● | ● | ● | ● |
|
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-6
00H,00H,72H,51H,51H,51H,4EH,00H
数字“6”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
|
|
|
| ● | ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-7
00H,00H,3EH,49H,49H,49H,26H,00H
数字“7”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
| ● | ● | ● | ● | ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
|
|
|
|
| ● |
|
|
图15-8
00H,00H,40H,40H,40H,4FH,70H,00H
数字“8”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-9
00H,00H,36H,49H,49H,49H,36H,00H
数字“9”代码建立如下图所示
1 2 3 4 5 6 7 8
|
|
|
|
|
|
|
|
|
|
| ● | ● | ● |
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● | ● |
|
|
|
|
|
|
| ● |
|
|
| ● |
|
|
| ● |
|
|
|
| ● | ● | ● |
|
|
图15-10
00H,00H,32H,49H,49H,49H,3EH,00H
14.5 C语言源程序
#include < ET45M052.H>
unsigned char code tab[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //片选信号
unsigned char code digittab[10][8]={ //码表
{0x00,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00}, //0
{0x00,0x00,0x00,0x00,0x21,0x7f,0x01,0x00}, //1
{0x00,0x00,0x27,0x45,0x45,0x45,0x39,0x00}, //2
{0x00,0x00,0x22,0x49,0x49,0x49,0x36,0x00}, //3
{0x00,0x00,0x0c,0x14,0x24,0x7f,0x04,0x00}, //4
{0x00,0x00,0x72,0x51,0x51,0x51,0x4e,0x00}, //5
{0x00,0x00,0x3e,0x49,0x49,0x49,0x26,0x00}, //6
{0x00,0x00,0x40,0x40,0x40,0x4f,0x70,0x00}, //7
{0x00,0x00,0x36,0x49,0x49,0x49,0x36,0x00}, //8
{0x00,0x00,0x32,0x49,0x49,0x49,0x3e,0x00} //9
};
unsigned int timecount;
unsigned char cnta;
unsigned char cntb;
void main(void)
{
TMOD=0x01; //设置定时器
TH0=(65536-3000)/256;
TL0=(65536-3000)%256;
TR0=1; //开定时器
ET0=1; //开定时器中断
EA=1; //开中断使能
while(1) //等待定时时间到
{;
}
}
void t0(void) interrupt 1 using 0
{
TH0=(65536-3000)/256;
TL0=(65536-3000)%256; //定时器置初值
P3=tab[cnta]; //片选某一位
P1=digittab[cntb][cnta]; //送显示数值
cnta++; //下次定时到来显示下一列
if(cnta==8)
{
cnta=0;
}
timecount++;
if(timecount==333) //循环显示一秒
{
timecount=0;
cntb++; //显示一下个数据
if(cntb==10) //到10归0
{
cntb=0;
}
}
}
文章评论(0条评论)
登录后参与讨论