C51学习归纳9 --- I2C通讯学习(重点)

2024-06-10 12:36
文章标签 学习 重点 归纳 通讯 i2c c51

本文主要是介绍C51学习归纳9 --- I2C通讯学习(重点),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        首先,我自己学习过以后的直观感觉,通信协议是单片机的灵魂之一,只有规定好了通信协议我们才能够正确的接收到信息,才能实现更加深入的研究。所以这一部分是需要好好学习的。

        本节借助一个可存储的芯片AT24C02,进行在I2C协议上的读取,实现掉电不丢失,永久存储数据并读取的工作。

一、I2C通信协议原理

        24C02的通讯方式是I2C,所以这个芯片端口的连接直接按照I2C所需直接预留出来。WE是写入使能,即可以存储内容。P2_1,2是控制我们写入时机,和内容的控制端口。后续讲完I2C通讯流程,大家应该就懂了。E0~2直接接地,是I2C的地址。

        上图是I2C通信协议开始和结束的信号标志。

        开始标志:在SCL高电平的时候,将数据线SDA下拉,从高电平到低电平。随后SCL拉低       

        结束标志:在SCL高电平的时候,将数据线SDA上拉,从低电平到高电平。 SCL提前拉高

         在收到开始信号以后,我们就可以发送数据了,现在讲数据怎么发送的。

        开始信号后,SCL=0,SDA=0。在SCL=0的期间,我们将数据放在SDA上,在下一次SCL=1时,从机读取数据。

       在讲数据怎么接收的。

       开始前SCL=0,SDA=0。在SCL=0的期间,从机将数据放在SDA上,在下一次SCL=1时,主机读取刚才SDA写入的数据。注意,这里需要主机释放SDA线。

         这一步其实是为了,确定从机是否接受,和确定从机是否发送完毕。保证系统通信的连贯和正确。        

         主机发送开始位,然后发送从机地址和设置为写模式,随后监听从机的接收应答,如果接收了,返回RA信号。然后主机开始发送数据,从机应答,以此往复,直到最后主机发送结束位。本次通信结束。

        地址,前四位是厂家定的,加下来三位由电路板的连接确定,电路板全部连地,所以就是000.最后一位是1为读,0为写。

         主机发送开始位,然后发送从机地址和设置为读模式,随后监听从机的接收应答,如果接收了,返回RA信号。然后从机开始发送数据,从机发送发送应答,以此往复,最后遇到SA=1时主机发送结束位。本次通信结束。

         这个就是将前两个组合起来,发送尾部去掉停止信号,直接加上接受操作。

         这里就是在指定位置写入、读取。

二、利用AT24C02 进行数据存储和读取

        那么我们要借助I2C通信,我们就要写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 接收到的一个字节数据*/
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  AckBit 应答位,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的通信协议

#include <REGX52.H>
#include "I2C.h"#define AT24C02_ADDRESS		0xA0/*** @brief  AT24C02写入一个字节* @param  WordAddress 要写入字节的地址* @param  Data 要写入的数据* @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  WordAddress 要读出字节的地址* @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;
}

         有了I2C的通信协议之后,我们的AT24C02就可以借助I2C的通信格式,编写自己的读写功能。这个也是刚才的逻辑图。

#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"unsigned char KeyNum;
unsigned int Num;void main()
{LCD_Init();LCD_ShowNum(1,1,Num,5);while(1){KeyNum=Key();if(KeyNum==1)	//K1按键,Num自增{Num++;LCD_ShowNum(1,1,Num,5);}if(KeyNum==2)	//K2按键,Num自减{Num--;LCD_ShowNum(1,1,Num,5);}if(KeyNum==3)	//K3按键,向AT24C02写入数据{AT24C02_WriteByte(0,Num%256);Delay(5);AT24C02_WriteByte(1,Num/256);Delay(5);LCD_ShowString(2,1,"Write OK");Delay(1000);LCD_ShowString(2,1,"        ");}if(KeyNum==4)	//K4按键,从AT24C02读取数据{Num=AT24C02_ReadByte(0);Num|=AT24C02_ReadByte(1)<<8;LCD_ShowNum(1,1,Num,5);LCD_ShowString(2,1,"Read OK ");Delay(1000);LCD_ShowString(2,1,"        ");}}
}

        编写了底层的代码之后,我们在主函数里只需要,直接调用就好了。

三、利用定时器实现秒表

        之前也实现了类似的功能,但是以前按键按下以后是阻塞的,不利于芯片的运行,现在使用定时器对按键扫描,克服以前的不足。

#include <REGX52.H>
#include "Timer0.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()
{Timer0_Init();while(1){KeyNum=Key();if(KeyNum==1)			//K1按键按下{RunFlag=!RunFlag;	//启动标志位翻转}if(KeyNum==2)			//K2按键按下{Min=0;				//分秒清0Sec=0;MiniSec=0;}if(KeyNum==3)			//K3按键按下{AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02Delay(5);AT24C02_WriteByte(1,Sec);Delay(5);AT24C02_WriteByte(2,MiniSec);Delay(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调用一次数秒表驱动函数}
}

        我们先看主要的逻辑,中断中,1、每次产生中断都要重新赋值;2、我们每20ms对独立按键进行扫描,判断当下的按键值;3、每2ms对数码管进行顺序刷新显示;4、每10ms,因为我们的毫秒只显示2位,所以我们每10ms显示依次新的毫秒数字,并并进位。

        讲完了中断,我们关注一下主函数的内容,每当我们扫描得到了键码值以后,我们针对不同的键码值做不同的工作。按键一:修改计时器开始。按键二:数据清理。按键三:写入。案件四:从芯片读出,显示在数码表。

        随后讲一下优势:

        1、使用定时器,避免了延时函数占用CPU,从而提高效率。

        2、使用定时器,定时扫描数码管,显示效果更好

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=1;Nixie_Scan(i,Nixie_Buf[i]);i++;if(i>=9){i=1;}
}

        3、使用定时器,可以消除按键抖动,配合如下代码

void Key_Loop(void)
{static unsigned char NowState,LastState;LastState=NowState;				//按键状态更新NowState=Key_GetState();		//获取当前按键状态//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测if(LastState==1 && NowState==0){Key_KeyNumber=1;}if(LastState==2 && NowState==0){Key_KeyNumber=2;}if(LastState==3 && NowState==0){Key_KeyNumber=3;}if(LastState==4 && NowState==0){Key_KeyNumber=4;}
}

这篇关于C51学习归纳9 --- I2C通讯学习(重点)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1048180

相关文章

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Nginx Location映射规则总结归纳与最佳实践

《NginxLocation映射规则总结归纳与最佳实践》Nginx的location指令是配置请求路由的核心机制,其匹配规则直接影响请求的处理流程,下面给大家介绍NginxLocation映射规则... 目录一、Location匹配规则与优先级1. 匹配模式2. 优先级顺序3. 匹配示例二、Proxy_pa

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

C#使用MQTTnet实现服务端与客户端的通讯的示例

《C#使用MQTTnet实现服务端与客户端的通讯的示例》本文主要介绍了C#使用MQTTnet实现服务端与客户端的通讯的示例,包括协议特性、连接管理、QoS机制和安全策略,具有一定的参考价值,感兴趣的可... 目录一、MQTT 协议简介二、MQTT 协议核心特性三、MQTTNET 库的核心功能四、服务端(BR

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

如何使用C#串口通讯实现数据的发送和接收

《如何使用C#串口通讯实现数据的发送和接收》本文详细介绍了如何使用C#实现基于串口通讯的数据发送和接收,通过SerialPort类,我们可以轻松实现串口通讯,并结合事件机制实现数据的传递和处理,感兴趣... 目录1. 概述2. 关键技术点2.1 SerialPort类2.2 异步接收数据2.3 数据解析2.

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert