[经验分享] 51单片机:中断、定时器与PWM整合手册

[复制链接]
1737|0
Zhiniaocun 发表于 2025-9-6 22:10 | 显示全部楼层 |阅读模式
一、中断系统
中断系统是单片机实现实时处理的核心机制,用于响应内外紧急事件,核心包含定义、源、流程、嵌套、向量表及控制寄存器六大模块。




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

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




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

(1)IE寄存器(中断允许,地址:A8H,可位寻址)




(2)TCON寄存器(定时/中断控制,地址:88H,可位寻址)




二、定时器系统
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,不可位寻址)







(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翻转)
(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)定时器控制和五个按键控制蜂鸣器不同频率的声音

#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

#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;
}



————————————————
版权声明:本文为CSDN博主「眰恦ゞLYF」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_69590097/article/details/151191158

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

65

主题

260

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部