宏晶 STC90C516RD+ 数据手册精读

8051 微处理器是指兼容Intel MCS-51体系架构的一系列单片机,全球有众多的半导体厂商都有基于这一体系架构的产品,例如:Atmel 的AT89C52、NXP 的P89V51、宏晶科技的STC89/90系列等。截至本篇文章成文之前,意法半导体推出的STM8系列单片机风头正劲,同为 8 位单片机产品,虽然 8051 在架构、功耗乃至价格上基本已无优势,但作为比较经典的嵌入式微处理器解决方案,依然具备着不错的学习价值。

本文介绍的STC90C516RD+属于5V单片机(工作电压为5.5V-3.3V),属于相对比较廉价和常见的国产单片机解决方案,片上拥有中央处理器CPU程序存储器Flash数据存储器RAM定时/计数器UART串口IO接口EEPROM看门狗等常用资源。

单片机最小系统

电源晶振复位是构成 STC90C516RD+ 单片机最小系统不可或缺的 3 个部分。

minimum

电源电路

STC90C516RD+ 单片机的工作电压为5.5-3.3V,其中 P0 口的灌电流(单片机引脚低电平时对外吸收的电流)最大为12mA,其它 I/O 口灌电流最大为6mA。而拉电流(单片机引脚高电平时对外输出的电流)则通常低于1mA,万用表实测开发板 LED 电路 P0 口实际输出仅为0.22mA。单片机的供电引脚分别位于40脚和20脚,其中40脚接+5V,通常称为VCC代表电源正极,20脚接GND代表电源负极。

power

单片机基本都采用 TTL 电平规范,STM32 单片机属于 3V 单片机,STC90C51 属于 5V 单片机,单片机电路出现 CMOS 器件的机率比较小。

晶振电路

晶体振荡器(crystal oscillator ['ɒsɪleɪtə(r)]),启动后会一直不停振荡,从而为单片机系统提供基准时钟信号(单片机内部以该时钟信号为基准进行工作)。STC90C516RD+的第 18 脚XTAL2和第 19 脚XTAL1是晶振引脚,电路中使用了12MHz的晶振(每秒振荡 1200 万次),外加两个33pF电容帮助晶振起振,并维持振荡信号稳定。

MCU

复位电路

下图的复位电路接到了单片机9脚的 RST 复位引脚上,单片机复位一般发生在下面 3 种情况:

  1. 上电复位:保证单片机每次都从一个固定的相同的状态开始工作。
  2. 手动复位:按下复位按键,让程序重新初始化重新运行。
  3. 程序自动复位:程序长时间失去响应,看门狗模块会自动重启复位单片机。
reset

针对于手动复位的情况,当上面的开发板电路处于稳态时,电容将起到隔离直流的作用,复位按键此时处于弹起状态,电路没有产生电压差,HRST 端的电位与 GND 相等,由于 STC90C516RD+ 单片机是高电平复位,低电平正常工作,因而此时单片机复位端口电压为低电平 0V 处于正常工作状态。

而对于上电复位的情况,上述电路在上电瞬间,电容 C5 将会逐步充电,电路的电压差将从电容刚开始充电时的 5V 逐步过渡至充满后的 0V。直至电容充满后电路上没有电流流动。由于 STC90C516RD+ 的上电复位要求 RST 引脚先保持一小段时间高电平然后再切换至低电平,因而在开发板电路上电的过程当中,单片机实质上也就完成了上电复位操作。

当复位按键按下时,也就是手动复位时,会经历两个过程:按键被按下之前单片机 RST 引脚为低电平,按下之后电路将会导通,电容 C5 将会瞬间完成放电,RST 此时将处于高电平复位状态,然后当按键松开弹起后,复位端口电压又将会处于低电平状态,此时实质上已经完成了一个上电复位的操作。按键接下的瞬间通常有数百毫秒,这个时间足够完成复位,但是伴随电容 C5 两端的 5V 电压被直接接通,将会形成一个较大的电流冲击,并在局部形成电磁干扰,为了抑制瞬间大电流产生的干扰,开发板在 HRST 后放置了一个 100 欧的电阻和型号为 8550 的 PNP 型三极管进行限流。

片上存储资源

STC90C516RD+ 系列单片机的程序存储器(Flash)和数据存储器(RAM)分别独立进行编址,而日常开发需要频繁使用的特殊功能寄存器 SFR则属于 RAM 内特殊的一块区域,下面一一就其功能进行解释。

程序存储器 Flash

Flash 闪存Flash Memory)主要包括 NORNADN 两种类型,可以在断电条件下存放用户的程序和数据,STC90C516RD+内部集成61K字节的 Flash 程序存储器,此外还具备访问64KB外部程序存储器的能力。当单片机复位之后,程序计数器 PC 初始值为 0000H,单片机将会从该位置运行代码。开发人员可以通过控制单片机的EA引脚来选择需要访问的是61K片内程序存储器(高电平),还是访问64KB的片外程序存储器(高电平)。如果访问的是片内程序存储器,就会从其 0000H 单元开始执行,当 PC 的值超出地址上限 3FFFH 时,单片机将自动跳转至片外程序存储器(如果存在)的起始地址4000H继续执行。

flash

中断服务程序的入口地址(中断向量)也位于程序存储器,每个中断都会在程序存储器当中拥有一个固定的入口地址,当中断发生并且冰屑得到响应之后,单片机就会自动跳转至相应的中断入口地址执行代码。外部中断 0 的中断服务程序入口地址位于0003H,定时器/计数器 0 的中断服务程序入口地址位于000BH,外部中断 1 的中断程序入口地址位于0013H,定时器/计数器 1 的中断入口地址是001BH等等。由于相邻的中断入口地址仅仅间隔 8 个字节,通常无法保存完整的中断服务程序,因此会在中断响应的地址区域存放一条指向真正存放中断服务程序地址空间的强制跳转指令。

早期单片机使用老式的 ROM 存储器(Read Only Memory)来保存程序数据,由于只能读不能写而逐步被市场淘汰。近年来可反复擦写十万次以上的 Flash 全面代替了 ROM 在嵌入式系统中的地位,用作存储 Bootloader 以及操作系统或程序代码。

数据存储器 RAM

RAM 随机存取存储器 Random Access Memory)用于存放程序执行的中间结果和过程数据,掉电后存储内容丢失,消费类电子产品上广泛使用的 DDR(Double Data Rate SDRAM)就属于 RAM 中的一种。STC90C516RD+内部集成了1280字节的 RAM,并在物理和逻辑上分为两个地址空间:256 字节的内部 RAM 和 1024 字节的内部扩展 RAM,此外还可以访问片外扩展的 64KB 外部扩展RAM(如果存在)。

ram

内部 RAM 一共 256 字节,分为三部分:低 128 字节 RAM(兼容传统 8051 架构,既可直接寻址也可间接寻址)、高 128 字节 RAM(扩展自 8052 架构,只能间接寻址)、特殊功能寄存器区只可直接寻址);高 128 字节 RAM 与特殊功能寄存器区地址空间虽然近似重叠,但是物理上是独立的,使用时通过不同寻址方式加以区分。

ram

低 128 字节 RAM 也称为通用 RAM 区,分为工作寄存器组区可位寻址区用户 RAM 区堆栈区

  • 工作寄存器组区地址从00H-1FH共 32 字节,分为 4 个寄存器组,每组包含 8 个 8 位工作寄存器,编号分别为 R0 ~ R7,由程序状态字寄存器 PSW 中的 RS1 和 RS0 组合选择当前使用的工作寄存器组,使用工作寄存器可以提高运算速度。
  • 可位寻址区地址20H-2FH占用16字节共16*8=128位,既可以按字节存取,也能够进行位操作(位地址指向的是一个位,字节地址指向的是一个字节单元,汇编程序中会使用不同的指令进行区分)。
  • 用户 RAM 区堆栈区位于30H-7FH,一个 8 位的堆栈指针 SP 指向堆栈区,单片机复位后堆栈指针 SP 为 07H, 指向工作寄存器组 0 当中的 R7;因此,用户初始化程序应对 SP 设置初值,一般设置为 80H 以后的单元。

特殊功能寄存器 SFR

SFR 特殊功能寄存器Special Function Register),对片内各功能模块进行管理、控制、监视的控制寄存器状态寄存器,特殊功能寄存器的操作主要通过汇编或 C 语言代码来进行。

程序状态寄存器 Program Status Word

程序状态寄存器 PSW 是微处理器核心部件运算器的组成部分,主要用来存放体现当前指令执行结果的各种状态信息(有无进位,有无溢出等)以及存放控制信息(如允许中断位,跟踪标志位等)。51 单片机的 PSW 是一个 8 位寄存器,用来存放指令执行后的一些的状态,通常由 CPU 进行控制,但是开发人员也可以手动改变各个状态位的值。

程序状态字寄存器 B7 B6 B5 B4 B3 B2 B1 B0
WPS CY AC F0 RS1 RS0 OV F1 P
释义 进位标志位 进位辅助标志位 用户标志位 0 工作寄存器组选择位 工作寄存器组选择位 溢出标志位 用户标志位 1 奇偶标志位

堆栈指针 Stack Pointer

堆栈指针 SP 是一个 8 位专用寄存器,用于标识堆栈顶部在内部 RAM 当中的位置,系统复位后堆栈指针初始化为 07H,使得堆栈实际从 08H 存储单元开始。考虑 08H-1FH 单元分别属于工作寄存器组 1-3,程序设计时最好将 SP 值修改为 80H 或更大的值。STC89C52RC+单片机的堆栈向上生长,即数据压入堆栈后,SP 当中的内容将会增大。

IO 管脚工作模式

STC90C516RD+ 单片机的每个 IO 口输出的驱动电流均可达到20mA,但是官方建议整个单片机 IO 的输出电流一共最大不宜超过120mA。STC90C516RD+拥有准双向/弱上拉强推挽/强上拉开漏输出仅输入/高阻态这 4 种工作模式,接下来的示意图将使用电压驱动的 MOS 管来说明其工作原理,由于 MOS 管特性与三极管类似,某些书籍也为会使用三极管符号代替。

准双向

STC90C516RD+ 的 P1、P2、P3 都是准双向 IO 口,如果内部输出高电平,那么经过一个反向器后将会被转换为低电平,此时 MOS 管不会导通,VCC 经过内部上拉电阻之后直接向外输出高电平。与之相反,如果内部输出低电平,那么经过反向器后转换为高电平,此时 MOS 管导通,VCC 经过内部上拉电阻之后直接与 GND 连接从而对外输出低电平。准双向模式与后面将要提到的开漏模式唯一区别在于:准双向模式内部拥有上拉电阻,而开漏模式则没有

开漏

STC90C516RD+ 的 P0 在上电复位后就属于开漏输出,即片内如果输出高电平,经过反向器后转换为低电平,此时 MOS 管不导通,由于没有内部上拉电阻,导致 VCC 直接与工作电压相同的外围电路相连,所以 IO 口不会输出任何信号。相反的,如果内部输出低电平,经过反向器后转换为高电平,此时 MOS 管导通,VCC 直接与 GND 相连并且对外输出低电平。概而言之,STC90C516RD+的 P0 脚在开漏输出模式下,只能输出低电平不能输出高电平,要输出高电平必须在外部电路增加一个上拉电阻。

强推挽

强推挽输出拥有比较强的电流驱动能力,当内部输出高电平的时候,由 MOS 管直接输出电流,由于不存在限流电阻,所以电流输出能力较大。同样的,如果内部输出低电平的时候,其反向通过的电流也可以较大。

高阻态

如果需要将 STC90C516RD+ 的 IO 口做为输入引脚,那么可以考虑将其设置为高阻态,高阻态引脚悬空的时候,万用表测量结果可能是高也可能是低,其状态完全取决于外部输入的电平信号,高阻态引脚对 GND 的等效电阻极大,因而被称为高阻。

IO 与上下拉电阻

STC90C516RD+ 管脚工作模式被设置为开漏输出的高电平或者高阻态时,会产生一个不确定的电平信号。此时上拉电阻会将这个不确定的信号通过一个电阻上拉至高电平,此时电阻本身也起到一个限流作用,上拉电阻通常串接在VCC一侧。相反的,下拉电阻就是将不确定信号下拉至低电平,通常被串接在GND一侧。连接至单片机 IO 管脚的上下拉电阻主要具有如下四类用处:

  1. 类似于开漏输出这样的OC门电路,如果需要要输出高电平,必须通过外部加上拉电阻才能正常工作。
  2. 增大普通 IO 管脚的电流驱动能力,标准 51 单片机的内部 IO 管脚上拉电阻一般在数十千欧,通过外部添加上拉电阻,可以形成与内部上拉电阻的并联,从而增大高电平时的电流输出能力。
  3. 诸如5V12V这样的常见电平转换电路中,上拉电阻起到的实质是限流电阻的作用。
  4. IO 管脚未使用处于悬空状态时,容易受到电磁干扰处于紊乱状态,虽然不会影响程序正常工作,但是会增加单片机功耗,此时通过添加一个对VCC的上拉电阻或者一个对GND的下拉电阻,可以有效的抵御电磁干扰。

电路设计时,上下拉电阻阻值的选取主要参考以下情况:

  1. 从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小。
  2. 从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。
  3. 在开漏输出时,过大的上拉电阻会导致信号上升沿变缓,从而影响信号的正确传递。

综合考量多种情况之后,STC90C516RD+ 单片机电路通常选取 1k 至 10k 欧姆的上下拉电阻,具体的阻值可以根据实际需求选取,并非一成不变的固定值。

复位

STC90C516RD+ 系列单片机支持外部 RST 引脚复位软件复位掉电/上电复位看门狗复位四种复位方式。

外部 RST 引脚复位

向单片机的 RST 引脚施加高电平至少 24 个时钟加 10us 后,就可以使单片机进行复位状态。将 RST 引脚拉回低电平后,单片机结束复位状态并从用户程序区的 0000H 处开始正常工作。

软件复位

嵌入式应用程序运行过程当中,有时会需要使单片机系统软复位(热启动),传统 8051 架构并没有提供硬件上的支持,用户必须通过复杂的代码模拟这一行为。STC 在传统 8051 架构的基础上,增加了特殊功能寄存器ISP_CONTR支持,通过控制它的 SWBS 和 SWRST 两位就可以实现软件复位(单片机所有特殊功能寄存器都会复位至初始值,I/O 口状态也会被初始化)。

寄存器名称 地址 B7 B6 B5 B4 B3 B2 B1 B0
ISP_CONTR E7H ISPEN SWBS SWRST - - WT2 WT1 WT0
  • ISPENISP/IAP 功能允许位,禁止(ISPEN=0)或允许(ISPEN=1)ISP/IAP 读写并擦除程序存储器 Flash 或者 EEPROM。
  • SWBS:选择从用户应用程序区启动(0),还是从 ISP 程序区启动(1)。
  • SWRSTSWRST=0不操作,SWRST=1产生软件系统复位,由硬件自动清零。

掉电/上电复位

当单片机电源电压 VCC 低于上电/掉电复位电路的门槛检测电压时就会发生复位,VCC 恢复正常电压然并延迟 32768 个时钟之后,上电/掉电复位结束。进入掉电模式之后,上电/掉电复位功能将会暂时关闭。

看门狗复位

高可靠性嵌入式系统当中,为了防止微控制器由于代码错误导致程序跑飞,通常需要引入看门狗(WDT,Watch Dog Timer)让硬件复位。看门狗本质是一个计数器,如果微处理器不在规定时间内按访问看门狗,看门狗就会认为当前处于异常状态而进行复位重新执行用户代码。STC89C52RC+系列单片机内置了支持看门狗功能的特殊功能寄存器WDT_CONTR

看门狗控制寄存器 地址 B7 B6 B5 B4 B3 B2 B1 B0
WDT_CONTR E1H - - EN_WDT CLR_WDT IDLE_WDT PS2 PS1 PS0
  • EN_WDT看门狗允许位,设置为1时启动看门狗。
  • CLR_WDT看门狗清零位,设置为1时,看门狗会重新计数,然后由硬件自动清零。
  • IDLE_WDT看门狗空闲模式位,设置为1时, 看门狗定时器在空闲模式计数,设置为0时, 看门狗定时器在空闲模式时不计数。
  • PS2/PS1/PS0:通过这三个位的组合来设置看门狗定时器的预分频值。

省电模式

STC90C516RD+ 可以运行空闲模式(Idle Mode)掉电模式(Power Down)两种省电模式以降低功耗,正常工作模式下单片机功耗为4mA-7mA,掉电模式典型功耗小于0.1uA,空闲模式下典型功耗是2mA,STC 官方文档不建议使用空闲模式。单片机省电模式的选择由电源控制寄存器 PCON 进行控制。

电源控制寄存器 地址 B7 B6 B5 B4 B3 B2 B1 B0
PCON 87H SMOD SMOD0 - POF GF1 GF0 PD IDL
  • SMOD, SMOD0:串口控制相关位,与电源控制无关。
  • POF上电复位标志位,单片机停电后,上电复位标志位置1,此时可以通过软件清零。
  • GF1,GF0通用工作标志位,用户可以酌情使用。
  • PD:置1时进入掉电模式,可由外部中断低电平触发或下降沿触发唤醒;进入掉电模式后,内部时钟停振,处理器、定时器、串口等部件停止工作,所有 IO 口、特殊功能寄存器维持进入掉电模式前的状态不变。由于掉电模式可由外部中断唤醒,所以仅外部中断会在掉电后继续工作,这里可以将单片机从掉电模式唤醒的管脚有INT0/P3.2INT1/P3.3INT2/P4.3INT3/P4.2;此外,外部复位也可以将单片机从掉电模式唤醒,复位唤醒后的单片机将从用户程序的 0000H 位置开始执行。
  • IDL:置1后进入空闲模式,除处理器不工作以外,其余片内功能继续工作,并可由任意中断唤醒。

指令寻址方式

寻址方式是嵌入式微控制器指令集当中必不可少的部分,寻址方式规定了处理器指令所操作数据的来源和目的地,STC90C516RD+单片机的寻址方式可概括为如下 7 种:

立即寻址

在指令操作数中直接给出参与运算的操作数(即立即数),该寻址方式会将#号放在立即数前,从而标识当前寻址方式为立即寻址

1
MOV A, #70H   ; 将立即数70H传递给累加器A。

直接寻址

操作数是参加运算操作数的地址,该方式只能用来表示特殊功能寄存器、位地址空间、内部数据寄存器,并且前两者只能使用直接寻址方式进行访问。

1
ANL 70H, #48H   ; 表示位于内部数据存储器RAM当中的直接地址为70H单元中的值与立即数48H与运算,并将结果存放至70H单元。

间接寻址

间接寻址建立在直接寻址基础之上,如前所述,直接寻址得到的数据是一个地址,间接寻址会通过该地址再找到最终数据,即进行两次寻址,第 1 次得到地址,第 2 次获取目标数据。在 8051 架构单片机当中,为了区别寄存器寻址寄存器间接寻址,会在寄存器名称前添加@符号表示寄存器间接寻址。

1
MOV A, @R1   ; 假如R1中的数据为内部数据存储单元的地址40H,那么这段代码会将该地址单元里存储的数据55H传送至累加器。

寄存器寻址

操作数全部位于寄存器当中,根据指令操作码低三位的值以及程序状态寄存器 PSW 当中 RS1 和 RS0 的状态,选中指定寄存器之后进行相应指令操作。由于操作数都存储在寄存器,减少了指令执行过程中读写存储器单元的次数,所以使用该寻址方式的指令执行速度较快,使用汇编语言开发程序时,可以尽量考虑使用这种寻址方式。

1
MOV AX, 1234h   ; 将源操作数1234h中的数据传递至寄存器AX中。

相对寻址

使用程序计数器 PC 的当前值为基地址,并以指令中的地址标号作为偏移量,将两者相加后形成新的 PC 值。相对寻址主要用于修改 PC 值,常用于实现程序的分支转移。

1
SJMP 80H   ; 以PC当前值为基地址,加上偏移量08H所得结果,即为指令转移的目的地址。

变址寻址

以指定寄存器内容为基地址,然后加上变址寄存器的内容作为修改量,最后形成真正的操作数地址。这种寻址方式用于频繁修改地址时,无需修改指令,只需修改变址值既可。由于变址寻址只能操作程序存储器中的数据,所以常用于访问程序存储存器当中的数据表格。由于程序存储器只读,因而变址寻址只有读而无写操作,使用时以MOVC作为指令符号。

1
MOVC A, @A+DPTR   ; 累加器A为偏移量寄存器,其内容与地址寄存器DPTR中内容相加,结果即为操作数的地址,取出该地址单元的内容送入累加器A。

位寻址

用于对内部数据存储器 RAM 的某些区域、特殊功能寄存器进行位操作,位寻址其实是一种直接寻址方式,不过其操作地址是位地址,使用时主要以操作码加以区分。

1
MOV C, 20H   ;将立即数20H传递给进位位C

Keil uVision 5

2013 年 10 月 ARM 公司发布最新集成开发环境 RealView MDK 开发工具中集成了最新版本的 Keil uVision 5,让编译器、软件模拟与性能分析等调试工具与 ARM 处理器完美匹配。

logo

界面布局

Keil µVision 的图形界面提供了用于选择命令的菜单以及带有命令按钮的工具栏,位于窗口底部的状态栏用于显示当前执行命令的信息和消息。编辑器当中的窗口可以重新定位并停靠在另一个物理屏幕,窗口布局会为每个项目自动保存,并在下次使用项目时恢复,如果需要也可以使用菜单里的Reset View to Defaults恢复默认布局。Keil 拥有创建应用程序的构建模式和用于分析应用程序的调试模式两种操作方式,它们各自提供了额外的窗口和对话框,下面的截图展示了构建模式当中的一些主要窗口:

keil-build

Keil 上对当前项目设置晶振频率,并在目标选项的 Debug 选项卡下勾选Use Simulator以后,就可以通过工具栏上的【Debug】按钮进入调试模式。

keil-debug

减少代码长度

Keil C 软件中选择目标选项的【C51】选项卡,可以将编译后生成的 HEX 文件尺寸最大减少 10K 左右,可以有效降低占用程序存储器 Flash 的空间。

keil-performance

代码格式化

Keil 本身并没有提供代码格式化功能,当代码量较大的时候,维护代码格式会给开发人员带来额外的负担,因此接下来将会基于开源格式化工具Astyle,为 Keil 添加上自定义的格式化命令以及对应的快捷键。

keil-format-1

输出日志

Keil uVision 编译二进制文件之后,会输出如下的日志信息:

  • data:可寻址片内 RAM 占用空间。
  • xdata:可寻址片外 RAM 占用空间。
  • code:编译后的.hex文件烧写至单片机后占用的 Flash 存储空间。
1
2
3
4
5
6
7
8
Rebuild target 'hank'
compiling main.c...
assembling STARTUP.A51...
linking...
Program Size: data=9.0 xdata=0 code=29
creating hex file from ".\Objects\stc"...
".\Objects\stc" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:00

其中,data + xdata是占用的数据存储器 RAM 的大小,相当于运行时占用的内存;code则是占用程序存储器 Flash 的大小,即程序本身烧写到单片机后所占用的空间。

8051C 语言

传统使用汇编语言进行单片机开发,编译后的机器语言虽然文件体积较小,但是代码可读性较差。8051C 代码虽然编译后体积较大,但是可读性更优秀,更加适合工程化开发。Keil uVision 5提供的 IDE 包含了 8051 开发环境以及 C51 交叉编译器,可以在 Windows 操作系统上交叉编译出可供 STC 单片机运行的程序。

数据类型

8051C 兼容传统的 C 语言数据类型,并且额外提供了一些单片机开发所需的特殊数据类型。

数据类型 字节 取值范围
bit 1 0 ‖ 1
signed char 8 1 -128 ➠ +127
unsigned char 8 1 0 ➠ 255
enum 16 2 -32768 ➠ +32767
signed short 16 2 -32768 ➠ +32767
unsigned short 16 2 0 ➠ 65535
signed int 16 2 -32768 ➠ +32767
unsigned int 16 2 0 ➠ 65535
signed long 32 4 -2,147,483,648 ➠ +2,147,483,647
unsigned long 32 4 0 ➠ 4,294,967,295
float 32 4 ±1.175494E-38 ➠ ±3.402823E+38
sbit 1 0 ‖ 1
sfr 8 1 0 ➠ 255
sfr16 16 2 0 ➠ 65535

bit关键字用来声明一个由01组成的可供 STC 单片机进行位寻址(即处于20H~2FH范围的 byte 位或者处于00H~7FH范围的 bit 位)的变量。

1
bit flag = 0;  // 声明一个bit变量,并将其值赋为0

sbit关键字类似于bit类型,用来指定单片机特殊功能寄存器(BIT Register)当中一个位地址(bit address)的值。

1
sbit P0 = PSW^0; // 声明sbit变量,并将其指定到PWS特殊功能寄存器上第0位的位置

sfr关键字用于声明特殊功能寄存器(BYTE Register)当中的一个字节地址(byte address)。

1
sfr PSW = 0xD0;  // 并将其引用至位地址(D0)H

sfr16关键字用来声明一个特殊功能寄存器的16 位字节地址,而 sfr 数据类型声明的只是一个8 位字节地址。

1
sfr16 DPTR = 0x82;  // 声明单片机内部DPL寄存器的地址

8051 体系结构的单片机包含有独立的标志位、状态位、控制位等可位寻址的特殊功能寄存器,这些特殊功能寄存器的地址都已经被预先声明到 Keil 编译器所提供的<reg51.h>头文件当中,直接使用头文件可以简化代码并提升可读性,该头文件当中声明的内容请参考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef __REG51_H__
#define __REG51_H__

/* 字节寄存器 */
sfr P0 = 0x80; sfr PSW = 0xD0; sfr DPL = 0x82; sfr TMOD = 0x89; sfr TH1 = 0x8D; sfr SBUF = 0x99;
sfr P1 = 0x90; sfr ACC = 0xE0; sfr DPH = 0x83; sfr TL0 = 0x8A; sfr IE = 0xA8;
sfr P2 = 0xA0; sfr B = 0xF0; sfr PCON = 0x87; sfr TL1 = 0x8B; sfr IP = 0xB8;
sfr P3 = 0xB0; sfr SP = 0x81; sfr TCON = 0x88; sfr TH0 = 0x8C; sfr SCON = 0x98;

/* 位寄存器 */
/* PSW */ /* TCON */ /* IE */ /* IP */ /* P3 */ /* SCON */
sbit CY = 0xD7; sbit TF1 = 0x8F; sbit EA = 0xAF; sbit PS = 0xBC; sbit RD = 0xB7; sbit SM0 = 0x9F;
sbit AC = 0xD6; sbit TR1 = 0x8E; sbit ES = 0xAC; sbit PT1 = 0xBB; sbit WR = 0xB6; sbit SM1 = 0x9E;
sbit F0 = 0xD5; sbit TF0 = 0x8D; sbit ET1 = 0xAB; sbit PX1 = 0xBA; sbit T1 = 0xB5; sbit SM2 = 0x9D;
sbit RS1 = 0xD4; sbit TR0 = 0x8C; sbit EX1 = 0xAA; sbit PT0 = 0xB9; sbit T0 = 0xB4; sbit REN = 0x9C;
sbit RS0 = 0xD3; sbit IE1 = 0x8B; sbit ET0 = 0xA9; sbit PX0 = 0xB8; sbit INT1 = 0xB3; sbit TB8 = 0x9B;
sbit OV = 0xD2; sbit IT1 = 0x8A; sbit EX0 = 0xA8; sbit INT0 = 0xB2; sbit RB8 = 0x9A;
sbit P = 0xD0; sbit IE0 = 0x89; sbit TXD = 0xB1; sbit TI = 0x99;
sbit IT0 = 0x88; sbit RXD = 0xB0; sbit RI = 0x98;

#endif

存储类型与存储模型

8051 单片机包含了多种内存空间,包含内外部的代码或数据存储器。因此,声明一个变量的时候,需要搞清楚这些变量归属于哪一类存储单元。下面的表格

存储类型 描述
code 代码存储器(64K 字节
data 可直接寻址的内部数据存储器(128 字节
idata 可直接寻址的内部数据存储器(256 字节
bdata 位寻址数据存储器(16 字节
xdata 外部数据存储器(64K 字节
pdata 被分页的外部数据存储器(256 字节

可以使用上面表格中提供的code关键字,去声明一个放置在代码存储区的变量。

1
char code message[] = "An error occurred!";  // 声明一个放置在代码存储区的字符数组

如果需要声明一个位于数据存储区的变量,则可以使用上表中的另外的 5 个关键字。

1
2
3
signed int data name;
bit bdata name;
unsigned int xdata name;

如果声明变量的时候忘记指定存储类型,或是希望所有声明的变量都被指定为一个默认存储类型(Memory Model),那么可以通过 C 语言的#Small指令或者 Keil 的target编译选项来指定存储模型,具体模型及描述请参见下面列表:

存储模型 描述
Small 变量默认指定到内部数据存储单元(data
Compact 变量默认指定到扩展数据存储单元的前 256 个字节,也就是存储器分页的第 1 页(pdata
Large 变量默认指定到扩展数据存储器(xdata
memory-model

实际开发环境下,通常会使用Small模型,生成的代码最快体积最小,仅在不适用的场景才会选择CompactLarge模型。

数组

数组可以用来存放相同数据类型的一组数据,例如下表展现了 10 进制数在 ASCII 当中的 16 进制编码。

二进制数 十六进制 ASCII 编码
0 30H
1 31H
2 32H
3 33H
4 34H
5 35H
6 36H
7 37H
8 38H
9 39H

在 8051C 程序当中,上面表格内的这些数据就可以存储为一个数组:

1
int table[10] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};

结构体

结构体用来存放一组不同数据类型的数据,可以使用struct关键字进行声明。

1
2
3
4
5
6
7
8
9
10
11
12
// 声明person结构体
struct person {
char name[4]; // C语言没有专门的字符串类型,需要以字符数组来代替
int age;
long birthday;
};

// 对person结构体进行赋值
struct person author = {"Hank", 26, 1985};

// 打印person结构体的成员
printf(author.name);

指针

8051 程序在编译阶段,寄存器可能需要存放一些数据的确切内存地址,当对这些寄存器进行操作的时候,就需要使用到指针。C 语言当中,指针pointer)是指用来存储数据的一种数据类型,而指针变量pointer variable)则是已经存放了数据地址的变量。

1
2
3
4
5
6
7
8
int number = 2018;
int *numberPointer = &number; // 指针所指向数据的数据类型 *指针名称

printf("%d\n", number); // 2018
printf("%d\n", &number); // 6422316
printf("%d\n", numberPointer); // 6422316
printf("%d\n", &numberPointer); // 6422312
printf("%d\n", *numberPointer); // 2018

值得注意的是,指针变量numberPointer当中存放的是number变量的内存地址,而非变量本身。

指针的存储类型

由于指针本身也属于一种数据类型,那么定义变量的时候也可以声明其存储类型,例如上面numberPointer的示例可以修改为下面这样:

1
int * xdata numberPointer = &number;  // 符号*后的xdata声明指定该变量会被存放至外部数据存储器

根据指针指向数据存储类型的不同,又可以将指针分为类型化指针非类型化指针

类型化指针

显式指定了指针所指向数据的存储类型的指针称为类型化指针,指针符号*之前的的存储类型说明符data用于指定指针所指向数据的存储器类型,而*之后的存储类型说明符xdata用于指定指针本身的存储类型。

1
int data * xdata numberPointer = &number;  // 指针变量`numberPointer`本身位于外部数据内存`xdata`,但是指向的数据存储在内部数据内存`data`当中

类型化指针占用的空间大小依赖于数据存储的类型,通常在12个字节。

非类型化指针

非类型化指针是指未显式声明指针所指向数据存储类型的指针,通常由3 个字节组成。由于其数据存储类型只有在程序运行时才能确切知道,所以运行速度相对类型化指针略慢。

1
int * xdata numberPointer = &number; // 没有显式声明指针所指向数据的存储类型,因此这是一个非类型化的指针

非类型化指针的第1个字节用于指定其数据存储类型(参见下表),第23个字节分别是指针所指向字节地址的高、低位。

数据存储类型
1 idata
2 xdata
3 pdata
4 databdata
5 code

函数

8051 C 语言当中函数的声明基本与标准 C 基本相同,但是也存在一些明显的区别:

1
2
3
返回类型 函数名称( 参数列表 ) memory/reentrant/interrupt/using {
return 返回值;
}

8051 C 语言在函数名称后面增加了如下四个可选的关键字声明:

  • memory:指定一个显式的存储模型(smallcompactlarge),缺省时默认值为small
  • reentrant:表示函数是否可重入(递 归),缺省时默认值为不可重入。
  • interrupt:声明该函数为一个中断服务程序,缺省时默认值为。
  • using:指定当前使用的寄存器区域(register bank),缺省时默认值为。

8051 C 函数的参数和变量都保存在固定的地址,为了确保其值不被递归操作覆盖,所以默认不可进行递归调用。声明reentrant关键字后,函数不但可以递归调用,并且可被多个进程同时调用。

下面的函数当中,省略了memoryreentrantinterruptusing关键字的声明,此时函数会使用这些关键字的默认值。

1
2
3
4
// 存储模型为small,不可重入与递归,非中断函数,存放在内部数据存储区,默认寄存器区域为0。
int sum(int a, int b) {
return a + b;
}

参数传递

8051 C 语言当中,函数参数都是通过寄存器内存进行传递的,其中通过寄存器传递参数更加快速,并且是默认的执行方式,下面的列表详细描述了参数传递相关的寄存器及其用途:

参数个数 字符型 char /1 字节指针 整型 int /2 字符指针 长整型 long / 浮点型 float 通用指针
1 R7 R6 & R7 R4-R7 R1-R3
2 R5 R4 & R5 R4-R7 -
3 R3 R2 & R3 - -

如果需要自定义具体的参数传递方式,那么可以通过REGPARMs控制指令指定所有参数都通过寄存器传递,或者使用NOREGPARMs控制指令指定所有参数都通过内存进行传递。由于 51 系列单片机只拥有8个寄存器,因此某些情况下可能没有足够的寄存器来传递参数,发生这种情况时,就可以将剩余的参数通过固定的内存地址传递。

返回值

8051 C 的函数返回值必须通过寄存器进行返回,下面的列表展示了不同类型函数返回值所使用到的寄存器:

返回值 寄存器 描述
bit 进位标记(C
字符型 char / 无符号字符型 unsigned char / 1 字节指针 R7
整型 int / 无符号整型 unsigned int/ 2 字节指针 R6 & R7 最高位在 R6,最低位在 R7
长整型 long / 无符号长整型 unsigned long R4 ~ R7 最高位在 R4,最低位在 R7
浮点型 float R4 ~ R7 32 位 IEEE 格式
通用指针 R1 ~ R3 存储类型在 R3,最高位在 R2,最低位在 R1

中断机制

中断是单片机的一种运行机制,当单片机正在处理某个任务的时候,发生了优先级更高的事件要求暂停当前任务,转而去处理这个高优先级任务,处理完成后再回到上次发生中断的位置继续处理之前任务,这样的过程被称为中断,向单片机请求中断的来源则被称为中断源

单片机的中断机制通常会拥有多个中断源,当这些中断源同时请求中断时,要求单片机处理中断任务的时候,会根据中断源的优先级,优先去处理高优先级的任务。当单片机正在处理一个中断任务及其对应中断服务程序的时候,发生了一个优先级比当前中断更高的事件,则单片机会转而处理该优先级更高的任务,完成后再返回继续处理之前的低优先级任务,这个过程被称为中断嵌套

能够执行这种嵌套中断任务的单片机系统称为多级中断系统,否则就认为是属于单级中断系统

STC90C516RD+ 系列单片机提供了 4 个中断优先级和 8 个中断请求源:外部中断 0(INT0)定时器 0 中断外部中断(INT1)定时器 1 中断定时器 2 中断串口(UART)中断外部中断 2(INT2)外部中断 3(INT3)。如果发生中断的中断源优先级相同,那么就会根据单片机的查询次序来决定首先响应哪个中断,这些中断源的的查询次序如下:

中断源 中断向量地址 中断查询次序号 中断优先级设置 优先级 0 ← 低 优先级 1 优先级 2 优先级 3 → 高 中断请求标志位 中断允许控制位
外部中断 0(INT0) 0003H 0 ↑ 高优先级 PX0H, PX0 0, 0 0, 1 1, 0 1, 1 IE0 EX0/EA
定时器 0 中断 000BH 1 PT0H, PT0 0, 0 0, 1 1, 0 1, 1 TF0 ET0/EA
外部中断 1(INT1) 0013H 2 PX1H, PX1 0, 0 0, 1 1, 0 1, 1 IE1 EX1/EA
定时器 1 中断 001BH 3 PT1H, PT1 0, 0 0, 1 1, 0 1, 1 TF1 ET1/EA
串口中断(UART) 0023H 4 PSH, PS 0, 0 0, 1 1, 0 1, 1 RI+TI
定时器 2 中断 002BH 5 PT2H, PT2 0, 0 0, 1 1, 0 1, 1 TF2+EXF2 ET2/EA
外部中断 2(INT2) 0033H 6 PX2H, PX2 0, 0 0, 1 1, 0 1, 1 IE2 EX2/EA
外部中断 3(INT3) 003BH 7 ↓ 低优先级 PX3H, PX3 0, 0 0, 1 1, 0 1, 1 IE3 EX3/EA

使用 8051C 语言进行开发的时候,上面表格中的中断查询次序号码就是中断函数声明当中的中断号:

1
2
3
4
5
6
7
8
void event0() interrupt 0;
void event1() interrupt 1;
void event2() interrupt 2;
void event3() interrupt 3;
void event4() interrupt 4;
void event5() interrupt 5;
void event6() interrupt 6;
void event7() interrupt 7;

中断系统结构

STC90C516RD+ 系列单片机的中断系统结构图如下:

上图展示了 STC90C516RD+ 中断系统的结构与触发流程,为了更加清晰的展示中断产生的条件,进一步将 8 种中断类型的触发条件总结为下表:

中断源 触发条件
外部中断 0 (INT0) IT0/TCON.0 = 1(下降沿触发)、IT1/TCON.0 = 0(低电平触发)。
外部中断 1 (INT1) IT1/TCON.2 = 1(下降沿触发)、IT1/TCON.2 = 0(低电平触发)。
外部中断 2 (INT2) IT2/XICON.0 = 1(下降沿触发)、IT2/XICON.0 = 0(低电平触发)。
外部中断 3 (INT3) IT3/XICON.4 = 1(下降沿触发)、IT3/XICON.4 = 0(低电平触发)。
定时器 0 中断 中断请求标志位是 TF0,当定时寄存器 TH0/TL0 发生溢出时,溢出标志位 TF0 会被置为1,引发中断并执行相应中断程序后会重新将 TF0 置为0
定时器 1 中断 中断请求标志位是 TF1,当定时寄存器 TH1/TL1 发生溢出时,溢出标志位 TF1 会被置为1,引发中断并执行相应中断程序后会重新将 TF1 置为0
定时器 2 中断 中断请求标志位是 TF2 和 EXF2;当定时寄存器 TH2/TL2 发生溢出时,溢出标志位 TF2 会被置为1,引发中断并执行相应中断程序后会重新将 TF2 置为0;当 EXEN2 被置为1并且 T2EX 的负跳变发生捕获或重装时,EXF2 会进行置位并引发中断。
UART 当 RI(串口接收中断请求标志位)和 TI(串口发送中断请求标志位)任何一位被置为1,就会触发串口中断。

数字电路中,数字电平由高电平1变为低电平0的一瞬间称为下降沿,逻辑电平由高向低变化时所产生的触发行为就被称为下降沿触发,即检测到的电位信号由高向低下降时就会产生的触发。相反的,由高电平0变为低电平1的瞬间称为上升沿上升沿触发就是指电位信号由低向高上升时产生的触发。

中断涉及的寄存器

下面的表格,列出了 STC90C516RD+ 单片机中断机制相关的全部寄存器,然后会逐一进行介绍:

符号 描述 地址 最高有效位 ← 位地址及符号 → 最低有效位 复位值
IE(Interrupt Enable) 中断允许寄存器 A8H EA - ET2 ES ET1 EX1 ET0 EX0 0x00 0000B
XICON(Auxiliary Interupt Control) 辅助中断控制寄存器 C0H PX3 EX3 IE3 IT3 PX2 EX2 IE2 IT2 0000 0000B
IP(Interrupt Priority Low) 中断优先级控制寄存器(低) B8H - - PT2 PS PT1 PX1 PT0 PX0 xx00 0000B
IPH(Interrupt Priority High) 中断优先级控制寄存器(高) B7H PX3H PX2H PT2H PSH PT1H PX1H PT0H PX0H 0000,0000B
TCON(Timer/Counter 0 & 1 Control) 定时器/计数器 0 和 1 控制寄存器 88H TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0 0000 0000B
T2CON(Timer/Counter 2 Control) 定时器/计数器 2 控制寄存器 C8H TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RL2 0000 0000B
SCON(Serial Control) 串口控制寄存器 98H SM0/FE SM1 SM2 REN TB8 RB8 TI RI 0000 0000B

中断允许相关寄存器 IE 和 XICON

IE 中断允许寄存器

指定当前哪个中断源被允许使用,可位寻址。

中断允许寄存器 B7 B6 B5 B4 B3 B2 B1 B0
IE EA - ET2 ES ET1 EX1 ET0 EX0
  1. EA总中断允许控制位EA=1开放中断,EA=0屏蔽所有中断。
  2. ET2定时/计数器 T2 溢出中断允许控制位ET2=1允许中断,ET2=0禁止中断。
  3. ES串行口中断允许控制位ES=1允许串口中断,ES=0禁止串口中断。
  4. ET1定时/计数器 T1 溢出中断允许控制位ET1=1允许中断,ET1=0禁止中断。
  5. EX1外部中断 1 中断允许控制位EX1=1允许中断,EX1=0禁止中断。
  6. ET0定时/计数器 T0 溢出中断允许控制位ET0=1允许中断,ET0=0禁止中断。
  7. EX0外部中断 0 中断允许控制位EX0=1允许中断,EX0=0禁止中断。

XICON 辅助中断控制寄存器

中断源使能的一些辅助控制,可位寻址。

辅助中断控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
XICON PX3 EX3 IE3 IT3 PX2 EX2 IE2 IT2
  1. PX3外部中断 3 中断优先级控制,与 IP 中断优先级控制器的 PX3H 位一起决定外部中断 3 的优先级。
  2. EX3外部中断 3 中断允许控制位EX3=1允许中断,EX3=0禁止中断。
  3. IE3外部中断 3 中断请求标志位,中断条件成立以后IE3=1,可由硬件自动清零。
  4. IT3外部中断 3 触发方式控制位,当IT3=1时设置外部中断 3 为下降沿触发,当IT3=0时则为低电平触发。
  5. PX2外部中断 2 中断优先级控制,与 IP 中断优先级控制器的 PX2H 位一起决定外部中断 2 的优先级。
  6. EX2外部中断 2 中断允许控制位EX2=1允许中断,EX2=0禁止中断。
  7. IE2外部中断 2 中断请求标志位,中断条件成立以后IE2=1,可由硬件自动清零。
  8. IT2外部中断 2 触发方式控制位,当IT2=1时设置外部中断 2 为下降沿触发,当IT2=0时则为低电平触发。

STC90C516RD+ 系列单片机复位之后IEXICON将会被清0

中断优先级涉及寄存器 IP、IPH、XICON

IP 中断优先级控制寄存器(低)

标准 8051 架构单片机通过设置IP可以拥有高、低2个中断优先级,可以实现两级中断嵌套,可位寻址。

中断优先级控制寄存器(低) B7 B6 B5 B4 B3 B2 B1 B0
IP 保留 保留 PT2 PS PT1 PX1 PT0 PX0

IPH 中断优先级控制寄存器(高)

STC90C516RD+ 单片机新增的IPH特殊功能寄存器可以将优先级设置为4个,不可位寻址。

中断优先级控制寄存器(高) B7 B6 B5 B4 B3 B2 B1 B0
IPH PX3H PX2H PT2H PSH PT1H PX1H PT0H PX0H

XICON 中断辅助控制寄存器

辅助中断的控制寄存器,可位寻址。

XICON 中断辅助控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
XICON PX3 EX3 IE3 IT3 PX2 EX2 IE2 IT2

中断优先级配置

  • PX0H、PX0外部中断 0 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PX1H、PX1外部中断 1 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PX2H、PX2外部中断 2 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PX3H、PX3外部中断 3 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PT1H、PT1定时器 1 中断优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PT0H、PT0定时器 0 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PT2H、PT2定时器 2 优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。
  • PSH、PS串口中断优先级控制0, 0优先级零,0, 1优先级一,1, 0优先级二,1, 1优先级三)。

中断优先级控制寄存器 IP 和 IPH 的各个位都由可以通过代码设置为1或者0,但是 IP 可以进行位操作,而 IPH 只能使用字节操作指令进行更新。STC89C52RC+单片机复位后 IP 和 IPH 均为 00H,各中断源均为低优先级中断。

SCON 串口控制寄存器

SCON 寄存器与中断有关的只有 TI 和 RI 两位,其它位与中断机制无关,可位寻址。

串口控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
SCON SM0/FE SM1 SM2 REN TB8 RB8 TI RI
  • RI串口接收中断标志位,若串口允许接收数据并且以方式 0 工作,则每当接收到第 8 位数据时被置为 1;如果以方式 1、2、3 工作并且 SM2=0 时,则每当接收到停止位中间时 RI 置为 1;当以方式 2、3 工作并且SM2=1时,则仅当接收到的第 9 位数据 RB8 等于1以后,同时还要在接收到停止位的中间时将 RI 置1RI=1表示串行口正在向单片机申请中断,然后必须在中断服务程序当中手动清零。
  • TI串口发送中断标志位,串行口以方式 0 发送时,每当发送完 8 位数据后由硬件置1;若以方式 1、2、3 发送时,则会在开始发送停止位时置 1TI=1表示串行口正在向单片机申请中断,但需要注意单片机响应中断请求并转向中断服务程序的执行时并不会将 TI 清零,必须由用户手动在中断服务程序当中清零。

关于 SCON 的进一步讲解将在后续串行口通信章节进行。

TCON/T2CON 定时器/计数器控制寄存器

定时器/计数器 T0、T1 是标准 8051 架构的配置,而定时器/计数器 T2 则是 8052 架构单片机所进行的增强,而 STC90C516RD+ 提供了对 T2 定时器的支持,相关寄存器的解析请参见下一章节的内容。

中断优先级

STC90C516RD+ 的所有中断都具备 4 个中断优先级(高优先级、低优先级),并可实现 2 级中断嵌套,正在执行的低优先级会被高优先级中断,但不能被另一个低优先级中断,这个过程会一直持续至遇到返回指令RETI执行结束。

当同时收到多个相同优先级的中断请求时,执行的先后顺序取决于内部的查询次序号,相关的次序以及 C 语言中断关键字声明如下表:

◄ 高 0 高 1 2 3 4 5 6 7 低 ►
外部中断 0(INT0) 定时器 0 中断(T0) 外部中断 1(INT1) 定时器 1 中断(T1) 串口中断(UART) 定时器 2 中断(T2) 外部中断 2(INT2) 外部中断 3(INT3)
void myTest0() interrupt0 void myTest1() interrupt1 void myTest2() interrupt2 void myTest3() interrupt3 void myTest4() interrupt4 void myTest5() interrupt5 void myTest6() interrupt6 void myTest7() interrupt7

中断向量与汇编指令执行流程

当某个中断被响应时,该中断源对应服务程序的起始地址将会被装载到程序计数器PC中,该中断源服务程序的入口地址就称为中断向量

◄ 高 0 ◄ 高 1 2 3 4 5 6 7 低 ►
外部中断 0(INT0) 定时器 0 中断(T0) 外部中断 1(INT1) 定时器 1 中断(T1) 串口中断(UART) 定时器 2 中断(T2) 外部中断 2(INT2) 外部中断 3(INT3)
0003H 000BH 0013H 001BH 0023H 002BH 0033H 003BH

单片机响应中断以后,原来的主程序执行流程会被打断,接下来执行一系列如下操作:

  1. 单片机中断事件发生;
  2. 单片机检测到中断触发信号之后,首先执行完当前执行的指令,然后将下一条指令的地址压入程序计数器 PC 堆栈;
  3. 单片机进入中断服务程序 ISR 的入口地址(中断向量),开始执行中断服务程序;
  4. 中断服务程序执行完成, 最后执行一条RETI中断返回指令;
  5. 程序计数器 PC 堆栈弹出之前保存的地址,返回中断前的指令继续执行。

中断开始执行时,引发中断的相应标志位将会由硬件自动清零。又由于中断向量的地址通常位于程序存储器开始的位置,所以主程序的首条汇编指令通常为越过中断向量区的跳转指令LJMP MAIN

外部中断

外部中断 0(INT0)、外部中断 1(INT1)、外部中断 2(INT2)、外部中断 3(INT3)都拥有 2 种触发方式:下降沿触发方式低电平触发方式。接下来,总结一下外部中断相关的寄存器配置:

  1. 请求上述 4 个外部中断的标志位是位于寄存器 TCON 当中的IE0/TCON.1、IE1/TCON.3、IE2/XICON.2、IE3/XICON.5
  2. 当外部中断服务程序被单片机响应之后,中断请求标志位IE0、IE1、IE2、IE3将会由硬件自动清零。
  3. 寄存器 TCON 中的IT0/TCON.0、IT1/TCON.2、IT2/XICON.0、IT3/XICON.4决定了 4 个外部中断是低电平触发还是下降沿触发方式。
    • 如果 IT(0,1,2,3) = 0,那么 INT(0,1,2,3)脚探测到低电平后产生外部中断。
    • 如果 IT(0,1,2,3) = 1,那么 INT(0,1,2,3)脚探测下降沿后发生外部中断。

STC90C516RD+ 的 4 个外部中断还可以用于将单片机从掉电模式唤醒。由于单片机每 1 个系统时钟对外部中断引脚采样 1 次,为了确保中断被正确检测,输入信号应该至少维持 2 个系统时钟。如果外部中断是下降沿触发,那么必须在相应引脚维持高、低电平分别至少 1 个系统时钟。同样,如果外部中断是低电平触发,则要求相应引脚维持低电平至少 2 个系统时钟。

定时器/计数器 T0 与 T1

前一节内容讨论的中断是单片机的一种运行机制,而本节内容将要讨论的定时器/计数器则是单片机内部真实存在的资源,只是在使用过程当中可以触发中断,但是并不能将两个概念混为一谈。STC89C52RC+内置两个 16 位定时器/计数器模块T0T1T 是 Timer 的缩写,同时具备定时和计数两种工作方式,为了书写方便,后续统一简称为定时器),其核心部件是一个对脉冲进行计数的加法计数器,每接收到一个脉冲计数器自动加1。如果脉冲来源于系统时钟则工作在定时方式,如果来自单片机 IO 引脚则工作在计数方式。定时器操作相关的寄存器如下表:

符号 描述 地址 最高有效位 MSB ←-- 符号与位地址 --→ LSB 最低有效位 复位值
TCON 定时器控制寄存器 88H TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0 0000 0000B
TMOD 定时器模式寄存器 89H GATE C/T M1 M0 GATE C/T M1 M0 0000 0000B
TL0 定时器 0 低位 8AH - 0000 0000B
TH0 定时器 0 高位 8BH - 0000 0000B
TL1 定时器 1 低位 8CH - 0000 0000B
TH1 定时器 1 高位 8DH - 0000 0000B

TL0 与 TH0 以及 TL1 与 TH1 通常称为定时值存储寄存器,用于存储定时器/计数器的计数值,其中TH0/TL0用于T0TH1/TL1用于T1

定时器的定时值存储寄存器每经过一个机器周期的时间(即\({0.0000001}\)秒)就会自动加1,直至达到上限值发生溢出然后重新归0进行计数。因此,在正式进入定时器的讨论之前,需要先明确机器周期及其相关概念:

  • 时钟周期时序中的最时间单位,计算方法为\(1\over 时钟源频率\);由于当前 STC90C516RD+ 电路板使用的时钟晶振频率为12MHz,那么对应的时钟周期就等于\({1\over{12\times10^6}} = {1\over12000000}\)秒。
  • 机器周期汇编语言每条语句的执行时间都会是机器周期的整数倍,由于 C 语言代码执行时间的不确定性,通常规定 8051 标准架构下\(1个机器周期=12个时钟周期\),那么当前 STC90C516RD+ 电路板的机器周期是\({12\times{1\over{12\times10^6}}} = {12\over12000000}\)秒。
  • 指令周期时序中的最时间单位,是指取出一条指令并分析执行的时间,通常由若干个机器周期组成。8051 标准架构是单周期指令,即\(指令周期 = 机器周期\),那么 STC90C516RD+ 电路板的指令周期为\({12\over12000000}={1\over1000000}={0.0000001}\)秒。

STC90C516RD+ 单片机的定时器拥有两种计数速率,一种是12T模式,每 12 个时钟+1,与传统 8051 架构单片机相同;另一种是6T模式,每 6 个时钟+1,速度是传统 8051 的两倍,具体的速率可以通过 STC-ISP 烧写工具进行设置。

TCON 定时器控制寄存器

定时器 T0、T1 的控制寄存器,同时也锁存溢出中断源以及外部请求中断源等,可位寻址。

定时器控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
TCON TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
  • TF1: T1 溢出标志位,从初值开始加1计数,当最高位发生溢出时由硬件置1并向 CPU 去请求中断,一直保持到 CPU 响应中断后才由硬件清零(也可以用程序手动清零)。
  • TR1: T1 运行控制位,该位由软件置位和清零,当TR1=1并且TMOD.7=0时就会允许 T1 开始计数,TR1=0时则禁止计数。当TR1=1并且TMOD.7=1时,且 INT1 输入高电平时才允许 T1 计数。
  • TF0: T0 溢出标志位,从初值开始加1计数,当最高位发生溢出时由硬件置1并向 CPU 请求中断,然后一直保持到 CPU 响应中断后才由硬件清零(或者程序手动清零)。
  • TR0: T0 运行控制位,该位由软件置位和清零,当TR0=1并且TMOD.3=0时就允许 T0 开始计数,TR0=0时则禁止计数。当TR0=1并且TMOD.3=1时,且 INT0 输入高电平时才允许 T0 计数。
  • IE1外部中断请求源 INT1 的标志位,当IE1=1时外部中断向 CPU 请求中断,当 CPU 响应该中断时 IE1 由硬件清零。
  • IT1外部中断请求源 INT1 的触发方式控制位IT1=0时 INT1 为低电平触发方式,即该引脚上施加低电平可触发外部中断 0。而IT1=1时外部中断 0 为下降沿触发方式。
  • IE0外部中断请求源 INT0 的标志位,当IE1=1时外部中断向 CPU 请求中断,当 CPU 响应该中断时 IE0 由硬件清零。
  • IT0外部中断请求源 INT0 的触发方式控制位IT0=0时 INT0 为低电平触发方式,即该引脚上施加低电平可触发外部中断 0。而IT0=1时外部中断 0 为下降沿触发方式。

边沿触发是指电平由高向低或由低向高瞬间所引起的触发动作,不能与前面提到的上升沿触发下降沿触发单独等同为一个概念。

TMOD 定时器工作模式寄存器

定时器 T0、T1 的工作模式寄存器,共有 4 种可供选择的模式,但是该寄存器不可进行位寻址

定时器工作模式寄存器 定时器 1 定时器 1 定时器 1 定时器 1 定时器 0 定时器 0 定时器 0 定时器 0
序号 7 6 5 4 3 2 1 0
TMOD GATE C/T M1 M0 GATE C/T M1 M0
  • TOMD.0 / TMOD.1M1/M0):选择定时器 0 的操作模式
    1. 0 013 位定时器,仅使用 TL0 的低 5 位和 TH0 的全部 8 位。
    2. 0 116 位定时器,TL0 和 TH0 全部使用。
    3. 1 08 位自动重装模式,发生溢出时将 TH0 的值自动重装至 TL0。
    4. 1 1双 8 位定时器,TL0 作为一个 8 位定时器由 T0 的控制位进行控制,TH0 作为另一个 8 位定时器由 T1 的控制位进行控制。
  • TMOD.2\(C/\overline{T}\)):选择定时器 0 的使用方式,设置为0将作为定时器使用(计数脉冲由系统时钟输入),设置为1则作为计数器使用(计数脉冲从 T0/P3.4 引脚输入)。
  • TMOD.3GATE):控制定时器 0,当该位设置为1时,需要同时将 INT0 设置为高电平且TR1=1时才能正确开启定时器 0。
  • TOMD.4 / TMOD.5M1/M0):选择定时器 1 的操作模式
    1. 0 013 位定时器,仅使用 TL1 的低 5 位和 TH1 的全部 8 位。
    2. 0 116 位定时器,TL1 和 TH1 全部使用。
    3. 1 08 位自动重载定时器,发生溢出时将 TH1 的值自动重装至 TL1。
    4. 1 1定时器 1 无效,即停止计数。
  • TMOD.6\(C/\overline{T}\)):选择定时器 1 的使用方式,设置为0将作为定时器使用(计数脉冲由系统时钟输入),设置为1则作为计数器使用(计数脉冲从 T1/P3.5 引脚输入)。
  • TMOD.7GATE):控制定时器 1,当该位设置为1时,需要同时将 INT1 设置为高电平且TR1=1时才能正确开启定时器 1。

定时器 0 工作模式

通过设置寄存器 TMOD 的 M0(TMOD.0)、M1(TMOD.1),定时器 0 可以有如下 4 种不同工作模式:

13 位定时器

由 TL0 的低 5 位与 TH0 的全部 8 位构成,当 TL0 低 5 位溢出时向 TH0 进位,TH0 溢出时置位 TCON 中的溢出标志位 TF0。当GATE/TMOD.3=0 时,如果TR0=1则定时器开始计数。当GATE/TMOD.3=1时,允许外部输入 INT0 控制定时器。TR0 是 TCON 寄存器里的定时器 0 运行控制位。当C/T=0时,多路开关连接到系统时钟的分频输出,T0 对时钟周期计数工作在定时方式。当C/T=1时,多路开关连接至外部脉冲输入(即单片机的 P3.4/T0 引脚),此时 T0 工作在计数方式。

16 位定时器

定时值存储寄存器由 TL0 与 TH0 的全部 8 位组成,除此之外,其它方面与上面的模式 0 完全相同。

8 位自动重装模式

可自动进行重装的 8 位计数器模式,即 TL0 的溢出不仅会置位 TF0,还会将 TH0 的内容重新装载到 TL0,其中 TH0 的内容由软件预置,重装时 TH0 的内容保持不变。

双 8 位定时器

定时器 0 的 TL0 和 TH0 将会作为两个独立的定时器进行使用。

定时器 1 工作模式

通过设置寄存器 TMOD 的 M0(TMOD.4)、M1(TMOD.5),定时器 0 可以有如下 3 种不同的工作模式:

13 位定时器

同定时器 0 的 13 位定时器工作模式操作基本相同,溢出时会置位 TCON 中的溢出标志位 TF1。当GATE/TMOD.7=0 时,如果TR1=1则定时器开始计数。当GATE/TMOD.7=1时,就允许外部输入 INT1 控制定时器。TR1 是 TCON 寄存器里的定时器 1 运行控制位。通过C/T设置当前处于定时器还是计数器的方式与前述基本相同。

16 位定时器

定时值存储寄存器由 TL0 与 TH0 全部 8 位组成,其它方面与 13 位定时器的使用完全相同。

8 位自动重装模式

可自动进行重装的 8 位计数器模式,操作方式基本与上一节讲述的定时器 T0 的自动重装模式相同。

定时器/计数器 T2

虽然定时器 T2 不属于标准 8051 架构,但是其功能比标准的 T0、T1 更加强大,是一个具有自动重装和捕获能力的 16 位定时器,计数时钟源同样可以来自内部机器周期,或是 P1.0 输入的外部时钟脉冲。下面的表格展示了定时器 T2 相关的寄存器:

符号 描述 地址 最高有效位 MSB ←-- 符号与位地址 --→ LSB 最低有效位 复位值
T2CON 定时器 2 控制寄存器 C8H TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RL2 0000 0000B
T2MOD 定时器 2 模式寄存器 C9H --- ---- ---- ---- ----- --- T2OE DECN 0000 0000B
RCAP2L 定时器 2 重载与捕获低字节 CAH - 0000 0000B
RCAP2H 定时器 2 重载与捕获高字节 CBH - 0000 0000B
TL2 定时器 2 低字节 CCH - 0000 0000B
TH2 定时器 2 高字节 CDH - 0000 0000B

T2CON 定时器 2 控制寄存器(可位寻址)

定时器 2 控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
T2CON TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RL2
  • TF2定时器 2 溢出标志位,定时器 2 溢出时置位,必须通过代码清除。当RCLK=1TCLK=1时,TF2 将不会置位。
  • EXF2定时器 2 外部标志位,当EXEN2=1并且 T2EX/P1.1 的负跳变产生捕获或重装时被置位。
  • RCLK定时器 2 接收时钟标志位,当RCLK=1定时器 2 的溢出脉冲将作为串口模式 1 和 3 的接收时钟。当RCLK=0时,定时器 1 的溢出脉冲将作为串口模式 1 与 3 接收时钟。
  • TCLK定时器 2 发送时钟标志位,当TCLK=1定时器 2 的溢出脉冲将作为串口模式 1 和 3 的发送时钟。当TCLK=0时,定时器 1 的溢出脉冲将作为串口模式 1 与 3 发送时钟。
  • EXEN2定时器 2 外部使能标志位,当EXEN2=1并且定时器 2 未作为串口时钟时,允许 T2EX 的负跳变产生捕获或重装。而当EXEN2=0时,T2EX 的跳变对定时器 2 无效。
  • TR2定时器 2 启动/停止控制位TR2=1时将会启动定时器 2。
  • C/T2定时器 2 功能选择位,当C/T2=0时定时器 2 将会作为内部定时器,C/T2=1则会作为外部事件计数器。
  • CP/RL2定时器 2 捕获/重装标志位EXEN2=1时,T2EX 的负跳变产生捕获;EXEN2=0时,溢出或 T2EX 负跳变都会让定时器 2 自动重装。如果RCLK=1TCLK=1,该位无效且定时器 2 强制为溢出时自动重装。

正跳变实质上就是之前提到的电平从高到低的上升沿,负跳变则是由低到高的下降沿。

T2MOD 定时器 2 模式选择寄存器(不可位寻址)

定时器 2 模式寄存器 B7 B6 B5 B4 B3 B2 B1 B0
T2MOD - - - - - - T2OE DCEN
  • T2OE定时器 2 输出使能位
  • DECN定时器 2 计数方式使能位

定时器 2 工作模式

定时器 2 具有捕获自动重新装载(递增或递减计数)、波特率发生器三种工作模式,开发人员可以通过 T2CON 当中的位进行选择:

模式 RCLK+TCLK CP/RL2 TR2
16 位捕获 0 1 1
16 位自动重装 0 0 1
波特率发生器 1 x 1
关闭 x x 0

16 位捕获模式

当 T2CON 中的EXEN2=0,定时器 2 作为 16 位定时器或计数器(通过 T2CON 的 C/T2 位选择)使用,溢出时置位 TF2,该位可以产生中断。而当EXEN2=1时,功能与以上相同,但增加了一个特性:外部输入 T2EX 的电平由高向低正跳变时,定时器 2 中 TL2、TH2 的当前值将会捕获到 RCAP2L、RCAP2H。另外,T2EX 由低向高负跳变会使 T2CON 的 EXF2 置位为1,EXF2 也与 TF2 一样能够产生中断,其向量与定时器 2 溢出中断地址相同,定时器 2 中断服务程序通过查询 TF2 和 EXF2 来确定引起中断的事件。

16 位自动重装

定时器 2 可以通过 C/T2 配置为定时器或计数器,此时可以通过 T2MOD 寄存器的 DCEN 使能计数方式,然后再通过 T2EX 控制具体的计数方向是递增还是递减。

DCEN=0时,定时器 2 自动递增计数,该模式下可以设置 EXEN2 位进行选择。如果EXEN2=0那么定时器 2 会递增计数至 0FFFFH,并在溢出后将 TF2 置为1,然后将 RCAP2L 和 RCAP2H 中的 16 位值(通过软件预设)重新装载到定时器 2;如果EXEN2=1,定时器 2 处于外部中断使能模式,此时 16 位重载会通过溢出或者 T2EX 从高电平1向低电平0的负跳变来实现,该负跳变会同时向 EXF2 置位;处于中断使能模式下的定时器 2,当 TF2 或者 EXF2 被置1时就会发生中断。

DCEN=1时,可以通过 T2EX 控制计数方向是递增还是递减的。当T2EX=1时,定时器 2 递增计数,计数到 0FFFFH 后溢出并置位 TF2,同时产生中断(如果中断被使能)。定时器 2 的溢出将会使 RCAP2L 和 RCAP2H 中的 16 位值作为重新装载值放入 TL2 和 TH2。当T2EX=0时,TL2 和 TH2 计数到等于 RCAP2L 和 RCAP2H 中的 16 位值时,定时器 2 将产生中断。

串口波特率发生器

T2CON 寄存器的 TCLK、RCLK 可以从定时器 1 或 2 获取串行口发送和接收的波特率。当TCLK=0或者TCLK=1 时,定时器 1 和定时器 2 分别可以 作为串口发送波特率的发生器,RCLK 对于串行口接收波特率具有相同效果。定时器 2 的独立波特率发生器模式与其自动重装模式类似,TH2 溢出时,波特率发生器模式会使定时器 2 寄存器重新装载寄存器 RCAP2H 和 RCAP2L 的 16 位值(由软件预置)。

当定时器 2 工作在波特率发生器模式,如果外部时钟信号由 T2 脚进入,此时波特率计算公式如下:

\[\mathsf{波特率=\frac{定时器2溢出速率}{16}}\]

如果定时器 2 采用内 公式如下:

\[\mathsf{波特率=\frac{SYSclk}{n\times[65536-(RCAP2H,RCAP2L)]}}\]

串行通信

STC90C516RD+ 内部集成有一个全双工串行通信模块,可以通过查询或中断方式对串行接收/发送进行处理。拥有 2 种波特率固定以及 2 种波特率可变的共 4 种工作方式,波特率由单片机内部的定时/计数器产生,其工作方式和波特率都可以通过寄存器进行配置。

符号 描述 地址 最高有效位 MSB ←-- 符号与位地址 --→ LSB 最低有效位 复位值
SCON(Serial Control) 串行控制寄存器 98H SM0/FE SM1 SM2 REN TB8 RB8 TI RI 0000 0000B
SBUF(Serial Buffer) 串行数据缓冲寄存器 99H 两个分别用于读写数据的缓冲器,可通过两个寄存器分别进行控制。 xxxx xxxxB
PCON(Power Control) 电源控制寄存器 87H SMOD SMOD0 - POF GF1 GF0 PD IDL 00x1 0000B
IE(Interrupt Enable) 中断允许寄存器 A8H EA - ET2 ES ET1 EX1 ET0 EX0 0x00 0000B
IP(Interrupt Priority Low) 中断优先级控制寄存器(低) B8H - - PT2 PS PT1 PX1 PT0 PX0 xx00 0000B
IPH(Interrupt Priority High) 中断优先级控制寄存器(高) B7H PX3H PX2H PT2H PSH PT1H PX1H PT0H PX0H 0000,0000B
SADEN(Slave Address Mask) 电源控制寄存器 B9H 从机地址掩模寄存器 0000 0000B
SADDR(Slave Address) 电源控制寄存器 A9H 从机地址寄存器 0000 0000B

SCON 串行控制寄存器

选择串行通信的工作方式等其它控制功能,可位寻址。

串行控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
SCON SM0/FE SM1 SM2 REN TB8 RB8 TI RI

SM0/FE:当寄存器 PCON 中的SMOD0/PCON.6=1时,该位用于帧错误检测。 SM1:当寄存器 PCON 中的SMOD0/PCON.6=0时,该位与 SM0 一起用于指定串行通信工作方式。 SM2:串行通信工作方式 2 或者 3 多机通信控制位。 REN允许/禁止串行接收控制位REN=1时允许启用 RxD 进行串行接收,当REN=0时则禁止接收。 TB8: 处于串行通信工作方式 2 或者 3 时,该位为需要发送的第 9 位数据。 RB8: 处于串行通信工作方式 2 或者 3 时,该位为接收到的第 9 位数据。 TI发送中断请求标志位,串行通信工作方式 0 当中,当串行数据第 8 位发送结束时,由硬件自动置位TI=1,并向单片机请求中断,中断响应完成后程序代码必须手动复位TI=0。其它方式当中,将会在停止位发送时由硬件自动置位,然后通过程序手动复位。 RI接收中断请求标志位,串行通信工作方式 0 当中,当串行数据第 8 位发送结束时,由硬件自动置位RI=1,并向单片机请求中断,中断响应完成后程序代码必须手动复位RI=0。其它方式当中,将会接收到停止位的中间时刻由硬件自动置位,然后必须通过程序手动复位。

通过SM0/FESM1可选择的四种串行通信工作方式,以及相应配置方法如下表所示:

SM0 SM1 工作方式 功能说明 波特率
0 0 方式 0 同步移位寄存器。 \(\frac{单片机系统时钟频率}{12}\)
0 1 方式 1 8 位 UART,波特率可变。 \(\frac{2^{SMOD}}{32}\times{定时器1溢出率}\)
1 0 方式 2 9 位 UART。 \(\frac{2^{SMOD}}{64}\times{单片机系统时钟频率}\)
1 1 方式 3 9 位 UART,波特率可变。 \(\frac{2^{SMOD}}{32}\times{定时器1溢出率}\)
  • 当单片机工作在6T模式时, 定时器 1 的溢出率等于单片机时钟频率 ÷ 6 ÷ (256-TH1)
  • 当单片机工作在12T模式时,定时器 1 的溢出率等于单片机时钟频率 ÷ 12 ÷ (256-TH1)

PCON 电源控制寄存器

电源控制寄存器中的SMOD/PCON.7用于设置方式 1、方式 2、方式 3 的波特率是否加倍。

电源控制寄存器 B7 B6 B5 B4 B3 B2 B1 B0
PCON SMOD SMOD0 - POF GF1 GF0 PD IDL
  • SMOD/PCON.7波特率选择位。当程序中SMOD=1时,串行通信方式 1、2、3 的波特率将会加倍;当SMOD=0时,各个工作方式的波特率都会加倍。
  • SMOD0/PCON.6帧错误检测有效控制位。当程序中SMOD0=1时,寄存器 SCON 中的 SM0/FE 位将用于帧错误检测功能;而当SMOD0=0的时候,寄存器 SCON 中的 SM0/FE 位将会用于 SM0,并与 SM1 一起指定串行口的工作方式。

IE/IP/IPH 串行中断相关寄存器

串行口中断允许位ES位于中断允许寄存器IE当中:

中断允许寄存器 B7 B6 B5 ** B4** B3 B2 B1 B0
IE EA - ET2 ** ES** ET1 EX1 ET0 EX0
  • EA:总中断允许控制位,当EA=1时开启总中断,EA=0关闭总中断。
  • ES:串行口中断允许位,当ES=1时允许串行口中断,ES=0禁止串行口中断。

串行中断优先级控制位PSPSH分别位于中断优先级控制寄存器IPIPH当中:

中断优先级控制寄存器(低) B7 B6 B5 B4 B3 B2 B1 B0
IP 保留 保留 PT2 PS PT1 PX1 PT0 PX0
中断优先级控制寄存器(高) B7 B6 B5 B4 B3 B2 B1 B0
IPH PX3H PX2H PT2H PSH PT1H PX1H PT0H PX0H
  • PSH=0并且PS=0时,串口 1 中断为最低优先级中断(优先级 0)。
  • PSH=0并且PS=1时,串口 1 中断为较低优先级中断(优先级 1)。
  • PSH=1并且PS=0时,串口 1 中断为较高优先级中断(优先级 2)。
  • PSH=1并且PS=1时,串口 1 中断为最高优先级中断(优先级 3)。

宏晶 STC90C516RD+ 数据手册精读

http://www.uinio.com/Embedded/STC90C51/

作者

Hank

发布于

2010-12-24

更新于

2013-03-27

许可协议