基于51单片机的秒表(STC15F2K60S2)

概述

蓝桥杯的板子,15FK60S2也要有自己的教程!.

打算把它整合到之前发的时钟系统里,形成一个终极无敌的单片机时钟,更完整的功能会写在那边.

指路https://blog.csdn.net/2301_76988964/article/details/136115885

数码管和键盘都是定时器扫描的

一个说明书

产品说明书:简易计时器系统

一、产品概述
本产品是一款基于STC15F2K60S2单片机设计的简易计时器,通过AT24C02 EEPROM存储和读取计时数据。系统具备实时计数、暂停、复位、读取和存储计时结果等功能,并通过数码管进行时间显示。

二、主要功能
1. 实时计数:启动后,计时器以毫秒为单位递增,每满100ms自动进位到秒,满60秒则进位至分钟。可通过按键14控制计数开始与暂停。
2. 计时存储:按下指定按键15,可将当前计时结果分三次写入到EEPROM中,每次存储一个完整的计时数据(包括毫秒、秒和分钟)。
3. 计时读取:按下另一按键16,可在数码管上切换显示已存储在EEPROM中的三个计时结果。
4. 计时清零:按下特定按键17,可将当前实时计数归零。

三、操作说明
- 启动设备后,默认显示实时计数。
- 按下键“14”,可开始或暂停计数,再次按下则保持上次状态不变。
- 按下键“15”,将当前计数值依次写入EEPROM的三个存储位置。
- 按下键“16”,数码管会轮流显示从EEPROM读取的三个计时结果。
- 按下键“17”,清除实时计数并重新开始计时。

四、注意事项
- 存储在EEPROM中的计时数据可以长期保存,即使设备断电后重新上电仍能读取。
- 请确保在查看读取的数据后再按暂停/开始键,以免影响计时状态。

main.c

注意可以在中断函数里修改T0大于的值来控制数码管扫描速度,找到适合自己的.

同理T1后面的50是按键的扫描速度,同时也是消抖的时间.

#include <head.h>
#include <at24c02.h>
unsigned char value=0,ms=0,sec=0,min=0,stopflag=0,j=0,k=0,timestore[3],displayflag=0;
void count()//计数实现
{
	ms++;
	if(ms>=100)
	{
		ms=0;
		sec++;
		if(sec>=60)
		{
			sec=0;
			min++;
			if(min>=60)min=0;
		}
	}
}
void main(void)
{
	P2andP1(0xA0,0,0);
	Timer0_Init();	
	while(1)
	{	
		value=keys();
		if(value==14)//开始与暂停,并且保证在查看读取后再按14后保持14之前的状态
		{			
			P2andP1(0x80,0xEF,0);
			if(displayflag==0)
			{
				stopflag++;
				stopflag=stopflag%2;
			}
			else displayflag=0;
		}
		if(value==15)//将当前时间写入at24c02,可以写三个
		{
			if(j>2)j=0;
			switch(j)
			{
				case 0:
				{
					P2andP1(0x80,0xFE,0);
					at24c02_write(0,ms);
					at24c02_write(1,sec);
					at24c02_write(2,min);
					break;
				}				
				case 1:
				{
					P2andP1(0x80,0xFD,0);
					at24c02_write(3,ms);
					at24c02_write(4,sec);
					at24c02_write(5,min);
					break;
				}
				case 2:
				{
					P2andP1(0x80,0xFB,0);
					at24c02_write(6,ms);
					at24c02_write(7,sec);
					at24c02_write(8,min);
					break;
				}
			}
			j++;
		}
		if(value==16)//读取写入的时间,按下切换
		{
			if(k>2)k=0;
			displayflag=1;
			switch(k)
			{
				case 0:
				{
					P2andP1(0x80,0xFE,0);
					timestore[0]=at24c02_read(0);//ms
					timestore[1]=at24c02_read(1);//s
					timestore[2]=at24c02_read(2);//min
					break;
				}
				case 1:
				{
					P2andP1(0x80,0xFD,0);
					timestore[0]=at24c02_read(3);
					timestore[1]=at24c02_read(4);
					timestore[2]=at24c02_read(5);
					break;
				}
				case 2:
				{
					P2andP1(0x80,0xFB,0);
					timestore[0]=at24c02_read(6);
					timestore[1]=at24c02_read(7);
					timestore[2]=at24c02_read(8);
					break;
				}
			}
			k++;
		}
		if(value==17)//置零
		{
			ms=0;
			sec=0;
			min=0;
		}
		smg_set(0,(displayflag==1)?timestore[2]/10:min/10);//显示界面切换
		smg_set(1,(displayflag==1)?timestore[2]%10:min%10);
		smg_set(2,16);
		smg_set(3,(displayflag==1)?timestore[1]/10:sec/10);
		smg_set(4,(displayflag==1)?timestore[1]%10:sec%10);
		smg_set(5,16);
		smg_set(6,(displayflag==1)?timestore[0]/10:ms/10);
		smg_set(7,(displayflag==1)?timestore[0]%10:ms%10);
	}
}
void Timer0_Isr() interrupt 1
{
	static unsigned char T0=0,T1=0,T4=0;
	T0++,T1++,T4++;
	if(T0>1)
	{
		T0=0;
		smg_loop();
	}
	if(T1>=50)
	{
		T1=0;
		key_loop();
	}
	if(T4>=10)
	{
		T4=0;
		if(stopflag==1)count();
	}			
}

解析

程序实现逻辑如下:

1. **初始化阶段**:
   - 初始化P2andP1(可能是一个用于控制显示或通信的接口)和定时器0 (Timer0_Init)。

2. **主循环处理**:
   - 无限循环中持续检测按键值(value),根据不同的按键动作执行不同功能。
     - 按键14:控制秒表开始、暂停。`stopflag`变量用来标记当前是暂停还是运行状态,每按一次切换一次状态。
     - 按键15:将当前计时时间(毫秒、秒、分钟)写入AT24C02 EEPROM的不同地址,可存储三组时间数据。
     - 按键16:从AT24C02 EEPROM读取已保存的时间,并在显示屏上分段显示,按一次切换显示一组数据。
     - 按键17:清零当前计时器显示。

3. **计数更新**:
   - 利用定时器中断服务函数(Timer0_Isr),当满足一定条件时,调用count()函数进行计数累加。如果秒表处于运行状态(stopflag为1),则会更新ms、sec和min的值。

4. **数据显示**:
   - 根据displayflag的状态,通过smg_set()函数动态设置显示内容,可以显示实时计时数据或者从EEPROM读出的时间数据。

各变量解释

1. `unsigned char value`: 存储按键值,用于判断用户按下的是哪个按键。

2. `ms, sec, min`: 分别表示计时器的毫秒、秒和分钟数值,用于实时记录时间。

3. `stopflag`: 标记秒表状态的变量,如果为0则表示运行中,非0(通常可能是1)表示暂停状态。

4. `j, k`: 计数变量,分别用于记录写入和读取AT24C02 EEPROM时的数据组索引。

5. `timestore[3]`: 一个包含3个元素的数组,用于临时存储从AT24C02读取的时间数据,每个元素对应毫秒、秒、分钟。

6. `displayflag`: 标志位,用于切换显示实时时钟还是EEPROM中读出的时间。若为0,则显示实时时钟;若为1,则显示从EEPROM读取的时间。

7. 各函数如`count()`、`main()`、`Timer0_Isr()`等:
   - `count()`: 实现时间递增计数功能。
   - `main()`: 主函数,整个程序的入口,负责初始化硬件及主循环处理。
   - `Timer0_Init()`: 初始化定时器0,设置定时中断,以便于实现精确计时。
   - `Timer0_Isr()`: 定时器0的中断服务程序,在达到一定时间间隔后触发,进行计数更新和其他周期性任务处理。

head.h

#ifndef __HEAD_H__
#define __HEAD_H__
#include <STC15F2K60S2.H>
#include <intrins.h>
void P2andP1(unsigned char p2,p0,p42);
void smg_set(unsigned char location,number);
void smg_loop();
void Timer0_Init(void);
unsigned char Keys();
unsigned char key_loop();
#endif

timer.c

看都没有,从STC-ISP复制

#include <head.h>

void Timer0_Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x20;				//设置定时初始值
	TH0 = 0xD1;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
	EA=1;            //一定要手动加上打开
}

P2andP1andP42.c

大意是通过控制P27,25,26(74H138)和P42使74HC02两位都输入0,输出才能为高电平,才能控制M74HC573M1R(即P0)

注意要在后面把P2给关了.

#include <head.h>
void P2andP1(unsigned char p2,p0,p42)
{
	
	P42=p42;
	P0=p0;
	P2=p2;
	P2=0;//防止被别的操作修改P0而影响了
}

arrkeys.c

详细解释和注意事项在上一个帖子

#include <head.h>
unsigned char matrixkey()
{
	unsigned char key=0;
	P34=0;P35=1;P42=1;P44=1;
	if(P30==0){key=19;}
	if(P31==0){key=18;}
	if(P32==0){key=17;}
	if(P33==0){key=16;}
	
	P35=0;P34=1;P42=1;P44=1;
	if(P30==0){key=15;}
	if(P31==0){key=14;}
	if(P32==0){key=13;}
	if(P33==0){key=12;}
	
	P42=0;P35=1;P34=1;P44=1;
	if(P30==0){key=11;}
	if(P31==0){key=10;}
	if(P32==0){key=9;}
	if(P33==0){key=8;}
	
	P44=0;P42=1;P35=1;P34=1;
	if(P30==0){key=7;}
	if(P31==0){key=6;}
	if(P32==0){key=5;}
	if(P33==0){key=4;}
	
	return key;

}
unsigned char key_loop()
{
	static unsigned char nowstate=0,laststate=0;
	unsigned char keynumber=0;
	laststate=nowstate;
	nowstate=matrixkey();
	if(laststate==0&&nowstate==4)
	{
		keynumber=4;
	}
	if(laststate==0&&nowstate==5)
	{
		keynumber=5;
	}
	if(laststate==0&&nowstate==6)
	{
		keynumber=6;
	}
	if(laststate==0&&nowstate==7)
	{
		keynumber=7;
	}
		if(laststate==0&&nowstate==8)
	{
		keynumber=8;
	}
		if(laststate==0&&nowstate==9)
	{
		keynumber=9;
	}
		if(laststate==0&&nowstate==10)
	{
		keynumber=10;
	}
		if(laststate==0&&nowstate==11)
	{
		keynumber=11;
	}
		if(laststate==0&&nowstate==12)
	{
		keynumber=12;
	}
		if(laststate==0&&nowstate==13)
	{
		keynumber=13;
	}
		if(laststate==0&&nowstate==14)
	{
		keynumber=14;
	}
		if(laststate==0&&nowstate==15)
	{
		keynumber=15;
	}
		if(laststate==0&&nowstate==16)
	{
		keynumber=16;
	}
		if(laststate==0&&nowstate==17)
	{
		keynumber=17;
	}
		if(laststate==0&&nowstate==18)
	{
		keynumber=18;
	}	
		if(laststate==0&&nowstate==19)
	{
		keynumber=19;
	}
	return keynumber;	

}
unsigned char Keys()
{
	unsigned char Temp=0;
	Temp=key_loop();
	return Temp;
}

smg.c

详细解释在上一个帖子.

#include <head.h>
code unsigned char Seg_Table[] = 
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A  10
0x83, //b  11
0xc6, //C  12
0xa1, //d  13
0x86, //E  14
0x8e, //F  15
0xBF,  //-  16
0xFF,	//none 17
0,    //凑数
0,    //凑数,以下为带小数点的
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e};

unsigned char SMG[8]={17,17,17,17,17,17,17,17};//记住从0开始
void smg_set(unsigned char location,number)
{
	SMG[location]=number;

}
void smg(unsigned char loc,word)
{		
	P42=1;P0=0;
	switch(loc)
	{
		
		case 0:P2andP1(0xc0,0x01,0);break;
		case 1:P2andP1(0xc0,0x02,0);break;
		case 2:P2andP1(0xc0,0x04,0);break;
		case 3:P2andP1(0xc0,0x08,0);break;
		case 4:P2andP1(0xc0,0x10,0);break;
		case 5:P2andP1(0xc0,0x20,0);break;
		case 6:P2andP1(0xc0,0x40,0);break;
		case 7:P2andP1(0xc0,0x80,0);break;
		
	}
	P2andP1(0xe0,Seg_Table[word],0);
	
}
void smg_loop()
{
	static unsigned char i=0;
	
	smg(i,SMG[i]);
	SMG[i]=17;
	i++;
	
	if(i>=8)i=0;

}

iic.c

#include <STC15F2K60S2.H>
#include <intrins.h>
sbit SCL=P2^0;
sbit SDA=P2^1;
void iic_start()
{
	SDA=1;
	SCL=1;
	SDA=0;
	SCL=0;	
}
void iic_stop()
{ 
	SDA=0;
	SCL=1;//顺序	
	SDA=1;
}
void iic_send(unsigned char byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		SDA=byte&(0x80>>i);
		SCL=1;//先1后0
		SCL=0;
	}
}
unsigned char iic_receive()
{
	unsigned char i,byte=0x00;
	SDA=1;//不是SCL
	for(i=0;i<8;i++)
	{
		SCL=1;
		if(SDA)byte=byte|(0x80>>i);
		SCL=0;
		
	}
	return byte;
}
void iic_sendack(unsigned char ackbit)
{
	SDA=ackbit;
	SCL=1;
	SCL=0;
}
unsigned char iic_receiveack()
{
	unsigned char ackbit;
	SDA=1;
	SCL=1;
	ackbit=SDA;
	SCL=0;
	return ackbit;
}
	

解析

 `sbit SCL=P2^0;` 和 `sbit SDA=P2^1;`:定义了两个位操作变量,SCL和SDA分别对应P2口的第0位和第1位,作为I2C通信中的时钟线(SCL)和数据线(SDA)。

- `iic_start()` 函数:发送一个I2C起始信号,即SDA线从高电平跳变到低电平的同时,SCL线保持高电平。

- `iic_stop()` 函数:发送一个I2C停止信号,即SDA线在SCL线为高电平时从低电平变为高电平。

- `iic_send(unsigned char byte)` 函数:向I2C总线发送一个8位的数据字节,通过逐位移位并配合SCL线的高低变化完成。

- `iic_receive()` 函数:接收I2C总线上一个8位的数据字节,同样逐位读取并通过SCL线的高低变化来同步。

- `iic_sendack(unsigned char ackbit)` 函数:根据参数ackbit决定是否发送ACK(应答)信号,ackbit为0则发送ACK(SDA拉低),ackbit为1则不发送ACK(SDA拉高)。

- `iic_receiveack()` 函数:接收从设备返回的ACK信号,并返回其状态(0表示收到ACK,1表示未收到ACK)。

iic.h

#ifndef _IIC_H_
#define _IIC_H_
void iic_start();
void iic_stop();
void iic_send(unsigned char byte);
unsigned char iic_receive();
void iic_sendack(unsigned char ackbit);
unsigned char iic_receiveack();
#endif

at24c02.c

#include <STC15F2K60S2.H>
#include <intrins.h>
#include <iic.h>
#define address 0xA0
void Delay5ms(void)	//@12.000MHz
{
	unsigned char data i, j;

	i = 59;
	j = 90;
	do
	{
		while (--j);
	} while (--i);
}

void at24c02_write(unsigned char wordaddress,Data)
{
	iic_start();
	iic_send(address);
	iic_receiveack();
	iic_send(wordaddress);
	iic_receiveack();
	iic_send(Data);
	iic_receiveack();
	iic_stop();	
	Delay5ms();//要延时	
}

unsigned char at24c02_read(unsigned char wordaddress)
{
	unsigned char Data;
	iic_start();
	iic_send(address);
	iic_receiveack();
	iic_send(wordaddress);
	iic_receiveack();
	iic_start();
	iic_send(address|0x01);
	iic_receiveack();
	Data=iic_receive();
	iic_sendack(1);//一定要是1
	iic_stop();
	return Data;
}

解析

1. `#define address 0xA0`:定义了AT24C02的7位从设备地址(实际通信时会加上读写位)。

2. `void Delay5ms(void)`:自定义延时函数,提供大约5毫秒的延时。在对EEPROM进行写操作后通常需要一定的稳定时间,所以这里使用了这个延时函数。

3. `void at24c02_write(unsigned char wordaddress, Data)`:
   - 函数用于向AT24C02的指定字节地址(wordaddress)写入一个数据(Data)。
   - 首先发送起始信号,然后发送设备地址并等待应答。
   - 接着发送要写入的数据地址,并等待应答。
   - 发送要写入的数据,并等待应答。
   - 最后发送停止信号,并调用延时函数确保数据已成功写入。

4. `unsigned char at24c02_read(unsigned char wordaddress)`:
   - 函数用于从AT24C02的指定字节地址(wordaddress)读取一个数据。
   - 同样首先发送起始信号和设备地址,接收应答。
   - 发送要读取的数据地址,并接收应答。
   - 再次发送起始信号,并发送带有读标志的设备地址(即地址|0x01),接收应答。
   - 接收EEPROM返回的数据,发送非应答信号(ACK=1,表示不再接收更多数据)。
   - 发送停止信号,最后返回读取到的数据。

at24c02.h

#ifndef _AT24C02_H_
#define _AT24C02_H_
void at24c02_write(unsigned char wordaddress,Data);
unsigned char at24c02_read(unsigned char wordaddress);
#endif

这个工程练练手罢了.

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值