文章目录
前言
介绍AT24C02和I2C总线规则,通过定时器代替delay函数消抖、消影。实现使用AT24C02写取数据、秒表案例
一、介绍部分
存储器介绍
RAM在没有电源时数据易丢失,可以快速存取。
ROM在没有电源时数据不易丢失,存储速度不快,刚开始只能读取数据不能写入,随着发展现在已经可以写入数据、删除数据和读取数据了。
存储器简化模型
存储器的数据读取和矩阵键盘类似,是扫描地址线,针对每条地址线的每条数据进行读取。
AT24C02介绍
引脚即原理图
官方电路图
完整电路图
引脚
内部结构框图
I2C总线
I2C总线介绍
I2C电路规范
弱上拉与开漏输出模式
I2C的时序结构
发送一个字节
接收一个字节
发送应答与接收应答
I2C数据帧
发送一帧数据
接收一帧数据
先发送再接收数据帧
字节写与随机读
二、使用方法
1.使用AT24C02存储数据(配合LCD1602)
将I2C的所有时序结构封装成函数放入I2C.c文件中
将字节写与随机读按照数据帧封装成函数放入AT24C02.c文件中
I2C.c内容如下
// I2C的时序结构
#include <REGX52.H>
// 引脚声明
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 Byte,要发送的字节
* @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 Byte,从SDA读取到的数据
*/
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 ACK,要发送应答的内容
* @retval 无
*/
void I2C_SendACK(unsigned char ACK){
// 0表示应答,1表示非应答
I2C_SDA = ACK;
I2C_SCL = 1;
I2C_SCL = 0;
}
/**
* @brief I2C接收应答
* @param 无
* @retval ACK,从SDA接收到的应答
*/
unsigned char I2C_ReceiveACK(void){
unsigned char ACK;
I2C_SDA = 1; // 释放SDA
I2C_SCL = 1;
ACK = I2C_SDA;
I2C_SCL = 0;
return ACK;
}
AT24C02.c内容如下
#include <REGX52.H>
#include "I2C.h"
// 从机地址
#define SLAVE_ADDRESS 0xa0
/**
* @brief 字节写
* @param WordAddress,要写入数据的地址 Data,要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data){
I2C_Start();
I2C_SendByte(SLAVE_ADDRESS);
I2C_ReceiveACK();
I2C_SendByte(WordAddress); // 发送地址内容
I2C_ReceiveACK();
I2C_SendByte(Data); // 发送数据内容
I2C_ReceiveACK();
I2C_Stop();
}
/**
* @brief 随机读取
* @param WordAddress,从此地址读取数据
* @retval Data,所读取的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress){
unsigned char Data;
I2C_Start();
I2C_SendByte(SLAVE_ADDRESS);
I2C_ReceiveACK();
I2C_SendByte(WordAddress); // 发送地址内容
I2C_ReceiveACK();
I2C_Start();
I2C_SendByte(SLAVE_ADDRESS | 0x01);
I2C_ReceiveACK();
Data = I2C_ReceiveByte();
I2C_SendACK(1);
I2C_Stop();
return Data;
}
main.c内容如下:
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned int Data;
void main(){
LCD_Init();
LCD_ShowNum(1,1,Data,5);
while(1){
KeyNum = Key();
// 数据+1
if(KeyNum == 1){
Data++;
LCD_ShowNum(1,1,Data,5);
}
// 数据-1
if(KeyNum == 2){
Data--;
LCD_ShowNum(1,1,Data,5);
}
// 将数据存入AT24C02中
if(KeyNum == 3){
// AT24C02的写周期为5ms,比单片机运行时间慢,需要延时才能读出
AT24C02_WriteByte(0,Data%256); // 高八位
Delayms(5);
AT24C02_WriteByte(1,Data/256); // 低八位
Delayms(5);
LCD_ShowString(2,1,"write success");
Delayms(1000);
LCD_ShowString(2,1," ");
}
// 从AT24C02读取数据
if(KeyNum == 4){
Data = AT24C02_ReadByte(0);
// char与int运算时 char会内部转化为int型参与运算 所以读出值左移八位不会溢出
Data |= AT24C02_ReadByte(1)<<8;
LCD_ShowNum(1,1,Data,5);
LCD_ShowString(2,1,"read success");
Delayms(1000);
LCD_ShowString(2,1," ");
}
}
}
2.实现秒表
使用定时器代替delay消影、消抖
改写按键key.c
#include <REGX52.H>
#include "Delay.h"
unsigned char KeyRealNum; // 用于接收按键值
unsigned char Key(){
// 中间变量接收按键值
unsigned char Temp = KeyRealNum;
KeyRealNum = 0; // 先归零再进行下次判断
return Temp;
}
// 按键状态
unsigned char Key_State(){
unsigned char KeyNum = 0;
if(P3_1==0){
KeyNum = 1;
}
if(P3_0==0){
KeyNum = 2;
}
if(P3_2==0){
KeyNum = 3;
}
if(P3_3==0){
KeyNum = 4;
}
return KeyNum;
}
void Key_Loop(){
static unsigned char OldKeyState,NewKeyState;
OldKeyState = NewKeyState;
NewKeyState = Key_State();
// 表示松开了此按键
if(NewKeyState==0 && OldKeyState==1){
KeyRealNum = 1;
}
if(NewKeyState==0 && OldKeyState==2){
KeyRealNum = 2;
}
if(NewKeyState==0 && OldKeyState==3){
KeyRealNum = 3;
}
if(NewKeyState==0 && OldKeyState==4){
KeyRealNum = 4;
}
}
改写数码管Nixie.x
#include <REGX52.H>
#include "Delay.h"
//数码管显示缓存区
unsigned char Nixie_Buf[9]={
0,10,10,10,10,10,10,10,10};
//数码管段码表
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=0;
Nixie_Scan(i,Nixie_Buf[i]);
i++;
if(i>=9){
i=0;}
}
秒表实现主要代码main.c
#include <REGX52.H>
#include "Time0Init.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag; // 秒表停止、运行状态
void main()
{
Time0_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
Delayms(5);
AT24C02_WriteByte(1,Sec);
Delayms(5);
AT24C02_WriteByte(2,MiniSec);
Delayms(5);
}
if(KeyNum==4) //K4按键按下
{
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 无
* @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 = 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调用一次数秒表驱动函数
}
}
补充
使用定时器代替delay消抖、消影可以对程序的运行影响更小,不会有按键一直按下时导致整个进程等待的情况。