数字秒表+普中51单片机+江科大自化协
1 系统框图
2 实验现象
一上电,数码管显示时间为00-00-00,即分钟-秒钟-Mini秒,范围为00-00-00——59-59-99,计时精度为0.01秒,能正确地进行计时,同时能记录一次时间,并在下一次计时后对上一次计时时间进行查询。当按键Key1按下时,秒表开始运行,再按下时,秒表停止;每按下一次,状态翻转一次;当按键Key2按下时,数码管显示时间清0;当按键Key3按下时,单片机将数码管显示的数值保存在AT24C02中,掉电不丢失;当按键Key4被按下时,单片机从AT24C02中读取数据,并显示在数码管上。
3 参考程序
3.1 主程序
#include <REGX52.H> #include "timer0.h" #include "key.h" #include "Nixie.h" #include "delayms.h" #include "at24c02.h" unsigned char KeyNum; unsigned char Min,Sec,MiniSec; unsigned char RunFlag; void main() { timer0_init(); while(1) { KeyNum=key(); if(KeyNum==1) //K1按键按下 { RunFlag=!RunFlag; //启动标识位翻转 } if(KeyNum==2) //K2按键按下 { Min=0; //时间清0 Sec=0; MiniSec=0; } if(KeyNum==3) //K3按键按下 { AT24C02_WriteByte(0,Min); //将分写入AT24C02的地址0 delayms(5); AT24C02_WriteByte(1,Sec); //将秒写入AT24C02的地址1 delayms(5); AT24C02_WriteByte(2,MiniSec);//将Mini秒写入AT24C02的地址2 delayms(5); } if(KeyNum==4) //K3按键按下 { Min=AT24C02_ReadByte(0); //读出AT24C02数据 Sec=AT24C02_ReadByte(1); MiniSec=AT24C02_ReadByte(2); } Nixie_SetBuf(1,Min/10); //设置显示缓存,显示数据 Nixie_SetBuf(2,Min%10); Nixie_SetBuf(3,11); Nixie_SetBuf(4,Sec/10); Nixie_SetBuf(5,Sec%10); Nixie_SetBuf(6,11); Nixie_SetBuf(7,MiniSec/10); Nixie_SetBuf(8,MiniSec%10); } } /** * @brief 秒表驱动函数,时间运行,在中断中调用 * @param 无,MiniSec:0-99, Sec:0-59, Min:0-59 * @retval 无 */ void Sec_Loop(void) { if(RunFlag) { MiniSec++; if(MiniSec>=100) { MiniSec=0; Sec++; if(Sec>=60) { Sec=0; Min++; if(Min>=60) { Min=0; } } } } } void timer0_routine() interrupt 1 { static unsigned int T0Count1,T0Count2,T0Count3; TL0=0x66; //设置定时初始值,1ms,@11.0592MHz TH0=0xFC; //设置定时初始值,1ms,@11.0592MHz T0Count1++; if(T0Count1>=20) { T0Count1=0; key_loop(); //20ms调用一次按键驱动函数 } T0Count2++; if(T0Count2>=2) { T0Count2=0; Nixie_Loop(); //2ms调用一次数码管驱动函数 } T0Count3++; if(T0Count3>=10) { T0Count3=0; Sec_Loop(); //10ms调用一次数秒表驱动函数 } }
3.2 按键扫描函数(定时器扫描按键,20ms一次,不断扫描)
#include <reg52.h> #include "delayms.h" sbit key1 = P3^1; sbit key2 = P3^0; sbit key3 = P3^2; sbit key4 = P3^3; unsigned char Key_Num; /** * @brief 获取按键键码 * @param 无 * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下 */ unsigned char key(void) { unsigned char Temp=0; Temp=Key_Num; Key_Num=0; return Temp; } /** * @brief 获取独立按键键码 * @param 无 * @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0 */ unsigned char key_getstate() { unsigned char KeyNumber = 0; if(key1==0){KeyNumber=1;} if(key2==0){KeyNumber=2;} if(key3==0){KeyNumber=3;} if(key4==0){KeyNumber=4;} return KeyNumber; } /** * @brief 按键驱动函数,在中断中调用 * @param 无 * @retval 无 */ void key_loop(void) { static unsigned char NowState,LastState; LastState=NowState; //按键状态更新 NowState=key_getstate(); //获取按键当前状态 //如果上个时间点按键按下,当前时间点未按下,则是按键释放瞬间,以此避免消抖和松手检测 if(LastState==1 && NowState==0) { Key_Num=1; } if(LastState==2 && NowState==0) { Key_Num=2; } if(LastState==3 && NowState==0) { Key_Num=3; } if(LastState==4 && NowState==0) { Key_Num=4; } }
#ifndef _key_h_ #define _key_h_ unsigned char key(); void key_loop(void); #endif
3.3 数码管驱动函数(定时器扫描数码管,2ms不断扫描)
#include <REGX52.H> #include "delayms.h" //数码管显示缓存区,其中10为不显示,对应Nixietable[10]=0x00 unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10}; //数码管段码表,0-9,不显示,- unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40}; /** * @brief 设置显示缓存区 * @param Location 要设置的位置,范围:1~8 * @param Number 要设置的数字,范围:段码表索引范围 * @retval 无 */ void Nixie_SetBuf(unsigned char Location,Number) { Nixie_Buf[Location]=Number; } /** * @brief 数码管扫描显示 * @param Location 要显示的位置,范围:1~8 * @param Number 要显示的数字,范围:段码表索引范围 * @retval 无 */ void Nixie_Scan(unsigned char Location,Number) { P0=0x00; //段码清0,消影 switch(Location) //位码输出 { case 1:P2_4=1;P2_3=1;P2_2=1;break; case 2:P2_4=1;P2_3=1;P2_2=0;break; case 3:P2_4=1;P2_3=0;P2_2=1;break; case 4:P2_4=1;P2_3=0;P2_2=0;break; case 5:P2_4=0;P2_3=1;P2_2=1;break; case 6:P2_4=0;P2_3=1;P2_2=0;break; case 7:P2_4=0;P2_3=0;P2_2=1;break; case 8:P2_4=0;P2_3=0;P2_2=0;break; } P0=NixieTable[Number]; //段码输出 } /** * @brief 数码管驱动函数,在中断中调用 * @param 无 * @retval 无 */ void Nixie_Loop(void) { static unsigned char i=1; Nixie_Scan(i,Nixie_Buf[i]); i++; if(i>=9){i=1;} }
#ifndef __NIXIE_H__ #define __NIXIE_H__ void Nixie_SetBuf(unsigned char Location,Number); void Nixie_Scan(unsigned char Location,Number); void Nixie_Loop(void); #endif
3.4 定时器函数(T0)
#include <REGX52.H> /** * @brief 定时器0初始化,1毫秒@11.0592MHz * @param 无 * @retval 无 */ void timer0_init(void) //1毫秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式,1111_0000,&,高四位保留,低四位清零 TMOD |= 0x01; //设置定时器模式,0000_0001,|,高四位保留,设置模式为T0 TL0 = 0x66; //设置定时初始值,1ms,@11.0592MHz TH0 = 0xFC; //设置定时初始值,1ms,@11.0592MHz TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0=1; //打开定时器T0中断开关 EA=1; //打开中断系统总开关 PT0=0; //设置T0中断优先级,低 }
#ifndef _timer0_h_ #define _timer0_h_ void timer0_init(void); #endif
3.5 I2C驱动函数
#include <REGX52.H> sbit I2C_SCL=P2^1; sbit I2C_SDA=P2^0; /** * @brief I2C通信开始 * @param 无 * @retval 无 */ void I2C_Start(void) { I2C_SCL=1; //空闲状态 I2C_SDA=1; //空闲状态 I2C_SDA=0; I2C_SCL=0; } /** * @brief I2C通信结束 * @param 无 * @retval 无 */ void I2C_Stop(void) { I2C_SDA=0; I2C_SCL=1; //回到空闲状态 I2C_SDA=1; //回到空闲状态 } /** * @brief I2C主机向从机发送一个字节,SCL为同步信号,低电平写数据 * @param Byte 要发送的字节 * @retval 无 */ void I2C_SendByte(unsigned char Byte) { unsigned char i; for(i=0;i<8;i++) //一个字节,8bit { I2C_SDA=Byte&(0x80>>i); //SCL为低电平,主机为发送器,写数据 I2C_SCL=1; //SCL为高电平,从机为接收器,读数据 I2C_SCL=0; //时序要求,51单片机速度比较慢 } } /** * @brief I2C主机接收从机一个字节,SCL为同步信号,高电平读数据 * @param 无 * @retval 接收到的一个字节数据 */ unsigned char I2C_ReceiveByte(void) { unsigned char i,Byte=0x00; I2C_SDA=1; //主机释放数据线SDA for(i=0;i<8;i++) { I2C_SCL=1; //主机作为接收器 if(I2C_SDA) Byte|=(0x80>>i); //读数据 I2C_SCL=0; //从机作为发送器,写数据 } return Byte; } /** * @brief I2C主机发送应答 * @param AckBit 应答位,0为应答,1为非应答 * @retval 无 */ void I2C_SendAck(unsigned char AckBit) { I2C_SDA=AckBit; I2C_SCL=1; I2C_SCL=0; } /** * @brief I2C主机接收应答位 * @param 无 * @retval 接收到的应答位,0为应答,1为非应答 */ unsigned char I2C_ReceiveAck(void) { unsigned char AckBit; I2C_SDA=1; I2C_SCL=1; AckBit=I2C_SDA; I2C_SCL=0; return AckBit; }
#ifndef _i2c_h_ #define _i2c_h_ void I2C_Start(void); void I2C_Stop(void); void I2C_SendByte(unsigned char Byte); unsigned char I2C_ReceiveByte(void); void I2C_SendAck(unsigned char AckBit); unsigned char I2C_ReceiveAck(void); #endif
3.6 AT24C02控制函数
#include <REGX52.H> #include "i2c.h" #define AT24C02_ADDRESS 0xA0 /** * @brief AT24C02写入一个字节 * @param WordAddress 要写入字节的地址 * @param Data 要写入的数据 * @retval 无 */ void AT24C02_WriteByte(unsigned char WordAddress,Data) { I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); I2C_SendByte(WordAddress); I2C_ReceiveAck(); I2C_SendByte(Data); I2C_ReceiveAck(); I2C_Stop(); } /** * @brief AT24C02读取一个字节 * @param WordAddress 要读出字节的地址 * @retval 读出的数据 */ unsigned char AT24C02_ReadByte(unsigned char WordAddress) { unsigned char Data; I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); I2C_SendByte(WordAddress); I2C_ReceiveAck(); I2C_Start(); I2C_SendByte(AT24C02_ADDRESS|0x01); I2C_ReceiveAck(); Data=I2C_ReceiveByte(); I2C_SendAck(1); I2C_Stop(); return Data; }
#ifndef _at24c02_h_ #define _at24c02_h_ void AT24C02_WriteByte(unsigned char WordAddress,Data); unsigned char AT24C02_ReadByte(unsigned char WordAddress); #endif
3.7 延时函数
#include <intrins.h> void delayms(unsigned int xms) //@11.0592MHz { unsigned char i, j; while(xms--) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } }
#ifndef _delayms_h_ #define _delayms_h_ delayms(unsigned int xms); #endif
4 参考资料