三种常用的串行数据传输总线
一、SPI
1.1 概念
SPI(Serial Peripheral Interface - 串行外设接口)是一种用于短距离通信(主要是嵌入式系统中)的同步串行通信接口规范,这种接口由Motorola发明,已经成了一种事实标准。广泛用于各种MCU处理器中,同传感器,串行ADC、DAC、存储器、SD卡以及LCD等进行数据连接。
有主(Master - 控制器)和从(Slave - 外设)之分,在总线中也就只有一个“主人”,其它都是处于服从的位置,也就是Slave,它是一种有时钟信号的同步串行总线,从器件的寻址是靠专用的片选信号线SS来实现的。
1.2 主要的信号线
SPI总线由4根主要的信号线组成以实现数据在主设备(Master)和从设备(Slave)之间的全双工(收、发同时执行)同步(由时钟同步)通信。
SCLK:串行时钟(由主设备输出),每个时钟周期将会移出一个新的数据位;
MOSI:主设备输出⇒从设备输入,数据由主设备进入从设备,器件A上的MOSI线连接到器件B上的MOSI线。
MISO:主设备输入⇐ 从设备输出,数据由从设备送到主设备(或其它从设备,采用菊花链配置),器件A上的MISO线连接到器件B上的MISO线。
SS(或SSN): 从设备选中(低电平有效),用于主设备控制从设备用,当该从选择信号线有效的时候表示主设备正在向相应的从设备发送数据或从相应的从设备请求数据。
1.3 四种工作模式
由时钟的极性(CPOL)和相位(CPHA)构成了4种不同的数据传输模式(0,1,2,3),分别对应四种可能的时钟配置。
CPOL: 时钟的极性,它控制着时钟信号的初始逻辑状态(0:低电平空闲状态;1:高电平空闲状态)。
CPHA: 时钟相位,它控制了数据转换和时钟转换之间的关系(0:第一个跳变沿;1:第二个跳变沿)。
模式0 CPOL = 0,CPHA = 0:
空闲时钟电平为低电平,在第一个跳变沿工作
模式0 CPOL = 0,CPHA = 1:
空闲时钟电平为低电平,在第二个跳变沿工作
模式2 CPOL = 1,CPHA = 0:
空闲时钟电平为高电平,在第一个跳变沿工作
模式3 CPOL = 1,CPHA = 1:
空闲时钟电平为高电平,在第二个跳变沿工作
1.4 主从连接方式
1.5 优点
1.支持全双工通信
2.推挽驱动(跟漏极开路正相反)提供了比较好的信号完整性和较高的速度
3.比I²C或SMBus吞吐率更高
4.协议非常灵活支持“位”传输,不仅限于8-bit一个字节的传输
5.可任意选择的信息大小、内容、以及用途
6.信号都是单方向的,非常容易进行电流隔离
7.对于时钟的速度没有上限,有进一步提高速度的潜力,很多MCU的SPI传输速率可以高达50Msps,可用于数据采集以及图像的传输。
1.6 缺点
1.相比于I²C总线需要更多的管脚
2.没有寻址机制,在共享的总线连接时需要通过片选信号支持多个设备的访问
3.一般只支持一个主设备(取决于设备的硬件构成)
4.没有查错机制
5.相对于RS-232, RS-485, 或CAN-总线,只能近距离传输
6.SPI不支持热交换(动态地增加一个节点)
1.7 使用场景
SPI比较适合系统中有一个主设备和少量的从设备,而且每一个从设备都有一个单独的“从设备选择”信号。
二、IIC
2.1概念
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
跟SPI对比,I2C没有天生的主、从之分,也就是说挂在两根线(数据线SDA和时钟线SCL)上的所有器件都是生而平等的。数据线SDA和时钟线SCL需要上拉电阻。
2.2 三种信号
I2C 总线在传送数据过程*有三种类型信号, 它们分别是:开始信号、结束信号和应答
信号。
开始信号: SCL 为高电平时, SDA 由高电平向低电平跳变,开始传送数据。
结束信号: SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
**应答信号:**接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。
2.3 数据读写
IIC总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信。
主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
**注:**在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。并不是真的主从关系。
IIC协议在读写数据时,总是要发送器件地址,这里需要注意的是,不是主机给从机发送地址,而是主机给地址总线上发送地址,挂IIC总线上的所有从机都能收到地址,如果发过来的地址和自己的地址匹配上了,从机就会给主机一个应答,这样就建立起来了一个通讯。
主设备往从设备写数据:
1) 产生start位
2) 传送器件地址ID_Address,器件地址的最后一位为数据的传输方向位,R/W,低电平0表示主机往从机写数据(W)。ACK应答,应答是从机发送给主机的应答。
3)传送写入器件寄存器地址,即数据要写入的位置。
4)传送要写入的数据。
5)产生stop信号。
主设备往从设备读数据:
1)产生start信号
2)传送器件地址(写ID_Address),ACK。
3)传送字地址(写REG_Address),ACK。
**4)**再次产生start信号
5)再传送一次器件地址,ACK。
6)读取一个字节的数据,读数据最后结束前无应答ACK信号。
7) 产生stop信号。
注意:
IIC读时序要写两次器件地址,首先传送器件地址到总线上找到器件,然后写入寄存器地址,也就是word address找到需要读取数据的地址。然后再传输一次器件地址后开始读数据。
2.4 IIC驱动
与驱动相关的函数定义
//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信号
具体函数的实现
//初始化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,接收应答失败,IIC直接退出
// 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;
}
2.5优点
1.管脚/信号数量少,即便挂了很多的设备,也只用两根线;
2.可以适应不同的从设备的要求;
3.可以支持多个主设备;
4.引入了ACK/NACK功能以提升应对错误的能力
2.6缺点
1.增加了固件和底层硬件的复杂度
2.增加了协议的负荷,降低了数据传输的吞吐率
3.需要上拉电阻,限制了时钟的速度,增加了功耗
4.半双工
2.7 使用场景
I2C比较适合复杂、多样化、需要通信设备灵活扩展的场景。
三、UART
3.1概念
UART 通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种通用的串行异步全双工数据收发传输器(总线)。
把“要传输的数据”在串行通信与并行通信之间转换。在嵌入式领域,作为把并行信号转成串行信号的硬件设备,UART通常被集成于MCU内部。
UART包括RS232、RS449、RS423、TTL电平等接口标准规范和总线标准规范。
3.2 串口配置基本属性
1)波特率
波特率是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如9600波特表示每秒钟发送9600个bit。串口通常用的波特率为1200、2400、4800、9600、14400、19200、28800、38400、57600、115200、128000、256000。其中如果串口速率越高,其传输的距离和稳定性就有所下降。一般常用为9600和115200。
2)数据位
数据位表征通信中实际数据位的参数。当计算机发送一个信息包,其中需指定有效数据位,一般有5、7和8位。常规使用一般定义为8位。
3)停止位
停止位表征单包数据的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,在传输中可能存在不同步的情况,因此停止位不仅仅是表示传输的结束,同时也是校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
4)奇偶校验位
在串口通信中一种简单的检错方式。偶奇校验,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个1。
例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位,保证逻辑高为奇数个。
3.3程序示例
void uart_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能时钟
//USART1_TX GPIOA.9初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC
//USART初始化配置
USART_InitStructure.USART_BaudRate = bound;//波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
USART_Cmd(USART1, ENABLE); //使能串口
}
u8 USART_RX_BUF[USART_REC_LEN]; //缓冲区
u16 USART_RX_STA=0; //接收状态标志
void USART1_IRQHandler(void) { //串口1中断服务程序
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { //接受中断(收到的数据必须以0x0d,0x0a结尾)
Res =USART_ReceiveData(USART1); //读取接受到的数据
if((USART_RX_STA&0x8000)==0){ //接受未完成
if(USART_RX_STA&0x4000){ //接收到了0x0d
if(Res!=0x0a)USART_RX_STA=0;//接收错误重新开始
else USART_RX_STA|=0x8000; //接收完成
}
else { //没有接收到0x0d
if(Res==0x0d)USART_RX_STA|=0x4000;
else{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收错误重新开始
}
}
}
}
}
3.4 优点
1.只需要使用两根信号线就可以实现全双工的数据传输(不算电源线)
2.无需时钟信号
3.有一个奇偶校验位提供硬件级别的错误检查
4.数据包的结构可以通过两端之间的协调来改变,比较灵活
5.相对比较容易配置和运行
3.5 缺点
1.与并行通信以及USART相比,数据传输的速度较慢
2.帧的大小被限定为最多9位
3.不支持多个从设备或多个主设备的功能
4.收发两个器件UART的波特率差别不能超过10%
3.6 使用场景
UART比较适合单点对单点的连接,因为UART没有标准的方式来寻址不同的设备或共享管脚
参考
https://blog.csdn.net/qq_38410730/article/details/80312357
https://www.bilibili.com/read/cv4149989
https://www.cnblogs.com/ninghechuan/p/8595423.html
https://blog.csdn.net/zuo_an/article/details/89151110