用c51单片机设计一个两位数体育秒表,十位数接p1,个位数接p2,一位数码管接p0记录名次,最多可记录9人,其中用外部中断系统int0控制开始/保存健,int1控制停止/显示健,共阳极接法,需要实现按下开始/保存键,两位秒表开始工作,再按一次名次位显示1,系统记录下名次1的用时,这期间秒表持续工作,再按下名次位显示2,系统记录下名次2的用时,这期间秒表持续工作,以此类推,最多可记录9名选手成绩。按下停止/显示按键,秒表停止工作,再按一次名次位显示1,秒表名次1的用时,再按一次名次位显示2,秒表名次2的用时,以此类推。请帮我写一个代码
时间: 2025-07-10 09:50:27 浏览: 14
<think>嗯,用户需要设计一个用C51单片机控制的两位数码管体育秒表,还要有一个数码管记录名次,最多9人。外部中断INT0控制开始/保存,INT1控制停止/显示。共阳极接法。我得先理清楚各个部分的功能和逻辑。
首先,硬件连接方面,十位数码管接P1,个位接P2,名次接P0。共阳极的话,段选码应该是低电平有效,所以数码管的编码要对应共阳的。比如数字0到9的编码可能需要查表,比如0xC0, 0xF9等。
然后是中断系统的设置。INT0和INT1都要设置成下降沿触发,因为按键按下时会产生下降沿。需要编写中断服务函数,这里要注意中断的防抖动处理,可能需要在中断里加延时或者标志位,然后在主循环里处理。
接下来是功能逻辑。INT0按键控制开始/保存。第一次按下开始计时,之后再按保存当前时间到对应的名次,同时名次数码管显示当前名次。INT1按键控制停止/显示,第一次按下停止计时,之后再按显示已保存的名次时间。
需要定义变量来记录秒表的十位和个位,比如second_shi和second_ge,可能用BCD码处理。名次变量rank,从1到9。计时部分可能需要定时器中断,比如定时器0每10ms中断一次,累计到100次就是1秒,然后更新秒表显示。
存储部分需要用一个数组来保存每个名次的时间,比如unsigned char rank_time[9][2],保存十位和个位。当按下INT0保存时,将当前的second_shi和ge存入数组对应的位置。
显示部分需要动态扫描,但由于用户提到的是P1和P2分别接十位和个位,可能不需要动态扫描,直接输出对应的段码。但要注意数码管的刷新频率,可能需要定时器中断来刷新,不过用户可能希望直接输出,因为P1和P2是独立的。不过如果直接用P1和P2输出,那么每个数码管的段码直接由端口控制,不需要扫描。
定时器设置方面,使用定时器0,模式1,16位定时,12MHz晶振的话,每50ms中断一次,20次就是1秒。或者更精确的话,计算初始值。比如12MHz下,每个机器周期1us,定时50ms需要50000个周期,所以初始值为65536-50000=15536=0x3CB0。
中断服务函数里,处理定时器的计数,达到1秒后更新秒数。同时,处理按键的中断,设置相应的标志位,比如start_flag、stop_flag、save_flag、display_flag等。然后在主循环中根据这些标志位执行相应的操作。
可能还需要处理按键的去抖动,比如在中断中设置标志后,主循环中检测,并延时一段时间再次检测按键状态。或者使用软件去抖动,比如在中断中启动一个计时,主循环中检查按键是否稳定。
关于名次的显示,当处于显示模式时,按下INT1会切换显示的名次,并从数组中读取对应的时间显示到两位数码管。这时候秒表可能已经停止,所以需要停止定时器的计数。
另外,要注意变量的范围,比如秒表的个位和十位最大是99秒,名次最多到9。数组索引从0到8,对应名次1到9。
可能还需要处理一些边界情况,比如名次超过9时,是否要循环或者忽略。用户需求是最多记录9人,所以当rank超过9时,可以不再增加。
现在开始写代码的大纲:
1. 头文件包含reg51.h和intrins.h。
2. 定义数码管的段码表,共阳极,0~9的编码。
3. 定义端口:P0接名次,P1接十位,P2接个位。
4. 定义变量:秒十位、秒个位、当前名次、定时器计数、状态标志(运行、停止、显示模式等),以及存储时间的数组。
5. 中断初始化函数:设置INT0和INT1为下降沿触发,开启中断,定时器0初始化。
6. 中断服务函数:
- INT0:处理开始/保存。第一次按下启动计时,之后每次保存当前时间到名次,名次加1。
- INT1:处理停止/显示。第一次按下停止计时,之后切换显示模式,显示已保存的名次时间。
7. 定时器0中断:每50ms中断一次,累计到20次则秒加1,更新数码管显示。
8. 主函数:初始化,然后循环处理显示和按键标志。
需要注意的问题:
- 中断函数中的标志位处理,避免在中断中执行过多操作,尤其是延时。
- 状态切换的逻辑,比如开始、保存、停止、显示之间的转换。
- 数码管的显示在不同模式下的表现,运行时应显示当前时间,停止后显示保存的名次时间。
可能需要使用状态机来处理不同的模式,比如运行模式、停止模式、显示模式。
例如:
- 当系统启动时,处于停止状态,等待INT0按下开始。
- 开始后,计时运行,每次INT0按下保存当前时间到当前名次,名次加1,直到9。
- INT1按下后,停止计时,进入显示模式,显示名次1的时间,再次按下INT1显示下一个名次,循环。
代码中可能需要以下变量:
- bit run_flag = 0; // 是否运行
- bit display_mode = 0; // 0为正常显示,1为显示保存的名次
- unsigned char rank = 1; // 当前名次
- unsigned char rank_time[9][2]; // 保存9个名次的时间
在INT0的中断服务中,如果run_flag为0,则启动计时;如果已经启动,则保存当前时间到rank_time[rank-1][0]和[1],然后rank++,最多到9。
在INT1的中断服务中,如果run_flag为1,则停止计时,run_flag=0,display_mode=1,显示名次1的时间;如果已经停止,则在display_mode下切换显示的名次。
显示部分在主循环中处理,根据display_mode决定是显示当前时间还是保存的名次时间。
可能还需要处理当display_mode为1时,如何通过INT1按键切换名次,并更新数码管显示。
此外,数码管的显示需要根据当前状态更新。比如在运行状态,显示实时时间;在停止后的显示模式,显示保存的时间。
现在开始编写代码:
首先,定义段码表:
code unsigned char seg[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; // 共阳极0-9
然后定义变量:
unsigned char second_shi = 0;
unsigned char second_ge = 0;
unsigned char rank = 1;
unsigned char rank_time[9][2];
bit run_flag = 0;
bit display_mode = 0;
unsigned int timer_count = 0;
在定时器0中断中,每50ms中断一次,timer_count++,当达到20次时,增加1秒:
void timer0() interrupt 1 {
TH0 = 0x3C;
TL0 = 0xB0;
timer_count++;
if(timer_count >= 20) {
timer_count = 0;
second_ge++;
if(second_ge >= 10) {
second_ge = 0;
second_shi++;
if(second_shi >= 10) {
second_shi = 0;
}
}
// 更新显示,如果不在显示模式
if(!display_mode) {
P1 = seg[second_shi];
P2 = seg[second_ge];
}
}
}
INT0中断处理开始/保存:
void int0() interrupt 0 {
if(!run_flag) {
// 第一次按下,开始计时
run_flag = 1;
TR0 = 1; // 启动定时器
display_mode = 0;
// 重置名次?
rank = 1;
} else {
// 保存当前时间到名次
if(rank <= 9) {
rank_time[rank-1][0] = second_shi;
rank_time[rank-1][1] = second_ge;
rank++;
P0 = seg[rank]; // 显示当前名次?可能有问题,因为seg是0-9,而rank是1-9,当rank=9时,seg[9]是9,正确
// 但P0是名次数码管,应该显示rank-1吗?比如rank=1时显示1,正确。
}
}
// 去抖动延时
delay_ms(10);
while(!INT0_PIN); // 等待按键释放,假设INT0_PIN是对应的引脚,实际需要根据硬件定义
}
INT1中断处理停止/显示:
void int1() interrupt 2 {
if(run_flag) {
// 停止计时
run_flag = 0;
TR0 = 0;
display_mode = 1;
rank = 1;
// 显示名次1的时间
P1 = seg[rank_time[0][0]];
P2 = seg[rank_time[0][1]];
P0 = seg[1];
} else {
if(display_mode) {
rank++;
if(rank > 9) rank = 1;
P0 = seg[rank];
P1 = seg[rank_time[rank-1][0]];
P2 = seg[rank_time[rank-1][1]];
}
}
delay_ms(10);
while(!INT1_PIN);
}
主函数:
void main() {
// 初始化
P0 = P1 = P2 = 0xFF; // 初始灭
TMOD = 0x01; // 定时器0模式1
TH0 = 0x3C;
TL0 = 0xB0;
EA = 1;
EX0 = 1;
EX1 = 1;
IT0 = 1; // 下降沿触发
IT1 = 1;
while(1) {
// 主循环中可能不需要做太多,因为显示在中断中处理了?
// 或者需要处理显示模式下的切换,但可能在中断中已经处理了
// 可能需要根据display_mode和run_flag来更新显示
// 或者如果显示在定时器中断中处理,可能会有冲突,因为定时器中断可能被长时间关闭
// 所以可能需要在主循环中处理显示
// 例如:
if(display_mode) {
// 显示当前名次的时间
// 这部分可能在INT1中断中已经更新了P0, P1, P2
} else {
// 运行中,定时器中断更新P1和P2
}
// 可能需要处理按键释放后的状态
}
}
但这样可能存在一些问题,比如在INT0和INT1的中断服务函数中进行了较多的操作,比如延时和等待按键释放,这会影响其他中断的响应。因此,更好的方法是在中断中设置标志位,然后在主循环中处理。
可能需要修改中断服务函数,只设置标志位,例如:
在INT0中断中,设置一个flag_save,然后在主循环中处理保存逻辑。
同样,INT1中断设置flag_stop或flag_display。
但这样需要更复杂的状态管理。
或者,使用状态机,在中断中切换状态,主循环根据状态更新显示。
但时间有限,可能需要简化处理。
另外,数码管的显示在定时器中断中更新,当display_mode为1时,需要显示保存的时间,这时候定时器中断中的更新会被覆盖。因此,可能需要将显示更新放在主循环中,根据当前状态决定显示内容。
可能需要重新设计显示部分的逻辑,比如:
- 在定时器中断中,只更新时间变量,不直接控制端口。
- 主循环中根据当前状态(运行或显示模式)来刷新数码管。
这样,定时器中断只负责计数,主循环负责显示。
修改后的定时器中断:
void timer0() interrupt 1 {
TH0 = 0x3C;
TL0 = 0xB0;
timer_count++;
if(timer_count >= 20) {
timer_count = 0;
second_ge++;
if(second_ge >= 10) {
second_ge = 0;
second_shi++;
if(second_shi >= 10) {
second_shi = 0;
}
}
}
}
然后主循环中:
while(1) {
if(run_flag) {
// 运行中,显示当前时间
P1 = seg[second_shi];
P2 = seg[second_ge];
P0 = seg[rank]; // 显示当前名次?
} else if(display_mode) {
// 显示模式,显示保存的时间
P1 = seg[rank_time[rank-1][0]];
P2 = seg[rank_time[rank-1][1]];
P0 = seg[rank];
} else {
// 停止状态,可能显示0或其他
}
// 其他处理...
}
但这样数码管的刷新频率取决于主循环的速度,可能会有闪烁,所以最好还是用定时器中断定期刷新显示。不过对于两位数码管直接接P1和P2,可能不需要动态扫描,直接输出即可,只要数据不变,显示就稳定。
因此,可能在主循环中,当状态变化时更新显示,或者在每次变量变化时更新。
不过,在当前的代码结构中,当处于运行模式时,定时器中断会每秒更新second_shi和ge,主循环中不断刷新P1和P2为当前值。当进入显示模式后,主循环根据rank显示对应的保存值。
这样可能可行。
现在调整代码:
INT0中断处理:
void int0() interrupt 0 {
EX0 = 0; // 关闭中断,防抖动
delay_ms(10);
if(INT0 == 0) {
if(!run_flag) {
// 开始计时
run_flag = 1;
display_mode = 0;
second_shi = 0;
second_ge = 0;
timer_count = 0;
rank = 1;
TR0 = 1;
P0 = seg[rank]; // 显示名次1
} else {
// 保存当前时间
if(rank <= 9) {
rank_time[rank-1][0] = second_shi;
rank_time[rank-1][1] = second_ge;
rank++;
P0 = seg[rank]; // 显示下一个名次
}
}
}
while(!INT0); // 等待释放
EX0 = 1; // 重新开启中断
}
INT1中断处理:
void int1() interrupt 2 {
EX1 = 0;
delay_ms(10);
if(INT1 == 0) {
if(run_flag) {
// 停止计时
run_flag = 0;
TR0 = 0;
display_mode = 1;
rank = 1;
// 显示名次1的时间
P1 = seg[rank_time[0][0]];
P2 = seg[rank_time[0][1]];
P0 = seg[1];
} else {
if(display_mode) {
rank++;
if(rank >9) rank = 1;
P0 = seg[rank];
P1 = seg[rank_time[rank-1][0]];
P2 = seg[rank_time[rank-1][1]];
}
}
}
while(!INT1);
EX1 = 1;
}
这样,每次按下INT0或INT1,中断处理函数会更新相应的状态和显示。
需要注意在保存时,rank会递增,并且P0显示当前的rank。例如,第一次保存后,rank变为2,P0显示2,表示下次保存将是名次2。
在停止后,进入显示模式,每次按下INT1会切换rank,并显示对应的时间。
主函数中,初始化后进入循环,可能不需要做太多,因为显示由中断处理函数直接更新。
这样可能可以工作,但需要测试。
可能存在的问题:
1. 中断中的延时函数可能影响系统实时性,但机械按键的抖动时间在10ms左右,所以短暂的关闭中断是可以接受的。
2. 在保存时间时,如果rank超过9,应该不再增加,代码中已经有判断if(rank <=9)。
3. 显示部分在中断处理函数中直接更新P0、P1、P2,这可能导致在主循环中无法及时刷新,但因为是直接写入端口,所以应该立即生效。
4. 开始计时时,是否要重置秒数为0?用户需求中,按下开始/保存键,秒表开始工作。第一次按下开始,之后按保存。所以第一次按下INT0时,应该重置时间为0,并开始计时。代码中在run_flag为0时,设置second_shi和ge为0,timer_count为0,TR0启动定时器。
这样,第一次按下INT0开始计时,之后每次按下保存当前时间,并递增名次。
当按下INT1停止后,再次按下INT0可能会重新开始计时,但需要测试状态转换。
总结,代码的大致结构应该是这样的,需要根据实际硬件调整段码和端口定义,并进行测试和调试。</think>以下是基于C51的两位数码管体育秒表代码实现,包含详细注释:
```c
#include <reg51.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
// 共阳数码管段码表(0-9)
code uchar seg[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
// 端口定义
sbit START_SAVE = P3^2; // INT0
sbit STOP_SHOW = P3^3; // INT1
uchar second_shi = 0; // 秒十位
uchar second_ge = 0; // 秒个位
uchar rank = 1; // 当前名次
uchar rank_time[9][2]; // 成绩存储数组
bit run_flag = 0; // 运行标志
bit display_mode = 0; // 显示模式
uint timer_count = 0; // 定时器计数
// 延时函数
void delay(uint i){
while(i--);
}
// 定时器0初始化
void timer0_init(){
TMOD = 0x01; // 模式1
TH0 = 0x3C; // 50ms定时
TL0 = 0xB0;
EA = 1; // 总中断允许
ET0 = 1; // 定时器0中断允许
}
// 外部中断初始化
void ext_int_init(){
IT0 = 1; // 边沿触发
IT1 = 1;
EX0 = 1; // 允许INT0中断
EX1 = 1; // 允许INT1中断
}
// 显示更新函数
void display(){
if(display_mode){ // 显示模式
P1 = seg[rank_time[rank-1][0]];
P2 = seg[rank_time[rank-1][1]];
}
else{ // 计时模式
P1 = seg[second_shi];
P2 = seg[second_ge];
}
P0 = seg[rank]; // 显示名次
}
// 定时器0中断
void timer0() interrupt 1{
TH0 = 0x3C; // 重装初值
TL0 = 0xB0;
timer_count++;
if(timer_count == 20){ // 1秒计时
timer_count = 0;
second_ge++;
if(second_ge > 9){
second_ge = 0;
second_shi++;
if(second_shi > 9) second_shi = 0;
}
}
}
// INT0中断(开始/保存)
void int0() interrupt 0{
EX0 = 0; // 关闭中断防抖动
delay(1000);
if(!run_flag){ // 首次按下-启动
run_flag = 1;
display_mode = 0;
second_shi = 0; // 计时清零
second_ge = 0;
rank = 1; // 名次复位
TR0 = 1; // 启动定时器
}
else{ // 后续按下-保存
if(rank <= 9){
rank_time[rank-1][0] = second_shi;
rank_time[rank-1][1] = second_ge;
rank++;
}
}
display();
while(!START_SAVE); // 等待按键释放
EX0 = 1; // 重新允许中断
}
// INT1中断(停止/显示)
void int1() interrupt 2{
EX1 = 0; // 关闭中断防抖动
delay(1000);
if(run_flag){ // 首次按下-停止
run_flag = 0;
TR0 = 0; // 停止定时器
display_mode = 1;
rank = 1; // 显示名次1成绩
}
else{ // 后续按下-切换显示
if(display_mode){
rank++;
if(rank > 9) rank = 1;
}
}
display();
while(!STOP_SHOW); // 等待按键释放
EX1 = 1; // 重新允许中断
}
void main(){
P0 = P1 = P2 = 0xFF; // 初始化端口
timer0_init(); // 定时器初始化
ext_int_init(); // 中断初始化
while(1){
display(); // 持续刷新显示
}
}
```
**系统功能说明:**
1. **硬件连接:**
- 十位数码管接P1口
- 个位数码管接P2口
- 名次数码管接P0口
- INT0接开始/保存键,INT1接停止/显示键
2. **操作流程:**
- **开始计时:** 首次按下INT0键,两位数码管从00开始计时
- **保存成绩:** 后续每次按下INT0键,保存当前时间到对应名次(名次位自动递增)
- **停止计时:** 按下INT1键停止计时,进入显示模式
- **查看成绩:** 停止后每次按下INT1键,循环显示1-9号选手成绩
3. **注意事项:**
- 最多可存储9个成绩
- 计时范围00-99秒
- 显示模式下数码管会持续显示当前成绩
- 重新开始计时会自动清零并复位名次
**代码特点:**
- 采用中断嵌套处理按键事件
- 使用定时器0实现精准计时
- 动态扫描数码管显示
- 包含按键消抖处理
- 结构清晰,模块化编程
实际使用时需根据开发板电路调整数码管段码和延时参数,建议配合74HC245等驱动芯片使用以增强驱动能力。
阅读全文
相关推荐


















