51单片机:中断、定时器与PWM整合手册

一、中断系统

中断系统是单片机实现实时处理的核心机制,用于响应内外紧急事件,核心包含定义、源、流程、嵌套、向量表及控制寄存器六大模块。

1. 中断与中断源
  • 中断:为使CPU具备对外界紧急事件的实时处理能力而设计的机制。当CPU执行当前程序时,被内外事件触发,暂停当前任务转去处理该事件(执行中断服务程序),处理完后返回原程序继续执行,类似"工作时接紧急电话"。
  • 中断源 :触发中断的"事件来源",即导致CPU暂停的具体原因。结合STC89C51RC/RD+系列,常见中断源如下:
    • 外部中断:INT0(P3.2)、INT1(P3.3)、INT2、INT3
    • 定时器中断:Timer0、Timer1、Timer2(溢出触发)
    • 串口中断:UART(接收/发送完成触发)
2. 中断处理流程

遵循"请求-判断-处理-恢复"逻辑,核心步骤共6步,需经过CPU内核的多层判断:

  1. 中断请求:中断源(如Timer0溢出、INT0按键)向CPU发送触发信号;
  2. 中断判断:CPU内核执行完当前指令后,先检查全局中断使能(EA)及该中断是否被屏蔽,
  3. 确认优先级:确认中断优先级(如高优先级中断优先响应);
  4. 保护现场:保存当前CPU寄存器(如累加器A、程序计数器PC)的值,避免后续处理覆盖原数据;
  5. 执行中断服务程序:通过中断向量表找到对应服务函数地址,跳转执行(如读取传感器、翻转IO口);
  6. 恢复现场:将保存的寄存器值还原,恢复CPU执行原程序的初始状态;
  7. 中断返回:跳回原程序被暂停的位置(恢复PC值),继续执行原任务。
3. 中断嵌套

高优先级中断可打断正在执行的低优先级中断服务程序,处理完高优先级中断后,返回继续执行低优先级中断,最终恢复原程序,本质是"优先级抢占"机制。

  • 示例:CPU正在处理"INT1按键中断"(低优先级),此时"Timer0溢出中断"(高优先级)触发,CPU会暂停INT1处理,优先执行Timer0服务程序,执行完后回到INT1中断,最后返回原程序。
  • 优先级控制:通过IP(中断优先级寄存器)、IPH(高优先级寄存器) 配置,STC89C51默认优先级从高到低为:INT0 > Timer0 > INT1 > Timer1 > UART > Timer2 > INT2 > INT3。
4. 中断向量表

存储"中断号(查询次序号)"与"中断服务程序入口地址"的指针数组,相当于CPU的"中断导航目录",用于快速定位服务程序。

  • 51单片机C语言编程中,中断号直接对应interrupt关键字后的参数,具体映射如下:

    中断类型 中断号 服务函数示例
    外部中断0(INT0) 0 void Int0_Routine(void) interrupt 0;
    定时器0(Timer0) 1 void Timer0_Routine(void) interrupt 1;
    外部中断1(INT1) 2 void Int1_Routine(void) interrupt 2;
    定时器1(Timer1) 3 void Timer1_Routine(void) interrupt 3;
    串口(UART) 4 void UART_Routine(void) interrupt 4;
    定时器2(Timer2) 5 void Timer2_Routine(void) interrupt 5;
    外部中断2(INT2) 6 void Int2_Routine(void) interrupt 6;
    外部中断3(INT3) 7 void Int3_Routine(void) interrupt 7;
5. 中断控制寄存器

中断的使能、类型(电平/边沿触发)需通过专用寄存器配置,核心寄存器包括IE(中断允许寄存器)TCON(定时器/中断控制寄存器)

(1)IE寄存器(中断允许,地址:A8H,可位寻址)
位序号 位名称 功能描述
B7 EA 全局中断使能位:1=允许全局中断;0=禁止所有中断
B6 - 保留位,无实际功能
B5 - 保留位,无实际功能
B4 ES 串口中断使能位:1=允许;0=禁止
B3 ET1 Timer1中断使能位:1=允许;0=禁止
B2 EX1 INT1中断使能位:1=允许;0=禁止
B1 ET0 Timer0中断使能位:1=允许;0=禁止
B0 EX0 INT0中断使能位:1=允许;0=禁止
(2)TCON寄存器(定时/中断控制,地址:88H,可位寻址)
位序号 位名称 功能描述
B7 TF1 Timer1溢出标志:计数溢出时硬件置1,CPU响应中断后硬件清0(也可软件清0)
B6 TR1 Timer1运行控制位:1=启动Timer1;0=停止Timer1
B5 TF0 Timer0溢出标志:功能同TF1,对应Timer0
B4 TR0 Timer0运行控制位:功能同TR1,对应Timer0
B3 IE1 INT1请求标志:INT1触发时硬件置1,CPU响应后硬件清0
B2 IT1 INT1触发类型位:1=下降沿触发;0=低电平触发
B1 IE0 INT0请求标志:功能同IE1,对应INT0
B0 IT0 INT0触发类型位:功能同IT1,对应INT0
二、定时器系统

51单片机定时器本质是"可编程计数器",基于机器周期(1机器周期=12晶振周期)实现定时或计数功能,核心包含工作原理、控制寄存器及实战代码。

1. 定时器工作原理

定时器核心是16位计数器(由高8位THx和低8位TLx组成) ,支持两种工作模式,需通过TMOD寄存器配置模式,TRx启动计数:

  • 定时模式 :计数器对内部机器周期 计数。从预设初值(THx/TLx)开始加1,计数到65535(0xFFFF)后溢出,触发TFx标志,向CPU请求中断(如定时1ms);
  • 计数模式 :计数器对外部引脚脉冲计数。引脚(T0=P3.4、T1=P3.5)每检测到1个脉冲上升沿/下降沿,计数加1,溢出后触发中断(如统计按键次数)。
2. 核心控制寄存器

除上述TCON外,定时器模式由TMOD配置,初值由THx/TLx设置:

(1)TMOD寄存器(定时器模式,地址:89H,不可位寻址)
位序号 位名称 功能描述(高4位对应Timer1,低4位对应Timer0)
B7/B3 GATE 门控位:1=由INTx引脚和TRx共同控制启动;0=仅由TRx控制启动
B6/B2 C/T 模式选择位:1=计数模式(外部脉冲);0=定时模式(内部机器周期)
B5~B4/B1~B0 M1~M0 工作模式位:00=13位定时/计数;01=16位定时/计数;10=8位自动重装;11=双8位
(2)THx/TLx寄存器(定时器初值寄存器)
  • TH0(地址8CH)/TL0(地址8AH):Timer0的高8位/低8位初值寄存器;
  • TH1(地址8DH)/TL1(地址8BH):Timer1的高8位/低8位初值寄存器;
  • 初值计算:若定时t ms,晶振频率f_{\\text{osc}}(如11.0592MHz),则初值 = 65536 - \\frac{t \\times 10\^{-3} \\times f_{\\text{osc}} \\times 10\^{6}}{12}
3. 实战代码示例
(1)定时器0控制P2口LED闪烁(定时500ms翻转)
c 复制代码
(1)定时器0控制P2口LED闪烁(定时500ms翻转)

#include <reg52.h>

// 定时器0初始化:16位定时模式,允许中断
void init_timer0(void) 
{
    IE |= (1 << 7) | (1 << 1);  // 使能全局中断(EA=1)和Timer0中断(ET0=1)
    TCON |= (1 << 4);           // 启动Timer0(TR0=1)
    TMOD &= ~(3 << 2);          // 清除Timer0模式位(M1~M0)
    TMOD &= ~(1 << 1);          
    TMOD |= (1 << 0);           // Timer0设为16位定时模式(M1=0,M0=1)
    TH0 = 64535 >> 8;           // 加载初值(定时约1ms,晶振11.0592MHz)
    TL0 = 64535;                
}

// Timer0中断服务函数:计数500次(500ms)翻转P2口
void timer0_handler(void) interrupt 1 
{
    static int t = 0;
    ++t;
    if (t >= 500) {             // 累计500ms
        P2 ^= 0xFF;             // 翻转P2所有引脚(LED闪烁)
        t = 0;
    }
    TH0 = 65035 >> 8;           // 重新加载初值(避免溢出后计数不准)
    TL0 = 65035;
}

int main(void) 
{
    init_timer0();
    P2 = 0xFF;                  // 初始P2口高电平(LED灭)
    while (1)
    {

    }                 // 主循环空转,等待中断
    return 0;
}

(2)定时器控制和五个按键控制蜂鸣器不同频率的声音

cs 复制代码
#include <reg52.h>
#include "key.h"  // 需确保该头文件中实现了init_key()(按键初始化)和key_pressed()(按键检测)

// 蜂鸣器频率对应的定时器0初值(晶振频率默认11.0592MHz,1机器周期=12/11059200≈1.085μs)
// 计算逻辑:定时时间=(65536-初值)×机器周期,蜂鸣器方波频率=1/(2×定时时间)(翻转一次为半个周期)
#define Hz200    63035   // 200Hz频率对应的定时器初值
#define Hz400    64285   // 400Hz频率对应的定时器初值
#define Hz800    64910   // 800Hz频率对应的定时器初值
#define Hz1000   65035   // 1000Hz频率对应的定时器初值
#define Hz2000   65285   // 2000Hz频率对应的定时器初值

unsigned short n = Hz200;  // 蜂鸣器初始频率对应的定时器初值(默认200Hz)

/**
 * @brief 定时器0初始化函数(16位定时模式,用于产生蜂鸣器驱动方波)
 * 配置说明:
 * 1. 使能全局中断(EA=1)和定时器0中断(ET0=1)
 * 2. 定时器0工作模式:模式1(16位定时,需手动重装初值)
 * 3. 初始加载默认频率(200Hz)的初值,定时器暂不启动(TR0=0,由按键控制启动)
 */
void init_timer0(void)
{
    IE |= (1 << 7) | (1 << 1);  // EA=1(全局中断使能),ET0=1(定时器0中断使能)
    // TCON |= (1 << 4);  // 注释:定时器0运行控制位TR0暂不置1,由按键触发启动
    
    // 配置定时器0工作模式(模式1:16位定时)
    TMOD &= ~(3 << 2);  // 清除定时器1的模式位(TMOD高4位,避免干扰)
    TMOD &= ~(3 << 0);  // 清除定时器0的模式位(TMOD低4位:M1M0初始化为00)
    TMOD |= (1 << 0);   // 定时器0模式1:M1=0,M0=1(16位定时模式)
    
    TH0 = n >> 8;       // 加载定时器0高8位初值(n为16位,右移8位取高字节)
    TL0 = n;            // 加载定时器0低8位初值
}

/**
 * @brief 定时器0中断服务函数(核心:翻转P2.1引脚,产生蜂鸣器驱动方波)
 * 逻辑说明:
 * 1. 每次定时器0溢出(达到定时时间),触发中断
 * 2. 翻转P2.1电平(高→低/低→高),产生方波信号
 * 3. 重新加载初值(16位模式无自动重装,需手动补初值,确保定时精度)
 */
void timer0_handler(void) interrupt 1
{
    P2 ^= (1 << 1);  // 翻转P2.1引脚电平(蜂鸣器接此引脚,方波驱动发声)
    TH0 = n >> 8;    // 重新加载定时器高8位初值(避免下一次定时时间偏差)
    TL0 = n;         // 重新加载定时器低8位初值
}

/**
 * @brief 主函数(按键检测+频率切换+定时器启停控制)
 * 功能流程:
 * 1. 初始化定时器0和按键
 * 2. 循环检测按键:
 *    - 按键1~5:设置对应频率的初值,启动定时器0(蜂鸣器按对应频率发声)
 *    - 无按键(key=0):停止定时器0(蜂鸣器静音)
 */
int main(void)
{
    int key;  // 优化:将key定义在函数开头,兼容部分51编译器的局部变量处理规则
    init_timer0();  // 初始化定时器0
    init_key();     // 初始化按键(需在key.h中实现,如配置按键引脚、消抖等)
    
    while(1)  // 主循环:持续检测按键
    {
        key = key_pressed();  // 读取按键值(返回0=无按键,1~5=对应按键)
        
        // 按键1:蜂鸣器频率设为200Hz,启动定时器0
        if(key == 1)
        {
            n = Hz200;
            TCON |= (1 << 4);  // TR0=1(启动定时器0,开始产生方波)
        }
        // 按键2:蜂鸣器频率设为400Hz,启动定时器0
        else if(key == 2)
        {
            n = Hz400;
            TCON |= (1 << 4);
        }
        // 按键3:蜂鸣器频率设为800Hz,启动定时器0
        else if(key == 3)
        {
            n = Hz800;
            TCON |= (1 << 4);
        }
        // 按键4:蜂鸣器频率设为1000Hz,启动定时器0
        else if(key == 4)
        {
            n = Hz1000;
            TCON |= (1 << 4);
        }
        // 按键5:蜂鸣器频率设为2000Hz,启动定时器0
        else if(key == 5)
        {
            n = Hz2000;
            TCON |= (1 << 4);
        }
        // 无按键:停止定时器0(蜂鸣器静音)
        else if(key == 0)
        {
            TCON &= ~(1 << 4);  // TR0=0(停止定时器0,方波中断,蜂鸣器不发声)
        }
    }
    return 0;  // 主函数返回(实际51单片机中此句可省略,循环永不退出)
}
    

key.c

cs 复制代码
#include <reg52.h>


void init_key(void)
{
	P1 |= (0x0f << 4);
}

int key_pressed(void)
{
	int ret = 0;

	if((P1 & (1 << 4)) == 0)
	{
		ret = 1;
	}
	else if((P1 & (1 << 5)) == 0)
	{
		ret = 2;
	}
	else if((P1 & (1 << 6)) == 0)
	{
		ret = 3;
	}
	else if((P1 & (1 << 7)) == 0)
	{
		ret = 4;
	}
	else if((P3 & (1 << 5)) == 0)
	{
		ret = 5;
	}
	return ret;
}
相关推荐
sheepwjl15 小时前
《嵌入式硬件(二):中断》
定时器·pwm·中断·按键·中断寄存器
逼子格1 天前
【Protues仿真】基于AT89C52单片机的温湿度测量
单片机·嵌入式硬件·定时器·硬件工程师·dht11·温湿度传感器·at89c52
逼子格3 天前
【Proteus仿真】8*8LED点阵控制系列仿真——循环显示数字/按键控制显示图案
单片机·嵌入式硬件·proteus·嵌入式·定时器·硬件工程师·led点阵
逼子格10 天前
【Protues仿真】定时器
单片机·嵌入式硬件·51单片机·定时器·硬件工程师·硬件工程师真题·at89c52
四谎真好看1 个月前
第六章第二节 定时器定时中断 & 定时器外部时钟
stm32·单片机·嵌入式硬件·定时器·timer
DIY机器人工房1 个月前
【科普】在STM32中有哪些定时器?
c语言·嵌入式·定时器·diy机器人工房
憧憬一下3 个月前
FreeRTOS定时器
嵌入式·freertos·定时器
Rocky4013 个月前
JavaEE->多线程:定时器
java·开发语言·多线程·定时器
Rocky4013 个月前
javaEE->多线程:线程池
java·运维·服务器·线程池·多线程·定时器