通信协议简述
- 三条线:串行数据线(SDA)、串行时钟线(SCL),地线;
- 半双工 一问一答 ,主从模式,多设备模式
- 总线协议:支持多个设备进行通信(多主多从)
- 异步模式(串口):通信双方约定好波特率,然后各自按照自己时钟进行通信。
好处:便捷;
缺点:双方必须时钟精确,否则对不齐了。
- 同步通信(IIC):由主机的时钟总线确定通信速率
- 选择IIC原因:考虑小型传感器没有晶振提供精确的时钟基准
通信协议过程
在IIC总线上,每个从机都有其唯一的默认设备地址【这个设备地址由谁规定的呢?】。发送数据时采用广播形式,数据最开始带着从机地址,其他设备会选择性失聪。
1、通信开始前:
两总线电平处于拉高状态:
主机在时钟线上产生恒定的时钟脉冲
2、主机发起通信:
在时钟线某个高电平时,数据线下拉;随机主机会在时钟线上产生一个恒定的时钟脉冲信号。
3、数据发送过程:
接下来每一个时钟周期内,SDL上发送电平基本小规则:
主机在低电平时钟时,设置数据;从机在高时钟时,读取数据。
字节数据发送主要规则:
每当发送器传输完一个字节(8 bit)的数据之后,发送端会等待一定的时间,等接收方的应答信号。接收端通过拉低SDA数据线,给发送端发送一个应答信号,以提醒发送端我这边已经接受完成,数据可以继续传输,接下来,发送端就可以继续发送数据了。如此反复,直到发送完所有数据。
即每一帧的数据传送与应答数据 共有9bit
总线寻址:
值得注意的是,IIC通信中,主机发送的第一个字节往往是某个从机在总线上的地址:
数据读写方向:
参考来源:
IIC详解,包括原理、过程,最后一步步教你实现IIC_iic协议-CSDN博客
4、通信终止信号:
然后主机会在时钟线处于高电平时,将数据线拉高,通信终止信号。整段通信完成。
起始信号和终止信号都是由主机发送的。在起始信号产生之后,总线就处于被占用的状态,在终止信号产生之后,总线就处于空闲状态。
注意:每次数据传送总是由主机产生的终止信号来结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
模拟IIC协议代码实现
代码实现-学习视频推荐
头文件
myiic.h
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//IO方向设置
//CRH,CRL 端口输入输出模式寄存器。CRH为8-15引脚,CRL为0-7引脚
#define SDA_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;}//PC11引脚为上拉/下拉输入
#define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}//PC11为推挽输出
//IO操作函数
#define IIC_SCL PCout(12) //SCL
#define IIC_SDA PCout(11) //SDA
#define READ_SDA PCin(11) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
//一下两个函数实际并未被定义
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif
.c文件
#include "myiic.h"
#include "delay.h"
//初始化IIC
//硬件I2C必须开漏输出
//模拟I2C则为推挽输出(方便输出高低电平?)
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//RCC->APB2ENR|=1<<4;//先使能外设IO PORTC时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
IIC_SCL=1;//PCout(12)
IIC_SDA=1;//PCout(11)
}
//产生IIC起始信号
//SCL为高时,SDA拉低,准备开始传数据。(记得再拉低SCL,这个是为什么?)
//SCL为高时,SDA拉高,结束传输数据。
//传输数据时,SCL为高时数据有效
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1; //SDA = 1,SCL = 1 空闲状态
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
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)//READ_SDA为0时表示接收到应答
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//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;//先发高位
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读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;
}
————————————————
版权声明:本文为CSDN博主「碑 一」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/dedell/article/details/96628955
模拟IIC应用实战:
stm32无人机:利用上面基础模拟IIC代码把数据从mpu6050传感器数据读取出来(未完成)
*stm32 HAL库 实现和使用IIC总线*
#include "stm32xxxxx.h"
#include "stm32xxxxx_hal.h"
I2C_HandleTypeDef hi2c1;
void SystemClock_Config()
{
// 配置系统时钟
// ...
}
void GPIO_Configuration()
{
// 配置GPIO引脚
// ...
}
void I2C_Configuration()
{
// 配置I2C外设
hi2c1.Instance = I2C1;
hi2c1.Init.Timing = 0x00303D5B; // 根据具体系统时钟和I2C时钟频率调整这个值
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
// 初始化I2C外设
HAL_I2C_Init(&hi2c1);
}
int main(void)
{
uint8_t sendData[] = {0x01, 0x02, 0x03};
uint8_t receiveData[3];
// 初始化系统时钟
SystemClock_Config();
// 配置GPIO引脚
GPIO_Configuration();
// 配置I2C外设
I2C_Configuration();
// 发送数据
HAL_I2C_Master_Transmit(&hi2c1, DeviceAddress, sendData, sizeof(sendData), HAL_MAX_DELAY);
// 延时等待数据发送完成
HAL_Delay(1); // 根据实际情况调整延时时间
// 接收数据
HAL_I2C_Master_Receive(&hi2c1, DeviceAddress, receiveData, sizeof(receiveData), HAL_MAX_DELAY);
while (1)
{
// 主程序逻辑
}
}
参考:
【物联网】I2C(IIC)通信协议详解与应用_iic通信_嵌入式小白—小黑的博客-CSDN博客
案例:利用IIC从单片机读取温湿度显示到电脑
数据显示到电脑上:
前几节用到的串口以及串口调试助手 - 从单片机发送数据到pc上,显示出来
IIC写驱动
项目架构
直接看“传感器数据手册”
重点关注:设备从机地址 0X71
从面试角度看IIC的提问点:
1、为什么选择IIC通信,和SPI UART 的区别,哪个更快?
2、IIC最大速率
3、IIC协议,帧格式,收发数据的时序流程
4、介绍一下IIC
5、调试IIC过程中遇到的问题
6、iic寻址,主机从机如何对接的