51单片机学习-AT24C02数据存储秒表(定时器扫描按键数码管)

news2024/11/26 3:25:09

在这里插入图片描述
首先编写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即可
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/835701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML+CSS+JavaScript:利用事件委托实现tab栏切换

一、需求 实现tab栏切换 二、代码素材 以下是缺失JS部分的代码&#xff0c;感兴趣的小伙伴可以先自己试着写一写 <!-- JS方法实现tab栏切换 --> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta …

[java刷算法]牛客—剑指offer链表复习、手写简易正则匹配

&#x1f9db;‍♂️个人主页&#xff1a;杯咖啡&#x1f4a1;进步是今天的活动&#xff0c;明天的保证&#xff01;✨目前正在学习&#xff1a;SSM框架,算法刷题&#x1f449;本文收录专栏 &#xff1a; java刷算法牛客—剑指offer&#x1f64c;牛客网&#xff0c;刷算法过面试…

【传统视觉】C#创建、封装、调用类库

任务 因为实现代码相对简单&#xff0c;然后又没有使用Opencv&#xff0c;所以就直接用C#实现&#xff0c;C#调用。 1.创建类库 1.1新建一个类库 vs2015 > 文件 > 新建 > 项目 using System; using System.Collections.Generic; using System.Linq;namespace Yo…

使用ChatGPT编写技术文档

技术文档对于任何项目都是至关重要的&#xff0c;因为它确保所有利益相关者都在同一层面上&#xff0c;并允许有效的沟通和协作。创建详细而准确的技术文档可能既耗时又具有挑战性&#xff0c;特别是对于那些不熟悉主题或缺乏强大写作技巧的人来说。ChatGPT 是一个强大的人工智…

服务器流量

1.服务器流量分为入流量和出流量 入流量&#xff08;Inbound Traffic&#xff09;是指流向服务器的数据流量&#xff0c;也就是客户端发送到服务器的数据。这些数据可能包括请求信息、文件上传等。 出流量&#xff08;Outbound Traffic&#xff09;是指从服务器流向客户端的数…

[C++] 类与对象(中)类中六个默认成员函数(2)-- 运算符重载 -- 取地址及const取地址操作符重载

1、前言 本篇我们以日期类来展开讲。对于一个日期&#xff0c;我们如何去比大小呢&#xff1f;对年月日依次进行比较可以&#xff0c;但是可以直接比较吗? 我们可以看到&#xff0c;对于自定义类型的日期类直接去比较两个日期的大小是错误的&#xff0c;因此我们需要对运算符赋…

C. Candy Store

Problem - 1798C - Codeforces 思路&#xff1a;要求的最小的标签数量&#xff0c;我们可以先考虑贪心&#xff0c;对于第一个来说它一定会使用一个标签&#xff0c;然后就让后面尽可能多的跟当前这个共用一个标签&#xff0c;如果不行&#xff0c;则在使用一个新的。那么对于共…

Python+Robot Framework实现接口自动化测试

最近在研究PythonRobot Framework的接口自动化&#xff0c;摸清了一些套路&#xff0c;想着总结一下&#xff0c;分享给大家&#xff0c;希望对做自动化的同学有所启发。 主要用到了Python的requests&#xff0c;json&#xff0c;hashlib库&#xff0c;下面以登录和开启文档/目…

“云教室”来了! 麒麟信安打造晋城市红星小学多媒体教室建设新标杆

当前&#xff0c;教育行业正面临教育信息化的重大变革。随着信息技术的发展&#xff0c;集声音、图像、视频动画等多种功能于一体的信息技术课逐渐成为学生群体最受欢迎的课程之一。 近日&#xff0c;麒麟信安为晋城市红星小学量身打造的“云教室”全新上线。据悉&#xff0c;…

IDEA如何生成 serialVersionUID

序列化和反序列化 Java是面向对象的语言&#xff0c;与其他语言进行交互&#xff08;比如与前端js进行http通信&#xff09;&#xff0c;需要把对象转化成一种通用的格式比如json&#xff08;前端显然不认识Java对象&#xff09;&#xff0c;从对象到json字符串的转换&#xff…

【css】css实现字母大小写转换

text-transform 属性用于指定文本中的大写和小写字母。 uppercase&#xff1a;将字母转为大写lowercase&#xff1a;将字母转为小写capitalize&#xff1a;将每个单词首字母转为大写 代码&#xff1a; <style> p.uppercase {text-transform: uppercase; }p.lowercase …

Mendix Excel导出介绍

一、前言 之前我们聊到企业实现应用现代化是一项长期的任重道远的工作&#xff0c;通过Excel导入组件可以作为一个好的起点把企业的业务从线下迁移到线上&#xff0c;当把应用迁移之后&#xff0c;最终结果会有各种的报表展示&#xff0c;这时基于实际业务的需求&#xff0c;我…

springboot jekins打包

新建Item 选择freestyle 配置下拉框 This project is parameterized 配置git 构建shell脚本 脚本 BUILD_IDDONTKILLME . /etc/profile export PROJ_PATHpwd export TOMCAT_APP_PATH/root/local/apache-tomcat-8.5.45 export BASE_PATH/root/localecho $PROJ_PATH echo $TO…

ESP32-C2开发板 ESP8684芯片 兼容ESP32-C3开发

C2是一个芯片采用4毫米x 4毫米封装&#xff0c;与272 kB内存。它运行框架&#xff0c;例如ESP-Jumpstart和ESP造雨者&#xff0c;同时它也运行ESP-IDF。ESP-IDF是Espressif面向嵌入式物联网设备的开源实时操作系统&#xff0c;受到了全球用户的信赖。它由支持Espressif以及所有…

IDEA的实用快捷键大全

目录 1.常规快捷键 1.1通用类 1.2注释类 1.3操作类 1.4展开与关闭 2.智能补全类快捷键 3.程序结构类快捷键 4.统一操作快捷键 1.常规快捷键 1.1通用类 像 Ctrl C 复制&#xff0c; Ctrl V 粘贴&#xff0c; Ctrl S保存文件&#xff0c; Ctrl X剪切&#xff0c;这种…

HCIP 打破BGP水平分割-反射器、联邦

打破IBGP的水平分割的条件 IBGP水平分割---避免IBGP环路---从一个IBGP邻居处学习到的路由条目不能传递给本地的其他IBGP邻居&#xff1b; 因为BGP可以非直连建邻&#xff0c;故正常一台运行了BGP协议的路由器&#xff0c;均存在EBGP邻居&#xff0c;需要从其他AS学习路由条目…

MySQL数据库如何实现AX规范

本文我们来讨论 MySQL 的 XA 规范有哪些应用相关的内容。 MySQL 为我们提供了分布式事务解决方案&#xff0c;在前面的内容中 聊一聊分布式事务的解决方案 提到过 binlog 的同步&#xff0c;其实是 MySQL XA 规范的一个应用&#xff0c;那么 XA 规范是如何定义的&#xff0c;具…

2023牛客暑期多校训练营4

A.Bobo String Construction s取全0串或者全1串,至少有一者满足题述条件 大致感受一下 随便举几个例子: t s t 0000|0000|0000 s为全0不符合,但s为全1符合 1001|00|1001 s为全0不符合,但s为全1符合 101|000000|101 s为全0或为全1均符合 所以只需判断全0串或者全1串哪个符合题述…

redis原理 3:未雨绸缪 —— 持久化

redis原理 3&#xff1a;未雨绸缪 —— 持久化 Redis 的数据全部在内存里&#xff0c;如果突然宕机&#xff0c;数据就会全部丢失&#xff0c;因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失&#xff0c;这种机制就是 Redis 的持久化机制。 Redis 的持久化机制有两种…

超越年龄限制:保持体能与积极心态的重要性(AI)

机器人和人工智能&#xff0c;没有年龄限制&#xff0c;并且越来越强&#xff0c;代表了最先进的生产力方向。 AI&#xff1a; 机器人和人工智能的发展代表了最先进生产力的方向&#xff0c;它们可以帮助人们更高效地完成各种任务&#xff0c;从而推动经济和社会的发展。机器人…