STM32: IIC通讯协议基础学习记录

一、概念

串口通讯:一般都是一主一从(UART)

总线通讯:N主N从(SPI/IIC/USB CAN总线)

串口通讯可以当成uart,uart就是一主一从,通讯就是一个接一个发;

三个以上的设备要通信的话,设备之间需要两两相连接,很麻烦,所以有了总线通讯;

1、SPI

三线制和四线制;时钟线,片选线等;

主往从发MOSI;从往主发是MISO;

2、I2C协议起源

一种说法是,恩智浦(NXP)半导体公司优化的;飞利浦公司(1960年成立的)恩智浦是2006年独立出去的;

I2C经典协议:可以有多个主机,也可以有多个从机;

只有两根线,一个时钟线一个数据线(SCK和SDA)

硬件层:可以是一主多从,也可以是多主多从;

如果主1和主2同时对从1进行通讯,会出现冲突,

因此提出方案:总线仲裁

二、总线仲裁

1、按位与操作(SDA的线与结构)

SCK对数据进行采样,只在高电平的时候进行采样;比如一个占空比为50%的方波上进行采样;进行仲裁的时候,在高电平的时候分别进行采样,看高电平的时候,SDA如果是高电平,那么如果这个主机发送的不是高电平,就pass这个主机;这就是仲裁机制;

2、总线仲裁两步骤

由多个主设备,先线与产生SDA;然后利用SCK(SCK是选择比较区域的,SDA是和主机电平作比较的)逐位将SDA与主1/主2进行比较,相同数据则满足条件,反之则停止发送

多个主机同时对从机进行数据传送时,产生冲突因而需要选择接收数据的次序;因此要用到我们的总线仲裁方案:对多个主机的电平线线与产生SDA(1-1为1,0-0为0,1-0为0),接着利用SCK(SCK是选择比较区域的,SDA是和主机电平作比较的)逐位将SDA与主1/主2进行比较,相同数据则满足条件,反之则停止发送

总线仲裁的应用:出现多个主机同时与相同的从机进行通讯;

3、总线仲裁的操作

线与:将主1与上主2等等,得到我们的SDA;

比较:让主1和SDA比较,主2和SDA比较…比较的时机是SCK的高电平;

规则:首次出现,主和SDA不相符的,立即退出,将SDA的主动权让给其他符合条件的主机。

三、硬件层

1、SDA、SCK

开漏+上拉

不这样的话,就存在烧坏(短路–大电流–烧坏)的风险

为什么呢?——(设备1为0,设备2为1的时候,就会短路,设备1是1,设备2是0的时候也会短路)

开漏模式下输出高电平——加上拉电阻;

SCK和SDA都各自加一个上拉电阻;设备1和设备2一个为0,一个为1时,电阻大小选择:4.7k,稍微大一点的电阻(因为电压是3.3V,电流是0.702mA,如果电阻是100欧,电流就会是33mA,电流过大了)用4.7k的电阻,这样子I2C运行稳定;如果要让I2C运行提速的话,选择一个稍微小点的电阻,其实电阻这里能影响I2C通讯的速度的;

问题:

两个从机同时向主机通讯,主机如何接收?

这个需要IIC协议层去解决;

四、软件层

1、概括

整个通讯协议分两步,主机往从机(写/W),从机往主机(读/R);当主机通过从机地址与某个从机通讯时,则其他从机没有SDA的使用权;

2、写的步骤

1找到从机地址;

2明确和从机通讯的方向:写(主–>从),明确是要往从机进行写操作;

3从机存放数据的地址(0x01/0x02/0x03…)

(pc指向分区的位置)(也就是将从机内部指针指向目标寄存器(内存))

4主->从发数据

3、读

(主机想和0x3f通讯)

从机地址;

读操作;

将PC指针指向03位置;

从机往主机发送数据(读操作)

找到从机的地址,要把这个从机的地址发送到主机,这其实也是一种写操作->在发从机地址和从机内存地址的时候,这两个其实就是写操作;

所以这个读操作需要优化:

1从机地址(0x3f)+写操作;

2从机内存地址(0x03)指针指向(0x03)

3主机给从机发送一个停止信号

4主机又发送从机地址+读操作

5主机开始接收从机的数据(因为操作2中,指针就指向了0x03了)(从当前PC的位置开始读)

123步也就是:找到从机,并且将从机内部指针指向目标寄存器(内存)

4、写

1.主机发一个从机地址(3f)+写操作

2.主机发0x3f的内存地址(02)

0x3f设备将自身的指针指向自己的02寄存器(从机的行为)

3.主机在已知从设备以及从设备的内存寄存器下,开始发送数据

4.主机停止发送数据,并且给出一个停止位

5、读(具体)

本质是从机往主机发送数据,但是前提条件是,指针指向的位置正确,所以主机还是要先往从机发数据,将从设备内部指针指向正确

例如:需求是:主机想读取0x4f中03的数据

1.先将从设备中的指针指向正确的内存地址

(主机发送从设备的地址(0x4f)+写操作

主机发送内存地址(0x4f的02)

从设备0x4f将自己的指针指向0x02寄存器

主机发停止位)

2.从当前指针的位置开始读数据

(主机发一个0x4f + 读操作

从机开始往主机发数据

主机发一个停止位)

以上是读、写的基本的原则

五、市面上常见的I2C设备

1、监测环境的相关传感器

(温度传感器,只负责去测量温度,这个芯片只放了一块内存,就只用来存储外界温度值;这种产品不需要写这个通讯协议,只需要读,而且不需要规定在哪里读,不需要知道内存地址,直接读即可)(通讯协议就可以这样简单设计:开始位,从机地址+读操作,ACK响应,传数据,NACK,STOP)

2、时钟芯片

(专门买个时钟芯片,这种都是用I2C通讯的)

液晶屏驱动芯片/LED驱动芯片(这个驱动芯片涉及到解耦思想)(液晶显示屏驱动芯片,需要yog指令去控制内存;有很多条指令,比如第一条指令是在0x00–0x2f之间读,第二条指令又在另一个地址读,这种设备,就需要根据指令去操作了:开始位,从机地址+写操作,驱动芯片给一个返回值ACK,写从机的指令,接收从机的返回ACK,还想发可以继续,不发的话就是一个STOP;

读数据的话:开始位,从机地址+读操作,ACK,指令,ACK,停止位STOP)

3、EEPROM芯片

(flash,内存相关)

4、ADC/DAC

(模数转换/数模转换)

5、GPIO拓展芯片

六、协议

协议组成:空闲、开始位、停止位、发送应答、接收应答、发送一个字节、接收一个字节

这些协议,是以主机为参考的;

1、组成介绍

空闲:SCK和SDA都为高电平,在这个时间点,都是空闲的;

开始位:SCK=1;SDA由高电平变为低电平;

停止位:SCK=1;SDA由低电平转化为高电平;

出现了一个上升沿;即出现上升沿时,代表通信已经结束了;

发送应答:SCK=1; SDA=0时,代表有应答;

SDA=1时,代表无应答;

发送应答、接收应答,主机和从机都可以

发送字节、接收字节:规则:

主机和从机发送字节,都必须遵循高位先行的规则

10110011

发送一位字节,必须在SCK=0时发;

接收一位字节,必须在SCK=1时收;

主机发第一个数据(1),有一个大的原则:时钟线高电平的时候(也就是SCK=1时要保证SDA的值稳定),数据一定要保持稳定;SCK变成高电平之前,SDA就要变化好;

2、实践代码

①IIC_Init( )初始化

②IIC Write( )-----
1、IIC_Start( )    
2、IIC_Addr( )(从机的真实地址+写操作位)    
3、IIC_Receive ACK( )    
4、IIC_addr( )(外设内部的地址)
5、依旧是ACK
6、IIC_SendData( )
7、IIC_Receive ACK( )   6和7是真正写入数据
8、IIC_Stop( ) 

③IIC_Read( )
1-5、从机将内部指针指向对应的寄存器(reg)
6、IIC_Addr( )   (这时候是地址+读操作) 
7、IIC_Receive ACK( )
8、IIC_Receive Data( )
9、IIC_Send ACK( )      8和9一直循环
直到IIC_Send NACK( )
10、IIC_Stop( )

IIC start和stop肯定需要的,接收数据和发送数据肯定是需要的; SendData ReceiveData

还有Send_ACK 和 Receive_ACK

3、IIC分为硬件IIC和软件IIC

硬件IIC有现成的接口:API: HAL IIC xx

软件IIC: 不用外设,只用GPIO,随机找两个引脚,

一个取名SCK,一个取名SDA;

实现方法:两个引脚配置:

SCK:输出->开漏加上拉

SDA:既有主机往从机发,又有从机往主机发;

解决方案:①:不断切换输入/输出模式

②:直接配置成开漏加上拉电阻;输出的时候也是可以进行读操作的;输出模式下不会影响输入功能;正常就是把两个引脚都配置成开漏加上拉;

软件实现的好处就是:引脚可以任意选择,只要配置正确就行;

4、伪代码

IIC_Start( ){
	SCK=1;
	SDA=1;
	SDA=0;
	SCK=0;
}

IIC_Stop( ){
	SCK=1;
	SDA=0;
	SDA=1;
	SCK=0;
}
IIC_Send ACK( ){
	SCK=0;
	SDA(0/1);(用hal库中的write函数)
	SCK=1;(SCK=1的时候进行校验)
	SCK=0;
}

IIC_Receive_ACK( ){(这时候ACK是从机发的)
	uint8 ACK;
	SDA=1;(1的时候是空闲状态)
	//主机放开主动权
	SCK=1;(数据必须在SCK=1的时候进行抓取)
	//主机等待读取的时机
	ACK=SDA( );(HAL库中的reading函数)
	SCK=0;(主动拉低时钟线,为下次做准备)
	return ACK;
}
IIC_SendByte(unit8_t Data)
原则:发送/接收原则:高位先行
SCK=0的时候,数据跳变
SCK=1的时候,数据要稳定
	SCK=0;
	SDA=第八位;
	SCK=1;
	SCK=0;
	SDA=第七位;
	SCK=1;
	需要循环八次-->
	真实代码:
	SCK=0;
	for(i=0;i<8;i++){
			if(Data&(0x80>>i)){
				SDA=1;
			}else{
				SDA=0;
				}
			SCK=1;
			SCK=0;
	}	
IIC_ReceiveByte( ){
	//定义data
	unit8_t Data=0;
	//主机让出使用权
	SDA=1;
	for(i=0;i<8;i++){
		SCK=1;
		if(SDA==1){
			data |=(0X80....);
		}
		SCK=0;
	}
}

七、编程

设置两个引脚,都配置为开漏+上拉

在工程文件夹中,创建IIC文件夹,在此文件夹中创建IIC.c和IIC.h文件

头文件都要包含;还要记得防止重复定义;

导入IIC.h文件

IIC.h文件

#ifndef IIC_H
#define IIC_H

#include "gpio.h"

#define SDA(x) HAL_GPIO_WritePin(SDA_GPIO_Port,SDA_Pin,x);
#define SCK(x) HAL_GPIO_WritePin(SCK_GPIO_Port,SCK_Pin,x);

#define Read_SDA() HAL_GPIO_ReadPin(SCK_GPIO_Port,SCK_Pin);

void IIC_Init();
void IIC_Start();
void IIC_Stop();
void IIC_SendByte(uint8_t data);
void IIC_ReceiveByte(uint8_t data);
void IIC_Write();
uint8_t IIC_Read(uint8_t addr);

#endif

IIC.c文件

#include "IIC.h"

void IIC_Init() {
    //SDA和SCK都是开漏+上拉
    SDA(1); //确保SDA线为高
    SCK(1); //确保SCK线为高
}

void IIC_Write(){
	IIC_Start();
    IIC_SendByte(address);
    IIC_SendByte(data);
    IIC_Stop();
}

uint8_t IIC_Read(uint8_t addr){
    uint8_t data=0;
    IIC_Start();
    IIC_SendByte(addr|0x01); 
    data=IIC_ReceiveByte(data); 
    IIC_Stop();
    return data;
}

void IIC_Start(){
	SCK(1);
	HAL_Delay(1);
	SDA(1);
	HAL_Delay(1);
	SDA(0);
	HAL_Delay(1);
	SCK(0);
	HAL_Delay(1);
}

void IIC_Stop(){
	SCK(1);
	HAL_Delay(1);
	SDA(0);
	HAL_Delay(1);
	SDA(1);
	HAL_Delay(1);
	SCK(0);
	HAL_Delay(1);

}

void IIC_SendByte(uint8_t data){
	SCK(0);
	for(int i=0;i<8;i++){
		if(data & (0x80 >> i)){
            SDA(1);
    }else{
            SDA(0); 
        }
		SCK(1);
		HAL_Delay(1);
		SCK(0);
	}
  
	SDA(1);
	SCK(1);
	HAL_Delay(1);
	if(SDA==0){
		
	}
	SCK(0);
}

void IIC_ReceiveByte(uint8_t data){
		data=0;
	SDA(1);
	for(int i=0;i<8;i++){
		SCK(1);
		if(SDA==1){
			data|=(0x80>>i);
		}
		SCK(0);
	}
  
	SDA(0);
	SCK(1);
	HAL_Delay(1);
	SCK(0);
	SDA(1);
	return data;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梳子烟YAN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值