1 前言
作为一个电子设计爱好者,平时喜欢捣鼓一些小玩意小工具,无论是作为一种乐趣或者是真的能用在平时的工作学习中,都是一件很有意思的事情。我们在电子设计中经常会使用到陀螺仪、OLED液晶屏这样的模块,将他们进行各种组合来构思我们自己的小创意。这些模块需要与主控芯片进行通讯,我们都知道,两台“设备”需要进行通讯,需要有一个通讯协议,否则通讯就难以进行。常用的器件之间的通讯协议有IIC通讯协议和SPI通讯协议,我们经常使用,但是他们具体是什么含义,协议内容是什么,分别有什么特点?可能你也和我一样,用的时候直接拿以前整理的代码Ctrl+C,Ctrl+V,然后修改端口号就直接用了。真让来说个子午寅卯来,还真不容易把这件事说明白,所以这里对SPI和IIC进行一个小总结,理一理思路,当别人问起来的时候也能具体讲两句。
2 IIC通讯协议具体内容
2.1 IIC通讯协议
IIC全称为Inter-Intergrated Circuit,是Philips公司于1980年代提出并发展起来的,用于主控芯片和外围设备之间进行低速通讯,IIC串行总线一般包括两根信号线,一根为数据信号线SDA,一根为时钟信号线SCL。IIC通讯属于半双工同步通讯方式。
单工通讯:只能一台设备发,另一台设备接收,例如遥控飞机,遥控车等。
半双工通讯:设备可以接收也可以发送,但是发的时候不能接收,接收的时候不能发送。
双工通讯:设备在发送的同时也可以接收,接收的同时也能发送数据。
2.1.1 IIC结构组成
IIC串行总线有两根信号线,一根为数据信号线SDA,一根为时钟信号线SCL,时钟信号一般由主控芯片产生。所有挂载在IIC总线上的设备都需要接入SDA和SCL线,同时挂载到总线上的每一个设备都有唯一的地址。
Fig1 IIC总线结构
2.1.2 IIC通讯协议的特点
(1)结构简单,通讯有效
IIC总线只需要两根线连接控制器和外围设备,因此占据空间非常小,只需要占用芯片的两个管脚即可高效通讯。同时IIC总线的有效通讯距离是25英尺(7.62米),并能以10Kbps支持40个设备进行通讯。
(2)多主控
其中任何挂载在总线上并能够进行发送个接收的设备都可以控制总线的数据传输和时钟频率,但是在某一时刻总线上只能有一个主控。
2.1.3 IIC协议内容
三种信号类型:起始信号、截止信号、应答信号
起始信号:时钟信号SCL为高电平时,数据信号SDA从高电平跳变为低电平,此时判定为通讯开始。
终止信号:时钟信号SCL为高电平时,数据信号SDA从低电平跳变为高电平,此时判定为通讯结束。
Fig2 IIC起始信号和截止信号
应答信号:发送端每发送一个字节数据(八位)之后的一个时钟周期释放数据信号线SDA,由接收端反馈一个应答信号。
当应答信号为低电平时,规定为应答(ACK),表示接收端已经成功接收到这一个字节数据。
当应答信号为高电平时,规定为非应答(NACK),表示接收端未成功接收到这一个字节数据。
Fig3 IIC应答与非应答
空闲状态:当IIC总线的SDA信号线号SCL信号线均处于高电平状态时,规定为总线的空闲状态。挂载在总线上的设备释放总线,设备内的输出级场效应管处于截止状态。
数据发送:挂载在IIC总线上的每一个设备都可以作为主设备,也可以作为从设备,但不能同时既是主设备又是从设备,因此IIC通讯协议属于半双工通讯协议。每一个设备都会对应一个唯一的地址,主从设备通过这个地址进行寻址访问。
确定目的主机:同时,协议规定主设备在发送有效数据之前需要指定从设备的地址,因此第一段数据包为地址包。大多是设备地址为7位,而发送端一开始是发送一个字节数据(八位)之后释放总线等待应答,因此主设备发送7位地址信号之后在最低位添加一位表示数据传输的方向,0表示主设备发,从设备收(主设备向从设备写数据),1表示主设备收,从设备发(主设备向从设备读设备)。
Fig4 数据发送序列示意图
注:当传输一个字节数据之后,主机释放SDA信号线,SDA信号线被上拉为高电平,从机接收到信号之后将SDA下拉,表明收到数据,给出ACK。
2.1.4 总结
(1)IIC总线由SDA数据信号线和SCL时钟信号线构成,两个线接上拉电阻,IIC总线的有效通讯距离大约是25英尺(7.62米),并能以10Kbps支持40个设备进行通讯。
IIC分为软件IIC和硬件IIC,软件IIC无需固定端口,只需要两个端口能输入输出即可实现。
(2)IIC协议的内容(三种信号一状态):
三信号:1)起始信号:SCL为高电平,SDA由高电平跳变为低电平,此为起始信号。
2)终止信号:SCL为高电平,SDA由低电平跳变为高电平,此为终止信号。
3)应答信号:主机发送一个字节数据后,释放总线,从机发送应答或非应答信号;拉低SDA数据信号线为ACK信号,表明收到收据,否则为NACK,表明未收到信号。
一状态:空闲状态:当IIC总线的SDA信号线号SCL信号线均处于高电平状态时,规定为总线的空闲状态。挂载在总线上的设备释放总线,设备内的输出级场效应管处于截止状态。
(3)数据发送过程:
起始信号发送之后,前一个字节中包含了从机的地址信息和数据流的方向信息,前七位表示从机地址,第八位数据流方向,0表示主->从,1表示从->主。
第九位主机释放SDA总线,从机发送ACK信号或者NACK信号。
(4)数据格式
1)主 -> 从
Fig5 主机向从机写数据
2)主 <- 从
Fig6 主机向从机读数据
3)主 -> 从 -> 从 -> 主
Fig7 主机向从机写数据之后再读数据
2.2 IIC协议STM32 C语言实现
/* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */ #define GPIO_PORT_I2C GPIOB #define RCC_I2C_PORT RCC_APB2Periph_GPIOB #define I2C_SCL_PIN GPIO_Pin_6 #define I2C_SDA_PIN GPIO_Pin_7 #define I2C_SCL_1() GPIO_SetBits(GPIO_PORT_I2C, I2C_SCL_PIN) #define I2C_SCL_0() GPIO_ResetBits(GPIO_PORT_I2C, I2C_SCL_PIN) #define I2C_SDA_1() GPIO_SetBits(GPIO_PORT_I2C, I2C_SDA_PIN) #define I2C_SDA_0() GPIO_ResetBits(GPIO_PORT_I2C, I2C_SDA_PIN)
static void i2c_Delay(void) { uint8_t i; /** 来源:野火 F103-MINI STM32 开发板 * 下面的时间是通过安富莱AX-Pro逻辑分析仪测试得到的。 * CPU主频72MHz时,在内部Flash运行, MDK工程不优化 * 循环次数为10时,SCL频率 = 205KHz * 循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us * 循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us * IAR工程编译效率高,不能设置为7 **/ for (i = 0; i < 10; i++); }
/* ********************************************************************************************************* * 函 数 名: i2c_Start * 功能说明: CPU发起I2C总线启动信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Start(void) { /* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */ I2C_SDA_1(); I2C_SCL_1(); i2c_Delay(); I2C_SDA_0(); i2c_Delay(); I2C_SCL_0(); i2c_Delay(); }
/* ********************************************************************************************************* * 函 数 名: i2c_Start * 功能说明: CPU发起I2C总线停止信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Stop(void) { /* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */ I2C_SDA_0(); I2C_SCL_1(); i2c_Delay(); I2C_SDA_1(); }
应答和非应答
/* ********************************************************************************************************* * 函 数 名: i2c_Ack * 功能说明: CPU产生一个ACK信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_Ack(void) { I2C_SDA_0(); /* CPU驱动SDA = 0 */ i2c_Delay(); I2C_SCL_1(); /* CPU产生1个时钟 */ i2c_Delay(); I2C_SCL_0(); i2c_Delay(); I2C_SDA_1(); /* CPU释放SDA总线 */ }
/* ********************************************************************************************************* * 函 数 名: i2c_NAck * 功能说明: CPU产生1个NACK信号 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_NAck(void) { I2C_SDA_1(); /* CPU驱动SDA = 1 */ i2c_Delay(); I2C_SCL_1(); /* CPU产生1个时钟 */ i2c_Delay(); I2C_SCL_0(); i2c_Delay(); }
发送数据
主 -> 从
/* ********************************************************************************************************* * 函 数 名: i2c_SendByte * 功能说明: CPU向I2C总线设备发送8bit数据 * 形 参:_ucByte : 等待发送的字节 * 返 回 值: 无 ********************************************************************************************************* */ void i2c_SendByte(uint8_t _ucByte) { uint8_t i; /* 先发送字节的高位bit7 */ for (i = 0; i < 8; i++) { if (_ucByte & 0x80) { I2C_SDA_1(); } else { I2C_SDA_0(); } i2c_Delay(); I2C_SCL_1(); i2c_Delay(); I2C_SCL_0(); if (i == 7) { I2C_SDA_1(); // 释放总线 } _ucByte <<= 1; /* 左移一个bit */ i2c_Delay(); } }
主 <- 从
/* ********************************************************************************************************* * 函 数 名: i2c_ReadByte * 功能说明: CPU从I2C总线设备读取8bit数据 * 形 参:无 * 返 回 值: 读到的数据 ********************************************************************************************************* */ uint8_t i2c_ReadByte(u8 ack) { uint8_t i; uint8_t value; /* 读到第1个bit为数据的bit7 */ value = 0; for (i = 0; i < 8; i++) { value <<= 1; I2C_SCL_1(); i2c_Delay(); if (I2C_SDA_READ()) { value++; } I2C_SCL_0(); i2c_Delay(); } if(ack==0) i2c_NAck(); else i2c_Ack(); return value; }
等待应答信号
/* ********************************************************************************************************* * 函 数 名: i2c_WaitAck * 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号 * 形 参:无 * 返 回 值: 返回0表示正确应答,1表示无器件响应 ********************************************************************************************************* */ uint8_t i2c_WaitAck(void) { uint8_t re; I2C_SDA_1(); /* CPU释放SDA总线 */ i2c_Delay(); I2C_SCL_1(); /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */ i2c_Delay(); if (I2C_SDA_READ()) /* CPU读取SDA口线状态 */ { re = 1; } else { re = 0; } I2C_SCL_0(); i2c_Delay(); return re; }
检查总线上是否存在设备
/* ********************************************************************************************************* * 函 数 名: i2c_CheckDevice * 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在 * 形 参:_Address:设备的I2C总线地址 * 返 回 值: 返回值 0 表示正确, 返回1表示未探测到 ********************************************************************************************************* */ uint8_t i2c_CheckDevice(uint8_t _Address) { uint8_t ucAck; i2c_GPIO_Config(); /* 配置GPIO */ i2c_Start(); /* 发送启动信号 */ /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */ i2c_SendByte(_Address|I2C_WR); ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */ i2c_Stop(); /* 发送停止信号 */ return ucAck; }
3 SPI通讯协议具体内容
3.1 SPI通讯协议
SPI的全称叫做Serial Peripheral interface的缩写,串行外围设备接口,是Motorla最开始在MC68HCXX系列处理器上使用,SPI接口主要应用在EEPEOM、FLASH、AD转换器、数字信号处理器(DSP)和数字信号解码器之间通讯使用。SPI是一种高速、全双工和同步的通讯总线,SPI总线需要使用四根线。
四根线的定义:
Fig8 SPI硬件连接
表1 — 引脚含义图
序号 | 引脚标号 | 说明 |
1 | SDO | 主设备数据输出,从设备数据输入 对应MOSI master output slave input |
2 | SDI | 主设备数据输入,从设备数据输出 对应MISO master input slave output |
3 | SCLK | 时钟信号,由主设备产生 |
4 | CS | 从设备使能信号,由主设备控制 |
和IIC通讯协议相同,都包含SCLK时钟信号,时钟信号有主设备控制,一个SPI总线上必须有一个主设备。
3.1.1 SPI总线结构
总线结构如下所示:
Fig9 SPI总线结构
3.1.2 SPI总线通讯四种模式
SPI总线通讯定义了四种通讯模式,通过配置时钟级性CPOL(Clock Polarity)和时钟相位CPHA(Clock Phase )确定主设备的通讯模式,配置如下:
Mode0: CPOL=0 CPHA=0
Mode1: CPOL=0, CPHA=1
Mode2: CPOL=1, CPHA=0
Mode3: CPOL=1, CPHA=1
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿
工作状态1:
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
工作状态2:
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
工作状态3:
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
工作状态4:
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
Fig10 SPI总线信号周期
SPI协议没有专门的起始信号和终止信号,而是主机控制时钟信号线来控制通讯的起始和终止,当没有数据交流的时候时钟线保持低电平或者高电平。