首先编写I2C模块,根据下面的原理图进行位声明:
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;
再根据下面的时序结构图编写函数:
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA = 1;
I2C_SCL = 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发送一个字节
* @param 要发送的字节
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i = 0; i < 8; i ++) {
I2C_SDA = Byte & (0x80 >> i); //由高位到低位依次取出写入
I2C_SCL = 1;
I2C_SCL = 0;
}
}
/**
* @brief I2C接受一个字节
* @param 无
* @retval 接收到的字节
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i, 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 应答位,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;
}
接下来利用写好的I2C模块,创建AT24C02模块来凑成数据帧:
同样根据图中的时序模拟就可以了:
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0 //写地址
/**
* @brief AT24C02写入一个字节
* @param 要写入字节的地址,0~255
* @param 要写入的数据
* @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 要读出字节的地址 0~255
* @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;
}
在main中调用看一下效果如何(这种存储,写入之后是掉电不丢失的):
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
unsigned char Data;
void main()
{
LCD_Init();
LCD_ShowString(1, 1, "Hello");
AT24C02_WriteByte(1, 66);
Delay(5); //写完不要马上去读,写需要时间
Data = AT24C02_ReadByte(1);
LCD_ShowNum(2, 1, Data, 3);
while(1)
{
}
}
接下来配合上按键就可以做出一个按键存储器啦,有了之前编写的模块,只要适当地调用就可以了,以下是按键存储器的main.c文件代码:
按键1:增大数据
按键2:减小数据
按键3:写入
按键4:读出
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1, 1, Num, 5);
while(1)
{
KeyNum = Key();
if(KeyNum == 1)
{
Num ++;
LCD_ShowNum(1, 1, Num, 5);
}
if(KeyNum == 2)
{
Num --;
LCD_ShowNum(1, 1, Num, 5);
}
if(KeyNum == 3) //写入
{
AT24C02_WriteByte(0, Num % 256); //Num十六位,先取低八位
Delay(5);
AT24C02_WriteByte(1, Num % 256); //存高八位
Delay(5);
LCD_ShowString(2, 1, "Write OK");
Delay(1000);
LCD_ShowString(2, 1, " ");
}
if(KeyNum == 4) //读出
{
Num = AT24C02_ReadByte(0); //拿到低八位
Num |= (AT24C02_ReadByte(1) << 8); //高八位左移8位或上去
LCD_ShowNum(1, 1, Num, 5);
LCD_ShowString(2, 1, "Read OK");
Delay(1000);
LCD_ShowString(2, 1, " ");
}
}
}
接下来,利用定时器扫描按键和数码管,制作一个秒表:
由于按键和数码管都需要中断函数,所以就为Key和Nixie都定义一个中断的时候需要执行的操作,然后在中断函数中调用它们即可。。
先来看重新编写的Key模块,只要在中断函数中每隔20ms执行一次Key_Loop()就能保证读到每一次按键松开时的键码值,这样也达到了定时器扫描按键的目的
#include <REGX52.H>
#include "Delay.h"
unsigned char Key_KeyNumber;
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下的按键的键码,范围0~4,无按键按下时返回0
*/
unsigned char Key(void)
{
unsigned char Temp;
Temp = Key_KeyNumber;
Key_KeyNumber = 0;
return Temp;
}
/**
* @brief 获取按键实时状态,0为松开
* @param 无
* @retval 无
*/
unsigned char Key_GetState()
{
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;
}
void Key_Loop(void) //捕捉按键松开瞬间,并得到键码
{
static unsigned char NowState, LastState;
LastState = NowState;
NowState = Key_GetState();
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;
}
}
接下来要改写Nixie,使定时器扫描数码管,每隔2ms调用一次Nixie_Loop();
#include <REGX52.H>
#include "Delay.h"
unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00};
unsigned char Nixie_Buf[9] = {0,10,10,10,10,10,10,10,10};
void Nixie_SetBuf(unsigned char Location, Number)
{
Nixie_Buf[Location] = Number;
}
/**
* @brief 在对应位置显示对应数字
* @param 位置,数字
* @retval 无
*/
void Nixie_Scan(unsigned char 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];
}
void Nixie_Loop(void) //每调用一次就向后显示一位
{
static unsigned char i = 1;
Nixie_Scan(i, Nixie_Buf[i]);
i ++;
if(i >= 9) i = 1;
}
因此,要同时维护这两个中断,中断函数需要这样写:
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count1, T0Count2;
TL0 = 0x66; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count1 ++;
if(T0Count1 >= 20) //20ms扫描一次
{
T0Count1 = 0;
Key_Loop();
}
T0Count2 ++;
if(T0Count2 >= 2) //2ms扫描一次
{
T0Count2 = 0;
Nixie_Loop();
}
}
有了以上这些模块,就可以开始编写秒表了:
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
unsigned char KeyNum;
unsigned char Min, Sec, MiniSec;
unsigned char RunFlag;
void main()
{
Timer0_Init();
while(1)
{
KeyNum = Key();
if(KeyNum == 1) //启动/暂停
{
RunFlag = !RunFlag;
}
if(KeyNum == 2) //清零复位
{
Min = 0;
Sec = 0;
MiniSec = 0;
}
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);
}
}
void Sec_Loop(void) //每过一个MiniSec执行一次
{
if(RunFlag == 0) return;
//暂停中不执行
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; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count1 ++;
if(T0Count1 >= 20) //20ms扫描一次
{
T0Count1 = 0;
Key_Loop();
}
T0Count2 ++;
if(T0Count2 >= 2) //2ms扫描一次
{
T0Count2 = 0;
Nixie_Loop();
}
T0Count3 ++;
if(T0Count3 >= 10) //一个MiniSec扫描一次
{
T0Count3 = 0;
Sec_Loop();
}
}
如果要和之前的AT24C02结合起来,那就KeyNum为3的时候写入秒表的时间,KeyNum为4的时候读出时间,利用WriteByte和ReadByte即可