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

STM32之SPI通讯应用

[复制链接]
gaosmile 发布时间:2020-6-17 12:41
        上一章我们讲到了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
微信图片_20200617124107.png ! T3 G1 ]% {7 t( _* n
  (1) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机STM32产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如STM32的SPI 时钟频率最大为fpclk /2,两个设备之间通讯时,通讯速率受限于低速设备。
1 E1 y9 }& o: Z* r: q, b  ^  (2) MOSI (Master Output, Slave Input):主设备STM32输出/从设备输入引脚。STM32的数据从这条信号线输出,从机由这条信号线读入STM32发送的数据,即这条线上数据的方向为STM32到从机。
4 l1 K; |. q* h& {. P  (3) MISO(Master Input,,Slave Output):主设备STM32输入/从设备输出引脚。STM32从这条信号线读入数据,从机的数据由这条信号线输出到STM32,即在这条线上数据的方向为从机到STM32。
  W1 ^8 K: N+ a/ c  (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
{
( n& b1 r! i# S) }: i4 ]GPIO_InitTypeDef GPIO_InitStructure;   
/ f: Z! k8 K# tRCC_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;
$ p" L9 v2 S7 E" x4 ~" E. uGPIO_Init(GPIOA, &GPIO_InitStructure);    * d: G  W5 x# }# a" F+ Z
GPIO_SetBits(GPIOA,GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);
2 F& g% M* m* B" B+ T}

/ B7 s- a9 s9 ~/ y- k- H2.设置SPI1工作模式
  接下来我们要初始化 SPI1,这在库函数中是通过 SPI_Init 函数来实现的,和STM32的其他外设一样同样需要配置这些参数,使用中我们不需要关心这些具体的设置。设置 SPI1 为主机模式,设置数据格式为 8 位,然设置 SCK 时钟极性及采样方式。并设置 SPI1 的时钟频率(最大 18Mhz),以及数据的格式(MSB 在前还是LSB 在前)。这在库函数中是通过 SPI_Init 函数来实现的。
SPI_InitTypeDef  SPI_InitStructure;
% Z+ p1 J% V& t  [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位开始
) E- R, Q/ b, Q( N+ R( l) K" FSPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式
# k7 {# }- l& V3 V. cSPI_Init(SPI1, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

2 D& ~8 @: K1 C; Q: P( e- f3.使能SPI1
初始化完成之后接下来是要使能 SPI1 通信了,在使能 SPI1 之后,我们就可以开始 SPI 通讯了。
SPI_Cmd(SPI1, ENABLE); //使能SPI外设
' v" `+ b7 N7 }* V4.使用SPI读写一个字节u8 SPI1_ReadWriteByte(u8 TxData)% H+ ]% F- |( z
{        1 q6 x# Q( @9 y/ A* n7 Q
    u8 retry=0;                 
0 g/ N$ J9 O7 ~9 X& Z& n6 _2 q    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET)//检查指定的SPI标志位设置与否:发送缓存空标志位! @' ^3 C7 ^% Y+ P& T# q
    {
# Q5 I# _" r0 N6 k        retry++;$ n7 Y# ]* X8 O( N* K! N" J7 E4 M& H
        if(retry>200)return 0;
, ~9 @8 }/ T( n8 J; Q- T# P    }             ( \" H  F- \- }8 e; v7 j
    SPI_I2S_SendData(SPI1, TxData);//通过外设SPIx发送一个数据
4 F8 M! Y/ ?7 J# s# R    retry=0;9 Z$ p% k1 ?& z" k5 x. s
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
% A$ ^& R* i. x! n2 ]    {
; e: B" ~; U& ~' o1 p1 W        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最近接收的数据                     
' h) T" I5 p. q, v}
1 e9 ^; I0 d  D. j/ |
  本函数中不包含 SPI 起始和停止信号,只是收发的主要过程,所以在调用本函数前后要做好起始和停止信号的操作;
, F; c+ F$ ]0 i  循环调用库函数SPI_I2S_GetFlagStatus 检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测,当检测200次都还没等待到事件则认为通讯失败,退出通讯;( D4 U# \4 G3 l7 q9 z" r
  通过检测TXE标志,获取发送缓冲区的状态,若发送缓冲区为空,则表示可能存在的上一个数据已经发送完毕;等待至发送缓冲区为空后,调用库函数 SPI_I2S_SendData 把要发送的数据“TxData”写入到 SPI的数据寄存器 DR,写入 SPI数据寄存器的数据会存储到发送缓冲区,由 SPI外设发送出去;
# c6 Z- E: ]& ^& Z; i  写入完毕后通过循环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 芯片的数据手册可了解各种它定义的各种指令的功能及指令格式。这里我就不过多的将写了。
如果觉得文章对你有帮助,欢迎转发、分享给你的朋友,感谢您的支持!如需转载请联系我!
收藏 2 评论0 发布时间:2020-6-17 12:41

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版