概述
蓝桥杯的板子,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
这个工程练练手罢了.