介绍
2 u( O/ l N% O5 o3 ]& ^SPI 简介9 d; w, W0 B' w3 ^! E
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。: I! c: U' v+ F0 C# I' V
SPI接口是全双工三线同步串行外围接口,采用主从模式架构;支持多slave模式应用,一般仅支持单Master.时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后;SPI接口有两根单向数据线,为全双工通信,目前数据速率可达几Mbps的水平,速率较高。
% i9 H8 d/ Y, U3 W
% d$ M% C% x0 USPI特点
7 l9 v U1 _6 r1.可以同时发出和接收串行数据;2.可以当作主机或从机工作;/ ~0 [: V7 g# K1 U
3.提供频率可编程时钟;
* S o# q. M& d* ]6 B4 R% {2 \4 b4.发送结束中断标志;
! {/ t7 W# q; l4 d6 i5.写冲突保护;
7 ]$ e8 m9 J( H' [8 ~: V6.总线竞争保护;& B/ x1 W' Y0 W: y" G# Q
7.传输速度快
' K4 d5 C$ r2 H' K, S, r1 `. t/ v e1 X3 `( C+ t0 _3 v1 ^- H* t
SPI 物理层/ a r" u+ S/ O
SPI总线是一种4线总线,因其硬件功能很强,所以与SPI有关的软件就相当简单,使中央处理器有更多的时间处理其他事务。) F% u5 `/ b. Z) Q6 U
1.MOSI:主器件输出,从器件数据输入;这条线上数据的方向为主机到从机。* c j3 n: X( q( ]& D$ m
2.MISO主器件数据输入,从器件数据输出,即在这条线上数据的方向为从机到主机。
/ T+ G' j" |, d8 N0 X4 i1 N3.SCLK:时钟信号,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。0 y. A# w8 x6 u% ?; N. b
4./SS:从器件使能信号,由主器件控制(片选)。而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从
; x+ u% Y2 d9 { V设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。' d3 ?4 e7 L2 ]+ `0 k& P
- C; A; P/ O/ u* ~5 }SPI 协议层
4 O# W/ d4 S# s) N2 L( @$ _SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。MOSI及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定。MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,1 r- q- ^8 l' F+ x5 A8 G+ C
在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO为下一次表示数据做准备。SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。/ v0 Y* E6 b+ ?% ]
$ a# A3 V% @; r; mSTM32 的 SPI 特性及架构8 p& J; k, |, Z( l# b2 s0 a3 R1 c7 j
STM32F1 的 SPI 功能很强大,SPI 时钟最高可以到 18Mhz,支持 DMA,可以配置为 SPI协议或者 I2S 协议。
' S; u# Y! {. f8 K3 u, J2 _" {STM32 的主模式配置步骤如下:4 H5 g$ A" x- y& T0 C r
1.配置相关引脚的复用功能,使能 SPI2 时钟。
' v/ ^8 o$ h, a: m2 K我们要用 SPI2,第一步就要使能 SPI2 的时钟,SPI2 的时钟通过 APB1ENR 的第 14 位来设置。其次要设置 SPI2 的相关引脚为复用输出,这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用功能 IO。2 e" M# q- Z% ], k& o
使能 SPI2 时钟的方法为:. g$ s' z$ C) J: j2 p
3 ^+ W7 S$ |+ j
- __HAL_RCC_SPI2_CLK_ENABLE(); //使能 SPI2 时钟
复制代码 , ~& F) O1 H% K" a# c
复用 PB13,14,15 为 SPI2 引脚通过 HAL_GPIO_Init 函数实现,代码如下:! x1 A: ]6 r# Y% }7 H! R+ t
5 ~, P- n& b+ r9 D' ?; n. D3 r( c
- GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;( `7 x" i1 G+ v. O, U
- GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
' C) v6 o& @( Y1 F6 M; a9 J - GPIO_Initure.Pull=GPIO_PULLUP; //上拉! \% x9 h, F# C" a4 B _: t5 f* u! s
- GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速
+ Z" k: L1 ]2 O6 {6 l - HAL_GPIO_Init(GPIOB,&GPIO_Initure);
复制代码
$ y0 y7 S* c5 T2.设置 SPI2 工作模式! J/ D) V5 @" d' ^, Q6 ]9 r/ n
这一步全部是通过 SPI2_CR1 来设置,我们设置 SPI2 为主机模式,设置数据格式为 8 位,然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI2 的时钟频率(最大18Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。在 HAL 库中初始化 SPI 的函数为:
: @& L# B5 U; X- r5 P9 r* Q) t0 _2 h. @$ x S+ c$ I
- HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
复制代码
( W( Z5 E6 j: rSPI 初始化实例代码如下:
* K5 w! B7 O& {0 s! O8 v
& ]8 O+ h& Y) d, \- SPI1_Handler.Instance= SPI2; // SPI2% I4 E% r" F- J- j! b8 d- n
- SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式
' U2 {; d# I e! N m& n - SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES;
' d% ^ V' F/ C0 J9 j - //设置 SPI 单向或者双向的数据模式:SPI 设置为双线模式
- a- C, |( s; l - SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT; @) a8 F5 @6 L( i
- //设置 SPI 的数据大小:SPI 发送接收 8 位帧结构) E) n1 I8 g4 X" O# P0 O) ?4 w( ~
- SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; 1 q$ Z$ s" d3 _ B4 w$ L" A: p q
- //串行同步时钟的空闲状态为高电平
% M7 d9 d* R3 K. C3 n - SPI1_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; % ]1 K" g0 {* C- F+ a4 {) f
- //串行同步时钟的第二个跳变沿(上升或下降)数据被采样0 N3 _. H7 V% j& N5 q8 u
- SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)还是软件, J% h+ S5 G! j8 P: h
- //(使用 SSI 位)管理:内部 NSS 信号有 SSI 位控制
: x/ }; d, Q3 Y: W4 V [% n3 K - SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;
. R/ m5 b3 c8 w3 s - //定义波特率预分频的值:波特率预分频值为 2560 u$ b( i; l3 V* L( d/ U
- SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB;
/ W% G, d; B7 z. p B% b; r - //指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始
) O: T6 V( p! T9 k( M - SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式
5 c% [& t/ q. A- e - SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;
$ D7 r+ t" k+ t& y& O - //关闭硬件 CRC 校验
5 e2 o6 A$ x8 w0 i* t3 C- z, `; x5 Q - SPI1_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式: z1 K9 }; T% b9 ?7 U7 t! Q8 b
- HAL_SPI_Init(&SPI2_Handler);//初始化
复制代码 8 k: _6 ?1 H& h. v4 W# R
同样,HAL 库也提供了 SPI 初始化 MSP 回调函数 HAL_SPI_MspInit,定义如下:
: B5 D) p+ s K8 G7 p- e/ g/ Y: M% s. M* X6 D
- void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);
复制代码
7 ]% f& i. z0 L" x! |' ^3.使能 SPI24 n6 i. u9 w U( [: _7 X6 h
$ q! |3 C& M% `8 w% s$ x
- __HAL_SPI_ENABLE(&SPI2_Handler); //使能 SPI2
复制代码
0 T6 w' i# H. U4 { W4.SPI 传输数据2 z8 v2 I, M/ r$ r' E3 @. o
g8 G8 |1 q+ ~6 Q" B* V) ], E
通信接口当然需要有发送数据和接受数据的函数,HAL 库提供的发送数据函数原型为:, b' i6 x, @2 e" Y6 a
2 I$ Q8 e& K. u; T7 v$ t- HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,
, ?8 C+ [8 Z$ U' ?; N - uint16_t Size, uint32_t Timeout);
复制代码
b1 e7 o. V5 h7 ^! t. z这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。, M8 u+ X! g) n2 e* x- y
HAL 库提供的接受数据函数原型为:6 \% c% w7 Q- g- l; D1 B: X
! W; P8 ?7 y) ]1 Q- HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
; R- W. ^ |8 {% m* n7 c4 q - uint16_t Size, uint32_t Timeout);
复制代码 9 ?" _; S1 D! W9 s: d
这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。& E' `; E. A' [0 k# x1 [" O
前面我们讲解了 SPI 通信的原理,因为 SPI 是全双工,发送一个字节的同时接受一个字节,发送和接收同时完成,所以 HAL 也提供了一个发送接收统一函数:" {" q" [! S5 ^% q/ k q
% q2 m3 k; c) J
- HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,) m, W5 N- l. p# N
- uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
复制代码 $ W3 r- i( p* w; z3 X
该函数发送一个字节的同时负责接收一个字节。) I; f* p0 L+ [! H3 C' H
! x5 J% `( v/ z$ r4 Z设计实现% z; X. p; y% a2 C$ p, N- @5 Y
1.SPI1的初始化8 `; M. W8 [. U6 n
以下是SPI模块的初始化代码,配置成主机模式。9 T6 p6 ^( ~" a8 b0 D6 K$ P: g
, |! r: I/ b q- void SPI2_Init(void)
# N' t0 g$ X5 j - {
# }& M1 t9 r. n, _/ v& { - SPI2_Handler.Instance=SPI2; //SPI2
. j2 l2 Z1 n z0 x - SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //设置SPI工作模式,设置为主模式
! [! _; a' S) V5 Z* u, G" x - SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置SPI单向或者双向的数据模式:SPI设置为双线模式
: W9 C- i6 h; U, Q# y8 Z! C4 W# R - SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置SPI的数据大小:SPI发送接收8位帧结构
8 H: J: I. S: |( k+ x! x( o/ r - SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平
( m0 s ~! G! g, J4 ` - SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
, m5 _6 B& R5 u- e* d& ] - SPI2_Handler.Init.NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制: z) y/ f& _) X5 J( l) ~/ K
- SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为256
1 ^6 e% d' \) d5 a - SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
" D4 i& E8 }! U/ N7 ^$ w& \ - SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭TI模式
! O; @3 ^% d Y - SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
& M/ z: S0 K; R) c7 E - SPI2_Handler.Init.CRCPolynomial=7; //CRC值计算的多项式 C( s% J T* n4 {& n, }. ^
- HAL_SPI_Init(&SPI2_Handler);//初始化& n- l# ]/ L( R: c P# k
0 d& k" i [* t2 p- __HAL_SPI_ENABLE(&SPI2_Handler); //使能SPI2
" M! z) y2 N' `5 T6 T7 r
; |( B8 Z1 H1 I \+ e" C4 X; _- SPI2_ReadWriteByte(0Xff); //启动传输
5 ]; l# Z. @! n9 g& u9 X - }
复制代码
0 f/ F: V ^# B+ R: ]; ?底层驱动,时钟使能,引脚配置6 |0 {2 X" C# E6 m
9 k6 ~6 C2 K5 H& g2 `
- void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
. o* r9 H# _- [ - {2 O+ U* E5 i" U! C, S2 r! {" j
- GPIO_InitTypeDef GPIO_Initure;
# P7 i. k5 y9 L3 D6 R) D, w% `
5 U: {9 W" E1 q9 y* b' k" _- __HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟$ B2 R7 h% N; z' X
- __HAL_RCC_SPI2_CLK_ENABLE(); //使能SPI2时钟4 f9 W! f! m3 J* Z& Q
3 u; Q3 {* S l6 s" o6 U% J- //PB13,14,15
- p7 [6 k: b4 `* D5 v - GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
4 r2 \8 T% { F( f - GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出# y% j* P$ [! s4 Q$ G% E9 f
- GPIO_Initure.Pull=GPIO_PULLUP; //上拉* S( N7 m! \. D7 A0 D) a. F, M/ Z) ?
- GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速 ' g5 N, w; G% e) _3 Z* s/ j
- HAL_GPIO_Init(GPIOB,&GPIO_Initure);. C* C6 I, s4 B( h9 [2 p/ x
- }
复制代码
/ v3 e& ~, l+ u# V& ^+ ^3.SPI速度设置函数
8 p3 v4 \! _& Z- void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)2 \; ~8 L( Q L$ ]# N% o
- {
& }& S* R- \. q* ]) C4 z - assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
q$ C) }, ?& x1 g- ` - __HAL_SPI_DISABLE(&SPI2_Handler); //关闭SPI
3 V- C' Z& W& s( `: j' j9 L. R - SPI2_Handler.Instance->CR1&=0XFFC7; //位3-5清零,用来设置波特率' I" |( O4 B* v8 B. K/ V2 D3 Y) [
- SPI2_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置SPI速度8 c0 b+ T' [& H9 G) n) w
- __HAL_SPI_ENABLE(&SPI2_Handler); //使能SPI
) p3 m: ]0 u! X, t - - a7 t8 j/ r3 g$ h2 a, t
- }
复制代码
8 B$ }) R, D+ W$ U" @4.读写一个字节
: X: `( d& R# n. v9 G: ?/ j- //TxData:要写入的字节
8 U/ ~7 t2 `/ b - //返回值:读取到的字节
! w5 R. b @/ Z/ q% v$ G: m. R4 R4 Q - u8 SPI2_ReadWriteByte(u8 TxData)
9 d$ k8 j% ]/ X/ p6 V, ]% x - {, m! T/ O, h9 m/ o! N
- u8 Rxdata;) r5 D1 b7 D) y. j
- HAL_SPI_TransmitReceive(&SPI2_Handler,&TxData,&Rxdata,1, 1000); 1 c' o6 b+ G6 T7 V1 f Z' d' c
- return Rxdata; //返回收到的数据 2 c6 `. u/ ^4 m) r$ G0 I
- }
复制代码 ; C, L/ P9 W: X
转载自: 跋扈洋
0 |% e5 y7 E. U5 [2 x4 a; D7 d6 O z6 C. R7 D1 ]
4 U- w" P5 Y. z: L* V7 [
|