下面我们从iic的物理层和协议层说起
物理层
I2C 通讯设备之间的常用连接方式
它的物理层有如下特点:
(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线
中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线
(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之
间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空
闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用
总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
协议层
总线信号 : SDA :串行数据线 SCL :串行时钟
总线空闲状态 :SDA :高电平 SCL :高电平
起始位:SCL为高电平期间 SDA出现下降沿
终止位:SCL为高电平期间 SDA出现上升沿
数据传输 :SDA的数据在SCL高电平期间被写入从机。所以SDA的数据变化要发生在SCL低电平期间。
地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“ 1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据
响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)” 信号,发送方接收到该信号后会产生一个停止信号,结束信号传输
.H
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IIC驱动 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入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"
//////////////////////////////////////////////////////////////////////////////////
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IIC驱动 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//////////////////////////////////////////////////////////////////////////////////
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=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)
{
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;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
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;
}
//另一种
。h
#ifndef _IIC_H
#define _IIC_H
#include "stm32l1xx_hal.h"
#include "stdint.h"
#include "main.h"
#define SCL_H HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_SET)
#define SCL_L HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_RESET)
#define SDA_H HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_SET)
#define SDA_L HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_RESET)
#define SCL_read HAL_GPIO_ReadPin(SCL_GPIO_Port,SCL_Pin)
#define SDA_read HAL_GPIO_ReadPin(SDA_GPIO_Port,SDA_Pin)
void I2C_delay(void);
bool I2C_Start(void);
void I2C_Stop(void) ;
void I2C_Ack(void);
void I2C_NoAck(void);
uint8_t I2C_WaitAck(void);
void I2C_SendByte(uint8_t SendByte);
uint8_t I2C_ReceiveByte(uint8_t ack);
bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData);
bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData);
uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr);
uint8_t I2C_Read2(uint8_t WriteAddr) ;
void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf);
void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf);
#endif
。C
#include "iic.h"
void I2C_delay(void)
{
uint16_t i=1000; //Set delay time value
while(i)
{
i--;
}
}
/**
* @brief I2C Start //产生IIC起始信号
* @param None
* @retval None
*/
bool I2C_Start(void)
{
SDA_H;
I2C_delay();
SCL_H;
I2C_delay();
if(!SDA_read)return FALSE; //SDA Always low return FALSE
SDA_L;
I2C_delay();
if(SDA_read) return FALSE; //SDA Always high return FALSE
SCL_L;
I2C_delay();
return TRUE;
}
/**
* @brief I2C Stop
* @param None
* @retval None
*/
void I2C_Stop(void)
{
SCL_L;
I2C_delay();
SDA_L;
I2C_delay();
SCL_H;
I2C_delay();
SDA_H;
I2C_delay();
}
/**
* @brief I2C Ack
* @param None
* @retval None
*/
void I2C_Ack(void)
{
SCL_L;
I2C_delay();
SDA_L;
I2C_delay();
SCL_H;
I2C_delay();
SCL_L;
I2C_delay();
}
/**
* @brief I2C No Ack
* @param None
* @retval None
*/
void I2C_NoAck(void)
{
SCL_L;
I2C_delay();
SDA_H;
I2C_delay();
SCL_H;
I2C_delay();
SCL_L;
I2C_delay();
}
/**
* @brief Wait Ack
* @param None
* @retval bool FALSE:0--->no ACK
* TRUE :1--->ACK
*/
uint8_t I2C_WaitAck(void)
{
uint8_t re;
SCL_L;
I2C_delay();
SDA_H;
I2C_delay();
SCL_H;
I2C_delay();
if(SDA_read)
{
re=0;
}
else re=1;
SCL_L;
return re;
}
/**
* @brief Send one Byte
* @param uint8_t SendByte
* @retval None
*/
void I2C_SendByte(uint8_t SendByte)
{
uint8_t i=8;
while(i--)
{
SCL_L;
I2C_delay();
if(SendByte&0x80)
{
SDA_H;
}
else
{
SDA_L;
}
SendByte<<=1;
I2C_delay();
SCL_H;
I2C_delay();
}
SCL_L;
}
/**
* @brief Receive one Byte
* @param uint8_t ack
* @retval receive receive one byte
*/
uint8_t I2C_ReceiveByte(uint8_t ack)
{
unsigned char i=8,receive=0;
SDA_H;
while(i--)
{
receive<<=1;
SCL_L;
I2C_delay();
SCL_H;
I2C_delay();
if(SDA_read)
{
receive|=0x01;
}
}
SCL_L;
if (!ack)
I2C_NoAck(); //Send Nack
else
I2C_Ack(); //Send ack
return receive;
}
/**
* @brief Write a Byte to the device
* @param uint8_t WriteAddr,uint8_t WriteData
* @retval bool FALSE: 0
* TRUE : 1
*/
bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData)
{
I2C_Start();
I2C_SendByte(dev); //Send write cmd
I2C_WaitAck();
I2C_SendByte(WriteAddr); //Send addr
I2C_WaitAck();
I2C_SendByte(WriteData); //Send data
I2C_WaitAck();
I2C_Stop(); //iic stop
return TRUE;
}
/********************************************************************/
bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData)
{
if (!I2C_Start()) return FALSE;
I2C_SendByte(0x10);//设置器件地址+段地址
if (!I2C_WaitAck())
{
I2C_Stop();
return FALSE;
}
I2C_SendByte(WriteAddr); //设置段内地址
I2C_WaitAck();
I2C_SendByte(WriteData);
I2C_WaitAck();
I2C_Stop();
return TRUE;
}
/************************************************************************/
/**
* @brief Read a byte from the device
* @param uint8_t WriteAddr
* @retval temp Return the read byte
*/
uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr)
{
uint8_t temp=0;
I2C_Start();
I2C_SendByte(dev); //Send write cmd
I2C_WaitAck();
I2C_SendByte(WriteAddr); //Send addr
I2C_WaitAck();
I2C_Start();
I2C_SendByte(dev|1); //Send read cmd
I2C_WaitAck();
temp=I2C_ReceiveByte(0);
I2C_Stop();
return temp;
}
/*********************************************************************************/
//读出1串数据
uint8_t I2C_Read2(uint8_t WriteAddr)
{
uint8_t tempDat=0;
if (!I2C_Start()) return FALSE;
I2C_SendByte(0x77);//设置器件地址+段地址
if (!I2C_WaitAck())
{
I2C_Stop();
return FALSE;
}
I2C_SendByte(WriteAddr); //设置低起始地址
I2C_WaitAck();
I2C_Start();
I2C_SendByte(0x77 | 0x01);
I2C_WaitAck();
tempDat = I2C_ReceiveByte(0);
I2C_Stop();
return tempDat;
}
/**
* @brief Read continuously
* @param uint8_t devaddr device addr
* uint8_t addr Start addr
* uint8_t len read data length
* uint8_t *rbuf read data buf
* @retval None
*/
void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf)
{
int i=0;
I2C_Start();
I2C_SendByte(devaddr);
if(!I2C_WaitAck())
{
I2C_Stop();
return ;
}
I2C_SendByte(addr); //address ++
if(!I2C_WaitAck())
{
I2C_Stop();
return ;
}
I2C_Start();
I2C_SendByte(devaddr|0x01);
if(!I2C_WaitAck())
{
I2C_Stop();
return ;
}
for(i=0; i<len; i++)
{
if(i==len-1)
{
rbuf[i]=I2C_ReceiveByte(0); //The last byte does not answer
}
else
rbuf[i]=I2C_ReceiveByte(1);
}
I2C_Stop( );
}
/**
* @brief Write continuously
* @param uint8_t devaddr device addr
* uint8_t addr Start addr
* uint8_t len read data length
* uint8_t *rbuf read data buf
* @retval None
*/
void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf)
{
int i=0;
I2C_Start();
I2C_SendByte(devaddr);
I2C_WaitAck();
I2C_SendByte(addr); //address ++
I2C_WaitAck();
for(i=0; i<len; i++)
{
I2C_SendByte(wbuf[i]);
I2C_WaitAck();
}
I2C_Stop( );
}
同步和异步通讯
首先是两者的不同:
同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。
1、同步通信效率高;异步通信效率较低。
2、同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
3、同步通信可用于点对多点;异步通信只适用于点对点。
异步通信
异步通信中的接收方并不知道数据什么时候会到达,收发双方可以有各自自己的时钟。发送方发送的时间间隔可以不均,接收方是在数据的起始位和停止位的帮助下实现信息同步的。这种传输通常是很小的分组,比如一个字符为一组,为这个组配备起始位和结束位。所以这种传输方式的效率是比较低的,毕竟额外加入了很多的辅助位作为负载,常用在低速的传输中。
以RS232协议规定为例,异步通信一个字符一个字符地传输,每个字符一位一位地传输,并且传输一个字符时,总是以“起始位”开始(低电平,逻辑值0),以“停止位”结束,字符之间没有固定的时间间隔要求。字符数据本身由5~8位数据位组成,接着字符后面是一位校验位(也可以没有校验位),最后是一位或一位半或二位停止位,停止位后面是不定长的空闲位。停止位和空闲位都规定为高电平(逻辑值1),这样就保证起始位开始处一定有一个下跳沿,
举个例子,我们的键盘按下一个按键,发出一个字符信号,异步传输机制就会为它加上前后的辅助同步信息,帮助接收方识别到我们按下了哪一个按键。因为我们敲击键盘的节奏不固定,所以异步是一种很适合的方式
同步通信
同步通信中双方使用频率一致的时钟 ,它的分组相比异步则大得多,称为一个数据帧,通过独特的bit串作为启停标识。发送方要以固定的节奏去发送数据,而接收方要时刻做好接收数据的准备,识别到前导码后马上要开始接收数据了。同步这种方式中因为分组很大,很长一段数据才会有额外的辅助位负载,所以效率更高,更加适合对速度要求高的传输,当然这种通信对时序的要求也更高。
同步通信是一种连续串行传送数据的通信方式,一次通信只传送一帧信息,由同步字符、数据字符和校验字符(CRC)组成。