目录
用定时器可实现秒表计时的功能,而要实现秒表存储数据的功能需要用到AT24C02。
一、概念
1、AT24C02
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。
●存储介质:E2PROM
●通讯接口:I2C总线
●容量:256字节
2、I2C总线
(1)I2C时序结构
在了解AT24C02前,我们先了解一下其通信接口,即I2C总线的时序结构。我们可以将通过I2C协议实现主机和从机通信的过程分为六个部分:
①设置起始条件:SCL高电平期间,SDA从高电平切换到低电平。
即保证SDA置1,后SCL也置1(为了时序拼图完整:均从1开始),SDA变为0,SCL后变为0。
SCL时钟频率最大为1000Hz,而单片机IO口的时钟频率为500Hz,故有足够的时间来读取数 据,不用设置延时。
②设置停止条件:SCL高电平期间,SDA从低电平切换到高电平。
即保证SDA为0,SCL变为或保持1,SDA后变为1.
③发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
④接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA) 。
⑤发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
⑥接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
(2)I2C数据帧
①发送一帧数据
用杆子的例子来解释,主机先控制杆子,发出地址,然后松开杆子的控制权,然后对应的从机控制杆子,给个0拉下杆子返回给主机,表示接收到数据了(应答),然后主机再次得到控制器,开始发数据,循环往复。
②接收一帧数据
两个合并
可以这样理解:开始上课,老师向小明提问,小明回答收到,老师问1+1=多少,小明回答收到,老师问1+2=多少,小明回答收到......问完,老师让小明回答,小明说收到,小明回答2,老师回答收到,小明回答3,老师回答收到......回答完毕老师让小明坐下,结束。
4、AT24C02数据帧
①字节写:在WORD ADDRESS处写入数据DATA②随机读:读出在WORD ADDRESS处的数据DATA
AT24C02的固定地址为1010,可配置地址本开发板上为000 所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
二、代码
功能:按键1按下开始计时,再按下计时暂停;按键2按下,分秒清0;按键3按下,将数据写入AT24C02;按键4按下,读出AT24C02的数据,并在数码管上显示。
1、main.c
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Delay.h"
#include "Nixie.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned char Min, Sec, CenSec;
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;
CenSec = 0;
}
if(KeyNum == 3) //K3按键按下
{
AT24C02_WriteByte(0, Min); //将分秒写入AT24C02
Delay(5);
AT24C02_WriteByte(1, Sec);
Delay(5);
AT24C02_WriteByte(2, CenSec);
Delay(5);
}
if(KeyNum == 4) //K4按键按下
{
Min = AT24C02_ReadByte(0); //读出AT24C02数据
Sec = AT24C02_ReadByte(1);
CenSec = 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, CenSec / 10);
Nixie_SetBuf(8, CenSec % 10);
}
}
/**
* @brief 秒表驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Sec_Loop(void)
{
if(RunFlag)
{
CenSec++;
if(CenSec >= 100)
{
CenSec = 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 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
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调用一次数秒表驱动函数
}
}
2、I2C.c
#include <REGX52.H>
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA = 1;//先拉高SDA
I2C_SCL = 1;//后拉高SCL
I2C_SDA = 0;
I2C_SCL = 0;
}
/**
* @brief I2C结束
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA = 0;//在停止之前保证SDA为0
I2C_SCL = 1;
I2C_SDA = 1;
}
/**
* @brief I2C发送一个字节
* @param byte 要发送的字节
* @retval 无
*/
void I2C_SendByte(unsigned char byte)
{
int i = 0;
for(i = 0; i < 8; i++)
{
I2C_SDA = byte & (0x80 >> i);
I2C_SCL = 1;
I2C_SCL = 0;
}
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval byte 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i = 0, byte = 0x00;
I2C_SDA = 1;
for(i = 0; i < 8; i++)
{
I2C_SCL = 1;
if(I2C_SDA)
{
byte |= (0x80 >> i);
}
I2C_SCL = 0;
}
return byte;
}
/**
* @brief I2C发送应答位
* @param AskBit 应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAsk(unsigned char AskBit)
{
I2C_SDA = AskBit;//发送数据位
I2C_SCL = 1;
I2C_SCL = 0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval AskBit 接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAsk(void)
{
unsigned char AskBit = 0;
I2C_SDA = 1;//接收前释放SDA
I2C_SCL = 1;//主机在SCL高电平期间读取数据位
AskBit = I2C_SDA;//接收数据位
I2C_SCL = 0;
return AskBit;
}
3、I2C.h
#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_SendAsk(unsigned char AskBit);
unsigned char I2C_ReceiveAsk(void);
#endif
4、AT24C02.c
#include <REGX51.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_ReceiveAsk();
I2C_SendByte(WordAddress);//写入字地址
I2C_ReceiveAsk();
I2C_SendByte(Data);
I2C_ReceiveAsk();
I2C_Stop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAsk();
I2C_SendByte(WordAddress);
I2C_ReceiveAsk();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS | 0x01);//读从机地址
I2C_ReceiveAsk();
Data = I2C_ReceiveByte();
I2C_SendAsk(1);
I2C_Stop();
return Data;
}
5、AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress, Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
6、Nixie.c
#include <REGX52.H>
//数码管显示缓存区
unsigned char Nixie_Buf[9] = {0, 10, 10, 10, 10, 10, 10, 10, 10};//第0位不要,第1~8位对应NixieTable[10]即不显示
//数码管段码表
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00, 0x40};//0--9, 10:不显示, 11:显示-
/**
* @brief 设置显示缓存区
* @param Location 要设置的位置,范围:1~8
* @param Number 要设置的数字,范围:段码表索引范围
* @retval 无
*/
void Nixie_SetBuf(unsigned int Location, Number)
{
Nixie_Buf[Location] = Number;
}
/**
* @brief 数码管扫描显示
* @param Location 要显示的位置,范围:1~8
* @param Number 要显示的数字,范围:段码表索引范围
* @retval 无
*/
void Nixie_Scan(unsigned int Location, Number)
{
P0 = 0x00;//消影:将上一位数据清零
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;
}
}
7、Nixie.h
#ifndef __NIXIE_H__
#define __NIXIE_H__
void Nixie_SetBuf(unsigned int Location, Number);
void Nixie_Loop(void);
void Nixie_Scan(unsigned char Location, Number);
#endif
8、Key.c
#include <REGX52.H>
unsigned char Key_KeyNumber;
/**
* @brief 获取按键键码
* @param 无
* @retval 按下按键的键码,范围:0,1~4, 0表示无按键按下
*/
unsigned char Key(void)
{
unsigned char Temp = 0;
Temp = Key_KeyNumber;
Key_KeyNumber = 0;
return Temp;
}
/**
* @brief 获取当前按键的状态,无消抖及松手检测
* @param 无
* @retval 按下按键的键码,范围:0,1~4, 0表示无按键按下
*/
unsigned char KeyState()
{
unsigned char KeyNumber = 0;
if(P3_1 == 0){KeyNumber = 1;}
if(P3_0 == 0){KeyNumber = 2;}
if(P3_2 == 0){KeyNumber = 3;}
if(P3_3 == 0){KeyNumber = 4;}
return KeyNumber;
}
/**
* @brief 按键驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Key_Loop(void)
{
static unsigned char NowState, LastState;
LastState = NowState;//将现状态赋给上状态
NowState = KeyState();//获取当前按键状态
//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
if(LastState == 1 && NowState == 0)//按键1按下松手
{
Key_KeyNumber = 1;
}
if(LastState == 2 && NowState == 0)//按键2按下松手
{
Key_KeyNumber = 2;
}
if(LastState == 3 && NowState == 0)//按键3按下松手
{
Key_KeyNumber = 3;
}
if(LastState == 4 && NowState == 0)//按键4按下松手
{
Key_KeyNumber = 4;
}
}
9、Key.h
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
void Key_Loop(void);
#endif
10、Timer0.c
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
PT0 = 0;
}
11、Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
12、Delay.c
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
13、Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
三、仿真
需要自取
链接:https://pan.baidu.com/s/1BDx7cT6DH1juG0078GBRpA?pwd=6664
提取码:6664
此文章为个人笔记,如有错漏还请各位看官指正。