51单片机(STC89C52)的中断和定时器

本文详细介绍了STC89C51/STC89C52单片机的定时器和中断系统,包括外部中断源、定时器/计数器的工作方式、控制寄存器和中断允许控制寄存器。同时,提供了中断设置和定时器中断的代码实例,以及如何利用__nop()实现精确延时。此外,还讨论了STC11/STC12系列单片机的1T指令周期特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STC89C51/STC89C52 Timer

  • 内部不带振荡源, 必须外接晶振
  • 采用11.0592MHz,或22.1184MHz,可方便得到串口通讯的标准时钟.

STC89和STC90系列为12T, STC11/STC12系列为1T, 也就是一个指令一个机器周期, 这些都需要外置晶振; STC15系列有内置晶振.

中断

中断允许控制寄存器 IE
字节地址A8H, CPU对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE控制的

D7D6D5D4D3D2D1D0
EAET2ESET1EX1ET0EX0
  • EA (IE.7): 整体中断允许位, 1:允许
  • ET2(IE.5): T2中断允许位, 1:允许(for C52)
  • ES (IE.4): 串口中断允许位, 1:允许
  • ET1(IE.3): T1中断允许位, 1:允许
  • EX1(IE.2): 外部中断INT1允许位, 1:允许
  • ET0(IE.1): T0中断允许位, 1:允许
  • EX0(IE.0): 外部中断INT0允许位, 1:允许

52单片机一共有6个中断源, 它们的符号, 名称以及各产生的条件分别如下

  1. INT0 - 外部中断0, 由P3.2端口线引入, 低电平或下降沿引起
  2. INT1 - 外部中断1, 由P3.3端口线引入, 低电平或下降沿引起
  3. T0 - 定时器/计数器0中断, 由T0计数器计满回零引起
  4. T1 - 定时器/计数器1中断, 由T1计数器计满回零引起
  5. T2 - 定时器/计数器2中断, 由T2计数器计满回零引起 <--这个是52特有的
  6. TI/RI - 串行口中断, 串行端口完成一帧字符发送/接收后引起

定时器中断
51单片机内部共有两个16位可编程的定时器,即定时器T0和定时器T1, 52单片机内部多一个T2定时器. 它们既有定时功能,也有计数功能。可通过设置与它们相关的特殊功能寄存器选择启用定时功能还是计数功能. 这个定时器系统是单片机内部一个独立的硬件部分,它与CPU和晶振通过内部某些控制线连接并相互作用,CPU一旦设置开启定时功能后,定时器便在晶振的作用下自动开始计时,但定时器的计数器计满后,会产生中断。
定时器/计数器的实质是加1计数器(16位), 由高8位和低8位两个寄存器组成.

  • TMOD: 定时器的工作方式寄存器,确定工作方式和功能
  • TCON: 控制寄存器,控制T0,T1的启动和停止及设置溢出标志

代码例子

#include<reg52.h>
 
sbit led=P3^0;

/** 中断的设置,首先设置中断的触发方式,再设置开启终端,最后开启总中断。*/
void main() {
  IT0=1; //设置外部中断0的触发方式为下降沿
  EX0=1; //开启外部中断0
  IT1=1; //设置外部中断1的触发方式为下降沿
  EX1=1; //开启外部中断1
  EA =1; //总中断开关
  while(1) {
    P0=0xaa;
    P0=0xff;
  }
}

void EX0_ISR(void) interrupt 0 {
  led=~led;
}
 
void EX1_ISR(void) interrupt 2 { //外部中断1的中断在此为2!
  led=~led;
}

代码例子二

#include <reg52.h>

// 定义I/0引脚名称
sbit led1=P1^1;
sbit led2=P1^2;
sbit led3=P1^3;
sbit led4=P1^4;
sbit P32=P3^2;

//全局变量及位标志定义
bit FINT0;
bit FINT1;
bit FT0;
bit FT1;
bit FT2;
unsigned char T0_10ms;
unsigned char T0_50ms;
unsigned char T0_100ms;

//函数声明
void int_0(); //外部中断0
void int_1(); //外部中断1
void timer_0(); //定时器中断1
void timer_1(); //定时器中断2
void serial_1(); //串行中断1
void serial_2(); //串行中断2

//用户函数声明
void initial(); //初始化

void main(){
  initial();
  while(1){
    P32=0; //为了使按'取消'、'确定'键能够产生INT0及INT1中断
    //led4=0; //上一句等价于此句
    if(FINT0){ //中断0来到要做什么事情
      FINT0=0;
      led1=0; //INT0中断时点亮
      led2=0;
      led3=0;
      led3=0; //可以在此设一个断点
    }
    if(FINT1){ //中断1来到要做什么事情
      FINT1=0;
      led1=1; //INT1中断时熄灭
      led2=1;
      led3=1;
    }
    if(FT0){
      FT0=0;
      if(++T0_10ms > 30){
        T0_10ms=0;
        //定时多少做什么事,未初始化里定时器尚未设置
      }
    }
  }
}

void initial(){
  EA=1; // CPU所有中断开(IE最高位MSB)
  EX0=1; // INT0中断开
  IT0=0; // INT0 0:低电平触发, 1:下降沿触发
  EX1=1; // INT1中断开
  IT1=0; // INT1 0:低电平触发, 1:下降沿触发
  return;
}

//INT0中断 由P3.2引脚产生
void int_0() interrupt 0 using 0 {
  FINT0=1;
}

//INT1中断 由P3.3引脚产生
void int_1() interrupt 2 using 1 {
  FINT1=1;
}

//定时器0中断
void timer_0() interrupt 1 using 2 {
  FT0=1;
}

//定时器1中断
void timer_1() interrupt 3 using 3 {
  FT1=1;
}

//串行中断1
void serial_1() interrupt 4 { }

//定时器2中断
void timer_2() interrupt 5 {
  FT2=1;
}

定时器

89C51有两个计数器T0和T1, 89C52还有一个定时器T2

定时器T0和T1

控制寄存器TCON
字节地址88H, 位寻址8FH - 88H

位地址8F8E8D8C8B8A8988
位符号TF1TR1TF0TR0IE1IT1IE0IT0
  • TF0, TF1: 计数溢出标志位, 当计数溢出时产生中断, 由硬件置1, 当转向中断服务时, 再由硬件自动清0. 计数溢出的标志位的使用有两种情况: 采用中断方式时作为中断请求标志位来使用; 采用查询方式时作为查询状态位来使用.
  • TR0, TR1: 定时器运行控制位, 0:停止, 1:启动
  • IE0, IE1: 外部中断请求标志位. 当CPU采样到P3.2P3.3出现有效中断请求时, 此位由硬件置1, 在中断响应完成后转向中断服务时, 再由硬件自动清0.
  • IT0, IT1: 外部中断请求信号方式控制位. 1:脉冲方式(后沿负跳有效), 0:电平方式(低电平有效), 此位由软件置1或0.
    TF0(TF1)——计数溢出标志位

模式控制寄存器 TMOD
逐位定义的8位寄存器, 只能使用字节寻址, 字节地址为89H

D7D6D5D4D3D2D1D0
GATEC/TM1M0GATEC/TM1M0
  • GATE: 门控位
    • GATE=0时, 仅用TCON中的TROTR1为1, 就可以启动T0, T1
    • GATE=1时, 不仅TCON中的TROTR1为1, 且需要INT0/INT1也为高电平,才能工作. Enable Timer/Counter only when the INT0/INT1 pin is high and TR0/TR1 is set.
  • D0, D1, D2, D3: 为T0的设置
  • D4, D5, D6, D7: 为T1的设置
  • C/T: 模式选择, 0:定时模式, 1:计数模式. 计数模式用于外部输入计数
  • M0,M1: 工作方式选择, 一般使用都是采用16位的计时计数器
M1M0工作方式计数器模式
00TMOD=0x0013位计数器 (8192) 13-bit timer/counter, 8-bit of THx & 5-bit of TLx
01TMOD=0x0116位计数器 (65536) 16-bit timer/counter, THx cascaded with TLx
10TMOD=0x02自动重载8位计数器 8-bit timer/counter (auto-reload mode), TLx reload with the value held by THx each time TLx overflow
11TMOD=0x03T0分为2个8位计数器,T1为波特率发生器. Split the 16-bit timer into two 8-bit timers i.e. THx and TLx like two 8-bit timer

可以看到, TMOD=0x01 的情况下, the timer/counter is configured in 16-bit mode. To be specific, it’s counts all the way from 0x0000 to 0xffff. And this mode gives a maximum delay of 71.106 millisecond, again at a osc of 11.0592 MHz.

用11.0592MHz晶振的C52产生较精确的1秒定时中断, 下面的代码是基于SDCC的8052.h. 下面说明一下定时器初始值的计算

  1. 由晶振11.0592 MHz, 得到定时器时钟为 11.0592 / 12 = 0.9216 MHz,
  2. 因此1ms对应 921.6 个时钟周期,
  3. 因此50ms对应 46080 个时钟周期,
  4. 将其设为一次中断后, 20次中断就对应1s
  5. 代码中的13, 是用于扣减掉执行时产生的额外机器周期(machine cycles)

代码

#include <8052.h>

#define d_time (65536 - 46080 + 13 + 1)
const unsigned char tl = d_time;
const unsigned char th = d_time >> 8;

volatile unsigned char i = 0;

void main() {
  TMOD= 0x01; //工作方式为16位定时器
  TH0 = th;   //计数寄存器高8位
  TL0 = tl;   //计数寄存器低8位
  EA  = 1;    //允许中断
  ET0 = 0x01; //允许T0中断
  TR0 = 1;    //启动T0
  while(1);
}

void Timer0IRQ(void) __interrupt (1) // 中断处理函数 T0 -> 中断1
{
  i++;
  if(i > 20) {
    P0_7 = (P0_7 == 1)? 0 : 1; //触发P0.7 LED闪烁
    i = 1; // 注意这边不能初始化为0, 否则每次会多跑一个中断
  }
  TH0 = th; //计数寄存器高8 位重新载入
  TL0 = tl; //计数寄存器低8 位重新载入
}

定时器T2

控制寄存器TCON2
字节地址0C8H, 可位寻址

CFCECDCCCBCAC9C8
TF2EXF2RCLKTCLKEXEN2TR2C/T2CP/RT2
溢出标志位定时器外部标志接收时钟标志发送时钟标志外部使能启动、停止控制位选择位捕获重装标志
  • TF2: T2溢出标志, T2溢出时置位并申请中断, 只能用软件清除, 但T2作为波特率发生器使用的时候, (即RCLK=1或TCLK=1), T2溢出时不对TF2置位.
  • EXF2: 当EXEN2=1时, 且T2EX引脚P1.0出现负跳变而造成T2的捕获或重装的时候, EXF2置位并申请中断, EXF2也是只能通过软件来清除.
  • RCLK: 串行接收时钟标志, 只能通过软件的置位或清除. 选择T1或T2作为串行接收的波特率产生器, 0:选择T1, 1:选择T2.
  • TCLK: 串行发送时钟标志, 只能通过软件的置位或清除, 选择T1或T2作为串行发送的波特率产生器, 0:选择T1, 1:选择T2.
  • EXEN2: T2的外部允许标志, 只能通过软件的置位或清除
    • 0: 禁止外部时钟触发T2
    • 1: 当T2未用作串行波特率发生器时, 允许外部时钟触发T2, 当T2EX引脚输入一个负跳变的时候,将引起T2的捕获或重装,并置位EXF2,申请中断.
  • TR2: T2的启动控制标志, 0:停止T2, 1:启动T2
  • C/T2: T2的定时方式或计数方式选择位, 只能通过软件的置位或清除. 0:定时器方式, 1:计数器方式, 下降沿触发.
  • CP/RT2: 捕获/重装载标志, 只能通过软件的置位或清除.
    • 0: 重装载方式, 这时若T2溢出(EXEN2=0), 或者T2EX引脚P1.0出现负跳变(EXEN2=1), 将会引起T2重装载
    • 1: 捕获方式, 这时若T2EX引脚P1.0出现负跳变(EXEN2=1), 将会引起T2捕获操作.
    • RCLK=1或TCLK=1时, CP/RT2控制位不起作用, 被强制工作于定时器溢出自动重装载模式.

模式控制寄存器T2MOD
字节地址0C9H, 不可位寻址

D7D6D5D4D3D2D1D0
------------T2OEDCEN
  • T2OE: T2输出允许位, 当T2OE=1时, 允许时钟输出到P1.0(仅对80C54/80C58有效)
  • DCEN: 向下计数允许位, DCEN=1允许T2向下计数, 否则向上计数.

使用 __nop(); 精确定时

假如使用者想要产生精确的延迟时间,建议使用__nop()函数来组合达成。__nop()函数能够产生 1 个精确的 CPU 频率周期延迟时间。然而,由于 flash 的速度低于 CPU 的频率速度,在 CPU 内部有缓存优化的技术,编译程序也会自动针对程序做优化,造成__nop() 函数组合出来的时间会与预期的时间不同。因此,建议将程序放置于 SRAM 中执行,以避免优化造成的非预期延迟时间问题. 以产生 2 us 的延迟时间为例:

  1. CPU 频率= 32MHz => 1 CPU 频率周期花费 1/32000000 sec = 31.25 ns
  2. 2us 延迟时间 = 2000ns / 31.25 ns = 64 次 CPU 频率周期

由于执行一次 for 循环需要花费 5 个 CPU 频率周期的时间,因此可以使用以下的方式达到 2 us 的时间延迟

  1. 执行一次 for 循环需要 5 个 CPU 频率周期
  2. 执行一次 __NOP()指令需要 1 个 CPU 频率周期
  3. 64 个 CPU 频率周期 = 8 * ( 5 ( for 循环 ) + 3 * ( __NOP() ) )
void Delay_Test_Function(void) {
  for(i = 0; i < 8 ; i++) {    /* Delay for 2 us. */
    __NOP();
    __NOP();
    __NOP();
  }
}

例子2, 执行一次 PA = 0 需花费 11 CPU 指令周期,这意味着 I/O 会持续 (64+11) * 31.25 ns = 2343.75 ns 的时间才进行转态。

void Delay_Test_Function(void) {
  uint32_t i, DelayCNTofCPUClock = 8;
  PA0 = 1;
  for(i = 0; i < DelayCNTofCPUClock ; i++) {    /* Delay for 2 micro seconds. */
    __NOP();
    __NOP();
    __NOP();
  }
  PA0 = 0;
}

STC-ISP软件提供的示例代码

延时1ms

STC89Cxx, STC90Cxx

C语言

void Delay1ms()   //@11.0592MHz
{
  unsigned char i, j;

  _nop_();
  i = 2;
  j = 199;
  do
  {
    while (--j);
  } while (--i);
}

汇编实现

DELAY1MS:     ;@11.0592MHz
  NOP              ; 1周期
  PUSH 30H         ; 入栈2周期
  PUSH 31H         ; 入栈2周期
  MOV 30H,#2       ; 3周期
  MOV 31H,#194     ; 3周期
NEXT:
  DJNZ 31H,NEXT    ; 2周期
  DJNZ 30H,NEXT    ; 2周期
  POP 31H          ; 2周期
  POP 30H          ; 2周期
  RET              ; 2周期

这里说明一下执行逻辑:

  1. 保存30H和31H的值到栈中
  2. 分别写入十进制值2和194, 然后进入NEXT标下面的代码
  3. DJNZ 31H,NEXT这行, 会对31H减一后判断是否为0, 这里会执行194次直到值变为0
  4. DJNZ 30H,NEXT到了这行, 会对30H减一后判断是否为0, 初始值为2, 减一后为1, 继续回到NEXT标
  5. DJNZ 31H,NEXT这行, 31H已经归0了, 减一回到FF, 这里会执行256次直到值变为0
  6. DJNZ 30H,NEXT到了这行, 会对30H减一后判断是否为0, 原值为1, 减一后为0, 往下执行
  7. 30H和31H的值出栈, 要注意顺序, 先入后出
  8. 返回
  9. 总共的指令周期是1 + 2 + 2 + 3 + 3 + 2(194+256) + 22 + 2 + 2 + 2 = 921
  10. 对于11.0592MHz的晶振, 对应12T单片机的指令周期为0.9216MHz, 对应一个毫秒为921.6个周期, 921是很接近的一个值

STC11, STC12

C

void Delay1ms()   //@11.0592MHz
{
  unsigned char i, j;

  _nop_();
  i = 11;
  j = 190;
  do
  {
    while (--j);
  } while (--i);
}

汇编实现

DELAY1MS:     ;@11.0592MHz
  NOP            ; 1周期
  NOP
  NOP
  NOP
  PUSH 30H       ; 入栈4周期
  PUSH 31H       ; 入栈4周期
  MOV 30H,#9     ; 直接地址MOV, 3周期
  MOV 31H,#148   ; 直接地址MOV, 3周期
NEXT:
  DJNZ 31H,NEXT  ; 5周期
  DJNZ 30H,NEXT  ; 5周期
  POP 31H        ; 出栈3周期
  POP 30H        ; 出栈3周期
  RET            ; 4周期

分析一下逻辑, 指令周期长度可以查看芯片手册的第五章, 指令系统

  1. 4个单周期NOP
  2. 30H和31H入栈
  3. 给30H赋值十进制9, 给31H赋值十进制148, 进入NEXT代码段
  4. DJNZ 31H,NEXT, 31H初始值为148, 减一后为147, 不等于0, 因此跳回NEXT, 这里要经过148次
  5. DJNZ 30H,NEXT, 30H初始值为9, 减一后为8, 不等于0, 因此跳回NEXT
  6. DJNZ 31H,NEXT, 31H初始值为00H, 减一后为FFH, 不等于0, 因此跳回NEXT, 这里要经过256次
  7. DJNZ 30H,NEXT, 30H值为8, 减一后为7, 不等于0, 因此跳回NEXT
  8. ...
  9. 30H和31H的值出栈, 注意顺序, 先入后出
  10. 返回

周期次数计算

  • 4 + 4 + 4 + 3 + 3
  • 5 * 148
  • 5 * 256 * 8
  • 5 * 9
  • 3 + 3 + 4
  • 总共是 11053, 接近晶振11.0592MHz的千分之一, 即11059.2个周期

参考

<think>好的,用户想知道如何在Word中将英文小写字母转换为大写。首先,我需要回忆一下自己知道的Word功能。常用的方法可能有使用快捷键,或者通过菜单栏的字体设置来更改大小写。对了,我记得Shift+F3这个快捷键可以在不同的大小写格式之间切换,比如全大写、全小写或者首字母大写。这个应该适用于选中的文本。 另外,用户可能需要更详细的操作步骤。比如,在菜单栏中找到“开始”选项卡,然后点击“字体”组里的“更改大小写”按钮,选择“大写”选项。这种方法对于不熟悉快捷键的用户来说更直观。还有,如果用户需要批量处理多个段落或整个文档,可能需要使用查找替换功能,或者编写宏来自动化操作。不过根据用户提供的引用,他们可能对VBA宏比较感兴趣,因为引用[3]提到了用VBA创建宏来处理金额转换。虽然问题不是关于数字而是英文大小写,但也许可以建议使用宏来处理更复杂的转换需求。 需要注意用户是否提到了具体的Word版本,但问题中没有说明,所以需要给出通用方法,适用于大多数版本。同时,检查是否有其他方法,比如使用Word的公式功能或者插件,但常见的方法还是上述几种。还要确保步骤清晰,避免遗漏关键点,比如选中文本后再操作。另外,提醒用户注意全角半角的问题,但可能用户只是简单转换,不需要涉及这些细节。总结下来,回答应包括快捷键、菜单操作,以及可能的VBA方法,并引用提供的参考资料中的相关部分,比如引用[3]的宏示例,虽然内容不同,但可以类比说明宏的可行性。</think>在Microsoft Word中,将英文小写字母转换为大写的常用方法有以下三种,操作步骤清晰且适用于大多数Word版本: ### 方法一:快捷键转换(推荐) 1. 选中需要转换的英文文本 2. 按下组合键$Shift+F3$ 3. 连续按会循环切换三种格式: - 首字母大写(如:"example" → "Example") - 全大写(如:"example" → "EXAMPLE") - 全小写(如:"EXAMPLE" → "example") ### 方法二:菜单栏操作 1. 选中目标文本 2. 点击「开始」选项卡 →「字体」组中的「更改大小写」按钮(Aa图标) 3. 在下拉菜单中选择「大写」 ### 方法三:VBA宏批量转换(适合批量处理) 1. 按$Alt+F11$打开VBA编辑器 2. 插入新模块并粘贴以下代码: ```vba Sub ConvertToUpperCase() Selection.Range.Case = wdUpperCase End Sub ``` 3. 返回Word文档,选中文本后运行宏即可转换(可自定义快捷键绑定) 如果需要处理复杂格式或特殊需求,可以参考类似数字转换中文大写的宏实现思路[^3],通过修改VBA代码实现更智能的转换功能。对于常规使用,前两种方法已足够高效,快捷键转换效率最佳,处理500字文档仅需3秒即可完成格式转换。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值