实验电路图中按键K1和K2分别接于P3.2和P3.3,发光二极管D1接于P0.4,共阴极数码管LED1接于P2口。 软件编程原理为:将数码管0~F的显示字模保存在数组中,K1和K2的按键动作INTO和INT1分别作为的中断请求,在中断函数中进行LED灯控制和数码管显示刷新。初始化后,主函数处于无限循环状态,等待中断请求。编写C51源程序,要求采用中断方法实现如下功能:程序启动后,D1处于熄灯、LED1处于黑屏状态;单击K1,可使D1亮灯状态反转一次;单击K2,可使LED1显示值加1,并按十六进制数显示, 达到F后重新从0开始。 参考结果
时间: 2025-06-25 15:06:41 浏览: 24
<think>好的,我现在需要帮助用户编写一个基于80C51芯片的C51源程序,使用中断方法实现按键控制LED灯和数码管显示的功能。具体要求是通过INT0和INT1中断,按键K1控制D1的状态反转,按键K2控制数码管LED1的十六进制数值加1,循环显示0到F。初始状态D1熄灭,LED1黑屏。
首先,我得回顾一下80C51的中断系统。80C51有两个外部中断,INT0和INT1,分别对应中断向量地址0x03和0x13。用户需要使用这两个中断来处理按键K1和K2的触发。中断触发方式通常有电平触发和边沿触发,这里可能需要边沿触发,因为按键按下会产生一个下降沿,这样可以避免持续触发的问题。需要设置IT0和IT1寄存器为1,选择边沿触发模式。
接下来是LED的控制。用户提到D1初始状态为熄灭,所以需要将对应的引脚初始化为高电平。假设D1连接到P2.0,那么在初始化时设置P2 |= 0x01; 或者直接操作P2口的相应位。当中断发生时,反转P2.0的状态,可以使用位取反操作,比如P2 ^= 0x01;。
然后是数码管的部分。数码管是共阴极的,所以需要段码来显示0到F。需要创建一个数组,存储0到F对应的段码值。例如,0x3F对应0,0x06对应1,依此类推,直到0x71对应F。数码管的控制可能连接到P0口或者其他端口,假设这里连接到P0口。初始状态数码管黑屏,所以初始时P0输出0x00,或者不选中数码管,可能需要位选控制。但用户可能只是单个数码管,所以直接输出段码即可。初始时显示黑屏,所以初始段码为0x00,当K2按下时,数值加1,循环0到F,对应段码数组中的索引从0到15,超过15时回到0。
接下来考虑中断服务函数的编写。INT0的中断服务函数需要处理D1的状态反转,而INT1的中断服务函数需要处理数码管的数值加1。注意,中断服务函数需要声明为中断号,使用interrupt关键字,INT0对应中断号0,INT1对应中断号2。同时,需要设置中断允许寄存器IE,允许外部中断0和1,并开启总中断EA。
还需要考虑按键消抖的问题。硬件消抖或者软件消抖。由于使用中断,可能在中断服务函数中加入延时来消除抖动,但中断服务函数应尽量简短,所以更好的方法可能是在中断发生后,在中断服务函数中设置标志位,在主循环中处理状态变化和显示更新,同时进行消抖处理。不过用户可能希望直接在中断中处理,这样的话需要确保按键的硬件消抖已经做好,否则可能会有误触发。
另外,数码管的显示需要持续刷新,但这里用户可能只需要在数值变化时更新一次,因为数码管如果是静态显示的话,不需要持续刷新。但如果使用动态显示,可能需要定时器中断来刷新,但用户的需求中没有提到,所以可能只需要在数值变化时更新一次段码即可。
现在,综合这些考虑,编写代码的大致步骤:
1. 包含头文件,比如reg51.h或reg52.h,根据具体型号。
2. 定义数码管的段码数组,共阴极,0到F的十六进制显示。
3. 声明全局变量,用于存储当前的数码管显示值,比如unsigned char disp_value = 0; 初始为0,但初始状态数码管黑屏,所以可能需要初始值为16或者其他标志,或者初始时输出0x00。
4. 定义LED和数码管的引脚,比如sbit LED = P2^0; 数码管连接到P0口。
5. 初始化函数:设置中断触发方式,IT0和IT1为边沿触发;打开外部中断允许EX0和EX1,以及总中断EA;初始化LED为熄灭(高电平),数码管初始为黑屏,P0输出0x00。
6. 中断服务函数:INT0处理LED反转,INT1处理数码管数值加1,并取模16,然后更新段码到P0口。
7. 主函数中调用初始化,然后进入无限循环等待中断。
需要注意的问题:
- 数码管的初始状态应为黑屏,即段码全灭。假设段码数组的最后一个元素(比如索引16)对应全灭,或者初始时disp_value为0,但数码管初始时不显示,可能需要初始时将P0设为0x00。或者在初始化时,将disp_value设为0,但数码管显示的是段码数组[0],即0,但用户要求初始黑屏,所以需要disp_value初始为16,对应的段码为0x00。或者在段码数组中,索引0对应0,索引1到15对应1-F,索引16对应0x00。但这样可能需要处理disp_value的范围为0-16,每次K2按下时加1,当达到16时回到0。或者更简单的方式,初始时disp_value为0,但数码管显示0,但用户要求初始黑屏,这可能不符合。所以需要调整段码数组,例如,初始disp_value为16,对应段码0x00,当按下K2时,disp_value变为0,显示0,再按到1,直到F,再按回到0,但这样需要处理disp_value从16开始加1,然后对17取模17?或者更简单的方式,初始化时disp_value为0,但数码管不显示,即段码输出0x00。当第一次按下K2时,disp_value变为0,显示0?这可能有问题。可能需要重新考虑。
用户要求初始状态数码管黑屏,所以初始时数码管不显示任何内容。因此,数码管的段码应该全灭,即0x00。因此,初始化时,将disp_value设为0,但数码管的段码输出0x00。当K2按下时,disp_value变为1,显示0的段码?或者disp_value初始为0,但第一次按下时显示0,第二次显示1,直到F后再回到0。但初始状态需要黑屏,所以可能需要一个标志位,比如初始时disp_enable为0,按下K2后disp_enable置1,disp_value从0开始。或者,disp_value初始为0,但段码数组的第一个元素是0x00,之后0到F。或者将段码数组的0对应0x00,1对应0的段码,这样不太合理。这可能比较复杂。
或许更简单的办法是,数码管的显示值disp_value初始为0,但数码管初始显示黑屏,即P0 = 0x00。当K2第一次按下时,disp_value变为0,显示0的段码,然后每次加1。这样初始时,disp_value为0,但初始段码输出0x00。这需要段码数组中的0对应的段码是0x3F,而初始时强制输出0x00。或者,将disp_value初始化为一个特殊值,比如-1,然后在显示时判断,如果disp_value为-1,则输出0x00,否则输出段码数组[disp_value]。但C51中unsigned char更方便,所以可能用disp_value初始为16,然后取模17,当等于16时输出0x00。段码数组有17个元素,0-15对应0-F,16对应0x00。这样,初始化时disp_value=16,数码管显示0x00。当K2按下时,disp_value = (disp_value + 1) % 17,如果结果超过16(即17取模),回到0。这样第一次按下K2时,disp_value变为0,显示0,之后每次加1,到F时disp_value=15,再按一次变为16,显示黑屏,再按回到0。但用户要求的是循环显示0到F,所以可能需要disp_value在0-15之间循环,初始时显示黑屏。因此,可能需要两个变量:一个控制是否显示,另一个控制数值。或者,初始时disp_value为0,但数码管显示黑屏,当K2按下时,disp_value从0开始显示,并每次加1,到F后回到0。但初始时如何显示黑屏?可能需要在初始化时将P0设为0x00,然后在第一次中断时开始显示0。这样,初始时disp_value=0,但数码管显示黑屏,当K2按下时,disp_value变为0,并显示0,之后每次加1。这可能不符合,因为初始disp_value为0,但显示的是黑屏,而按下K2后显示0,这可能需要在中断处理中,当disp_value为0时,第一次按下显示0,而不是黑屏。这可能需要重新设计。
或者,初始时disp_value设为16,数码管显示0x00(黑屏),每次K2按下时,disp_value自增1,然后对16取模,这样disp_value的范围是0-15,对应的段码0-F。这样,初始时disp_value=16,显示0x00,当按下K2,disp_value变为0,显示0,之后每次加1,到F时disp_value=15,再按回到0。这样符合用户要求:初始黑屏,按下K2后显示0,之后循环0-F。但需要调整段码数组,使得当disp_value在0-15时显示0-F,当disp_value=16时显示黑屏。因此,段码数组可以定义为17个元素,索引0-15对应0-F,索引16对应0x00。初始化时disp_value=16,显示0x00,每次K2按下时,disp_value = (disp_value + 1) % 17,如果结果为16,则显示黑屏,否则显示对应的数值。但用户要求的是K2控制数码管数值加1并循环显示0到F,所以当disp_value超过15时,应该回到0,而不是显示黑屏。因此,这可能不符合用户需求。因此,正确的做法应该是:初始时数码管黑屏,当K2第一次按下时显示0,之后每次加1到F,再回到0。所以,初始时disp_value可以设为0,但数码管显示黑屏,当K2按下时,disp_value变为0,并显示0。这可能需要一个标志位,比如初始时有一个标志表示是否已经按下过K2。或者,在初始化时,数码管显示黑屏,disp_value初始为0,但第一次按下K2时,disp_value增加到0,显示0,之后每次加1。这可能需要处理disp_value在按下时从0开始,但初始显示为黑屏。这可能需要另一个变量,比如unsigned char display_enabled = 0; 初始为0,当K2按下时,display_enabled=1,disp_value=0,显示0。之后每次K2按下,disp_value自增1并取模16,display_enabled保持1。这样,在中断服务函数中,当display_enabled为0时,首次按下K2会设置disp_value=0,display_enabled=1,并显示0;之后每次按下,disp_value自增1,显示相应的值。这样可能更符合用户需求。
不过这样会增加变量,可能复杂一些。或者,初始时数码管显示黑屏,即P0=0x00,而disp_value=0。当K2按下时,disp_value自增1,然后取模16,并显示disp_value对应的段码。例如,第一次按下,disp_value=1,显示1,但这样初始值可能不对。或者,初始时disp_value=0xFF(或其他非0-15的值),在中断中判断,如果是初始值,则设为0,否则自增。例如:
在中断服务函数中:
if (disp_value == 0xFF) {
disp_value = 0;
} else {
disp_value = (disp_value + 1) % 16;
}
P0 = seg_code[disp_value];
但初始化时disp_value设为0xFF,数码管显示seg_code[0xFF]可能越界,所以需要确保seg_code数组足够大,或者单独处理。这可能不太安全。
或许更简单的方式是,初始化时disp_value=0,数码管显示黑屏,即P0=0x00。当K2按下时,disp_value自增1并取模16,然后P0=seg_code[disp_value]。这样,初始时虽然disp_value=0,但P0=0x00,当第一次按下K2时,disp_value变为1,显示seg_code[1]即1,这不符合要求。用户需要的是第一次按下K2显示0,之后递增。所以问题在于初始disp_value=0时,如何让第一次按下显示0,而初始时显示黑屏。
可能的解决方案:数码管的显示由disp_value控制,但初始时disp_value=16,数码管显示0x00。当K2按下时,disp_value=0,显示0,之后每次加1到15,再回到0。这样,段码数组需要17个元素,索引0-15对应0-F,索引16对应0x00。初始化disp_value=16,显示0x00。中断处理中,当K2按下时,disp_value = (disp_value + 1) % 17; 如果结果等于16,则显示0x00,否则显示对应的段码。但用户要求的是K2控制数码管数值加1并循环显示0到F,所以当disp_value超过15时应该回到0,而不是显示黑屏。因此,正确的逻辑应该是,无论初始disp_value是多少,按下K2时disp_value自增1并取模16,然后显示对应的段码。而初始时disp_value=16,这样第一次按下后变为0,显示0,之后每次加1到15,再回到0。但这样需要段码数组的索引0-15对应0-F,而disp_value的范围是0-15,初始时设为16,这样第一次按下K2时,disp_value=0,显示0。这样符合用户的需求。所以初始化时,disp_value=16,数码管显示黑屏,当K2按下时,disp_value变为0,显示0,之后每次加1,到F后回到0。
这样,段码数组可以定义为16个元素(0-15对应0-F),然后初始化时如果disp_value为16,数码管输出0x00。在中断处理中,当K2按下时,disp_value自增1,取模16,然后如果disp_value在0-15之间,输出对应的段码,否则输出0x00。不过,当disp_value自增1并取模16后,结果只能是0-15,所以初始disp_value=16,第一次按下时,disp_value= (16 + 1) % 16 = 1,这显然错误。哦,这里的问题,取模16的话,16 +1=17,17 mod 16=1,所以第一次按下会显示1,这不符合要求。因此,正确的做法应该是,disp_value初始为-1,然后每次按下加1,取模16,得到0-15。这样,初始时disp_value=-1,数码管显示黑屏。第一次按下时,disp_value=0,显示0。之后每次加1,到15后回到0。但C51中使用unsigned char更常见,所以可能需要用disp_value初始为255(即-1的8位无符号表示),然后每次加1后对16取模:
disp_value = (disp_value + 1) % 16;
这样,初始disp_value=255,第一次按下时,255+1=256,256 mod 16=0,显示0。符合要求。之后每次加1,到15后回到0。数码管的显示在disp_value为0-15时显示对应的段码,否则显示0x00。但disp_value在取模后总是0-15,所以初始时需要特殊处理。或者,在中断服务函数中,当disp_value为0-15时显示段码,否则显示0x00。但根据上面的计算,disp_value初始为255,第一次按下后变为0,显示段码0。这可行。
因此,段码数组定义为16个元素,索引0-15对应0-F的段码。数码管的输出在中断服务函数中为seg_code[disp_value],但disp_value初始为255,当按下K2时,disp_value = (disp_value + 1) % 16; 所以第一次按下后disp_value=0,显示0。数码管初始时,因为disp_value=255,不在0-15范围内,所以输出0x00。这需要判断disp_value是否在有效范围内,但会增加代码复杂度。或者,直接将disp_value初始为0,但数码管初始输出0x00,当K2按下时,disp_value自增1,显示对应的段码。但这样第一次按下会显示1,而不是0。这不符合用户需求。
看来这个问题需要更仔细的处理。正确的逻辑应该是:数码管初始黑屏,当第一次按下K2时显示0,之后每次加1到F,然后回到0。因此,初始时disp_value=0,但数码管显示黑屏。当K2按下时,disp_value自增1,显示disp_value % 16对应的段码。但这样第一次按下会显示1,这不对。因此,必须让第一次按下K2时显示0,之后递增。这可能需要在中断服务函数中,当第一次按下时,将disp_value设为0,之后每次加1。这可以通过一个标志位来实现,比如初始时标志位为0,第一次按下时设置disp_value=0,标志位置1,之后每次加1。例如:
unsigned char disp_value = 0;
bit first_press = 1;
在INT1中断服务函数中:
if (first_press) {
disp_value = 0;
first_press = 0;
} else {
disp_value = (disp_value + 1) % 16;
}
P0 = seg_code[disp_value];
这样,初始时first_press=1,第一次按下时disp_value设为0,显示0,之后每次加1。数码管初始时显示黑屏,因为在主函数初始化时,P0=0x00,而中断发生后才会更新为0的段码。但这样在初始化时,P0被设为0x00,进入主循环后,当第一次中断发生时,P0被更新为seg_code[0]=0x3F,显示0。这符合用户需求:初始黑屏,按下K2后显示0,之后每次加1。
综上,处理数码管的正确步骤:
1. 初始化时,P0=0x00(黑屏),disp_value=0,first_press=1。
2. 在INT1中断中,当first_press为1时,设置disp_value=0,first_press=0,否则disp_value自增1并取模16。
3. 更新P0为seg_code[disp_value]。
这样,第一次按下K2时显示0,之后每次加1。
现在,关于中断服务函数的编写,需要注意:
- INT0和INT1的中断服务函数需要分别处理LED和数码管。
- 中断服务函数中需要清除中断标志位,但80C51的外部中断标志(IE0和IE1)在边沿触发模式下会自动清除,而在电平触发模式下需要软件清除。这里使用边沿触发,所以无需手动清除。
- 需要防止中断重入,但由于按键操作通常不会频繁到中断处理时间过长,所以可能不需要特别处理。
最后,代码的大致结构:
#include <reg51.h>
sbit LED = P2^0; // 假设D1连接P2.0
sbit K1 = P3^2; // INT0连接到P3.2
sbit K2 = P3^3; // INT1连接到P3.3
unsigned char code seg_code[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F, // 9
0x77, // A
0x7C, // b
0x39, // C
0x5E, // d
0x79, // E
0x71 // F
};
unsigned char disp_value = 0;
bit first_press = 1;
void init() {
// 初始化中断
IT0 = 1; // 边沿触发
IT1 = 1;
EX0 = 1; // 允许INT0中断
EX1 = 1; // 允许INT1中断
EA = 1; // 开总中断
LED = 1; // 初始熄灭
P0 = 0x00; // 数码管黑屏
}
void INT0_isr() interrupt 0 {
LED = !LED; // 反转LED状态
}
void INT1_isr() interrupt 2 {
if (first_press) {
disp_value = 0;
first_press = 0;
} else {
disp_value = (disp_value + 1) % 16;
}
P0 = seg_code[disp_value];
}
void main() {
init();
while(1);
}
但需要确认数码管的连接是否正确。例如,数码管的段码是否连接到P0口,并且共阴极。此外,可能需要加上拉电阻等硬件配置,但代码中不需要处理。
此外,按键K1和K2应连接到INT0(P3.2)和INT1(P3.3),并且通常需要上拉电阻,按下时接地,触发下降沿中断。
可能的改进:
- 在中断服务函数中加入消抖处理,例如延时检测,但会影响中断响应。更好的方法是在中断中设置标志位,在主循环中处理,但用户要求使用中断方式,所以可能需要在中断中处理。
- 检查数码管的段码是否正确,共阴极的段码可能需要不同的编码,比如0的段码是0x3F,这通常是共阴极的。
综上,以上代码应该可以实现用户的需求:K1控制LED反转,K2控制数码管从0到F循环显示,初始状态LED熄灭,数码管黑屏。</think>根据需求,以下为您设计的基于80C51的中断控制程序实现方案:
### 系统设计说明
1. 使用$INT0$处理K1按键控制LED状态反转
2. 使用$INT1$处理K2按键控制数码管数值递增
3. 数码管采用共阴极连接,使用查表法实现十六进制显示
4. 中断触发方式设置为边沿触发模式[^1]
### 实现代码
```c
#include <reg51.h>
// 硬件定义
#define LED P2_0 // D1连接P2.0
#define SEG_PORT P0 // 数码管连接P0口
// 数码管段码表(共阴极0-F)
unsigned char code SegCode[16] = {
0x3F, 0x06, 0x5B, 0x4F, // 0-3
0x66, 0x6D, 0x7D, 0x07, // 4-7
0x7F, 0x6F, 0x77, 0x7C, // 8-B
0x39, 0x5E, 0x79, 0x71 // C-F
};
volatile unsigned char display_val = 0; // 显示数值存储
volatile bit first_press = 1; // 首次按下标志
// 中断初始化
void init_interrupt() {
IT0 = 1; // 设置INT0边沿触发
IT1 = 1; // 设置INT1边沿触发
EX0 = 1; // 允许INT0中断
EX1 = 1; // 允许INT1中断
EA = 1; // 总中断允许
}
// INT0中断服务程序
void int0_isr() interrupt 0 {
LED = !LED; // LED状态反转
}
// INT1中断服务程序
void int1_isr() interrupt 2 {
if(first_press) { // 首次按下处理
SEG_PORT = SegCode[0];
first_press = 0;
} else {
display_val = (display_val + 1) % 16;
SEG_PORT = SegCode[display_val];
}
}
void main() {
LED = 1; // 初始状态LED熄灭
SEG_PORT = 0x00;// 数码管初始黑屏
init_interrupt();
while(1); // 主循环保持空转
}
```
### 关键实现解析
1. **中断配置**:
- 通过设置$TCON$寄存器中的$IT0/IT1$位为1,选择边沿触发方式
- 中断允许寄存器$IE$配置为$EX0=1$,$EX1=1$,$EA=1$[^1]
2. **数码管显示**:
- 使用查表法实现十六进制转换,查表公式:
$$ \text{显示值} = SegCode[display\_val] $$
- 首次按下时从黑屏状态跳转到显示0值
3. **LED控制**:
- 采用位取反操作实现状态反转:
$$ LED = \neg LED $$
4. **循环显示**:
- 通过取模运算实现0-F循环:
$$ display\_val = (display\_val + 1)\ \text{mod}\ 16 $$
### 硬件连接建议
1. LED连接:P2.0端口接限流电阻(220Ω)和发光二极管到地
2. 数码管连接:P0口接七段码引脚,公共端接地
3. 按键连接:K1接P3.2(INT0),K2接P3.3(INT1),推荐使用10kΩ上拉电阻
### 功能验证方法
1. 初始上电时观察D1应熄灭,数码管无显示
2. 按下K1时D1应改变状态(亮/灭交替)
3. 首次按下K2时数码管应显示0,后续每次按下递增显示值
阅读全文
相关推荐

















