51单片机学习笔记_8 IICAT24C02 芯片的应用

news2025/8/18 18:51:06

I2C EEPROM

I2C

I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式 串行总线,用于连接微控制器及其外围设备。

I2C 结构

I2C 只有两根双向信号线,一根是 SDA 数据线,一根是 SCL 时钟线。

主机:启动数据传送并产生时钟信号的设备;

从机:被主机寻址的器件;

多主机:同时有多于一个主机尝试控制总线但不破坏传输;

主模式:用 I2CNDAT 支持自动字节计数的模式; 位 I2CRM,I2CSTT,I2CSTP 控制数据的接收和发送;

从模式:发送和接收操作都是由 I2C 模块自动控制的;

仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并 使传输不被破坏的过程;

同步:两个或多个器件同步时钟信号的过程;

发送器:发送数据到总线的器件;

接收器:从总线接收数据的器件。

I2C 协议

①时钟信号周期性的高电平低电平。只有时钟信号为低电平时,数据信号才能变化。

②起始和停止信号:SDA高→低和低→高。

③发送器件传输完一个字节8位数据后,后面必须紧跟一个ACK/NACK校验位,判断接受是否完成,数据传送是否可以继续。

image-20230104204527296

④总线寻址方式

从机有自己的地址,总线寻址采用7或10位的寻址位数。如7位:位定义D0表示数据传送方向位(是主机从从机读取数据?还是主机向从机写数据?代表这个从机是接收器还是发送器),D7~D1是从机地址位。

主机发送一个地址后,所有从机前7位地址和主机比较,如果相同再判断从机是接收还是发送器。

从机地址包含固定位和可编程位,可编程位决定了这个部件可以最多有多少个接入总线。如4位固定位,3位可编程位,说明2^3=8个最多可以接入总线。

⑤数据传输

起始信号 S+7位从机地址+数据方向位+ACK+数据+NACK+终止信号。如果主机还想要新的数据传送,可以不终止,继续发出起始信号向另一从机寻址。

数据方向位=0:主机向从机发送数据。主机一直发到从机返回 NACK 为止。

数据方向位=1:从机向主机发送数据,发送到主机返回 NACK 为止。

AT24C02

是一个2k位串行CMOS,主板上的主板上的一块可读写的并行或串行FLASH芯片。该芯片有 I2C 接口,是个从机。而且有写保护功能,其中写入的数据断电不丢失。

我们可以通过单片机的模拟 IIO 功能,将数据写入该芯片永久存储,下次断电时也能访问。

创建多文件工程

下面编写的程序要求:

设计一个系统,可以写入、读取 AT24C02 中的数据,并将其中的数据用数码管显示出来。

这个系统涉及之前学过的按键、数码管信息,还涉及新的 AT24C02 的使用,三部分代码。由于内容太多,所以这次我们创建一个多文件系统,以后也会用这个系统模板更好的管理文件。

本项目主要包含:

App 文件夹:存储各类函数

Obj 文件夹:存放编译产生的 hex 文件、列表清单等。

Public 文件:存放公共文件,如延时、变量重定义。

User:存放 main.c 等主函数文件。

创建步骤:

①新建一个项目文件夹,在该项目文件夹中新建以上四个文件夹。

②在 Keil 中新建项目,选中这个项目文件夹。选择不复制 StartUp 代码。

③点击魔术棒右边的三色方块,创建三个分组 Group。这里创建的分组是一个逻辑上的分组,并不是指选中这三个具体的文件夹。与三个文件夹起名相同是为了方便管理.

image-20230105115404825

④编写程序.

清楚了我们的需求,我们想一下要写那些代码,放在什么地方。

公共内容:public 中。

使用按钮代码:App中。

使用数码管代码:App中。

使用 AT24C02 代码:App 中。

调用以上几部分内容代码:main 中。

image-20230105134459205

把普中部分代码复制到单片机中,大意:按键1将当前数据写入芯片,按键2读取芯片中存储的数据,按键3将当前数据+1,按键4将当前数据清零。

key部分代码:装有key_scan函数,用于识别当前按下的是四个按钮中的哪一个并返回。

//key.h
#ifndef _key_H
#define _key_H
#include "reg52.h"
//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;
//定义 LED1 控制脚
sbit LED1=P2^0;
sbit LED2=P2^1;
sbit LED3=P2^2;
sbit LED4=P2^3;
//使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0
u8 key_scan(u8 mode);
#endif

//key.c
#include "public.h"
#include "key.h"

u8 key_scan(u8 mode)
{
	static u8 key=1;
	if(mode)key=1;//连续扫描按键
	if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
	{
		delay(1000);//消抖
		key=0;
		if(KEY1==0)
		return KEY1_PRESS;
		else if(KEY2==0)
		return KEY2_PRESS;
		else if(KEY3==0)
		return KEY3_PRESS;
		else if(KEY4==0)
		return KEY4_PRESS;
	}
	else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
	{
		key=1;
	}
	return KEY_UNPRESS;
}

smg.c(我的命名是display.c,也代表数码管?),传入数码管要显示的数字信息(第一个参数是要显示的数字数组,如0,0,7.第二个参数是从左往右数第几位开始显示数字,从0开始。如要显示007,因为一共8位,0~4位都不显示,所以从第五位开始显示)。

/display.h
#ifndef _display_H
#define _display_H
#include "reg52.h"
#include "public.h"
sbit LSA = P2 ^ 2;
sbit LSB = P2 ^ 3;
sbit LSC = P2 ^ 4;
void smg_display(u8 dat[], u16 index);
#endif 

display.c
#include "public.h"
#include "display.h"
#define Display P0
unsigned char gsmg_code[17] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
							   0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
#define SMG_A_DP_PORT P0 // 使用宏定义数码管段码口

// dat:要传入的数据集合
// index:起始从哪一位开始显示数字。0则显示0~7 8位数字;7则只显示第7位数字。也能让函数知道数组的长度
void smg_display(u8 dat[], u16 index)
{
	while (1)
	{
		int i;
		// 位选
		for (i = index; i < 8; i++)
		{
			switch (i)
			{
			case 0:
				LSC = 1;LSB = 1;LSA = 1;
				break;
			case 1:
				LSC = 1;LSB = 1;LSA = 0;
				break;
			case 2:
				LSC = 1;LSB = 0;LSA = 1;
				break;
			case 3:
				LSC = 1;LSB = 0;LSA = 0;
				break;
			case 4:
				LSC = 0;LSB = 1;LSA = 1;
				break;
			case 5:
				LSC = 0;LSB = 1;LSA = 0;
				break;
			case 6:
				LSC = 0;LSB = 0;LSA = 1;
				break;
			case 7:
				LSC = 0;LSB = 0;LSA = 0;
				break;
            default:break;
			}
			SMG_A_DP_PORT = dat[i - index];
			delay(100);			  // 延时一段时间,等待显示稳定
			SMG_A_DP_PORT = 0x00; // 消音
		}
	}
}

iic:定义i2c总线的一些方法。如开始等待接收数据、结束关闭、ack、nack、wait ack、读取写入数据等方法。

/iic.h
#ifndef _iic_H
#define _iic_H

#include "public.h"

//定义EEPROM控制脚
sbit IIC_SCL=P2^1;//SCL时钟线
sbit IIC_SDA=P2^0;//SDA数据线


//IIC所有操作函数				 
void iic_start(void);			//发送IIC开始信号
void iic_stop(void);	  		//发送IIC停止信号
void iic_write_byte(u8 txd);	//IIC发送一个字节
u8 iic_read_byte(u8 ack);		//IIC读取一个字节
u8 iic_wait_ack(void); 			//IIC等待ACK信号
void iic_ack(void);				//IIC发送ACK信号
void iic_nack(void);			//IIC不发送ACK信号

#endif


///iic.c
#include "public.h"
#include "iic.h"

void iic_start()
{
    IIC_SCL = 1; // SCL为高电平时,SDA的数据才有效
    IIC_SDA = 1;
    delay(10);
    IIC_SDA = 0; // SCL SDA 都由高变低,是起始标志
    
	delay(10);
    IIC_SCL = 0;
}

void iic_stop()
{
    IIC_SCL = 1;
    IIC_SDA = 0;
    delay(10);
    IIC_SDA = 1;
    delay(10);
}

void iic_ack()
{
    IIC_SCL = 0;
    IIC_SDA = 0;
    delay(10);
    IIC_SCL = 1;
    delay(10);
    IIC_SCL = 0;
}

void iic_nack()
{
    IIC_SCL = 0;
    IIC_SDA = 1;
    delay(10);
    IIC_SCL = 1;
    delay(10);
    IIC_SCL = 0;
}

u8 iic_wait_ack()
{
    u16 time_temp = 0;
    IIC_SCL = 1;
    while (IIC_SDA)
    { // 等待数据变成0
        if (++time_temp > 100)
        {
            iic_stop();
            return 1;
        }
    }
    IIC_SCL = 0;
    return 0;
}

u8 iic_read_byte(u8 ack)
{
    u8 i = 0, receive = 0;
    for (i; i < 8; i++)
    {
        IIC_SCL = 0;
        delay(1);
        IIC_SCL = 1;
        receive <<= 1;
        if (IIC_SDA)
            receive += 1;
        delay(1);
    }
    if (!ack)
        iic_nack();
    else
        iic_ack();
    return receive;
}

void iic_write_byte(u8 dat)
{
    u8 i = 0;
    IIC_SCL = 0;
    for (i; i < 8; i++)
    {
        if ((dat & 0x80) > 0)
            IIC_SDA = 1;
        else
            IIC_SDA = 0;
        dat <<= 1;
        delay(1);
        IIC_SCL = 1;
        delay(1);
        IIC_SCL = 0;
        delay(1);
    }
}

24c02:主要就是芯片调用iic来写入或取出数据的函数。看上去倒没有多少自己的东西。

/24c02.h
#ifndef _24c02_H
#define _24c02_H

#include "public.h"
void at24c02_write_one_byte(u8 addr, u8 dat);
u8 at24c02_read_one_byte(u8 addr);
#endif

/24c02.c
#include "public.h"
#include "24c02.h"
#include "iic.h"

void at24c02_write_one_byte(u8 addr, u8 dat){
    iic_start();
    iic_write_byte(0xA0);//写命令
    iic_wait_ack();
    iic_write_byte(addr);//发送写地址
    iic_wait_ack();
    iic_write_byte(dat);
    iic_wait_ack();
    iic_stop();//停止条件
    delay(1);
}

u8 at24c02_read_one_byte(u8 addr){
	u8 temp;
    iic_start();
    iic_write_byte(0xA0);//写命令
    iic_wait_ack();
    iic_write_byte(addr);//发送写地址
    iic_wait_ack();
    iic_write_byte(0xA1);//进入接收模式
    iic_wait_ack();
    temp=iic_read_byte(0);//读取条件
    iic_stop();//停止条件
    return temp;
}

public.h和.c里面就是装了u8 u16 和 delay 函数的定义。不用多说。

main.c:

#include <reg52.h>
#include <public.h>
#include <key.h>
#include <display.h>
#include <24c02.h>
#define EEPROM_ADDRESS 0//数据存入EEPROM 的起始地址
void main(){
    u8 key_temp=0;
    u8 save_value=0;//每次刚打开单片机时,起始值都=0
    u8 save_buf[3];
    while(1){
        key_temp=key_scan(0);//单词扫描按键获取当前按键
        switch(key_temp){//按键1:写入
            
			case KEY1_PRESS:
                at24c02_write_one_byte(EEPROM_ADDRESS,save_value);
                break;
            case KEY2_PRESS:
                save_value=at24c02_read_one_byte(EEPROM_ADDRESS);
                break;
            case KEY3_PRESS:
                if(save_value<255)save_value++;
                break;
            case KEY4_PRESS:
                save_value=0;
                break;
            default:
                break;
        }
        save_buf[0]=save_value/100;
        save_buf[1]=save_value/10%10;
        save_buf[2]=save_value%10;
        smg_display(save_buf,5);
    }
}

其实写完main.c发现了一个地方,就是数码管展示函数每次传入的长度都是3,也就是0~255都是显示3位,高位补0.只有个位就是00几,有个位和十位就是0几几。当时我觉得那这个参数要不要也没啥必要啊。数码管展示函数说不定也能改的更简洁一点。但是后来想到代码的拓展性,这个数码管函数只是针对这次案例永远会只显示5位,说不定其他例子就不一样了。所以不改也好。

运行效果:

一开始运行,数码管显示当前数字000.

按下按钮1,会把当前数字写入 AT24C02 芯片。

按下按钮2,会读取 AT24C02 中存储的数字显示出来。断电也能保存。

按下按钮3,当前数字会+1.

按下按钮4,当前数字会清0.

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

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

相关文章

2-选择题练手

1.HASH函数冲突处理方式不包括以下哪一项 A.开放定址法 B.链地址法 C.插入排序法 D.公共溢出区发 答&#xff1a;C 析&#xff1a; HASH函数冲突处理方式有&#xff1a; 开放定址法&#xff1a;&#xff08;线性探测再散列&#xff0c;二次探测再散列&#xff0c;伪随机…

Netty进阶——粘包与半包(固定分隔符方式解决粘包问题)

目录一、固定分隔符方式解决粘包问题&#xff08;代码示例&#xff09;1.1、固定分隔符解决粘包问题的服务端代码示例1.2、固定分隔符方式解决粘包问题的客户端代码示例1.3、分别启动服务端&#xff0c;客户端&#xff0c;查看服务端结果输出一、固定分隔符方式解决粘包问题&am…

vitepress(四):引入vue组件

这节课的内容需要有前置的良好的Vue基础&#xff0c;如果你仅仅想搭建一个存放md文件的网站的话&#xff0c;可以不必学习后面的内容&#xff0c;当然如果你想个性化自己的站点&#xff0c;那么推荐你学习一下引用的方式和注意点&#xff0c;开始你的个性化之旅 编写VUE组件 …

ARES Map地理信息系统(GIS)

ARES Map地理信息系统(GIS) ARES地图是GRAEBERT的地理信息系统(GIS)产品。该软件是一个复合解决方案&#xff0c;它将GIS的知识和内容放在一个CAD丰富的DWG系统上&#xff0c;并允许您同时使用它们。该软件的地图和设计将以DWG格式自然存储&#xff0c;但它们也可以包含GIS信息…

【阶段三】Python机器学习02篇:机器学习项目流程

本篇的思维导图: 机器学习项目流程 大致分为以下6个环节: (1)项目背景(问题定义) (2)数据收集 (3) 数据预处理与探索性数据分析 (4) 特征工程 (5)构建模型:机器学习模型(算法)的选择

TCP 慢启动突发丢包

TCP 慢启动会导致持续突发丢包。 慢启动以 y2xy2^xy2x 增加窗口&#xff0c;在 BDP 已经填满时&#xff0c;后续的慢启动过程如下&#xff1a; ​每一个 ACK 触发 2 个 报文&#xff0c;最终至少丢掉 1 个 BDP 的数据后 sender 才能检测到丢包而退出慢启动并进行重传。 这是…

C语言蓝桥杯刷题:明码

题目链接 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 汉字的字形存在于字库中&#xff0c;即便在今天&#xff0c;16*16 点阵的字库也仍然使用广泛。 16*16 点阵的字库把每个汉字看成是 1616 个像素信息。并把这些信息记…

电子书销售是一种可以躺赚的商业模式么?

文章目录前言电子书销售市场规模到底有多大&#xff1f;电子书产业链电子书阅读平台电子书销售平台国外国内其它销售模式探讨创建电子书创建电子书的工具电子书下载好去处相关法规前言 不知何时&#xff0c;有了网赚一词&#xff0c;例如去各大平台撸羊毛薅羊毛&#xff0c;利…

Vector - VT System - 继电器板卡_VT2820

这块板卡应该是我个人最喜欢的板卡了&#xff0c;虽然说有很多的模拟板卡、数字板卡等仿真板卡&#xff0c;但是在实际的应用中&#xff0c;我们仿真还是很难做到绝对的信号一致&#xff0c;但是如果有实际的硬件&#xff0c;通过继电器板卡就很好的模拟实际车上的场景&#xf…

定时器(Timer)

一、定时器是什么&#xff1f; 定时器类似于我们生活中的闹钟&#xff0c;可以设定一个时间来提醒我们。 而定时器是指定一个时间去执行一个任务&#xff0c;让程序去代替人工准时操作。 标准库中的定时器: Timer 方法作用void schedule(TimerTask task, long delay)指定dela…

JS数组对象——中文按照首字母排序(分组)sort()、localeCompare()

JS数组对象——中文按照首字母排序&#xff08;sort 、localeCompare&#xff09;往期同类文章场景复现根据中文首字母排序1、首字母基础排序2、排序并且分组往期同类文章 文章内容文章链接JS数组对象——根据日期进行排序&#xff0c;按照时间进行升序或降序排序https://blog…

PLC算法系列之数字低通滤波器(离散化方法:双线性变换)

低通滤波器在信号处理专栏有后向欧拉法的详细介绍和源代码,请查看相应的文章,链接如下: PLC信号处理系列之一阶低通(RC)滤波器算法_RXXW_Dor的博客-CSDN博客_rc滤波电路的优缺点1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波…

windows下安装不同版本Python教程

前言 博主也是很长一段时间没有更新文章了吧&#xff0c;因为最近都在忙着升级我的API管理系统&#xff0c;还有准备会考&#xff0c;时隔大概一个月&#xff0c;我带来了本次文章&#xff0c;如何在windows系统下安装多个版本Python&#xff0c;且各版本Python有不同的全局命…

认真学习MySQL中的那些日志文件-二进制日志(binlog)

binlog即binary log&#xff0c;二进制日志文件&#xff0c;也叫作变更日志&#xff08;update log&#xff09;。它记录了数据库所有执行的DDL和DML等数据库更新事件的语句&#xff0c;但是不包含没有修改任何数据的语句&#xff08;如数据查询语句select、show等&#xff09;…

Allegro174版本新功能介绍之关闭拷贝铜皮带网络属性功能

Allegro174版本新功能介绍之关闭拷贝铜皮带网络属性功能 Allegro在172以及以下的版本的时候,拷贝铜皮的时候会自动带上被铜皮的网络属性,在升级到了174版本的时候,是可以随时关闭和打开这个功能的,如下图 除了铜皮,过孔也是可以关闭和打开这个功能的,具体操作如下 选择Se…

NOTE:2022年11月27日以后精密星历采用长命名

IGS切换到新的参考框架—IGS20&#xff0c;以作为其产品的基础。IGS20 与 2022 年 4 月发布的 ITRF2020 密切相关。最新的卫星和地面天线校准 igs20.atx 也将同时生效&#xff0c;与 IGS20 一起使用。IGS 打算从 GPS 第 2238 周&#xff08;2022 年 11 月 27 日&#xff09;的产…

一篇分析Linux虚拟化KVM-Qemu分析之timer虚拟化

说明&#xff1a; KVM版本&#xff1a;5.9.1QEMU版本&#xff1a;5.0.0工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 先从操作系统的角度来看一下timer的作用吧&#xff1a; 通过timer的中断&#xff0c;OS实现的功能包括但不局限于上图&#xff1a; 定时…

RootPort的completion timeout为什么不能防止MCE

PCIe split transaction协议在解释completion timeout机制前&#xff0c;我们首先说一下PCIe split transaction协议是什么&#xff0c;以及为什么PCIe要选择split transaction&#xff1f;Split transaction协议是从PCI-X总线的引入的一个重要特性&#xff0c;该传输协议替代了…

微服务 分布式配置中心Apollo详解

微服务 分布式配置中心Apollo详解1. 配置中心概述1.1 配置中心简介1.2 配置中心特点1.3 配置中心对比2. Apollo概述2.1 Apollo简介2.2 Apollo特点3. Apollo设计实现3.1 基础模型3.2 架构设计3.3 Why Eureka3.4 模块说明4. Apollo安装部署4.1 部署说明4.2 环境准备3.3 下载安装包…

JavaScript奇技淫巧:隐形字符

JavaScript奇技淫巧&#xff1a;隐形字符 本文&#xff0c;分享一种奇特的JS编程技巧&#xff0c;功能是&#xff1a;可以使字符串“隐形”、不可见&#xff01; 效果展示 如下图所示&#xff0c;一个字符串经物别的操作之后&#xff0c;其长度有621字节&#xff0c;但内容却…