51单片机快速入门之 模拟 I2C 用精准中断来控制

51单片机快速入门之 模拟 I2C 用精准中断来控制

首先复习一下51单片机快速入门之定时器和计数器(含中断基础)

再看看之前的I2C操作 51单片机快速入门之 IIC I2C通信


定时器/计数器是51单片机中用于实现精确延时的硬件资源。通过配置定时器的初始值和工作模式,可以实现不同长度的延时。

例如,如果使用12MHz晶振,一个机器周期为1μs,可以通过设置定时器的初值来实现精确的延时。

// 假设需要延时1ms              

void delay_1ms()               

{

unsigned int i;               

TMOD |= 0x01; // 设置定时器0为工作方式1

TH0 = 0xFC; // 设置初值,1ms延时     

TL0 = 0x18;                   

TR0 = 1; // 启动定时器            

while(!TF0); // 等待定时器溢出       

TR0 = 0; // 关闭定时器            

TF0 = 0; // 清除溢出标志

        


软件延时是通过循环语句实现的,虽然不如定时器精确,但在不需要极高精度的场合也可以使用。为了提高精度,可以使用_NOP_()函数,它是C51编译器提供的一个空操作指令,执行时间为1个机器周期。

#include <intrins.h>

// 假设需要延时10μs

void delay_10us(unsigned char t)

{

do

{

_nop_(); _nop_(); _nop_(); _nop_(); // 4个_NOP_()指令,共4μs

_nop_(); _nop_(); _nop_(); _nop_(); // 另外4μs

}

while(--t);

}


下面开始写代码:

#include <STC89C5xRC.H>

sbit SCL=P2^0;  //时钟线
sbit SDA=P2^1;  //数据线
sbit DQ=P2^4;//读取按键
void delay_2us(); // 延时函数声明
void startI2C();//开始信号
void stopI2C();//停止信号
bit ack; //用于存储ACK信息
void ack_i2c(); //用于处理ACK信息
void SendByte(unsigned char date);//发送字节数据

void main()
{
    startI2C();//开始信号
    SendByte(0xA2);//发送从机地址
    ack_i2c();
    SendByte(0x00);//设置数据需要保存在哪个地方  地址!
    ack_i2c();
    SendByte(0X66);//发送 数据
    ack_i2c();

    stopI2C();//发送停止信号








    while(1); // 防止程序重复运行
}



void startI2C() //开始信号

{
    SDA=1;  //拉高SDA,
    delay_2us(); //延时2us
    SCL=1;  //拉高SCL 初始化
    delay_2us(); //延时2us
	  delay_2us(); //延时2us
    SDA=0;  //拉低SDA 使其进入下降沿
    delay_2us(); //延时2us
    SCL=0; //拉低SCL准备接收数据
    delay_2us(); //延时2us
	  delay_2us(); //延时2us
	  delay_2us(); //延时2us

}

void stopI2C()  //停止信号
{

    SDA=0; //拉低SDA准备进入上升沿
    delay_2us(); //延时2us
    SCL=1;//拉高SCL让其 波形长度领先
    delay_2us(); //延时2us
    delay_2us(); //延时2us
    SDA=1;
    delay_2us(); //延时2us

    /*SCL领先 SDA 此时进入上升沿 占用 现在SCL与SDA波长相等*/

}


void SendByte(unsigned char date)//发送字节数据
{
    unsigned char j; //创建一个变量用于传输
    for(j=0; j<8; j++) {
        if(date<<j&0x80)
            /*每次往左移动j 变量位 这里加()是防止意外错误 再 和 0x80 1000 0000 进行与运算AXB  */
        {
            SDA=1;      //当其最高位为1时,  结果是1  SDA 拉高
        } else {
            SDA=0;   //当其最高位为0时,结果是0 SDA 拉低
        }

        delay_2us(); //延时2us
				SCL=1;//拉高以发送现在SDA状态 到从机
        delay_2us(); //延时2us
				delay_2us(); //延时2us
        SCL=0;  //拉低开始接收下一个数据
        delay_2us(); //延时2us
				delay_2us(); //延时2us
				delay_2us(); //延时2us
		   
    } 
	  SDA=1;//拉高预防因数据最后一位是0 SDA=0 而导致 进入停止信号
    SCL=1;//拉高以发送现在SDA状态 到从机		
    SDA=1;//拉高进入ack检测
    delay_2us(); //延时2us
    
    if(SDA==0) { //从机发来ACK响应
        ack=1;   //表接收到ACK响应    
		SCL=0;
    delay_2us(); //延时2us
    delay_2us(); //延时2us
		delay_2us(); //延时2us
    } else {
        stopI2C();
    }
		


}

void ack_i2c()
{
    if(ack==1) {
        ack=0;   //满足条件初始化
    } else {
        while(1);   //不满足条件阻塞程序不让其往后执行
    }

}



void delay_2us()
{
    TMOD=0x10;//设置使用定时器1    0001 0000  不需要io触发   断开引脚输入 16位模式
    TH1 = 255; // 因为65534 / 256 = 255   存放高8位  256x256=65536
    TL1 = 254; // 因为65534 % 256 =      存放低8位  初值计算 255*256+254=65,534
    /*65536-65534=2 us
    12MHz 晶振振荡 12分频之后,为1MHz 当其从0-65536 时,需要65536μs 微秒  */
    EA=1;//打开总中断
    ET1=1;//允许定时器1中断
    TR1=1;//打开定时器1
    while(!TF1);//判断是否溢出
    /*感叹号 ! 是逻辑非运算符
     如果 TF1 是 1(定时器1溢出),  则 !TF1 是 0(假)。 向中断发送了请求
    如果 TF1 是 0(定时器1未溢出),则 !TF1 是 1(真)。
     一旦 TF1 变为1(溢出),!TF1 就变为0,循环结束
    */
    TR1=0;//关闭定时器1
    TF1=0;//初始化为未溢出状态

}

写入24c02c 运行效果(单字节写入):

  1. 开始信号
  2. 7从机地址和 写入0
  3. 从机回复ACK
  4. 数据存储地址
  5. 从机回复 ACK 
  6. 数据
  7. 从机回复ACK
  8. 停止信号

数据读取代码(选择读取):

  1. 开始信号
  2. 7从机地址和 写入0 伪写操作
  3. 从机回复ACK
  4. 数据存储地址
  5. 从机回复ACK
  6. 开始信号
  7. 7从机地址和 读取1 读取操作
  8. 从机回复ACK
  9. 获取从机发来的8位数据
  10. 拉高SDA 主机发送NACK 
  11. 停止信号
读取数据并控制P1 寄存器 应用 以验证效果 ,代码如下
#include <STC89C5xRC.H>

sbit SCL=P2^0;  //时钟线
sbit SDA=P2^1;  //数据线
sbit DQ=P2^4;//读取按键
void delay_2us(); // 延时函数声明
void startI2C();//开始信号
void stopI2C();//停止信号
bit ack; //用于存储ACK信息
void ack_i2c(); //用于处理ACK信息
void SendByte(unsigned char date);//发送字节数据
unsigned char D1,C1;//D1用于发送8位脉冲,C1用于保存读取到的数据
void REI2C();//读取8位函数
void main()
{
    startI2C();//开始信号
    SendByte(0xA2);//发送从机地址
    ack_i2c();
    SendByte(0x00);//设置数据需要保存在哪个地方  地址!
    ack_i2c();
    SendByte(0X66);//发送 数据
    ack_i2c();

    stopI2C();//发送停止信号

	  while(DQ==1); //当其为高电平时,表示按钮未按下阻塞程序
	  startI2C();//开始信号
    SendByte(0xA2);//发送 伪写入从机地址
    ack_i2c();
    SendByte(0x00);//设置要读取的 数据存储地址
    ack_i2c();
	  startI2C();//开始信号
    SendByte(0xA3);//发送 读取从机地址
    ack_i2c();
		REI2C();
	  P1 = 0x00; // 先将 P1 置为 0 
    P1 = C1;   // 再赋值 
	  ack_i2c();
		stopI2C();//停止信号
	 






    while(1); // 防止程序重复运行
}



void startI2C() //开始信号

{
    SDA=1;  //拉高SDA,
    delay_2us(); //延时2us
    SCL=1;  //拉高SCL 初始化
    delay_2us(); //延时2us
	  delay_2us(); //延时2us
    SDA=0;  //拉低SDA 使其进入下降沿
    delay_2us(); //延时2us
    SCL=0; //拉低SCL准备接收数据
    delay_2us(); //延时2us
	  delay_2us(); //延时2us
	  delay_2us(); //延时2us

}

void stopI2C()  //停止信号
{

    SDA=0; //拉低SDA准备进入上升沿
    delay_2us(); //延时2us
    SCL=1;//拉高SCL让其 波形长度领先
    delay_2us(); //延时2us
    delay_2us(); //延时2us
    SDA=1;
    delay_2us(); //延时2us

    /*SCL领先 SDA 此时进入上升沿 占用 现在SCL与SDA波长相等*/

}


void SendByte(unsigned char date)//发送字节数据
{
    unsigned char j; //创建一个变量用于传输
    for(j=0; j<8; j++) {
        if(date<<j&0x80)
            /*每次往左移动j 变量位 这里加()是防止意外错误 再 和 0x80 1000 0000 进行与运算AXB  */
        {
            SDA=1;      //当其最高位为1时,  结果是1  SDA 拉高
        } else {
            SDA=0;   //当其最高位为0时,结果是0 SDA 拉低
        }

        delay_2us(); //延时2us
				SCL=1;//拉高以发送现在SDA状态 到从机
        delay_2us(); //延时2us
				delay_2us(); //延时2us
        SCL=0;  //拉低开始接收下一个数据
        delay_2us(); //延时2us
				delay_2us(); //延时2us
				delay_2us(); //延时2us
		   
    } 
	  SDA=1;//拉高预防因数据最后一位是0 SDA=0 而导致 进入停止信号
    SCL=1;//拉高以发送现在SDA状态 到从机		
    SDA=1;//拉高进入ack检测
    delay_2us(); //延时2us
    
    if(SDA==0) { //从机发来ACK响应
        ack=1;   //表接收到ACK响应    
		SCL=0;
    delay_2us(); //延时2us
    delay_2us(); //延时2us
		delay_2us(); //延时2us
    } else {
        stopI2C();
    }
		


}

void ack_i2c()
{
    if(ack==1) {
        ack=0;   //满足条件初始化
    } else {
        while(1);   //不满足条件阻塞程序不让其往后执行
    }

}

void REI2C()
{  SDA=1;//初始化SDA 
    for(D1=0;D1<8;D1++)
{
		 SCL=1;//读取当前SDA 
	  if(SDA==1)
{
	C1=C1 | 1<<D1; 
	/* |与运算A+B 运行一个循环之后 C1此时为1 0000 0001 1为0000 0001 当 D1=1时 左移一位 0000 0010   
0000 0001 
0000 0010
----------
	0000 0011 C1此时就为这个值	
	D1=2时 位移两位 0000 0100 
	0000 0011
	0000 0100
	此时C1=0000 0111 
	
	注意每次移位都只是 对0000 0001 操作 ! 谨防思维混乱
	
	*/
}//因为D1是递增的,所以如果 当D1=2 时 if不满足 下次 D1=3 就会多出一个0 
		    SCL=0;  //拉低开始接收下一个数据
        delay_2us(); //延时2us
				delay_2us(); //延时2us
				delay_2us(); //延时2us
	   
}
SDA=1;//拉高预防因数据最后一位是0 SDA=0 而导致 进入停止信号
    SCL=1;//拉高以发送现在SDA状态 到从机		
    SDA=1;//拉高进入ack检测
    delay_2us(); //延时2us
    
    if(SDA==0) { //从机发来ACK响应
        ack=1;   //表接收到ACK响应    
		SCL=0;
    delay_2us(); //延时2us
    delay_2us(); //延时2us
		delay_2us(); //延时2us
    } else {
        stopI2C();
    }

}





void delay_2us()
{
    TMOD=0x10;//设置使用定时器1    0001 0000  不需要io触发   断开引脚输入 16位模式
    TH1 = 255; // 因为65534 / 256 = 255   存放高8位  256x256=65536
    TL1 = 254; // 因为65534 % 256 =      存放低8位  初值计算 255*256+254=65,534
    /*65536-65534=2 us
    12MHz 晶振振荡 12分频之后,为1MHz 当其从0-65536 时,需要65536μs 微秒  */
    EA=1;//打开总中断
    ET1=1;//允许定时器1中断
    TR1=1;//打开定时器1
    while(!TF1);//判断是否溢出
    /*感叹号 ! 是逻辑非运算符
     如果 TF1 是 1(定时器1溢出),  则 !TF1 是 0(假)。 向中断发送了请求
    如果 TF1 是 0(定时器1未溢出),则 !TF1 是 1(真)。
     一旦 TF1 变为1(溢出),!TF1 就变为0,循环结束
    */
    TR1=0;//关闭定时器1
    TF1=0;//初始化为未溢出状态

}

运行效果!可以看到我们读取到了之前存入的 0x66 数据! 

0110 0110 注意看P1的状态

 

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

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

相关文章

Unable to open nested entry ‘********.jar‘ 问题解决

今天把现网版本的task的jar拖回来然后用7-zip打开拖了一个jar进去替换mysql-connector-java-5.1.47.jar 为 mysql-connector-java-5.1.27.jar 启动微服务的时候就报错下面的 Exception in thread "main" java.lang.IllegalStateException: Failed to get nested ar…

《Python游戏编程入门》注-第2章2

《Python游戏编程入门》的“2.2.5 绘制线条”中提到了通过pygame库绘制线条的方法。 1 相关函数介绍 通过pygame.draw模块中的line()函数来绘制线条&#xff0c;该函数的格式如下所示。 line(surface, color, start_pos, end_pos, width1) -> Rect 其中&#xff0c;第一…

开源限流组件分析(二):uber-go/ratelimit

文章目录 本系列漏桶限流算法uber的漏桶算法使用mutex版本数据结构获取令牌松弛量 atomic版本数据结构获取令牌测试漏桶的松弛量 总结 本系列 开源限流组件分析&#xff08;一&#xff09;&#xff1a;juju/ratelimit开源限流组件分析&#xff08;二&#xff09;&#xff1a;u…

部署前后端分离若依项目--CentOS7宝塔版

准备&#xff1a; CentOS7服务器一台 通过网盘分享的文件&#xff1a;CentOS 7 h 链接: https://pan.baidu.com/s/17DF8eRSSDuj9VeqselGa_Q 提取码: s7x4 大家有需要可以下载这个&#xff0c;密码61 若依前端编译后文件 通过网盘分享的文件&#xff1a;ruoyi-admin.jar 链…

生信软件39 - GATK最佳实践流程重构,提高17倍分析速度的LUSH流程

1. LUSH流程简介 基因组测序通常用于分子诊断、分期和预后&#xff0c;而大量测序数据在分析时间方面提出了挑战。 对于从FASTQ到VCF的整个流程&#xff0c;LUSH流程在非GVCF和GVCF模式下都大大降低了运行时间&#xff0c;30 X WGS数据耗时不到2 h&#xff0c;从BAM到VCF约需…

【计网】UDP Echo Server与Client实战:从零开始构建简单通信回显程序

目录 前言&#xff1a; 1.实现udpserver类 1.1.创建udp socket 套接字 --- 必须要做的 socket&#xff08;&#xff09;讲解 代码实现&#xff1a;​编辑 代码讲解&#xff1a; 1.2.填充sockaddr_in结构 代码实现&#xff1a; 代码解析&#xff1a; 1.3.bind sockfd和…

linux中级wed服务器(https搭建加密服务器)

一。非对称加密算法&#xff1a; 公钥&#xff1a;公共密钥&#xff0c;开放 私钥&#xff1a;私有密钥&#xff0c;保密 1.发送方用自己的公钥加密&#xff0c;接受方用发送方的私钥解密&#xff1a;不可行 2.发送方用接受方的公钥加密&#xff0c;接受方用自己的私钥解密…

ffmpeg视频滤镜: 色温- colortemperature

滤镜简述 colortemperature 官网链接 》 FFmpeg Filters Documentation 这个滤镜可以调节图片的色温&#xff0c;色温值越大显得越冷&#xff0c;可以参考一下下图&#xff1a; 咱们装修的时候可能会用到&#xff0c;比如选择灯还有地板的颜色的时候&#xff0c;选暖色调还是…

【论文+源码】基于spring boot的垃圾分类网站

创建一个基于Spring Boot的垃圾分类网站涉及多个步骤&#xff0c;包括环境搭建、项目创建、数据库设计、后端服务开发、前端页面设计等。下面我将引导您完成这个过程。 第一步&#xff1a;准备环境 确保您的开发环境中安装了以下工具&#xff1a; Java JDK 8 或更高版本Mav…

Unity URP ShaderGraph 基本设置

先简单了解一下各种渲染管线之间的区别 Unity 从 2019.3 版本开始正式支持通用渲染管线&#xff08;URP&#xff0c;Universal Render Pipeline&#xff09;。URP 是轻量渲染管线&#xff08;LWRP&#xff0c;Lightweight Render Pipeline&#xff09;的升级和重命名版本&…

【解决】使用Hypermark将Markdown文件转化为HTML文件

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 文章目录 一、文件准备&#xff08;一&#xff09;HTML模板文件&#xff08;二&#xff09;MD文件夹和储存文件夹 二、文件转…

COSCon'24 志愿者招募令:共创开源新生活!

亲爱的开源爱好者们&#xff0c; 第九届中国开源年会&#xff08;COSCon24&#xff09;即将在北京中关村国家自主创新示范区会议中心于2024年11月2日至3日隆重举行。今年的主题是“Open Source, Open Life&#xff5c;开源新生活”&#xff0c;旨在探索开源技术如何在各个领域推…

日常记录,使用springboot,vue2,easyexcel使实现字段的匹配导入

目前的需求是数据库字段固定&#xff0c;而excel的字段不固定&#xff0c;需要实现excel导入到一个数据库内。 首先是前端的字段匹配&#xff0c;显示数据库字段和表头字段 读取表头字段&#xff1a; 我这里实现的是监听器导入&#xff0c;需要新建一个listen类。 读Excel …

uniApp 加载google地图 并规划路线

uniApp 加载google地图 并规划路线 备注:核心代码实例 备注: 打开谷歌地图失败的话 参考google开发文档 https://developers.google.com/maps/documentation/urls/ios-urlscheme?hlzh-cn#swift核心代码 mounted() {this.loadGoogleMapsScript(); }, methods: {//加载loadGo…

AI服务器HBA卡的国产PCIe4.0/5.0 switch信号完整性设计与实现,支持定制(二)

表 &#xff12; 展示了 &#xff30;&#xff23;&#xff22; 板所选介质材料 &#xff30;&#xff33;&#xff32;&#xff14;&#xff10;&#xff10;&#xff10;&#xff21;&#xff35;&#xff33;&#xff17;&#xff10;&#xff13; &#xff0c; &#xff3…

解决Redis缓存穿透(缓存空对象、布隆过滤器)

文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库 常见的解决方案有两种&#xff0c;分别…

使用DolphinScheduler接口实现批量导入工作流并上线

使用DS接口实现批量导入工作量并上线脚本 前面实现了批量生成DS的任务&#xff0c;当导入时发现只能逐个导入&#xff0c;因此通过接口实现会更方便。 DS接口文档 DS是有接口文档的地址是 http://IP:12345/dolphinscheduler/swagger-ui/index.html?languagezh_CN&lang…

安全见闻---清风

注&#xff1a;本文章源于泷羽SEC&#xff0c;如有侵权请联系我&#xff0c;违规必删 学习请认准泷羽SEC学习视频:https://space.bilibili.com/350329294 安全见闻1 泷哥语录&#xff1a;安全领域什么都有&#xff0c;不要被表象所迷惑&#xff0c;无论技术也好还是其他方面…

网站建设中需要注意哪些安全问题?----雷池社区版

服务器与应用安全指南 1. 服务器安全 1.1 操作系统安全 及时更新补丁&#xff1a;确保操作系统始终安装最新补丁&#xff0c;以防范系统漏洞。例如&#xff0c;Windows Server 定期推送安全更新&#xff0c;修复如远程代码执行等潜在威胁。优化系统服务配置&#xff1a;关闭不…

PoissonRecon学习笔记

1. Screened Poisson Reconstruction (SPR) 源码&#xff1a;https://github.com/mkazhdan/PoissonRecon However, as noted by several researchers, it suffers from a tendency to over-smooth the data. 泊松重建存在过度平滑的现象。 方法&#xff1a;position and gradi…