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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

redis原理 6:小道消息 —— PubSub

前面我们讲了 Redis 消息队列的使用方法&#xff0c;但是没有提到 Redis 消息队列的不足之处&#xff0c;那就是它不支持消息的多播机制。 img 消息多播 消息多播允许生产者生产一次消息&#xff0c;中间件负责将消息复制到多个消息队列&#xff0c;每个消息队列由相应的消费组…

【Leetcode刷题】位运算

本篇文章为 LeetCode 位运算模块的刷题笔记&#xff0c;仅供参考。 位运算的常用性质如下&#xff1a; a ^ a 0 a ^ 0 a a ^ 0xFFFFFFFF ~a目录 一. 基本位运算Leetcode29.两数相除Leetcode89.格雷编码 二. 位运算的性质Leetcode136.只出现一次的数字Leetcode137.只出现一…

好用的数据库管理软件之idea(idea也有数据库???)

1.建立maven项目&#xff08;maven项目添加依赖&#xff0c;对于后期连接数据库很方便&#xff09; 2.连接数据库。。。 这里一定注意端口号&#xff0c;不要搞错了 和上一张图片不一样哦 3.数据库测试代码。。。 然后你就可以在这里边写MySQL代码了&#xff0c;这个工具对于新…

RunnerGo条件控制器使用方法

在做性能测试时我们需要根据业务需求、业务场景来配置测试脚本&#xff0c;举个例子&#xff1a;在登录注册场景中&#xff0c;可能会有账号密码全部正确、账号格式错误、密码错误等多种情况&#xff0c;这里的“登录/注册”事件可以视为一个场景。一个真实业务中的场景&#x…

人工智能学习07--pytorch23--目标检测:Deformable-DETR训练自己的数据集

参考 https://blog.csdn.net/qq_44808827/article/details/125326909https://blog.csdn.net/dystsp/article/details/125949720?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-125949720-blog-125326909.235^v38^pc_releva…

【Python】Web学习笔记_flask(3)——上传文件

用GET、POST请求上传图片并呈现出来 首先还是创建文件上传的模板 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>上传图片</title> </head> <body> <form action""…

MySQL 远程操作mysql

可以让别人在他们的电脑上操作我电脑上的数据库 create user admin identified with mysql_native_password by admin; //设置账号密码都为admingrant all on *.* to admin; //给admin账号授权 授权完成

直播课 | 大橡科技研发总监丁端尘博士“类器官芯片技术在新药研发中的应用”

从类器官到类器官芯片&#xff0c;正在生物科学领域大放异彩。 药物研发需要新方法 众所周知&#xff0c;一款新药是一个风险大、周期长、成本高的艰难历程&#xff0c;国际上有一个传统的“双十”说法——10年时间&#xff0c;10亿美金&#xff0c;才可能成功研发出一款新药…

【安全测试】Web应用安全之XSS跨站脚本攻击漏洞

目录 前言 XSS概念及分类 反射型XSS(非持久性XSS) 存储型XSS(持久型XSS) 如何测试XSS漏洞 方法一&#xff1a; 方法二&#xff1a; XSS漏洞修复 原则&#xff1a;不相信客户输入的数据 处理建议 资料获取方法 前言 以前都只是在各类文档中见到过XSS&#xff0c;也进…

微信小程序animation动画,微信小程序animation动画无限循环播放

需求是酱紫的&#xff1a; 页面顶部的喇叭通知&#xff0c;内容不固定&#xff0c;宽度不固定&#xff0c;就是做走马灯&#xff08;轮播&#xff09;效果&#xff0c;从左到右的走马灯&#xff08;轮播&#xff09;&#xff0c;每播放一遍暂停 1500ms &#xff5e; 2000ms 刚…

Python web实战之 Django 的模板语言详解

关键词&#xff1a; Python、web开发、Django、模板语言 概要 作为 Python Web 开发的框架之一&#xff0c;Django 提供了一套完整的 MVC 模式&#xff0c;其中的模板语言为开发者提供了强大的渲染和控制前端的能力。本文介绍 Django 的模板语言。 1. Django 模板语言入门 Dj…

宇树Unitree Z1机械臂使用教程

宇树Unitree Z1机械臂使用教程 作者&#xff1a;Herman Ye Galbot Auromix Auromix是一个机器人爱好者组织&#xff0c;欢迎参与我们Github上的开源项目 更新日期&#xff1a;2023/08/04 注意&#xff1a;此文档在该日期下测试有效。 以下内容参考宇树官方的Z1 Docs。 由宇树…

JavaScript |(四)正则表达式 | 尚硅谷JavaScript基础实战

学习来源&#xff1a;尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版 文章目录 &#x1f4da;正则表达式&#x1f4da;正则表达式字面量方式&#x1f4da;字符串&正则表达式&#x1f407;split()&#x1f407;search()&#x1f407;match()&#x1f407;replace()…

Vue3_语法糖—— <script setup>以及unplugin-auto-import自动引入插件

<script setup>import { ref , onMounted} from vue;let obj ref({a: 1,b: 2,}); let changeObj ()>{console.log(obj)obj.value.c 3 //ref写法}onMounted(()>{console.log(obj)})</script> 里面的代码会被编译成组件 setup() 函数的内容。 相当于 <…

牛客网Verilog刷题——VL51

牛客网Verilog刷题——VL51 题目答案 题目 请编写一个十六进制计数器模块&#xff0c;计数器输出信号递增每次到达0&#xff0c;给出指示信号zero&#xff0c;当置位信号set 有效时&#xff0c;将当前输出置为输入的数值set_num。模块的接口信号图如下&#xff1a; 模块的时序图…

Redis的Java客户端

目录 1.Jedis的使用 前置工作-ssh进行端口转发 JedisAPI的使用 Jedis连接池 2.SpringDataRedis的使用 1.创建项目 2.配置文件 3.注入RedisTemplate对象 4.编写代码 3.SpringRedisTemplate 哈希结构用法 ​总结 1.Jedis的使用 Jedis&#xff1a;以Redis命令作为方法…

mysql重置和修改密码 Ubuntu系统

忘记密码要重置密码 cat /etc/mysql/debian.cnf/etc/mysql/debian.cnf这个只有Debian或者Ubuntu服务器才有&#xff0c;里面有mysql安装后自带的用户&#xff0c;作用就是重启及运行mysql服务。我们用这个用户登录来达到重置密码的操作 使用上面的那个文件中的用户名和密码登…

强化学习分享(一) DQN算法原理及实现

摘要&#xff1a;主要讲解DQN算法的原理&#xff0c;伪代码解读&#xff0c;基于pytorch版本的DQN小游戏编程&#xff0c;同时对该代码进行详细标注&#xff0c;以及奉上原码。 &#xff08;一&#xff09;强化学习算法介绍 DQN&#xff0c;顾名思义&#xff0c;Deep Q Learni…

SpringCloud《Eureka、Ribbon、Feign、Hystrix、Zuul》作用简单介绍

概述 SpringCloud是一个全家桶&#xff0c;包含多个组件。 本文主要介绍几个重要组件&#xff0c;也就是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。 一、业务场景介绍 业务流程&#xff0c;支付订单功能 订单服务改变为已支付订单服务调用库存服务&#xff0c;扣减…

中国农村程序员学习此【正则表达式进阶】发明cahtGPT,购买大平层,开上帕拉梅拉,迎娶白富美出任CEO走上人生巅峰

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 限制可能的用户名匹配空白字符匹配非空白字符指定匹配的上限和下限只指定匹配的下限指定匹配的确切数量检查全部或无正向先行断言和负向先行断言检查混合字符组使用捕获组重用模式使用捕获组搜索和替换删除…