赞
踩
一、什么是51单片机?AT89C51、STC89C51、BF7615BM28这些都是51单片机吗?
答:51单片机是兼容Intel 8051指令系统的单片机的总称,与具体生产厂商和单片机型号没有直接关联。上述单片机都使用了Intel 8051指令系统(含自主增加指令的增强型),所以都是51单片机。
二、MCS-51和8051是否是相同的意思?
答:MCS是Intel公司单片机的系列符号,有MCS-48、MCS-51、MCS-96等系列,而经典的MCS-51系列包含三个基本型80C31、8051、8751,以及对应的低功耗型号80C31、8051、87C51,因而MCS-51特指Intel的这几种型号。由于2007年后Intel停止了MCS系列单片机的生产,所以准确来说,后面其他厂商生产的AT89C51等单片机不属于MCS-51系列。
8051一般是指Intel 8051指令系统(指令集),只要单片机仍使用Intel 8051指令系统,那么仍可称为8051单片机,简称51单片机。
三、8051经典内部结构是怎样的?
四、AT89C51是51单片机,那么AT89C52是52单片机吗?
答:不是,51单片机是指使用了Intel 8051指令系统(指令集)的单片机,一般没有52单片机的说法。之所以存在AT89C52这一型号的单片机,是因为在早期AT89C51单片机的基础上额外增加了一个定时器/计数器T2,并且升级了RAM和ROM的内存,所以AT89C52可以视作为AT89C51的强化版。
五、学生时代,国内常见的51单片机为STC公司产的STC89C52RC,编程时需要注意什么?
答:STC89C52RC是STC(宏晶科技)公司基于8051内核推出的一款51单片机,编程前需要通过STC推出的ISP下载软件将器件包注入Keil软件安装目录下,准确选择单片机型号。
当然,由于都使用了8051指令系统,单片机型号选择为AT89C52也可以编译,但是STC多出的部分特殊功能寄存器无法直接使用(头文件未声明部分寄存器),需要自行使用SFR声明。
六、时常看见程序开头有内置头文件,#include<REG51.h>和#include<REG52.h>有何不同?
答:其实头文件中写的很明白,相关描述如下:
REG51.h:Header file for generic 80C51 and 80C31 microcontroller.
REG52.h:Header file for generic 80C52 and 80C32 microcontroller.
通过WinMerge对比分析这两个文件,我们可以清晰地发现 REG52.h在REG51.h的基础上增加了对定时器/计数器T2相关寄存器的声明。也就是说,如果在程序中使用了定时器/计数器T2相关寄存器,#include<REG51.h>时就需要自行对相关寄存器声明,否则编译器就会报错。头文件REG52.h中已经对相关寄存器做出声明,在程序中可直接使用。
七、#include<REG51.h>和#include"REG52.h"有何不同?
答:尖括号包含的头文件是编译器自带的头文件,这一类文件通常放在编译器安装目录下。英文双引号包含的头文件一般是自己编写的头文件,通常放在工程文件目录下,当程序编译时找不到该文件时也会到编译器安装目录下找一遍,但是还是建议按照规范编程。
八、为什么有时候程序中看不到#include<REG51.h>或#include"REG52.h",这样的程序也可以运行吗?
答:头文件不是必要的,如果不嫌麻烦,在程序中自行声明自己用到的寄存器也可以。
九、为什么有时候程序中会见到#include <STC89C5xRC.H>,却没有#include<REG52.h>?
答:STC89C5xRC.H其实是STC(宏晶科技)自己在REG51F.H的基础上增添了部分自行设计的特殊功能寄存器,内置STC89C5xRC.H时就无需再声明STC自行增添的寄存器。简单来说,REG51F.H是STC89C5xRC.H的真子集。
十、如果厂商对每一款单片机都有定义头文件,但是我不知道名字该如何添加到程序中呢?
答:其实只需在程序中单击右键,就能看到插入头文件的相关提示了。
十一、新建工程时,会有提示将标准8051启动文件代码到工程中,这个是必要的文件吗?
答:是的,单片机上电时候一般需要对堆栈、引脚等初始化,这些都需要依靠使用汇编编写的启动代码来执行。
十二、源文件底下的头文件是如何添加进去的呢?
答:在源文件中使用#include"xxx.h"就可以添加进去,不过在编译之前得让编译器知道这个头文件在哪儿,这就需要添加头文件路径,方式如图12.1所示:
注:使用编译器自带的头文件(即:#include<xxx.h>)无需执行此操作。
十三、sfr、sfr16、sbit 这3个关键字作用是什么?
答:sfr、sfr16、sbit 这3个关键字并不属于C99规范,只在Keil C51中可以使用。研究过8051存储结构的都知道,8051特殊功能寄存器和内部RAM区的0x80~0xff共用相同的地址(但是物理存储单元各自独立),这在执行读写时就需要通过寻址方式做出区分。根据规定,特殊功能寄存器读写使用直接寻址(部分可位寻址),内部RAM区的0x80~0xff只能使用间接寻址。
sfr、sfr16、sbit 这3个关键字就承担起特殊功能寄存器区变量的声明,sfr用于声明8位变量,sfr声明16位变量,sbit用于声明位变量。特殊功能寄存器中,地址为8的整数倍的变量支持位寻址(如:0x80、0x88),也就是说支持用sbit声明位变量,具体如图13.2所示。
十四、sbit 和bit有什么不同之处?
答:sbit是用于对支持位寻址的特殊功能寄存器变量进行声明的,bit则是用于定义位变量的,定义的位变量存储在内部通用RAM区(内部RAM地址址区(0x20~0x2f)支持位寻址)。如果还不理解,下面通过生成的汇编指令解答疑惑。
sbit 常用用法:
- C语言:sbit P0_0 = P0^0; P0_0=1;//P0已通过sfr声明,其地址为0x80
- 汇编指令:SETB P0_0(0x80.0)
sbit 常用用法:
- C语言:bit P0_0; P0_0=1;//为了演示效果,变量特意重名
- 汇编指令:SETB P0_0(0x20.0)
可以看出,bit定义的变量存储在低128字节内部RAM中(地址0x20.0,注意.0不可忽视),sbit声明的变量存储在SFRs区且地址为8的整数倍(如示例中的0x80)。
为了验证sbit只能在地址为8的整数倍的SFRs区声明,下面通过实际具体加以验证。通过图13.3可知,基地址确实不能任意给定。
十五、可以通过指针访问特殊功能寄存器区吗?
答:我试了一下,结果是不可以。不排除有其他办法,欢迎批评指正。
相关代码对比如下:
15.1使用指针变量访问时:
- C语言:unsigned char *P0=0x80;*P0=0x0C;
- 汇编指令:
- // 12: unsigned char *P0=0x80;
- C:0x0003 7B00 MOV R3,#0x00
- C:0x0005 7A00 MOV R2,#0x00
- C:0x0007 7980 MOV R1,#P0(0x80)
- C:0x0009 8B08 MOV 0x08,R3
- C:0x000B 8A09 MOV 0x09,R2
- C:0x000D 890A MOV 0x0A,R1
- // 13: *P0=0x0C;
- C:0x000F AB08 MOV R3,0x08
- C:0x0011 AA09 MOV R2,0x09
- C:0x0013 A90A MOV R1,0x0A
- C:0x0015 740C MOV A,#0x0C
- C:0x0017 120020 LCALL C?CSTPTR(C:0020)
-
- C?CSTPTR:
- C:0x0020 BB0106 CJNE R3,#0x01,C:0029
- C:0x0023 8982 MOV DPL(0x82),R1
- C:0x0025 8A83 MOV DPH(0x83),R2
- C:0x0027 F0 MOVX @DPTR,A
- C:0x0028 22 RET
- C:0x0029 5002 JNC C:002D
- C:0x002B F7 MOV @R1,A
- C:0x002C 22 RET
- C:0x002D BBFE01 CJNE R3,#0xFE,C:0031
- C:0x0030 F3 MOVX @R1,A
- C:0x0031 22 RET
15.2使用SFR访问时:
- C语言:sfr P0 = 0x80; P0 = 0x0c;
- 汇编指令:
- // 12: P0=0x0C;
- C:0x000F 75800C MOV P0(0x80),#0x0C
15.3内置ABSACC.H,使用绝对地址访问时:
- 头文件部分:
- #define CBYTE ((unsigned char volatile code *) 0)
- #define DBYTE ((unsigned char volatile data *) 0)
- #if !defined (__CX2__)
- #define PBYTE ((unsigned char volatile pdata *) 0)
- #endif
- #define XBYTE ((unsigned char volatile xdata *) 0)
-
- #define CWORD ((unsigned int volatile code *) 0)
- #define DWORD ((unsigned int volatile data *) 0)
- #if !defined (__CX2__)
- #define PWORD ((unsigned int volatile pdata *) 0)
- #endif
-
- C语言用户代码:
- DBYTE[0x80]=0x0c;
-
- 其实没有验证的必要,code指向代码FLASH区,data指向通用RAM区(0x00~0xff),
- xdata指向片内拓展通用RAM区(0x0000~0xffff),pdata指向片外拓展64K内存区,都不指向SFRs
十六、可以在多处使用sfr对特殊功能寄存器声明吗?
答:不可以,会提示重复定义( redefinition)。
十七、sbit对特殊功能寄存器声明时为何存在多种写法?
答:解释如下,常见的写法如下:
- 方式1:sbit P0_2 = P0^2; //前提:P0已通过sfr声明
- 方式2:sbit P0_2 = 0x80^2; //0x80就是特殊功能寄存器中支持位寻址的寄存器
- 方式3:sbit P0_2 = 0x82; //此0x82为直接寻址中的位地址,需要和直接寻址中的字节地址区分开
-
- 注:^ 叫异或,可以了解一下他的运算规则
十八、51单片机支持DEBUG(仿真)吗?
答:支持。keil C51支持虚拟仿真(simulator)和实物仿真,STC的IAP系列还能抛开仿真器进行片上仿真,实际方法见其他博主的《宏晶STC单片机片上仿真法》。
十九、Keil C51中相较于C99标准,多出哪些扩展关键词?
答:多出部分如下表所示:
_at_ | alien | bdata | bit | code | compac | data |
far | idata | interrupt | large | pdata | _priority_ | reentrant |
sbit | sfr | sfr16 | small | _task_ | using | xdata |
------参考资料《KeilC51基本关键字》
二十、Keil C51中可以观察程序的运行时间嘛?
答:可以,在仿真模式下观察Register一栏中sec的变化量(单位为秒)即可。
二十一、Keil C51中可以观察引脚电平的变化嘛?
答:可以,在仿真模式下,右击变量名称,将需要观察的变量添加到逻辑分析仪即可。
配置好要查看的变量名称、显示类型、数值范围、位于操作、左移位数,点击程序运行按钮即可观察到变量变化情况。
二十二、Keil C51中可以观察变量变化情况嘛?
答:可以,在仿真模式下,将需要查看的变量添加到watch窗口即可。
二十二、可以查看某个地址下面的具体值嘛?
答:可以,通过Memory窗口查看,根据查看的物理地址分区不同,需要输入的前缀也不同。
代码区(CODE) | 输入C:绝对地址(如:C:0X20) |
低128字节通用RAM区(DATA) | 输入D:绝对地址(如:D:0X20) |
高128字节通用RAM区(IDATA) | 输入I:绝对地址(如:I:0X80) |
内部扩展1024字节通用RAM区(XDATA) | 输入X:绝对地址(如:X:0X80) |
二十三、使用位段(位域)操作和使用为变量实现原理有何不同?
23.1位域实现位操作:
- C语言:
- #include <STC89C5xRC.H>
- #include<absacc.h>
-
- union BYTE
- {
- unsigned char byte;
- struct
- {
- unsigned char bit0:1;
- unsigned char bit1:1;
- unsigned char bit2:1;
- unsigned char bit3:1;
- unsigned char bit4:1;
- unsigned char bit5:1;
- unsigned char bit6:1;
- unsigned char bit7:1;
- }BIT;
- };
-
- int main()
- {
- union BYTE data value;
- value.BIT.bit0 = 1;
-
- return 0;
- }
- 汇编代码:
- 126: ?C_STARTUP: LJMP STARTUP1
- 127:
- 128: RSEG ?C_C51STARTUP
- 129:
- 130: STARTUP1:
- 131:
- 132: IF IDATALEN <> 0
- C:0x0000 020003 LJMP STARTUP1(C:0003)
- 133: MOV R0,#IDATALEN - 1
- C:0x0003 787F MOV R0,#0x7F
- 134: CLR A
- C:0x0005 E4 CLR A
- 135: IDATALOOP: MOV @R0,A
- C:0x0006 F6 MOV @R0,A
- 136: DJNZ R0,IDATALOOP
- C:0x0007 D8FD DJNZ R0,IDATALOOP(C:0006)
- 185: MOV SP,#?STACK-1
- 186:
- 187: ; This code is required if you use L51_BANK.A51 with Banking Mode 4
- 188: ;<h> Code Banking
- 189: ; <q> Select Bank 0 for L51_BANK.A51 Mode 4
- 190: #if 0
- 191: ; <i> Initialize bank mechanism to code bank 0 when using L51_BANK.A51 with Banking Mode 4.
- 192: EXTRN CODE (?B_SWITCH0)
- 193: CALL ?B_SWITCH0 ; init bank mechanism to code bank 0
- 194: #endif
- 195: ;</h>
- C:0x0009 758108 MOV SP(0x81),#0x08
- 196: LJMP ?C_START
- C:0x000C 02000F LJMP main(C:000F)
- 20: int main()
- 21: {
- 22: union BYTE data value;
- 23: value.BIT.bit0 = 1;
- 24:
- C:0x000F 430801 ORL 0x08,#0x01
- 25: return 0;
- C:0x0012 E4 CLR A
- C:0x0013 7E00 MOV R6,#0x00
- C:0x0015 7F00 MOV R7,#0x00
- 26: }
- C:0x0017 22 RET
23.2位变量实现位操作:
- C语言:
- #include <STC89C5xRC.H>
- #include<absacc.h>
-
- int main()
- {
- bit bit0 = 1;
- return 0;
- }
-
- 汇编指令:
- 126: ?C_STARTUP: LJMP STARTUP1
- 127:
- 128: RSEG ?C_C51STARTUP
- 129:
- 130: STARTUP1:
- 131:
- 132: IF IDATALEN <> 0
- C:0x0000 020003 LJMP STARTUP1(C:0003)
- 133: MOV R0,#IDATALEN - 1
- C:0x0003 787F MOV R0,#0x7F
- 134: CLR A
- C:0x0005 E4 CLR A
- 135: IDATALOOP: MOV @R0,A
- C:0x0006 F6 MOV @R0,A
- 136: DJNZ R0,IDATALOOP
- C:0x0007 D8FD DJNZ R0,IDATALOOP(C:0006)
- 185: MOV SP,#?STACK-1
- 186:
- 187: ; This code is required if you use L51_BANK.A51 with Banking Mode 4
- 188: ;<h> Code Banking
- 189: ; <q> Select Bank 0 for L51_BANK.A51 Mode 4
- 190: #if 0
- 191: ; <i> Initialize bank mechanism to code bank 0 when using L51_BANK.A51 with Banking Mode 4.
- 192: EXTRN CODE (?B_SWITCH0)
- 193: CALL ?B_SWITCH0 ; init bank mechanism to code bank 0
- 194: #endif
- 195: ;</h>
- C:0x0009 758120 MOV SP(0x81),#0x20
- 196: LJMP ?C_START
- C:0x000C 02000F LJMP main(C:000F)
- 20: int main()
- 21: {
-
- 24: bit bit0 = 1;
- C:0x000F D200 SETB 0x20.0
- 25: return 0;
- C:0x0011 E4 CLR A
- C:0x0012 7E00 MOV R6,#0x00
- C:0x0014 7F00 MOV R7,#0x00
- 26: }
- C:0x0016 22 RET
差异:
- 位域实现原理:ORL 0x08,#0x01
- 位操作实现原理:SETB 0x20.0
可以对比,使用位域定义的变量并未存放在支持位寻址地址区,通过ORL将立即数(0x01)与直接地址单元(0x08)进行或操作,指令代码占用3字节,需要耗费24个机器周期(12MHz晶振下需要24us时间)。使用位寻址定义的变量通过SETB直接对地址0x20.0进行置位,指令代码占用2字节,需要耗费12个机器周期(12MHz晶振下需要12us时间)。
由此可见,二者实现原理并不相同,使用硬件位寻址定义的变量操作起来更快,占用内存更小。位域操作需要先读取变量值进行逻辑操作,再重新存入地址,数据读写过程中受内外因素干扰,存在数据紊乱的可能。
二十四、51单片机有可供使用的RTOS嘛?
答:51单片机是支持RTOS的,但是他匮乏的硬件资源注定支持的RTOS非常轻量化。在Keil_C51\C51\RtxTiny2下有一个名称叫RTX51的RTOS,但是已经被封装成LIB库了,我们看不到具体的源代码。根据库的精简程度,又分为全功能版的RTX51-FULL和精简功能的RTX51-TINY。RTX51-FULL在RTX51-TINY的基础上支持了任务的优先级设定,相关工程配置可参考其他博主介绍。
24.1 经典12T单片机工程配置:硬件平台为STC89C52RC,软件为Keil C51
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。