首先编写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即可