
上一章我们讲到了IIC通讯,这一章来说一说SPI通信,同样的很多模块也用到了SPI通信,比如0.96寸的OLED模块。玩过单片机的小伙伴都知道OLED有4针的也有7针的,4针的就是IIC通信,7针的就是SPI通信。) K- z9 C# [6 v 一、 SPI 简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32 也有 SPI 接口。SPI通讯使用 3 条总线及片选线,3条总线分别为 SCK、MOSI、MISO、SS。/ Y2 u5 |* c! c8 k& ^% i) J
![]() (1) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机STM32产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI 时钟频率最大为fpclk /2,两个设备之间通讯时,通讯速率受限于低速设备。 (2) MOSI (Master Output, Slave Input):主设备STM32输出/从设备输入引脚。STM32的数据从这条信号线输出,从机由这条信号线读入STM32发送的数据,即这条线上数据的方向为STM32到从机。 (3) MISO(Master Input,,Slave Output):主设备STM32输入/从设备输出引脚。STM32从这条信号线读入数据,从机的数据由这条信号线输出到STM32,即在这条线上数据的方向为从机到STM32。 (4) SS( Slave Select):片选信号线,也称为 NSS、CS。当有多个SPI 从设备与 SPI 主机STM32相连时,设备的其它信号线 SCK、MOSI及 MISO同时并联到相同的 SPI总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。1 G6 C5 v( z& W$ @9 k' G( m- G 二、 具体代码编写1.SPI引脚初始化 我们使用 STM32 的 SPI1 的主模式,第一步就要是能 SPI1 的时钟,SPI1 的时钟通过 APB2ENR 的第12位来设置。其次要设置 SPI1 的相关引脚为复用输出,这样才会连接到 SPI1 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PA5、PA6、PA7 这 3 个,所以设置这三个为复用 IO。 void SPI1_Init(void)/ B9 Q# u- `; k4 C{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE ); 1 V/ }" q I' \ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;2 A9 ?, k- ~! a6 d GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 b. v" o1 R' E: R5 V& ^ GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); * d: G W5 x# }# a" F+ Z GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7); } 2.设置SPI1工作模式 接下来我们要初始化 SPI1,这在库函数中是通过 SPI_Init 函数来实现的,和STM32的其他外设一样同样需要配置这些参数,使用中我们不需要关心这些具体的设置。设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现的。 SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工! f8 |$ j- C( V C2 Z3 f' B SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:设置为主SPI3 s9 A2 [; E6 }) R) ?1 \ SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8位帧结构7 s6 i3 V+ c* h* ?: D* ^& ? SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高0 c ?! J, p8 b( O SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//数据捕获于第二个时钟沿+ Y$ W$ _1 {% _ SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制- ~! ]/ T. t% h: }# m6 }2 F SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//定义波特率预分频的值:波特率预分频值为256' t5 R- ]) g9 P+ w8 ? SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式 SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 3.使能SPI1 初始化完成之后接下来是要使能 SPI1 通信了,在使能 SPI1 之后,我们就可以开始 SPI 通讯了。 SPI_Cmd(SPI1, ENABLE); //使能SPI外设4.使用SPI读写一个字节u8 SPI1_ReadWriteByte(u8 TxData)% H+ ]% F- |( z { 1 q6 x# Q( @9 y/ A* n7 Q u8 retry=0; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)//检查指定的SPI标志位设置与否:发送缓存空标志位! @' ^3 C7 ^% Y+ P& T# q { retry++;$ n7 Y# ]* X8 O( N* K! N" J7 E4 M& H if(retry>200)return 0; } ( \" H F- \- }8 e; v7 j SPI_I2S_SendData(SPI1, TxData);//通过外设SPIx发送一个数据 retry=0;9 Z$ p% k1 ?& z" k5 x. s while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位 { retry++;4 c0 e( {/ @8 I1 B5 @ if(retry>200)return 0;/ \$ U$ |' k' u( i) Q5 \# [' m } b. W4 ^. w- S3 W( M( h& k; y return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据 }1 e9 ^; I0 d D. j/ | 本函数中不包含 SPI 起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作; 如果觉得文章对你有帮助,欢迎转发、分享给你的朋友,感谢您的支持!如需转载请联系我!循环调用库函数SPI_I2S_GetFlagStatus 检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测200次都还没等待到事件则认为通讯失败,退出通讯;( D4 U# \4 G3 l7 q9 z" r 通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数 SPI_I2S_SendData 把要发送的数据“TxData”写入到 SPI的数据寄存器 DR,写入 SPI数据寄存器的数据会存储到发送缓冲区,由 SPI外设发送出去; 写入完毕后通过循环200次等待RXNE 事件,即接收缓冲区非空事件。由于 SPI 双线全双工模式下MOSI 与 MISO 数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;9 U0 g" g- I# Z8 S6 s 等待至接收缓冲区非空时,通过调用库函数 SPI_I2S_ReceiveData 读取 SPI 的数据寄存器 DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据作为SPI_SendByte 函数的返回值。8 d' E+ j# g4 S# X) |: n 以上的步骤我们就搞定了SPI 的基本收发单元,还需要了解如何对FLASH 芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32 利用 SPI 总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI 通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。查看FLASH 芯片的数据手册可了解各种它定义的各种指令的功能及指令格式。这里我就不过多的将写了。 |
NUCLEO-H723ZG开发板试用 ——串口点灯测试
OpenBLT移植到STM32F103战舰开发板上适用于所有STM32F103系列的Bootloader
MultiButton移植到STM32F103战舰开发板
汇编浮点库qfplib移植STM32F769I-DISCO开发板与硬件浮点运算性能测试对比
【STM32MP157】从ST官方例程中分析RPMsg-TTY/SDB核间通信的使用方法
S2LP 的 sniff 模式如何同时满足通讯和低功耗要求
双证齐发!移远通信通过ISO 26262功能安全流程认证及产品认证
意法半导体 STHV200超声波 IC单片简化设计
了解NFC技术优势与应用场景
基于STM32软件实现底层函数经验分享—IIC通信