你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32之SPI通讯应用

[复制链接]
gaosmile 发布时间:2020-6-17 12:41
        上一章我们讲到了IIC通讯,这一章来说一说SPI通信,同样的很多模块也用到了SPI通信,比如0.96寸的OLED模块。玩过单片机的小伙伴都知道OLED有4针的也有7针的,4针的就是IIC通信,7针的就是SPI通信。9 o' W. B3 o9 r
一、 SPI 简介
  SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其 MC68HCXX 系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32 也有 SPI 接口。SPI通讯使用 3 条总线及片选线,3条总线分别为 SCK、MOSI、MISO、SS。! F% @& I( _, i8 Y0 l- @+ A# l
微信图片_20200617124107.png ; q' ?6 b! f7 p/ g, A3 p+ C
  (1) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机STM32产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI 时钟频率最大为fpclk /2,两个设备之间通讯时,通讯速率受限于低速设备。
2 t+ f  T/ q4 d6 _8 B2 S& N  (2) MOSI (Master Output, Slave Input):主设备STM32输出/从设备输入引脚。STM32的数据从这条信号线输出,从机由这条信号线读入STM32发送的数据,即这条线上数据的方向为STM32到从机。: u* U$ e% Y. y3 L' H# r
  (3) MISO(Master Input,,Slave Output):主设备STM32输入/从设备输出引脚。STM32从这条信号线读入数据,从机的数据由这条信号线输出到STM32,即在这条线上数据的方向为从机到STM32。! G0 Q% A# z( e  b
  (4) SS( Slave Select):片选信号线,也称为 NSS、CS。当有多个SPI 从设备与 SPI 主机STM32相连时,设备的其它信号线 SCK、MOSI及 MISO同时并联到相同的 SPI总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。
9 y: O9 G/ O+ H5 G- y
二、 具体代码编写1.SPI引脚初始化
  我们使用 STM32 的 SPI1 的主模式,第一步就要是能 SPI1 的时钟,SPI1 的时钟通过 APB2ENR 的第12位来设置。其次要设置 SPI1 的相关引脚为复用输出,这样才会连接到 SPI1 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PA5、PA6、PA7 这 3 个,所以设置这三个为复用 IO。
void SPI1_Init(void)) e+ G+ K) ~% a/ u" G
{
" r8 d; {  V; Z& y, }GPIO_InitTypeDef GPIO_InitStructure;   
3 f  D/ n, Z% T* ~% ]/ y8 b$ ]  z$ F5 GRCC_APB2PeriphClockCmd(    RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );  / D$ J/ {% i; l/ o+ k
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
7 y3 V' f5 w- p( nGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出7 \1 O4 H9 `% P
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
! F2 s8 \* d* y8 e3 j; V  uGPIO_Init(GPIOA, &GPIO_InitStructure);    ( f. j6 t% v5 L# I
GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);7 q2 i3 B8 y) w5 t' @$ c: G7 g& y' Y- s
}
$ ?5 |: I7 d; G' ~
2.设置SPI1工作模式
  接下来我们要初始化 SPI1,这在库函数中是通过 SPI_Init 函数来实现的,和STM32的其他外设一样同样需要配置这些参数,使用中我们不需要关心这些具体的设置。设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现的。
SPI_InitTypeDef  SPI_InitStructure;- |$ o& N+ A) Z" p/ D6 d4 s3 P" E
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
- ^: n4 h& D2 J1 V) [8 |# d( ~' [SPI_InitStructure.SPI_Mode = SPI_Mode_Master;//设置SPI工作模式:设置为主SPI
. \$ g1 r/ J% r( D. USPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;//设置SPI的数据大小:SPI发送接收8位帧结构# F* g1 _8 E2 [' @9 |- w- v
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//选择了串行时钟的稳态:时钟悬空高
6 N* A8 g, e0 u# u) \- [3 C- K7 ^SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//数据捕获于第二个时钟沿/ ^& J. V9 f9 Q0 e6 [
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
" b. N$ ~; @$ p7 n4 `/ v1 C" SSPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//定义波特率预分频的值:波特率预分频值为256; I4 \2 K2 Z/ g! T5 `
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始, @/ Y1 R- a2 r, h% z7 h' D
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式" Q' C2 A( U/ i  v) s
SPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

& A6 a% c! k3 r7 P* i3.使能SPI1
初始化完成之后接下来是要使能 SPI1 通信了,在使能 SPI1 之后,我们就可以开始 SPI 通讯了。
SPI_Cmd(SPI1, ENABLE); //使能SPI外设0 r' I/ [* ?/ k9 C
4.使用SPI读写一个字节u8 SPI1_ReadWriteByte(u8 TxData)
6 N0 U$ g& r! j' A1 ^8 a{        0 c4 a8 l5 z( D( o8 |, d
    u8 retry=0;                 6 L- u6 m1 `8 f* h2 T- Q
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)//检查指定的SPI标志位设置与否:发送缓存空标志位
" W0 B6 [; A. a    {, d$ {; h3 }& S# d
        retry++;1 {  r3 h7 ^+ ~; q! h2 Z
        if(retry>200)return 0;
; Y$ ~% y+ t& a, v4 Z4 {% a8 O3 k    }            
2 O9 P, d0 y/ \2 ^    SPI_I2S_SendData(SPI1, TxData);//通过外设SPIx发送一个数据" c. z( E" y  T0 H5 N
    retry=0;
5 T1 e: ?$ j3 t1 P( V1 a; O5 c; \    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
  i8 I9 ]( I* t$ s! ?    {8 K5 S9 p2 w" R
        retry++;
; A- b9 a3 O: ^        if(retry>200)return 0;
* H" U. R7 K- T: F4 X% r# N    }                              
7 v$ l2 t& @" r: n" U6 H2 z- w    return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据                     
+ R0 X, o$ l0 |; [) n}
, s! R3 c) t% @9 {8 }0 G& l
  本函数中不包含 SPI 起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作;
$ {2 T2 X+ f5 v  循环调用库函数SPI_I2S_GetFlagStatus 检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测200次都还没等待到事件则认为通讯失败,退出通讯;
5 w3 Z' n) ?; }3 \  通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数 SPI_I2S_SendData 把要发送的数据“TxData”写入到 SPI的数据寄存器 DR,写入 SPI数据寄存器的数据会存储到发送缓冲区,由 SPI外设发送出去;% D2 O* X- `4 z$ o* ~6 l
  写入完毕后通过循环200次等待RXNE 事件,即接收缓冲区非空事件。由于 SPI 双线全双工模式下MOSI 与 MISO 数据传输是同步的,当接收缓冲区非空时,表示上面的数据发送完毕,且接收缓冲区也收到新的数据;/ ]2 I  N$ e9 U. y
  等待至接收缓冲区非空时,通过调用库函数 SPI_I2S_ReceiveData 读取 SPI 的数据寄存器 DR,就可以获取接收缓冲区中的新数据了。代码中使用关键字“return”把接收到的这个数据作为SPI_SendByte 函数的返回值。
' C- v1 V/ R0 U  z( z* n: _  以上的步骤我们就搞定了SPI 的基本收发单元,还需要了解如何对FLASH 芯片进行读写。FLASH 芯片自定义了很多指令,我们通过控制 STM32 利用 SPI 总线向 FLASH 芯片发送指令,FLASH芯片收到后就会执行相应的操作。而这些指令,对主机端(STM32)来说,只是它遵守最基本的 SPI 通讯协议发送出的数据,但在设备端(FLASH 芯片)把这些数据解释成不同的意义,所以才成为指令。查看FLASH 芯片的数据手册可了解各种它定义的各种指令的功能及指令格式。这里我就不过多的将写了。
如果觉得文章对你有帮助,欢迎转发、分享给你的朋友,感谢您的支持!如需转载请联系我!
赞 收藏 2 评论0 发布时间:2020-6-17 12:41

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版