江协科技51单片机学习- p27 I2C AT24C02存储器

  🚀write in front🚀  
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​ 

💬本系列哔哩哔哩江科大51单片机的视频为主以及自己的总结梳理📚 

前言:

本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记,在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。

引用:

51单片机入门教程-2020版 程序全程纯手打 从零开始入门_哔哩哔哩_bilibili

​​​​​c51语言变量语句意思,C51中循环语句-CSDN博客

51单片机和AT24C02链接:

51之AT24C02数据存储(I2C总线)_at24c02的地址线-CSDN博客

江协科技 51单片机 AT24C02(I2C总线) 学习笔记——略带拓展_iic总线江协科技-CSDN博客

51单片机系列--AT24C02(总线)_at24c02电路-CSDN博客

ROM简介:

ROM学习笔记1-ROM、PROM、EPROM、EEPROM_掩膜rom-CSDN博客

正文:

0. 🌿概述

在淘宝上购买了江协科技51单片机开发板套件(普中科技STC51单片机A2型号),就上在上一篇博文里说的自己计划学习下江协科技51单片机开发教程,通过STC51单片机这种MCU这种贴近于裸机的开发来增加对于系统硬件层面知识的了解和掌握。

术语和缩略语:

缩写全称说明
I2CInter-Integrated Circuit一种串行通信总线
SDA(Serial Data)串行数据线,用于数据传输
SCL(Serial Clock)串行时钟线

1. 🚀 存储器介绍

    正如图所示,存储器主要分为2大类,RAM(Random Access Memory,随机存取存储器)和ROM(Read-Only Memory,只读存储器)。RAM由于是随机存储,所以存储速度快,但是它存在掉电数据丢失的缺点;ROM就正好相反,它存储速度较慢,但是掉电不丢失。

1.1 RAM

  RAM主要分成两种:SRAM和DRAM。

        🌳SRAM(Static RAM,静态RAM),速度比DRAM还更快,内部结构主要是使用触发器来完成数据存储,现在广泛使用于我们的电脑CPU等需要快速缓存的地方,缺点就是相对较贵。

        🌳DRAM(Dynamic RAM,动态RAM),速度稍慢于SRAM,内部主要基于电容来存储数据,所以价格稍微更低。但是电容在存入电信号之后会放电导致电流失,所以DRAM就要每隔一段时间再把数据重新读入,从而完成数据的持续存储。

1.2 ROM

  ROM分为很多种,有:Mask ROM(掩膜ROM),PROM(可编程ROM),EPROM(可擦除编程ROM),E2PROM(电可擦除可编程ROM),Flash(闪存),以及硬盘,软盘,光盘等。

  • 🌳Mask ROM(掩膜ROM)
  • 🌳PROM(可编程ROM)
  • 🌳EPROM(可擦除编程ROM,有一个开窗可以用紫外线照射进行ROM数据擦除)
  • 🌳E2PROM(电可擦除可编程ROM)
  • 🌳Flash(闪存)
  • 🌳硬盘,软盘,光盘等

1.2.1Mask ROM(掩膜ROM)

作为最早出现的一种ROM,它的缺陷自然是很多的,首先它的主要结构是下面的这种二极管:

 并且,由于使用的是这种二极管为基础的电路,它存储的数据就无法被更改,所以得名只读存储器。Mask ROM的数据写入是不可逆的,即一旦数据被写入,就无法更改。这种类型的存储器通常用于存储固定内容,因为它的成本相对较低,且由于其不可擦写的特性,适合于大批量生产。Mask ROM的优点包括成本较低和稳定性较高,因为它们不像其他可擦写存储器那样需要额外的擦写机制。然而,它们的缺点是缺乏灵活性,一旦制造完成,就不能更改存储在其中的数据。

🌳掩膜ROM的读取原理,是按照行列的方式扫描,横线为地址线,竖线为数据输出线,地址横线选中加上高电平,因为二极管的单向导电性,掩膜有二极管的交叉线(相当于一个bit位)就读取到高电平,没有掩膜二极管的就读取到低电平,通过这种方式就可以存储0或者1,这也就是掩膜ROM的工作模型。

1.2.2PROM(可编程ROM)

 PROM,即Program ROM,也称为可编程ROM,它的主要基本原理也是基于二极管:

🌲这里的蓝色二极管是根据我们写入的程序更改的,不同于Mask ROM,Mask ROM的数据是在厂家生产时就定下来了,PROM的数据则是在写入程序的时候生成,所有的电路都具备一对反向的二极管,这样的组成,当我们写入数据为高电平的时候,会触发击穿电压,从而达到写入数据的目的,因为是把这个蓝色的二极管击穿,所以我们写入程序也叫“烧入程序”,名字就是由此而来。但是PROM依旧是不能够擦除数据,只能写入程序,所以它的局限性依旧很大。

1.2.3EPROM(可擦除编程ROM)

由于时代的变迁和技术的革新,ROM的材料也开始变革,所以产生了EPROM,即可擦除编程ROM,它无非就是在PROM的二极管基础上更新材料,变成了可以恢复的一种材料,导致半导体击穿之后还可以通过一定的技术手段恢复,所以称为可擦除编程ROM。

通过紫外线照射擦写ROM的数据,ROM芯片封装上有一个开口,用紫外线灯照射就可以擦除数据。

擦除:EPROM的die会漏在外面,如下图所示。擦除时,只需要用如紫外灯照射20min即可。

1.2.4E2PROM(电可擦除可编程ROM)

由于技术的革新,产生了电可擦除编程ROM,E2PROM和前面的EPROM的区别就是:E2PROM可以通过电直接擦除数据,但是EPROM是不能用电直接擦除的,所以使用电擦除数据,也是一种使用电控制电的手段,也是现在大部分ROM存储的前身。

🌲EEPROM全称Electricially Erasable Programmable Read Only Memory, 通过分析EPROM的原理可知,SIMOS的二氧化硅层比较厚,势垒比较高,所以不管是往浮栅中充入电子还是擦除电子,都需要给电子充较大的能量,使用上的体现就是较高的电压和长时间的光照擦除。后来科学家门对其结构进行改进,在浮栅和漏区设置了一个极薄的氧化层区域,如下图所示。这样的mos管学名叫浮栅隧道氧化层MOS管(Floating-gate Tunnel Oxide MOS),又称Flotox管。

🌲Flotox管的工作原理与SIMOS类似,只是给浮栅充电方式是隧穿(Fowler-Nordheim隧穿),而不是热电子注入。当隧道区的电场足够大时,电子就能在电场的作用下通过隧道形成隧道电流,这种现象叫隧道效应。

  • 🌲编程前,浮栅上没有电荷,就是一个普通NMOS。
  • 🌲编程时,漏源均接地,控制栅上施加一个20V的脉冲。此时,隧道区产生强电场,吸引漏极的电子穿过隧道到达浮栅。撤除电压后,浮栅上的电子可以永久保留(通常可达十年以上)。
  • 🌲擦除时,源极与控制栅接地,在漏极上施加一个20V的脉冲。类似编程,隧道区产生一个反向的强电场,浮栅上的电子又被吸引回漏极,从而擦除信息。

EEPROM的存储单元由两个MOS管组成,T1为Flotox管,T2为普通mos管。
读过程:当要读这个存储单元时,首先给字线施加一个高电平,选通T2。如果T1管未经过编程,浮栅上不带电,在控制栅上施加正电压可以导通T1,位线上读到0;若浮栅中带电荷,控制栅上给高电平无法使T1管导通,位线上读到1(位线上有未画出的上拉到高电平的部分,常态读取时将电压拉高)。
擦除(相当于写1):擦除时,在字线和控制栅线上施加一个高电平脉冲,同时将位线拉低。在脉冲时间内,T2导通,T1的Ugd之间为20V高压,T1管被编程,浮栅上带了电荷。
写过程:对于要写0的存储单元,在字线和位线上施加一个高电平脉冲,同时将控制栅线拉低。在脉冲时间内,T2导通,T1的Ugd之间为-20V高压,T1管浮栅上的电荷被释放(如果有电荷的话)。

1.2.5Flash(闪存)

现在最为广泛使用的ROM存储手段,非常适合用作便携式设备的存储介质,如USB闪存驱动器、智能手机、平板电脑和其他嵌入式系统,广泛适用于各个地方,包括我们的51单片机,堪称ROM唯一真神。

 当然,除了上面这些,还有硬盘,软盘,光盘等ROM式的存储,我们这里就不多赘述了。

2.地址总线

 地址总线较为复杂,我们可以简化成上面的原理图进行说明:首先,地址总线上对应的是不同的设备,一般前面还会加一个译码器,通过对应的地址找到需要选中的设备,并且只能选中一行,这样就可以实现每次只有一个设备写入读取数据,就不会搞混了。选中设备后,设别就把数据写入到数据总线中去了。

3.AT24C02芯片简介

AT24C02芯片
存储介质: E2PROM
通讯接口: I2C 总线
容量: 256 字节

引脚

功能

VCC、GND

电源(1.8V~5.5V )

WP

写保护(高电平有效)

SCL、SDA

I2C接口

A0、A1、A2

I2C地址

AT24C02芯片是一个典型的掉电不丢失的ROM芯片,在单片机中,它的原理图是这样的:

  这里写的是EEPROM,其实就是我们前面所说的E2PROM,电可擦除编程ROM,这里的这个引脚好像和前面标注的不太一样,其实是一样的,只是不同的工程师对它有不同的命名罢了。

🌳还有这里有一个WE,上面有一个横线,在这里就表示Write Eable,即写入使能且低电平有效,意思就是接低电平这个芯片才可以写入数据,在原来的引脚定义图里面其实是WP即Write Protect,写保护,高电平有效,意思就是高电平开启写保护,此时无法写入数据,相反的,想要写入数据就要低电平,在普中单片机的原理图里面,这个引脚接GND,即默认这个引脚就是低电平,无论什么时候都是可以直接写入数据的。

所以,普中科技51单片机开发板在使用单片机的时候其实只要配置SCL,SDA还有三根数据线就行了。它们都是使用的I2C接口,所以我们这里就先了解一下I2C总线。

4.I2C总线

I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线,它使用两根通信线:SCL(Serial Clock)、SDA(Serial Data)。

  • 🌳工作特点是:同步、半双工,带数据应答
  • 🌳前面在讲串口通信的时候稍微提了一下I2C还有这个半双工和同步
  • 🌳同步且半双工就是下面这样:两个设备通过一根时钟线约定时钟周期(同步),在使用通信的两个设备可以通过同一个时钟线互相传递数据。

  • 1.🌿所有I2C设备的SCL连在一起,SDA连在一起。总线结构,在线上外接各种设备。如图中所示。
  • 2🌿.设备的SCL和SDA均要配置成开漏输出模式

这张图就是我们单片机IO口的输出线路,可以将控制器理解为一个电源连上一个无限大的电阻,Q1是一个控制开关的MOS口,当Q1打开时相当于此处不分压,所以输出低电平;当Q1关闭时,Q1处的电阻无限大,外部的VCC和控制器内部形成一个串联电路电阻相比于Q1很小,所以输出高电平。(我们可能在视频中常听到的强/弱上拉就是改变上拉电阻的阻值来达到控制输出电流电压大小的情况。)

  • 🌿开漏输出模式就是将此图中的VCC和上拉电阻去掉,那么在Q1关闭时电路相当于断开,输出的电平是不稳定,非常容易被影响的。

  • 3.🌿SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
  • 4.🌿开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题 


这是一个设备的内部电路图:

  • 🌿I2C总线协议使用两条通信线,一条是SCL串行时钟线,一条是SDA串行数据线。
  • 🌿I2C总线上的所有设备的SCL总线连接在一起,SDA总线连接在一起。
  • 🌿I2C总线是同步,半双工,带应答的协议。
  • 🌿I2C总线的IO口需要配置为开漏输出模式,并且SCL,SDA总线上接上拉电阻,一般接上拉电阻为 4.7K~10K。
  • 🌿I2C总线当SCL为高电平,SDA为高电平时为空闲状态。
  • 🌿I2C总线的通信速率一般是 100Kb/s,高速I2C总线速率为 400Kb/s。

I2C总线是半双工通信协议,也就是同一时间数据只能由主机发送从机接受,或者从机发送主机接收,I2C总线的通信时钟总是由主机控制的,从机按照主机的时钟信号做出反应。

🔍️I2C总线是真正的多主机多从机通信协议,I2C主机和从机通信之间需要先进行从机地址呼叫,I2C从机的地址一般是7位,最多支持2 ^7=128个从机地址。

🔍️I2C从机地址的高4位是向飞利浦公司申请的,第3位地址是设备厂商自行设置的。

4.1 I2C通信协议

起始条件SCL为高电平,SDA从高电平变为低电平表示起始信号
结束条件SCL为高电平,SDA从低电平变为高电平表示接受信号
应答

I2C固定每发送或接受8个数据位,需要回复应答ACK或者非应答

应答ACK=0

非应答NACK=1

非应答

I2C固定每发送或接受8个数据位,需要回复应答ACK或者非应答

应答ACK=0

非应答NACK=1

发送bit在SCL为高电平时SDA必须保持不变,在SCL为低电平时SDA总线上数据才允许变化
接收bit

在SCL为高电平时SDA必须保持不变,在SCL为低电平时SDA总线上数据才允许变化。

主机释放SDA,在主机发送SCL低电平时,从机将0或者1发送到SDA总线,在SCL为高电平时,主机读取SDA总线获取从发送的数据。

4.2 数据帧(针对AT24C02芯片)

  1. Start起始信号
  2. 从机地址7位+1位写位
  3. 等待从机应答ACK
  4. 发送8位地址
  5. 等待从机应答ACK
  6. 发送8位数据
  7. 等待从机应答ACK
  8. 发送Stop信号

  1. Start起始信号
  2. 从机地址7位+1位写位
  3. 等待从机应答ACK
  4. 发送8位地址
  5. 等待从机应答ACK
  6. 重新Start起始信号 (Restart)
  7. 从机地址7位+1位读位
  8. 等待从机应答ACK
  9. 接收从机发送8位数据
  10. 回复NACK给从机,通知从机读取结束
  11. 发送Stop信号

4.3 I2C从机地址 

📒这里我们发现,在S(即开始)之后,有一个SLAVE ADRESS+R/W的一个字节,这个字节就是我们前面说过的匹配对象的过程同时也确定了主机为接收端还是发送端。

📒SLAVE ADRESS+R/W分为前面七个位表示地址和最后一位表示读/写模式

 在不同的设备中,这里都有不同的含义,这里就主要用我们这次使用的AT24C02芯片为例:

首先,对于AT24C02芯片而言,前面四位为它的固定位,这四个位是在I2C原厂商为AT24C02制定的固定地址位,为1 0 1 0,不能更改,后面的三位可以仔细看看:

4.4 I2C时序

根据上述原理图,写程序时我们要建立六个子函数模块,开始传输数据时SCL和SDA都应该处于高电平,先拉低SDA电平再拉低SCL电平,在传输或者接收数据时要拉高并保持SCL高电平然后读取放到SDA上的数据,结束传输数据就和开始时相反,还要设置应答模块在主机和从机间建立应答通讯。

📚️发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节 。注:真正传输时只有一条SDA

📚️接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,SDA置1)  

4.5 AT24C02写周期

AT24C02写之后,内部需要5ms的写周期,数据才能写入到内部存储位置。

我们可以发现,这里最短时间限制少于1us,然而我们的单片机最短反应时间足足有1us,所以我们根本不需要担心时间不够的问题,直接使用,不需要延时,单片机完全有时间反应过来。

✏️注意:

写进去立马读出来能读到吗?

✏️不能!

为什么不能?

我们看一下手册上的写周期是5ms

5. 程序源码

I2C.c中模拟I2C总线的时序,起始信号,结束信号,发送字节,接收字节,发送ACK,接收ACK。

I2C.c

#include <REGX52.H>
#include <INTRINS.H>
#include "Delay.h"
#include "I2C.h"


sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;

/**
  * @brief  I2C延时通过 
  * @param  
  * @retval 
  */
void I2C_Nop(void)
{
	_nop_();
	_nop_();
	_nop_();
	_nop_();
	_nop_();
}

/**
  * @brief  I2C发送起始信号Start
  * @param  无
  * @retval 无
  */
void I2C_Start(void)
{
	I2C_SDA = 1;
	I2C_SCL = 1;	//SCL时钟拉高电平
	I2C_SDA = 0;	//当SCL为高电平时,SDA从高变低表示Start
	I2C_Nop();
	I2C_SCL = 0;
}

/**
  * @brief  I2C发送停止信号Stop
  * @param  无
  * @retval 无
  */
void I2C_Stop(void)
{
	I2C_SDA = 0;	//SDA拉低
	I2C_SCL = 1;	//SCL拉高
	I2C_SDA = 1;	//当SCL为高电平时SDA从低变为高电平表示Stop
}

/**
  * @brief  I2C发送8位数据
  * @param  无
  * @retval 无
  */

void I2C_SendByte(unsigned char byte)
{
	unsigned char i = 0;
	
	for(i=0; i < 8; i++){
		I2C_SDA = byte & (0x80 >> i);
		I2C_Nop();
		I2C_SCL = 1;
		I2C_Nop();
		I2C_SCL = 0;
	}
}

/**
  * @brief  I2C接收8位数据
  * @param  无
  * @retval 接收到的8位数据
  */
unsigned char I2C_RecvByte(void)
{
	unsigned char i = 0;
	unsigned char byte = 0;
		
	byte = 0;
	
	for(i=0; i < 8; i++){
		I2C_SDA = 1;				//释放SDA总线
		I2C_SCL = 1;				//SCL拉高
		if(I2C_SDA){
			byte |= (0x80 >> i);	//读取SDA
		}
		I2C_Nop();
		I2C_SCL = 0;				//SCL拉低
	}
	
	return byte;
}

/**
  * @brief  I2C发送ACK应答信号
  * @param  无
  * @retval 无
  */
void I2C_SendAck(void)
{
	I2C_SDA = 0;
	I2C_SCL = 1;
	I2C_Nop();
	I2C_SCL = 0;
}

/**
  * @brief  I2C发送NACK应答信号
  * @param  无
  * @retval 无
  */
void I2C_SendNack(void)
{
	I2C_SDA = 1;
	I2C_SCL = 1;
	I2C_Nop();
	I2C_SCL = 0;
}

/**
  * @brief  获取I2C ACK应答
  * @param  无
  * @retval 返回值为0或1,0表示ACK,1表示NACK
  */
unsigned char I2C_RecvAck(void)
{
	unsigned char ack = 0;
	
	I2C_SDA = 1;		//释放SDA总线
	I2C_SCL = 1;		//拉高SCL总线
	if(I2C_SDA == 1)	//读取SDA总线
		ack = 1;
	else
		ack = 0;
	
	I2C_Nop();
	I2C_SCL = 0;
	
	return ack;
}


I2C.h

#ifndef __I2C_H__
#define __I2C_H__

void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char byte);
unsigned char I2C_RecvByte(void);
void I2C_SendAck(void);
void I2C_SendNack(void);
unsigned char I2C_RecvAck(void);

#endif

AT24C02.C

#include <REGX52.H>
#include <INTRINS.H>
#include "I2C.h"
#include "AT24c02.h"

#define AT24C02_ADDR		0xA0

/**
  * @brief  AT24C02 EPROM存储器指定地址写一个字节数据
  * @param  addr 写存储器地址
  * @param  data 写存储器数据
  * @retval 0 写成功,1 写失败
  */
unsigned char AT24c02_WriteByte(unsigned char addr, unsigned char byte)
{
	I2C_Start();
	
	I2C_SendByte(AT24C02_ADDR);
	I2C_RecvAck();
	
	I2C_SendByte(addr);
	I2C_RecvAck();
	
	I2C_SendByte(byte);
	I2C_RecvAck();
	
	//结束条件
	I2C_Stop();
}

/**
  * @brief  AT24C02 EPROM存储器指定地址读一个字节数据
  * @param  addr 读存储器地址 
  * @retval 
  */
unsigned char AT24c02_ReadByte(unsigned char addr)
{
	unsigned char byte = 0;
	
	//起始条件
	I2C_Start();
	
	//从机地址 写
	I2C_SendByte(AT24C02_ADDR);
	I2C_RecvAck();
	
	//写存储器地址
	I2C_SendByte(addr);
	I2C_RecvAck();
	
	//Restart起始条件
	I2C_Start();
	
	//从机地址 读
	I2C_SendByte(AT24C02_ADDR | 0x01);
	I2C_RecvAck();
	
	byte = I2C_RecvByte();
	I2C_SendNack();
	
	
	//结束条件
	I2C_Stop();
	
	return byte;
}

AT24C02.H

#ifndef __AT24C02_H__
#define __AT24C02_H__

unsigned char AT24c02_ReadByte(unsigned char addr);
unsigned char AT24c02_WriteByte(unsigned char addr, unsigned char byte);

#endif

main.c

#include <REGX52.H>
#include <INTRINS.H>
#include "Delay.h"
#include "Key.h"
#include "timer0.h"
#include "LCD1602.h"
#include "Nixie.h"
#include "AT24c02.h"

unsigned char Minute;
unsigned char Second;
unsigned char HunSecond;
unsigned char Second_Enable = 1;

void main()
{
	unsigned char Key_Num = 0;
	unsigned char Byte = 0;
	unsigned int i = 0;
	
	
	Timer0_Init();
	P2 = 0xff;
	//LCD_Init();
	
	//LCD_ShowString(1,1,"Hello!");
	

	
	Nixie_Setbuffer(1, 0);
	Nixie_Setbuffer(2, 0);
	Nixie_Setbuffer(3, 10);
	Nixie_Setbuffer(4, 0);
	Nixie_Setbuffer(5, 0);
	Nixie_Setbuffer(6, 10);
	Nixie_Setbuffer(7, 0);
	Nixie_Setbuffer(8, 0);
	
	Minute = AT24c02_ReadByte(0x00);
	Second = AT24c02_ReadByte(0x01);
	HunSecond = AT24c02_ReadByte(0x02);
	
	
	while(1)
	{
		Key_Num = Key();
		if(Key_Num == 1){
			Second_Enable = !Second_Enable;
		}
		if(Key_Num == 2){
			AT24c02_WriteByte(0x00, Minute);
			Delay(5);
			AT24c02_WriteByte(0x01, Second);
			AT24c02_WriteByte(0x02, HunSecond);
			
			//LCD_Init();
			//LCD_ShowString(1,1,"Saving Data");
			//Delay(1000);
			//LCD_Init();
			
		}
		if(Key_Num == 2){
			Minute = AT24c02_ReadByte(0x00);
			Second = AT24c02_ReadByte(0x01);
			HunSecond = AT24c02_ReadByte(0x02);
			
			//LCD_Init();
			//LCD_ShowString(1,1,"Read Data");
			//Delay(1000);
			//LCD_Init();
			
		}
		
		//Delay(1000);
	}
}


/**
  * @brief  定时器0中断处理函数模版
  * @param  无
  * @retval 无
  */
void Timer_Routine(void) interrupt 1
{
	static unsigned int key_count = 0;
	static unsigned int nixie_count = 0;
	static unsigned int sec_count = 0;
	
	TR0 = 0;
	
	key_count++;
	if(key_count >= 20)
	{
		Key_Loop();
		key_count = 0;		
	}
	
	
	nixie_count++;
	if(nixie_count >= 2)
	{
		Nixie_Loop();
		nixie_count = 0;		
	}
	
	
		
	sec_count++;
	if(sec_count >= 10)		//10ms
	{
		sec_count = 0; 		//10ms counter
		
		
		if(Second_Enable)
		{
			HunSecond++;
			if(HunSecond >= 100)
			{
				HunSecond = 0;
				Second++;
			}
			if(Second >= 60)
			{
				Second = 0;
				Minute++;
			}
			if(Minute >= 60)
			{
				Minute = 0;
			}
			
			
			
			Nixie_Setbuffer(1, HunSecond%10);
			Nixie_Setbuffer(2, HunSecond/10);
			Nixie_Setbuffer(3, 10);
			Nixie_Setbuffer(4, Second%10);
			Nixie_Setbuffer(5, Second/10);
			Nixie_Setbuffer(6, 10);
			Nixie_Setbuffer(7, Minute%10);
			Nixie_Setbuffer(8, Minute/10);
		}

	}
		
	//定时器溢出之后需要重新装载
	TH0 = (65535 - 1000) / 256;					//12MHz晶振,12分频
	TL0 = (65535 - 1000) % 256 + 1; 			//
	
	TR0 = 1;
}

6. 实验结果

使用Keil5编译代码,并使用STC-ISP烧录到普中科技51单片机开发板上上效果如下:

使用淘宝购买的24MHz逻辑分析仪抓取的51单片机IO口和AT24C02之间的I2C通信时序:

🌳该时序是51单片机IO口通过I2C总线线AT24C02的字地址 0x00 写入数据 0x00时的SCL,SDA总线上的电平时许。

🌳使用逻辑分析仪来分析I2C通信程序故障和抓程序Bug,简直是一把利器,通过逻辑分析仪抓取到的51单片机IO口输出的时序波形对照程序源码可以很容易的发现程序工作异常的问题。

7. 实验遇到问题总结

⚠️ 实验遇到了问题,当把更新数码管显示缓冲区数组的如下10行代码放大定时器中断里的话,发现定时器独立按键扫描不灵敏,有时间会漏扫描按键,按键按下 Key1并松开了但是在定时器中断里的独立按键扫描循环却没有扫描到按键按下和松开。

⚠️有时间检测不到按键被按下

✅️如果将定时器Timer0中断里的更新数码管缓存区的这8行代码从中断处理函数中移出到主函数main(),定时器中断按键扫描就不会漏掉按键按下了。

 

 

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

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

相关文章

【数据结构】栈和队列的深度探索,从实现到应用详解

&#x1f48e;所属专栏&#xff1a;数据结构与算法学习 &#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ &#x1f341;1. 栈的介绍 栈是一种后进先出的数据结构&#xff0c;栈中的元素只能从栈顶进行插入和删除操作&#xff0c;类似于叠盘子&#xff0c;最后放上去的盘子最…

【题解】—— LeetCode一周小结28

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结27 8.寻找数组的中心下标 题目链接&#xff1a;724. 寻找数组的…

【CICID】GitHub-Actions-SpringBoot项目部署

[TOC] 【CICID】GitHub-Actions-SpringBoot项目部署 0 流程图 1 创建SprinBoot项目 ​ IDEA创建本地项目&#xff0c;然后推送到 Github 1.1 项目结构 1.2 Dockerfile文件 根据自身项目&#xff0c;修改 CMD ["java","-jar","/app/target/Spri…

docker部署canal 并监听mysql

1.部署mysql 需要开启mysql的binlong&#xff0c;和创建好用户等 可以参考这个 Docker部署Mysql数据库详解-CSDN博客 2.部署canal 参考这一篇&#xff1a; docker安装Canal&#xff0c;开启MySQL binlog &#xff0c;连接Java&#xff0c;监控MySQL变化_docker canal-CSD…

简单搭建卷积神经网络实现手写数字10分类

搭建卷积神经网络实现手写数字10分类 1.思路流程 1.导入minest数据集 2.对数据进行预处理 3.构建卷积神经网络模型 4.训练模型&#xff0c;评估模型 5.用模型进行训练预测 一.导入minest数据集 MNIST--->raw--->test-->(0,1,2...) 10个文件夹 MNIST--->raw-…

【Linux】基于环形队列RingQueue的生产消费者模型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 环形队列的概念及定义 POSIX信号量 RingQueue的实现方式 RingQueue.hpp的构建 Thread.hpp Main.cc主函数的编写 Task.hpp function包装器的使用 总结 前言…

《Python数据科学之一:初见数据科学与环境》

《Python数据科学之一&#xff1a;初见数据科学与环境》 欢迎来到“Python数据科学”系列的第一篇文章。在这个系列中&#xff0c;我们将通过Python的镜头&#xff0c;深入探索数据科学的丰富世界。首先&#xff0c;让我们设置和理解数据科学的基本概念以及在开始任何数据科学项…

《C专家编程》 C++

抽象 就是观察一群数据&#xff0c;忽略不重要的区别&#xff0c;只记录关注的事务特征的关键数据项。比如有一群学生&#xff0c;关键数据项就是学号&#xff0c;身份证号&#xff0c;姓名等。 class student {int stu_num;int id_num;char name[10]; } 访问控制 this关键字…

安全防御:防火墙概述

目录 一、信息安全 1.1 恶意程序一般会具备一下多个或全部特点 1.2 信息安全五要素&#xff1a; 二、了解防火墙 2.1 防火墙的核心任务 2.2 防火墙的分类 2.3 防火墙的发展历程 2.3.1 包过滤防火墙 2.3.2 应用代理防火墙 2.3.3 状态检测防火墙 补充防御设备 三、防…

Torch-Pruning 库入门级使用介绍

项目地址&#xff1a;https://github.com/VainF/Torch-Pruning Torch-Pruning 是一个专用于torch的模型剪枝库&#xff0c;其基于DepGraph 技术分析出模型layer中的依赖关系。DepGraph 与现有的修剪方法&#xff08;如 Magnitude Pruning 或 Taylor Pruning&#xff09;相结合…

uniapp实现水印相机

uniapp实现水印相机-livePusher 水印相机 背景 前两天拿到了一个需求&#xff0c;要求在内部的oaApp中增加一个卫生检查模块&#xff0c;这个模块中的核心诉求就是要求拍照的照片添加水印。对于这个需求&#xff0c;我首先想到的是直接去插件市场&#xff0c;下一个水印相机…

《Python数据科学之五:模型评估与调优深入解析》

《Python数据科学之五&#xff1a;模型评估与调优深入解析》 在数据科学项目中&#xff0c;精确的模型评估和细致的调优过程是确保模型质量、提高预测准确性的关键步骤。本文将详细探讨如何利用 Python 及其强大的库进行模型评估和调优&#xff0c;确保您的模型能够达到最佳性能…

docker中1个nginx容器搭配多个django项目中设置uwsgi.ini的django项目路径

docker中&#xff0c;1个nginx容器搭配多个django项目容器&#xff0c;设置各个uwsgi.ini的django项目路径 被这个卡了一下&#xff0c;真是&#xff0c;哎 各个uwsgi配置应该怎样设置项目路径 django项目1中创建的django项目名为 web 那么uwsgi.ini中要设置为 chdir …

【Vue3 ts】echars图表展示统计的月份数据

图片展示 此处内容为展示24年各个月份产品的创建数量。在后端统计24年各个月份产品数量后&#xff0c;以数组的格式发送给前端&#xff0c;前端负责展示。 后端 entity层&#xff1a; Data Schema(description "月份统计")public class MonthCount {private Stri…

得物六宫格验证码分析

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(lianxi a…

算法的时间复杂度和空间复杂度-例题

一、消失的数字 . - 力扣&#xff08;LeetCode&#xff09; 本题要求的时间复杂度是O(n) &#xff0c;所以我们不能用循环嵌套&#xff1b; 解法一&#xff1a; int missingNumber(int* nums, int numsSize){int sum10;for(int i0;i<numsSize;i){sum1i;}int sum20;for(i…

C到C嘎嘎的衔接篇

本篇文章&#xff0c;是帮助大家从C向C嘎嘎的过渡&#xff0c;那么我们直接开始吧 不知道大家是否有这样一个问题&#xff0c;学完C的时候感觉还能听懂&#xff0c;但是听C嘎嘎感觉就有点难度或者说很难听懂&#xff0c;那么本篇文章就是帮助大家从C过渡到C嘎嘎。 C嘎嘎与C的区…

MPC轨迹跟踪控制器推导及Simulink验证

文章目录 MPC轨迹跟踪控制器推导及Simulink验证MPC的特点MPC轨迹跟踪控制器推导一 系统离散化二 预测区间状态和变量推导三 代价函数推导四 优化求解 <center> 基于MPC的倒立摆控制系统相关资料Reference&#xff1a; MPC轨迹跟踪控制器推导及Simulink验证 MPC的特点 多…

SAP 消息输出 - Adobe Form

目录 1 安装链接 2 前台配置 - Fiori app 2.1 维护表单模板 (maintain form templates) 2.2 管理微标 (manage logos) 2.3 管理文本 (manage texts) 3 后台配置 3.1 定义表单输出规则 3.2 分配表单模板 SAP 消息输出&#xff0c;不仅是企业内部用来记录关键业务操作也是…