常用通信协议——IIC协议编程实现[通俗易懂]

示意图

大家好,又见面了,我是你们的朋友全栈君。

一、IIC连接实物示意图

在这里插入图片描述

二、IIC协议程序编写的要点:

1、空闲状态
2、开始信号
3、停止信号
4、应答信号
5、数据的有效位
6、数据传输

三、IIC驱动编写

1、硬件准备

此处使用正点原子Mini板STM32F103,使用的IO口为C11、C12。由于使用的IO口并不是自带硬件IIC口,所以在此我们使用软件模拟IIC传输。(没有硬件的同学也可以继续看下去,协议的实现与硬件没有太大关系)

2、程序编写

由上文得知IIC协议程序编写的要点:下面我们来依次实现

第一步、首先我们先来初始化一下IO口(只是理解IIC协议原理的同学,可直接跳过)
void IIC_Init(void)
{ 
   
    GPIO_InitTypeDef GPIO_Initure; 
    __HAL_RCC_GPIOC_CLK_ENABLE();   //使能GPIOC时钟 
    //PC11,12初始化设置
    GPIO_Initure.Pin=GPIO_PIN_11|GPIO_PIN_12;
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
    HAL_GPIO_Init(GPIOC,&GPIO_Initure);  
    IIC_SDA=1;
    IIC_SCL=1;  
}

此处初始化需注意要上拉两个IO口,因为IIC协议设定SDA=1,SCL=1为空闲状态。
此处使用的是HAL库,HAL对IO口的结构体定义如下:

typedef struct
{ 
   
  uint32_t Pin;/*引脚*/       /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */
  uint32_t Mode;/*模式*/      /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIO_mode_define */
  uint32_t Pull;/*上拉或下拉*/      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins. This parameter can be a value of @ref GPIO_pull_define */
  uint32_t Speed;/*IO口速度*/     /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
第二步、对SDA、SCL进行宏定义(只是理解IIC协议原理的同学,可直接跳过)
//IO操作
#define IIC_SCL PCout(12) //SCL
#define IIC_SDA PCout(11) //SDA
#define READ_SDA PCin(11) //输入SDA
第三步、对各个要点进行实现
1、空闲状态

无数据发送接收时

void IIC_Leisure(void)
{ 
   
    IIC_SDA=1;
    IIC_SCL=1; 
}
2、开始信号

在这里插入图片描述
根据时序图,可知,开始信号:SCL为期间, SDA由的跳变;
特别注意:启动信号是一种电平跳变时序信号,下面是程序编写:

void IIC_Start(void)
{ 
   
    SDA_OUT();     	/*设置C12为输出模式*/
	IIC_SDA=1;	  	/*拉高保持空闲状态*/  
	IIC_SCL=1;		/*拉高保持空闲状态*/  
	delay_us(4);    /*延时保证电平稳定*/  
 	IIC_SDA=0;   	/*当SCL为高时,SDA由高到低的跳变*/  
	delay_us(4);    /*延时保证电平稳定*/ 
	IIC_SCL=0;		/*钳住I2C总线,准备发送或接收数据*/
}	
3、停止信号

在这里插入图片描述
根据时序图,可知,开始信号:SCL为期间, SDA由的跳变;
特别注意:停止信号是一种电平跳变时序信号,同时此处有个小细节,如时序图,SDA只有在SCL变高时才能变换。下面是程序编写:

void IIC_Stop(void)
{ 
   
	SDA_OUT();	/*设置C12为输出模式*/
	IIC_SCL=0;
	IIC_SDA=0;
 	delay_us(4);
	IIC_SCL=1; 
	IIC_SDA=1;/*当SCL为高时,SDA由高到低的跳变*/ /*如出现电平不稳,也可在本句上方+一个延时*/
	delay_us(4);		
}				
4、应答信号

根据我们此项目来说,我们是使用单片机读取AT24C02里的数据。故此处发送器为AT24C02,接收器为单片机。
发送器(AT24C02)每发送一个字节, 就在时钟脉冲第9个期间释放数据线,由接收器(单片机)反馈一个应答信号。

当应答信号为低电平时, 表示接收器已经成功地接收了该字节,规定为应答位(ACK)
当应答信号为高电平时, 表示接收器接收该字节失败。规定为非应答位(NACK)
在这里插入图片描述
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低, 并且确保在该时钟的高电平期间为稳定的低电平

同时我们还需要对应答信号进行判断,所以我们此处需要编写三个程序:1、产生应答信号;2、产生非应答信号;3、检测应答信号,其中产生应答信号和产生非应答信号是接收器(单片机)使用的,检测应答信号为发送器(AT24C02)使用的。

(1)产生应答信号

void IIC_Ack(void)
{ 
   
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

(2)产生非应答信号

void IIC_NAck(void)
{ 
   
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}	

(3)检测应答信号

u8 IIC_Wait_Ack(void)
{ 
   
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入 
	IIC_SDA=1;delay_us(1);	   
	IIC_SCL=1;delay_us(1);	 
	while(READ_SDA)
	{ 
   
		ucErrTime++;
		if(ucErrTime>250)
		{ 
   
			IIC_Stop();
			return 1;/*数据传送失败。检测为非应答信号*/
		}
	}
	IIC_SCL=0;//时钟输出0 
	return 0;  /*数据传送失败。检测为应答信号*/
} 
5、数据传输、数据的有效性

(1)数据的有效性
在这里插入图片描述
I2C总线进行数据传送时, 时钟信号为高电平期间, 数据线上的数据必须保持稳定, 只有在时钟线上的信号为低电平期间, 数据线上的高电平或低电平状态才允许变化。

即: 数据在SCL的上升沿到来之前就需准备好。 并在在下降沿到来之前必须稳定。(如图示,SCL周期小于SDA周期,且被包含在内)
(2)数据传输
数据位的传输采用边沿触发
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(同步控制), 即在SCL串行时钟的配合下, 在SDA上逐位地串行传送每一位数据 。

写数据

//IIC发送一个字节//返回从机有无应答//1,有应答//0,无应答 
void IIC_Send_Byte(u8 txd)
{ 
                           
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;/*拉低时钟开始数据传输*/
    for(t=0;t<8;t++)
    { 
                 
        IIC_SDA=(txd&0x80)>>7;/*需要发送的数据经过和0x80的与运算之后,右移7位*/
        txd<<=1;/*左移7位*/
		delay_us(2);   /*对延时是必须的*/
		IIC_SCL=1;
		delay_us(2);  /*对延时是必须的*/
		IIC_SCL=0;	
		delay_us(2); /*对延时是必须的*/
    }	 
} 

IIC_SDA=(txd&0x80)>>7; txd<<=1;这两句有疑问的同学可以看我下面推导:
在这里插入图片描述
读数据

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK 
u8 IIC_Read_Byte(unsigned char ack)
{ 
   
	unsigned char i,receive=0;
	SDA_IN();/*SDA设置为输入*/
    for(i=0;i<8;i++ )
	{ 
   
        IIC_SCL=0; 
        delay_us(2);
		IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();/*发送NACK*/
    else
        IIC_Ack(); /*发送ACK*/
    return receive;
}

参考文献:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f103_mini.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/129669.html原文链接:https://javaforall.net

(0)
上一篇 2022年4月14日 下午3:00
下一篇 2022年4月14日 下午3:20


相关推荐

  • 时间轮算法[通俗易懂]

    时间轮算法[通俗易懂]时间轮算法最近工作中使用了Xxl-Job框架来做分布式调度,内部采用了时间轮做整体调度,顺便学习并总结一下。概述绝对时间和相对时间定时任务一般有两种:1.约定一段时间后执行。2.约定某个时间点执行。​ 其实这两者是可以互相转换的,比如现在有一个定时任务是12点执行,当前时间是9点,那就可以认为这个任务是3小时后执行。同样,现在又有一个任务,是3小时后执行,那也可以认为这个任务12点执行。​ 假设我们现在有3个定时任务A、B、C,分别需要在3点、4点和9点执行,我们把

    2026年4月20日
    5
  • SpringBoot自动装配原理分析

    SpringBoot自动装配原理分析先看看SpringBoot的主配置类:里面有一个main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解的类。@SpringBootApplicationSpringBoot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动SpringBoot项目。那@…

    2022年8月21日
    10
  • 扣子工作流新手实操教程

    扣子工作流新手实操教程

    2026年3月12日
    2
  • gitee使用教程,创建项目仓库并上传代码

    gitee使用教程,创建项目仓库并上传代码文章目录一 关于 gitee 二 安装 git 三 登录 gitee 四 生成 SSH 公钥五 配置 SSH 公钥六 创建一个项目七 克隆仓库到本地八 关联本地工程到远程仓库九 添加文件十 执行 git 命令 提交文件十一 常用的 git 命令一 关于 giteegitee 中文名 码云 原名 Git OSC 是开源中国推出的基于 git 的代码托管服务 国内访问 GitHub 速度比较慢 如果想托管自己的代码到云端 gitee 是个不错的选择 华为的鸿蒙 2 0 源码也是放在 gitee 上的 二 安装 git 要使用 gitee 需要先安装 gi

    2026年3月19日
    2
  • fast发现_fasttext

    fast发现_fasttextfastai错误提示找不到’fastai.conv_learner’输入pipinstallfastai==0.7.0pipinstalltorchtext==0.2.3

    2025年10月2日
    3
  • Pycharm设置utf-8自动显示

    Pycharm设置utf-8自动显示File gt Setting gt Editor gt FileandCodeT gt pythonscript 右侧框框内输入 nbsp coding utf 8 nbsp 然后重新启动 Pycharm 这样每个新建的 Python 文件行首都会显示 nbsp coding utf 8

    2026年3月27日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号