51单片机(STC89C52)的中断和定时器 您所在的位置:网站首页 t1-1t 51单片机(STC89C52)的中断和定时器

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

2023-12-18 13:34| 来源: 网络整理| 查看: 265

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

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

中断

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

D7 D6 D5 D4 D3 D2 D1 D0 EA — ET2 ES ET1 EX1 ET0 EX0 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个中断源, 它们的符号, 名称以及各产生的条件分别如下

INT0 - 外部中断0, 由P3.2端口线引入, 低电平或下降沿引起 INT1 - 外部中断1, 由P3.3端口线引入, 低电平或下降沿引起 T0 - 定时器/计数器0中断, 由T0计数器计满回零引起 T1 - 定时器/计数器1中断, 由T1计数器计满回零引起 T2 - 定时器/计数器2中断, 由T2计数器计满回零引起 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

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

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

D7 D6 D5 D4 D3 D2 D1 D0 GATE C/T M1 M0 GATE C/T M1 M0 GATE: 门控位 GATE=0时, 仅用TCON中的TRO或TR1为1, 就可以启动T0, T1 GATE=1时, 不仅TCON中的TRO或TR1为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位的计时计数器 M1 M0 工作方式 计数器模式 0 0 TMOD=0x00 13位计数器 (8192) 13-bit timer/counter, 8-bit of THx & 5-bit of TLx 0 1 TMOD=0x01 16位计数器 (65536) 16-bit timer/counter, THx cascaded with TLx 1 0 TMOD=0x02 自动重载8位计数器 8-bit timer/counter (auto-reload mode), TLx reload with the value held by THx each time TLx overflow 1 1 TMOD=0x03 T0分为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. 下面说明一下定时器初始值的计算

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

代码

#include #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, 可位寻址

CF CE CD CC CB CA C9 C8 TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/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, 不可位寻址

D7 D6 D5 D4 D3 D2 D1 D0 -- -- -- -- -- -- T2OE DCEN T2OE: T2输出允许位, 当T2OE=1时, 允许时钟输出到P1.0(仅对80C54/80C58有效) DCEN: 向下计数允许位, DCEN=1允许T2向下计数, 否则向上计数. 使用 __nop(); 精确定时

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

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

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

执行一次 for 循环需要 5 个 CPU 频率周期 执行一次 __NOP()指令需要 1 个 CPU 频率周期 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周期

这里说明一下执行逻辑:

保存30H和31H的值到栈中 分别写入十进制值2和194, 然后进入NEXT标下面的代码 DJNZ 31H,NEXT这行, 会对31H减一后判断是否为0, 这里会执行194次直到值变为0 DJNZ 30H,NEXT到了这行, 会对30H减一后判断是否为0, 初始值为2, 减一后为1, 继续回到NEXT标 DJNZ 31H,NEXT这行, 31H已经归0了, 减一回到FF, 这里会执行256次直到值变为0 DJNZ 30H,NEXT到了这行, 会对30H减一后判断是否为0, 原值为1, 减一后为0, 往下执行 30H和31H的值出栈, 要注意顺序, 先入后出 返回 总共的指令周期是1 + 2 + 2 + 3 + 3 + 2(194+256) + 22 + 2 + 2 + 2 = 921 对于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周期

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

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

周期次数计算

4 + 4 + 4 + 3 + 3 5 * 148 5 * 256 * 8 5 * 9 3 + 3 + 4 总共是 11053, 接近晶振11.0592MHz的千分之一, 即11059.2个周期 参考 C51 汇编写的延时函数说明及时钟频率 http://www.51hei.com/mcuteach/247.html Very helpful SDCC C51 code examples https://github.com/hungtcs-lab/8051-examples 8051 DJNZ Instruction https://www.refreshnotes.com/2016/02/8051-djnz-instruction.html https://www.keil.com/support/man/docs/is51/is51_djnz.htm


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有