SPI通信
前言
之前已经介绍了STM32的ADC、DMA、EXTI、TIME、NVIC、USART以及普通IO模式,此系列笔者还打算写最后三个大的内容,分别是SPI通信、IIC通信以及看门狗,后面就看大家的需求了,需要什么可以留在评论区,本文首先来介绍SPI的有关知识。
SPI总线概述
在通信协议分类的介绍中,提到过SPI,它是一种同步 串行 全双工(也可半双工)通信协议,是最常用的板级通信总线。为什么要加总线作为它的定语呢,原因就是这个协议可以实现一主多从的通信,多个从机和主机通过SPI所需的信号线连接在一起,就拿之前的串口通信来说,串口通信是一主一从的通信方式,主机TX接从机的RX,主机的RX接从机的TX,除了共地以外还需要两个通信线;而SPI通信除了共地以外需要四个信号线进行传输,根据它同步串行全双工的特点,可以分析出它必然具有同步时钟线,以及两个串行数据线来实现全双工通信,至于第四个信号线,是用来判断具体与哪个从机进行通信的片选线。
名称 | 功能 |
---|---|
MOSI | 主输出从输入 |
MISO | 主输入从输出 |
SCLK | 同步时钟线 |
CS | 片选线 |
SPI通信拓扑图
上面提到过,SPI是一种通信总线,这也意味着在一组信号线上可能存在多个从机,那么具体的连接方式拓扑图是怎么样的呢
1.一主一从
注意上图中的MOSI和MOSI分别用了DO和DI来表示,这里需要了解一下生产厂商常用的别称,如下所示:
MISO:SIMO、DOUT、DO、SDO或SO(在主机端);
MOSI:SOMI、DIN、DI、SDI或SI(在主机端);
CS:CE、NSS、CE或SSEL;
SCLK也可以是SCK;
2.一主多从:
如下图所示,就是一个主机与多个主机连接的一种方式,主机与各个从机直接共同连接在SCLK、MOSI、MISO上,除此之外,每个从机还单独有一个SS片选与主机连接,通信时,主机通过拉低对应从机的SS来选择通信对象。
除此之外还有一个菊花链的拓扑连接方式,这个大家可以去下面两篇博客中查看,关于SPI的详细介绍也可以查看这两篇大佬的分析,很透彻。
STM32的SPI通信
关于STM32的SPI通信,也有两种方案,这个在串口通信的时候我们也提到过,
方案一就是使用STM32集成好的SPI控制器,与之前使用USART一样,按照自己需求配置好对应的寄存器以及通信模式后就可以直接通过SPI控制器来实现收发,不需要编程实现底层的具体发送方式;
方案二是使用IO口模拟SPI的时序,参照SPI通信的时序图,操作GPIO口实现数据的收发,模拟SPI的好处在于不必拘束于固定的管脚,随便一组管脚都可以,只是需要自己编写底层的发送函数,实现01010之类的发送。本文笔者会将两种方式都介绍一下。
首先,还是参照之前的模式,先介绍使用控制器实现的过程。
SPI的特性
首先还是来过一下STM32的SPI的特性,
第一,基于三条线的全双工同步传输,也就是常说的四线SPI,这里的三条线是不包括NSS片选线的;注意,还有一种三线SPI,这里的三线是缺少MOSI或者是MISO其中的一根,双方通过一个数据线进行数据交换,同一时间只能主发从收或者主收从发,这种三线SPI是一种半双工模式,有些类似后面会介绍的IIC的通信方式;
第二,STM32 SPI的数据帧可以是八位或者是16位数据,也就是说SPI控制器可以一次性发送16位数据或者8位数据,而我们之前的USART是只能发送8位或者9位数据;
第三,SPI控制器具有主从模式可以设置,也就是说,STM32上集成的SPI既可以做主机也可以作为从机,在使用过程中需要我们进行配置;
第四,SPI的最大通信频率是40MHZ,而fPCLK是84MHZ(由所挂接的时钟线决定),所以最大的通信速率是fPCLK/2,40MHZ的传输速率已经比IIC和USART快很多了。而且SPI控制器还可以使用快速模式的通信方式,将两个数据线同时作为传输,一般用不上,做个了解即可。
第五,SPI控制器有可编程的时钟极性与时钟相位,关于时钟极性与时钟相位,它们两个分别有两个状态,时钟极性有0与1两个状态,决定时钟是高有效还是低有效;而时钟相位决定的是第一个时钟边沿有效还是第二个时钟边沿有效,它们两两组合,构成了SPI的四种模式,
时钟极性CPOL
当时钟极性为0的时候,时钟线空闲状态是低电平状态
当时钟极性为1的时候,时钟线空闲状态是高电平状态
时钟相位CPHA
当时钟相位为0的时候,数据在第一个跳变沿被采样(数据采集)
当时钟相位为1的时候,数据在第二个跳变沿被采样
模式 | CPOL(时钟极性) | CPHA(时钟相位) | 数据收发 |
---|---|---|---|
模式0 | 0 | 0 | 空闲时时钟线为低电平,第一个时钟边沿采集数据 |
模式1 | 0 | 1 | 空闲时时钟线为低电平,第二个时钟边沿采集数据 |
模式2 | 1 | 0 | 空闲时时钟线为高电平,第一个时钟边沿采集数据 |
模式3 | 1 | 1 | 空闲时时钟线为高电平,第二个时钟边沿采集数据 |
常用的是模式3与模式0,而且一般来说支持模式0的器件也支持模式3的通信方式(上升沿写入数据,下降沿读取数据),支持模式1的也支持模式2的通信方式(下降沿读取数据,上升沿写入数据)。
第六,SPI控制器也有对应的状态位可以产生发送完成和接收完成的标志,而且其传输是高位先发还是低位先发都可以进行编程控制。
SPI控制器的框图
在了解了SPI控制器的相关特性后,接下来就是它的框图介绍了,其整体框图如下图所示,不难发现,这个框图的整体不是很复杂,我们来稍作拆分介绍一下。
引脚
首先是左边的四个GPIO口,它们各自的功能如下图所示:
这里注意的NSS片选,我们在使用过程过程中一般禁止此处的NSS,用内部的软件管理来屏蔽这个NSS,用另外一个GPIO来专门操作控制从机,在需要通信时拉低对应的管脚即可。
数据收发过程
当使用四线SPI实现收发时,主机数据发送过程如下图中绿色线的流程,首先由MCU将数据写入发送缓冲区,然后再将数据由发送缓冲区并行转移到移位寄存器,根据LSB的设置,看是低位先发还是高位先发,然后再由移位寄存器一位一位的将数据发送到MOSI管脚上
主机数据接收过程,如下图红色线所示,由MISO输入数据,然后进入移位寄存器,接收完毕后经过移位寄存器并行转移到接收缓冲区,然后CPU在接收缓冲区中读取数据。
注:
- 下图中蓝色框里面还有两个箭头,一个是从MOSI指向MISO的,另一个是从MISO指向MOSI的,而且看起来是受到主控制逻辑的管理的,这里的两个箭头有两个作用:(1)使用三线SPI时,会舍弃一个数据传输脚,使用MOSI或者MISO中的任意一个,此时这一个数据脚既要发送又要接收,就需要使用到蓝色框内的箭头;(2)使用更快的传输模式时,会使用两个数据脚同时由主机向从机或者由从机向主机写数据,这个时候也需要使用到蓝色框内的箭头。
- 橙色框的NSS片选本来是SPI控制器内部集成的片选脚,但是我们实际使用过程一般不用,所以在配置过程中会使用软件管理来屏蔽掉它,让它作为一个普通的GPIO口;而实际的片选脚会根据硬件连接的管脚进行配置。
类似下图,假设此时使用三线SPI的半双工模式,只有MOSI一个脚,此时数据数据输入时就需要使用到上图蓝色框内的箭头,变成下图橙色线的流程。而输出方式不变,同样的,只留下MISO亦是如此。
时钟以及控制部分
首先来看时钟部分,作为主机使用时,STM32的SPI控制器需要提供Sclk时钟信号,其产生方式就是下图的波特率发生器,对于这个波特率发生器在框图中可以看出留给我们操作的只有CR1寄存器的BR[2:0]共三位,按照前面的介绍以及以前的经验,这三位肯定是用来空时分频系数的,也就是决定通信速率的,在特性那里我们提到了,SPI的最大通信速率是40Mhz,而MCU的晶振频率大于40MHZ,也就是说,此处的分频估计最少也是一个2分频。
然后是主控制逻辑,主控制逻辑部分就是关于模式,单个数据线还是两个数据线,主模式还是从机模式、是否只读这些进行选择。具体的在编程手册查看寄存器SPI_CR1寄存器介绍。
最后是通信控制,通信控制部分首先是最右边橙色框内有一个二选一数据选择器,其中一路输入来自上面提到得NSS,另外的一路输入来自SSI,而控制脚是SSM。当SSM置一时,系统会选择SSI,也就是内部从器件选择,从而屏蔽控制器绑定的指定CS GPIO。
然后通讯控制还有输出上方蓝色框的各种标志位,发送完成以及接收完成这些,主要用于中断或者DMA关于这个详细的在SPI_CR2寄存器的介绍。这里仅需要知道有这个东西即可。
SPI寄存器简介
1.SPI 控制寄存器 1 (SPI_CR1)(不用于 I2S 模式)
写法:SPIX->CR1
位 0 CPHA:时钟相位 (Clock phase)
位1 CPOL:时钟极性 (Clock polarity)
位 2 MSTR:主模式选择 (Master selection) 选择主模式
位 5:3 BR[2:0]:波特率控制 (Baud rate control) 选2分频
位 6 SPE:SPI 使能 (SPI enable) 放在最后
位 7 LSBFIRST:帧格式 (Frame format) 一般选择高位先发
位8 SSI: 内部从器件选择
位 9 SSM:软件从器件管理 (Software slave management)
位9:8 设置软件管理。什么是软件管理?
NSS引脚属于硬件SPI 片选引脚。把NSS引脚设置为软件管理之后,这个引脚相当于当作普通IO口使用。为什么要把它设置成普通IO口使用。因为当SPI总线外接多个从机时,是通过片选引脚进行选择。把NSS引脚设置为软件管理之后,片选引脚就可以接任意一个IO口都可以,这就方便很多了。
把位9 和 位8 都 设置为1,就是把NSS设置为软件管理。
位 10 RXONLY:只接收 (Receive only) 全双工
位 11 DFF:数据帧格式 数据位
位 15 BIDIMODE:双向通信数据模式使能 (Bidirectional data mode enable)
选择3线制SPI 还是4线制的SPI 结合硬件
2.SPI 控制寄存器 2 (SPI_CR2)
写法:SPIx->CR2
主要是中断使能和DMA使能
3.SPI 状态寄存器 (SPI_SR)
写法:SPIx->SR
位 0 RXNE:接收缓冲区非空 (Receive buffer not empty)
位 1 TXE:发送缓冲区为空 (Transmit buffer empty)
4.SPI 数据寄存器 (SPI_DR)
写法:SPIx->DR
发送数据SPI1->DR=data
接收数据data = SPI1->DR
如果是8位数据位,则数据寄存器的低8位为有效位
如果使用16为数据为,则整个DR为有效位
SPI初始化代码流程
根据上面的介绍即可总结出SPI控制器的初始化流程
伪代码:
SPI初始化
{
/*IO口控制器配置*/
//端口时钟使能
//端口模式配置
//具体复用功能配置
//输出类型配置
//输出速度配置
/*SPI1控制器配置*/
//SPI1模块时钟使能
//CR1
//双向单向 全双工
//8位数据帧
//全双工
//软件从器件管理//
//主机模式
//先发高位
//4分频
//主配置
//0.0模式
//CR2
//MOT模式,一般都选择摩托罗拉的模式,而不选择TI的模式
//CFGR
//SPI模式 选择为SPI模式,而非I2S
//SPI使能
}
SPI数据收发函数
{
//等待发送
发送数据
//等待接收
接收数据
}
SPI初始化代码
/*******************************************
*函数名 :spi1_init
*函数功能 :SPI1初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :
SCK------PB3 //复用输出
MISO-----PB4 //复用输出
MOSI-----PB5 //复用输出
*********************************************/
void Spi1_Init(void)
{
/*IO口控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<1); //B组时钟使能
//端口模式配置
GPIOB->MODER &= ~((3<<6)|(3<<8)|(3<<10));
GPIOB->MODER |= ((2<<6) |(2<<8)| (2<<10));
//具体复用功能配置
GPIOB->AFR[0] &= ~((15<<12)| (15<<16) | (15<<20));
GPIOB->AFR[0] |= ((5<<12)| (5<<16)|(5<<20));
//输出类型配置
GPIOB->OTYPER &= ~((1<<3) | (1<<4) | (1<<5));
//输出速度配置
GPIOB->OSPEEDR &= ~((3<<6)| (3<<8) | (3<<10));
GPIOB->OSPEEDR |= ((2<<6)| (2<<8) | (2<<10)); //50M
/*SPI1控制器配置*/
//SPI1模块时钟使能
RCC->APB2ENR |= (1<<12);//SPI1时钟使能
//CR1
SPI1->CR1 &= ~(1<<15); //双线单向
// SPI1->CR1 |= (1<<15); //单线双向
SPI1->CR1 &= ~(1<<11); //8位数据帧
SPI1->CR1 &= ~(1<<10); //全双工
SPI1->CR1 |= (1<<9); //软件从器件管理//
SPI1->CR1 |= (1<<8); //主机模式
SPI1->CR1 &= ~(1<<7); //先发高位
SPI1->CR1 |= (1<<3); //4分频
SPI1->CR1 |= (1<<2); //主机模式
SPI1->CR1 &= ~(3<<0); //清零(0.0模式)模式0
SPI1->CR1 |= (3<<0); //1.1模式模式3
//CR2
SPI1->CR2 &= ~(1<<4); //MOT模式,一般都选择摩托罗拉的模式,而不选择TI的模式
//CFGR
SPI1->I2SCFGR &= ~(1<<11); //选择为SPI模式,而非I2S
//SPI使能
SPI1->CR1 |= (1<<6);
}
/*******************************************
*函数名 :Spi_Send_Data
*函数功能 :SPI1传输一个字节函数
*函数参数 :u8 data
*函数返回值:u8
*函数描述 :
发送数据时候只需要关注参数
接收数据的时候,关注返回值,
参数随便传一个数值
*********************************************/
u8 Spi_Send_Data(u8 data)
{
u8 val;
/*发送*/
//等待之前的数据发送完成
while(!(SPI1->SR & (1<<1)));
//将要发送的数据赋值给DR
SPI1->DR = data;
/*接收*/
//等待有数据就接收
while(!(SPI1->SR & (1<<0)));
//将DR数据赋值给变量
val = SPI1->DR;
return val;
}
SPI使用IO模拟的代码思路
找到对应的硬件设备接口
LCD_SPI2_MOSI ---- 发送数据 PB15
LCD_SPI2_SCLK PB13
LCD_SPI2_MISO--------并不是屏幕上面没有这根线,而是没有使用到所以没有接
//注意使用的是IO口模拟来实现的功能,所以GPIO口需要配置为推挽输出。
SPI的初始化
{
//打开时钟 PB
//配置IO控制器
//PB13 PB15通用推挽输出
}
SPI数据收发函数
{
u8 buff;
时钟线拉低
数据线输出拉高
for(循环八次)
{
//如何发送
时钟线拉低; //控制发送,下降沿发送
If(data & 0x80>>i)
{
数据线输出拉高
}
Else
{
数据输出拉低
}
时钟线拉高; //接收 上升沿读取
}
}
由于笔者使用的屏幕不需要给主机反馈数据,所以这里少配置了一个MISO的管脚,且这里的GPIO初始化使用了GPIO的库函数来实现。
/*******************************************
*函数名 :Spi_Gpio_Init
*函数功能 :GPIO模拟SPI的初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :将GPIO配置为通用推挽输出,模拟产生SPI的时序
*********************************************/
void Spi_Gpio_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
//IO控制器
GPIO_InitTypeDef gpio_InitTypeDef; //定义了一个结构体变量
gpio_InitTypeDef.GPIO_Mode = GPIO_Mode_OUT; //通用输出模式
gpio_InitTypeDef.GPIO_OType = GPIO_OType_PP; //推挽输出
gpio_InitTypeDef.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; //一起使用的前提条件 必须是同一个端口
gpio_InitTypeDef.GPIO_PuPd = GPIO_PuPd_NOPULL; //无上下拉
gpio_InitTypeDef.GPIO_Speed = GPIO_Medium_Speed; //中速
GPIO_Init(GPIOB,&gpio_InitTypeDef);
}
/*******************************************
*函数名 :LCD_GSend_Byte
*函数功能 :使用模拟SPI发送一个八位的数据到LCD
*函数参数 :无
*函数返回值:无
*函数描述 :模式0或者模式3
*********************************************/
void LCD_GSend_Byte(u8 data)
{
u8 i;
//空闲状态
LCD_SCL_L;
LCD_MOSI_H;
//具体的发送过程
for(i=0;i<8;i++)
{
LCD_SCL_L; //发送数据
if(data & (0x80>>i))
{
LCD_MOSI_H; //发送数据1
}
else
{
LCD_MOSI_L;
}
LCD_SCL_H; //拉高接收数据
}
//开始读取返回数据
}
总结
关于SPI的控制器实现以及GPIO,模拟实现的介绍就记录到这里,具体的实际使用,笔者抽空再单开一片,应该是LCD的显示或者是对W25Q64烧录GB2312的字库。想要那个大家可以私信或者留在评论区。然后文中如有不足欢迎批评指正。
M4系列目录
1.嵌入式学习笔记——概述
2.嵌入式学习笔记——基于Cortex-M的单片机介绍
3.嵌入式学习笔记——STM32单片机开发前的准备
4.嵌入式学习笔记——STM32硬件基础知识
5.嵌入式学习笔记——认识STM32的 GPIO口
6.嵌入式学习笔记——使用寄存器编程操作GPIO
7.嵌入式学习笔记——寄存器实现控制LED小灯
8.嵌入式学习笔记——使用寄存器编程实现按键输入功能
9.嵌入式学习笔记——STM32的USART通信概述
10.嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置
11.嵌入式学习笔记——STM32的USART收发字符串及串口中断
12.嵌入式学习笔记——STM32的中断控制体系
13.嵌入式学习笔记——STM32寄存器编程实现外部中断
14.嵌入式学习笔记——STM32的时钟树
15.嵌入式学习笔记——SysTick(系统滴答)
16.嵌入式学习笔记——M4的基本定时器
17.嵌入式学习笔记——通用定时器
18.嵌入式学习笔记——PWM与输入捕获(上)
19.嵌入式学习笔记——PWM与输入捕获(下)
20.嵌入式学习笔记——ADC模数转换器
21.嵌入式学习笔记——DMA
22.嵌入式学习笔记——SPI通信
23.嵌入式学习笔记——SPI通信的应用
24嵌入式学习笔记——IIC通信