51单片机_中断&定时器

51单片机_中断&定时器

一、中断的基本概念

1、什么是中断

中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。
当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。
当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。

简单来说就是:

CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生),那么CPU就会暂停当前的工作(A事件),去执行B事件(中断响应和中断服务),然后B事件做完之后,再回到原来的事件(A事件)中继续工作。(中断的返回)。

image-20260111222302768

2、中断的作用与优势

随着计算机技术的应用,人们发现中断技术不仅解决了快速主机与I/O设备的数据传送问题,而且还有具有如下的优点:

  1. 分时操作:CPU可以分时为多个I/O设备服务,提高了计算机的利用率。
  2. 实时操作:CPU能够及时处理应用系统的随机事件,系统的实时性大大增强。
  3. 可靠性高:CPU具有处理设备故障及掉电等突发性事件能力,从而使系统可靠性更高。

3、51单片机的中断源

80C51 的中断系统有5个中断源(80C52 有6个),两个优先级,可实现二级中断嵌套。

  • 外部中断0 (INT0)
  • 外部中断1 (INT1)
  • 定时器/计数器0溢出中断
  • 定时器/计数器1溢出中断
  • 串行口中断

二、中断优先级

1、51单片机的中断优先级机制

51单片机支持多个中断源,每个中断源都有其特定的功能和用途。以下是常见的51单片机中断源及其对应的中断向量地址:

中断源 描述 中断向量地址 特殊功能寄存器 (SFR)
外部中断0 (INT0) 外部硬件引脚P3.2上的下降沿或低电平触发 0003H IE (Interrupt Enable) 寄存器, TCON (Timer/Counter Control) 寄存器
定时器0溢出中断 (TF0) 定时器0计数溢出时触发 000BH IE 寄存器, TCON 寄存器
外部中断1 (INT1) 外部硬件引脚P3.3上的下降沿或低电平触发 0013H IE 寄存器, TCON 寄存器
定时器1溢出中断 (TF1) 定时器1计数溢出时触发 001BH IE 寄存器, TCON 寄存器
串行口中断 (RI/TI) 串行口接收或发送完成时触发 0023H IE 寄存器, SCON (Serial Control) 寄存器

image-20260112102414835

一、(P3.2),可由 IT0 (TCON.0) 选择其为低电平有效还是下降沿有效。当CPU检测到 P3.2 引脚上出现有效的中断信号,中断标志 IE0 (TCON.1) 置1,向CPU申请中断。

二、 (P3.3),可由 IT1 (TCON.2) 选择其为低电平有效还是下降沿有效。当CPU检测到 P3.3 引脚上出现有效的中断信号,中断标志 IE0 (TCON.3) 置1,向CPU申请中断。

三、TF0 (TCON.5),片内定时器/计数器 T0 溢出中断请求标志。当定时/计数器 T0 发生溢出时,置位 TF0,并向 CPU 申请中断。

四、TF1 (TCON.7),片内定时/计数器 T1 溢出中断请求标志。当定时/计数器 T1 发生溢出时,置位 TF1,并向 CPU 申请中断。

五、RI (SCON.0) 或 TI (SCON.1),串行口中断请求标志。当串行口接收完一帧串行数据时置位 RI 或当串行口发送完一帧串行数据时置位 TI,向 CPU 申请中断。

2、中断源的配置

每个中断源都需要通过特殊功能寄存器(SFR)进行配置,以启用或禁用相应的中断。主要的SFR包括:

  • IE (Interrupt Enable) 寄存器:用于全局使能和个别中断源的使能。
  • TCON (Timer/Counter Control) 寄存器:用于控制定时器和外部中断的状态。
  • SCON (Serial Control) 寄存器:用于控制串行通信的状态。

例如,要启用外部中断0 (INT0),可以在程序中添加以下代码:

// 全局中断使能
EA = 1;
// 使能外部中断0
EX0 = 1;

3、设置和修改中断优先级的方法

中断优先寄存器 IP

D7 D6 D5 D4 D3 D2 D1 D0
功能 / / / PS PT1 PX1 PT0 PX0

IP寄存器用于设置每个中断源的优先级。IP寄存器的地址是B8H,其各位定义如下:

描述 默认值 功能
PS 串行口中断 (RI/TI) 的优先级 0 0=低优先级, 1=高优先级
PT1 定时器1 (TF1) 的优先级 0 0=低优先级, 1=高优先级
PX1 外部中断1 (INT1) 的优先级 0 0=低优先级, 1=高优先级
PT0 定时器0 (TF0) 的优先级 0 0=低优先级, 1=高优先级
PX0 外部中断0 (INT0) 的优先级 0 0=低优先级, 1=高优先级

4、示例代码

以下是如何在程序中配置IP寄存器的示例代码:

#include <reg51.h>
void main() {
    // 将外部中断0 (INT0) 设置为高优先级
    IP |= 0x01;  // 设置 PX0 位为 1
    // 将定时器0 (TF0) 设置为高优先级
    IP |= 0x04;  // 设置 PT0 位为 1
    // 将外部中断1 (INT1) 设置为低优先级
    IP &= ~0x02; // 清除 PX1 位
    // 将定时器1 (TF1) 设置为低优先级
    IP &= ~0x08; // 清除 PT1 位
    // 将串行口中断 (RI/TI) 设置为低优先级
    IP &= ~0x10; // 清除 PS 位
    // 其他初始化代码...
}

5、高优先级中断抢占低优先级中断

  • 高优先级中断可以抢占低优先级中断:如果当前正在处理一个低优先级中断,而此时发生了高优先级中断,CPU会暂停当前的低优先级中断服务程序(ISR),转而处理高优先级中断。
  • 同级中断不能相互抢占:如果两个中断源具有相同的优先级,那么先发生的中断会被优先处理,后发生的中断需要等待前一个中断处理完毕后才能被响应。
  • 嵌套中断:在某些情况下,可以允许中断嵌套,即在一个中断服务程序中再次进入另一个中断。这通常需要手动管理中断标志位和返回状态。

三、中断使能与禁止

中断允许寄存器 IE

D7 D6 D5 D4 D3 D2 D1 D0
功能 EA / / ES ET1 EX1 ET0 EX0
描述 默认值 功能
EA 全局中断使能 0 0=禁止所有中断, 1=允许所有已使能的中断
ES 串行口中断 (RI/TI) 使能 0 0=禁止串行口中断, 1=允许串行口中断
ET1 定时器1 (TF1) 中断使能 0 0=禁止定时器1中断, 1=允许定时器1中断
EX1 外部中断1 (INT1) 使能 0 0=禁止外部中断1, 1=允许外部中断1
ET0 定时器0 (TF0) 中断使能 0 0=禁止定时器0中断, 1=允许定时器0中断
EX0 外部中断0 (INT0) 使能 0 0=禁止外部中断0, 1=允许外部中断0

1、示例代码

#include <reg51.h>

void main() {
    // 启用全局中断
    EA = 1;
    // 启用外部中断0 (INT0)
    EX0 = 1;
    // 启用定时器0 (TF0) 中断
    ET0 = 1;
    // 禁用外部中断1 (INT1)
    EX1 = 0;
    // 禁用定时器1 (TF1) 中断
    ET1 = 0;
    // 禁用串行口中断 (RI/TI)
    ES = 0;
    // 其他初始化代码...
}

image-20260111222433321

2、中断控制LED灯

中断的响应条件:

  • 中断源有中断请求
  • 此中断的中断允许位为 1
  • CPU 开中断 (即EA = 1)

同时满足时,CPU才有可能响应中断。

#include <REGX52.H>

unsigned char flag = 0;       // 标志位定义,初始化为0

void Delay(unsigned int xms);  // 延时函数

void main()
{
    P2 = 0xfe;      // 初始点亮LED 1111 1110
    IT0 = 1;        // INT0 下降沿触发
    IT1 = 1;        // INT1 下降沿触发
    EA = 1;         // 总中断允许
    EX0 = 1;        // 开启 INT0 中断
    EX1 = 1;        // 开启 INT1 中断
    
    while(1)
    {
        if(flag == 1)
        {
            P2 = 0xfe;        // 点亮LED
            flag = 0;         // 清除标志
        }
        if(flag == 2)
        {
            P2 = 0xFF;        // 熄灭LED
            flag = 0;         // 清除标志
        }
    }
}

// INT0 中断函数(中断号 0)
void INT0_ISR() interrupt 0
{
    // 不需要手动开关中断,51单片机会自动处理
    flag = 1;      // 设置为模式1
}

// INT1 中断函数(中断号 2)
void INT1_ISR() interrupt 2
{
    flag = 2;      // 设置为模式2
}

void Delay(unsigned int xms)
{
    unsigned char i, j;
    while(xms--)
    {
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
    }
}

3、外部中断 1 控制流水灯

#include <REGX52.H>
#include <INTRINS.H>  // 包含移位函数头文件

unsigned char flag = 0;       // 标志位定义,初始化为0

void Delay(unsigned int xms);  // 延时函数

void main()
{
    P2 = 0xfe;      // 初始点亮LED 1111 1110
    IT1 = 1;        // INT1 下降沿触发
    EA = 1;         // 总中断允许
    EX1 = 1;        // 开启 INT1 中断
    
    while(1)
    {
        while(flag == 0)
        {
						P2 = _crol_(P2,1);
						Delay(500);
        }
    }
}


// INT1 中断函数(中断号 2)
void INT1_ISR() interrupt 2
{
		EX0 = 0;
    flag = !flag;
		EX0 = 1;
		IE1 = 0;  // 清除INT1中断标志
}

void Delay(unsigned int xms)
{
    unsigned char i, j;
    while(xms--)
    {
        i = 2;
        j = 239;
        do
        {
            while (--j);
        } while (--i);
    }
}

四、定时器中断

定时器/计数器的工作原理

STC89C5X 单片机内有两个可编程的定时/计数器 T0、T1 和一个特殊功能定时器 T2。定时/计数器的实质是加 1 计数器(16 位),由高 8 位和低 8 位两个寄存器 THx 和 TLx 组成。

它随着计数器的输入脉冲进行自加 1,也就是每来一个脉冲,计数器就自动加 1,当加到计数器为全 1 时,再输入一个脉冲就使计数器回零,且计数器的溢出使相应的中断标志位置 1,向 CPU 发出中断请求(定时/计数器中断允许时)。

如果定时/计数器工作于定时模式,则表示定时时间已到;如果工作于计数模式,则表示计数值已满。可见,由溢出时计数器的值减去计数初值才是加 1 计数器的计数值。

image-20260111214847107

image-20260111215450036

image-20260111215741431

image-20260111220532905

image-20260111220926555

image-20260111222649359

image-20260111223337438

image-20260111232951124

image-20260111233216308

五、实践应用

1)定时器实现LED灯闪烁

#include <REGX52.H>
#define uchar unsigned char
#define uint unsigned int
	
uint num = 0;
sbit led0 = P2^0;

void main()
{
	TMOD = 0x01;									// 设置定时器 T0 工作方式 1
	TH0 = (65536 - 18432) / 256;                    // 设置定时 500 ms 初值高 8 位	
	TL0 = (65536 - 18432) % 256;                    // 设置定时 500 ms 初值低 8 位 
	IE = 0x82;										// 允许定时器 T0 工作
	TR0 = 1;									    // T0运行位设置
	
//	机器周期 = 12 / 11.0592MHz ≈ 1.085μs
//  定时时间 = 18432 × 1.085μs ≈ 19999.872μs ≈ 20.000ms

	while(1)
	{
		if(num == 25)                // 20 * 25 = 500ms
		{
			num = 0;
			led0 = ~led0;
		}
	}
}

// 定时器中断程序
void T0_TIMER() interrupt 1
{
	TH0 = (65536 - 18432) / 256;  // 设置定时 500 ms 初值高 8 位	
	TL0 = (65536 - 18432) % 256;  // 设置定时 500 ms 初值低 8 位 
	num++;
	
}

常用定时器初值速查表(12MHz晶振)

定时时间 计数值 初值(十进制) TH0 TL0
1ms 1000 64536 252 24
5ms 5000 60536 236 120
10ms 10000 55536 217 16
18.432ms 18432 47104 184 0
20ms 20000 45536 177 224
50ms 50000 15536 60 176
65.536ms 65536 0 0 0
/**
 * @file Timer0_LED_Blink.c
 * @brief 使用定时器0实现1秒LED闪烁
 * @details 
 * - 系统时钟:12MHz,每个机器周期1μs
 * - 定时器0:模式1(16位定时器)
 * - 定时时间:1ms
 * - LED闪烁周期:1秒(1000次中断翻转一次)
 */

#include <REGX52.H>  // 包含51单片机寄存器定义头文件

/* 全局变量定义 */
unsigned int T0Count = 0;  // 定时器中断次数计数器,用于累计1ms中断次数

/* 函数声明 */
void Timer0_Init(void);    // 定时器0初始化函数

/**
 * @brief 主函数
 * @note 初始化定时器后进入无限循环,LED闪烁由中断处理
 */
void main(void)
{
    Timer0_Init();  // 调用定时器初始化函数
    
    while(1)        // 主循环
    {
        // 此处可以添加其他非时间敏感的任务
        // LED闪烁在中断服务程序中处理,不占用主循环时间
    }
}

/**
 * @brief 定时器0初始化函数
 * @details 配置定时器0为模式1(16位定时器),定时1ms
 * 配置步骤:
 * 1. 设置定时器模式
 * 2. 设置定时器初值
 * 3. 使能中断
 * 4. 启动定时器
 */
void Timer0_Init(void)
{
    /* 第一步:设置定时器0工作模式 */
    // TMOD寄存器:定时器模式寄存器
    // TMOD = 0x01 对应二进制 0000 0001
    // 低4位用于定时器0,高4位用于定时器1
    // 定时器0设置为模式1:16位定时器模式(TH0+TL0)
    TMOD = 0x01;
    
    /* 第二步:设置定时器初值 */
    // TF0:定时器0溢出标志,初始清零
    TF0 = 0;
    
    // 计算并设置定时器初值(1ms定时,12MHz晶振)
    // 计算公式:初值 = 65536 - (定时时间/机器周期)
    // 定时时间 = 1ms = 1000μs
    // 机器周期 = 1μs(12MHz晶振)
    // 初值 = 65536 - 1000 = 64536 = 0xFC18
    // 注意:原代码使用64535,这里修正为标准的64536
    // TH0 = 高8位 = 0xFC = 64536 / 256 = 252
    // TL0 = 低8位 = 0x18 = 64536 % 256 = 24
    TH0 = 64536 / 256;  // 设置高8位
    TL0 = 64536 % 256;  // 设置低8位
    
    /* 第三步:使能中断 */
    // ET0:定时器0中断使能位,置1使能
    ET0 = 1;
    // EA:总中断使能位,置1打开总中断
    EA = 1;
    // PT0:定时器0中断优先级,0为低优先级,1为高优先级
    PT0 = 0;  // 设置为低优先级
    
    /* 第四步:启动定时器0 */
    // TR0:定时器0运行控制位,置1启动定时器
    TR0 = 1;
}

/**
 * @brief 定时器0中断服务函数
 * @details 每1ms执行一次,累计1000次后翻转LED状态
 * @note interrupt 1 表示定时器0中断(中断向量号为1)
 */
void Timer0_Routine(void) interrupt 1
{
    /* 第一步:重新装入定时器初值 */
    // 模式1不是自动重装模式,需要手动重装初值
    TH0 = 64536 / 256;  // 重新装入高8位
    TL0 = 64536 % 256;  // 重新装入低8位
    
    /* 第二步:中断次数计数 */
    T0Count++;  // 中断次数加1
    
    /* 第三步:判断是否达到1000次(1秒) */
    if (T0Count >= 1000)  // 1000次×1ms = 1000ms = 1秒
    {
        T0Count = 0;     // 计数器清零
        
        /* 第四步:翻转LED状态 */
        // P2_0是P2口的第0位,对应一个LED
        // ~P2_0:读取P2_0的当前值并取反,实现翻转
        P2_0 = ~P2_0;    // LED状态翻转
    }
}

image-20260111232201553

image-20260112192830757

2)设计定时器流水灯

/**
 * @file LED_Control.c
 * @brief 使用定时器和按键控制LED流水灯
 * @details
 * - 使用Timer0实现500ms定时
 * - 按键K1切换LED流动方向
 * - 晶振:11.0592MHz
 */

#include <REGX52.H>
#include <INTRINS.H>

// 类型重定义
#define uchar unsigned char
#define uint unsigned int

// 全局变量
uchar KeyNum = 0;        // 按键键值
uchar LEDMode = 0;       // LED模式:0=左移,1=右移
sbit led0 = P2^0;        // P2.0引脚

// 函数声明
void Timer0_Init(void);  // 定时器0初始化
uchar Key(void);         // 按键扫描函数
void Delay(uint xms);    // 延时函数

/**
 * @brief 主函数
 */
void main(void)
{
    P2 = 0xFE;            // 初始状态:P2.0亮,其他灭(1111 1110)
    Timer0_Init();        // 初始化定时器0
    
    while(1)              // 主循环
    {
        KeyNum = Key();   // 获取按键键值
        
        if(KeyNum)        // 如果有按键按下
        {
            if(KeyNum == 1)  // 如果K1按键按下
            {
                LEDMode++;    // 模式切换
                if(LEDMode >= 2)  // 只有0和1两种模式
                {
                    LEDMode = 0;
                }
            }
            // 可以添加其他按键的处理
        }
        // 可以添加其他主循环任务
    }
}

/**
 * @brief 定时器0初始化
 * @note 使用11.0592MHz晶振,定时20ms
 */
void Timer0_Init(void)
{
    TMOD &= 0xF0;         // 清零Timer0位
    TMOD |= 0x01;         // 设置Timer0为模式1(16位定时器)
    
    // 计算20ms定时初值(11.0592MHz)
    // 机器周期 = 12 / 11.0592MHz ≈ 1.085μs
    // 20ms = 20000μs,需要计数值 = 20000 / 1.085 ≈ 18432
    // 初值 = 65536 - 18432 = 47104 = 0xB800
    TH0 = 0xB8;           // 高8位:0xB8 = 184
    TL0 = 0x00;           // 低8位:0x00 = 0
    
    TF0 = 0;              // 清除溢出标志
    ET0 = 1;              // 允许Timer0中断
    EA = 1;               // 开总中断
    TR0 = 1;              // 启动定时器
}

/**
 * @brief 定时器0中断服务函数
 * @note 每20ms中断一次,通过计数实现500ms定时
 */
void T0_TIMER() interrupt 1
{
    static uint T0Count = 0;  // 中断次数计数器
    
    // 重新装入初值(20ms定时)
    TH0 = 0xB8;          // 高8位
    TL0 = 0x00;          // 低8位
    
    T0Count++;           // 中断次数加1
    
    if(T0Count >= 25)    // 20ms × 25 = 500ms
    {
        T0Count = 0;     // 计数器清零
        
        // 根据模式控制LED流动
        if(LEDMode == 0)          // 模式0:左移
        {
            P2 = _crol_(P2, 1);   // 循环左移1位
        }
        else if(LEDMode == 1)     // 模式1:右移
        {
            P2 = _cror_(P2, 1);   // 循环右移1位
        }
    }
}

/**
 * @brief 按键扫描函数
 * @return 按键键值:1=K1, 2=K2, 3=K3, 4=K4, 0=无按键
 */
uchar Key(void)
{
    uchar KeyNumber = 0;
    
    // 检测K1(P3.1)
    if(P3_1 == 0)
    {
        Delay(20);               // 消抖延时
        while(P3_1 == 0);        // 等待按键释放
        Delay(20);               // 释放消抖
        KeyNumber = 1;
    }
    
    // 检测K2(P3.0)
    if(P3_0 == 0)
    {
        Delay(20);
        while(P3_0 == 0);
        Delay(20);
        KeyNumber = 2;
    }
    
    // 检测K3(P3.2)
    if(P3_2 == 0)
    {
        Delay(20);
        while(P3_2 == 0);
        Delay(20);
        KeyNumber = 3;
    }
    
    // 检测K4(P3.3)
    if(P3_3 == 0)
    {
        Delay(20);
        while(P3_3 == 0);
        Delay(20);
        KeyNumber = 4;
    }
    
    return KeyNumber;
}

/**
 * @brief 延时函数
 * @param xms 延时毫秒数
 * @note 粗略延时,不精确
 */
void Delay(uint xms)
{
    uint i, j;
    for(i = xms; i > 0; i--)
    {
        for(j = 110; j > 0; j--);
    }
}

3) 定时器时钟

main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hour;

void main()
{
	LCD_Init();
	Timer0Init();
	
	LCD_ShowString(1,1,"Clock:");	//上电显示静态字符串
	LCD_ShowString(2,1,"  :  :");
	
	while(1)
	{
		LCD_ShowNum(2,1,Hour,2);	//显示时分秒
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowNum(2,7,Sec,2);
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)	//定时器分频,1s
	{
		T0Count=0;
		Sec++;			//1秒到,Sec自增
		if(Sec>=60)
		{
			Sec=0;		//60秒到,Sec清0,Min自增
			Min++;
			if(Min>=60)
			{
				Min=0;	//60分钟到,Min清0,Hour自增
				Hour++;
				if(Hour>=24)
				{
					Hour=0;	//24小时到,Hour清0
				}
			}
		}
	}
}

LCD1602.c

#include <REGX52.H>

//引脚定义
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_E=P2^7;
#define LCD_DataPort P0

/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()		//@12.000MHz 1ms
{
	unsigned char i, j;

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

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_E=1;
	LCD_Delay();
	LCD_E=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_E=1;
	LCD_Delay();
	LCD_E=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init(void)
{
	LCD_WriteCommand(0x38);
	LCD_WriteCommand(0x0C);
	LCD_WriteCommand(0x06);
	LCD_WriteCommand(0x01);
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else
	{
		LCD_WriteCommand(0x80|(Column-1)+0x40);
	}
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number/LCD_Pow(10,i-1)%10);
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number1/LCD_Pow(10,i-1)%10);
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	unsigned char SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData('0'+SingleNumber);
		}
		else
		{
			LCD_WriteData('A'+SingleNumber-10);
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number/LCD_Pow(2,i-1)%2);
	}
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_Init(void);
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

Time0.c

#include <REGX52.H>

/**
  * @brief  定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		  //设置定时初值
	TH0 = 0xFC;		  //设置定时初值
	TF0 = 0;		    //清除TF0标志
	TR0 = 1;		    //定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}

Time0.h

#ifndef __TIME0_H__
#define __TIME0_H__

void Timer0_Init(void);

#endif

51单片机中断 - Violet_fan - 博客园

posted @ 2026-01-12 20:24  Q&25  阅读(59)  评论(1)    收藏  举报