|
STM32CubeMX-HAL库的SPI接口使用 7 U5 V6 @9 A: V; ^, ]* p
8 M. f" |( g0 A8 w# _' _& P本文主要介绍STM32的SPI接口、cubeMX软件配置SPI接口和分析SPI相关代码。
{" Y" d! x% B* f0 cSTM32之SPI简介:) U4 S1 w/ I" O" ~* ^# A
SPI协议【Serial Peripheral Interface】4 J2 p, p; U: [! z; m0 `6 f: K
串行外围设备接口,是一种高速全双工的通信总线。主要用在MCU与FLASH\ADC\LCD等模块之间的通信。
1 x1 A2 A1 L4 h' U w. {) {SPI信号线& v! W4 G0 K: n) [: I4 S
SPI 共包含 4 条总线。
6 F5 K7 B3 a3 ~& @2 Q6 QSS(Slave Select):片选信号线,当有多个SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。
& ~$ S9 q$ Z: u; E" c% z i! W2 bSCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。
& x7 w2 ^) ~" y. BMOSI (Master Output Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。. ]* _& v, f* g) R% G
MISO(Master Input Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。# H7 M0 S" w& e5 w6 m" Z
其中SCK,MOSI,MISO是接在一起的,NSS分别接到不同的IO管脚控制。主器件要和从器件通信就先拉低对应从器件的NSS管脚使能。默认状态IO1,IO2,IO3全为高电平,当主器件和从器件1通信时,拉低IO1管脚使能从器件1。而从器件2,3不使能,不作响应。下图是主器件与多个从器件通信图。% M5 [! |$ s% O# o
* w: w2 l7 K( e3 E
SPI特性: B: e3 s! j; H$ Y7 s% u
单次传输可选择为 8 或 16 位。波特率预分频系数(最大为 fPCLK/2) 。时钟极性(CPOL)和相位(CPHA)可编程设置。数据顺序的传输顺序可进行编程选择,MSB 在前或 LSB 在前。! w/ k5 e. G9 b# P
注:MSB(Most Significant Bit)是“最高有效位”,LSB(Least Significant Bit)是“最低有效位”。
: I. a: }, A1 b% T! s 可触发中断的专用发送和接收标志。可以使用 DMA 进行数据传输操作。下图是STM32的SPI框架图。
/ ]' C) A7 u5 o; [% A0 B% F) d
7 a- M# ^. e; y* I 如上图,MISO数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 MOSI数据线。SCK 的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。
& h4 W3 i+ C: p7 R2 h9 A0 \/ o 控制寄存器 CR1掌管着主控制电路,STM32的 SPI模块的协议设置(时钟极性、相位等)就是由它来制定的。而控制寄存器 CR2则用于设置各种中断使能。5 Q7 w! x6 K; c# `5 W
最后为 NSS引脚,这个引脚扮演着 SPI协议中的SS片选信号线的角色,如果我们把 NSS引脚配置为硬件自动控制,SPI模块能够自动判别它能否成为 SPI的主机,或自动进入 SPI从机模式。但实际上我们用得更多的是由软件控制某些 GPIO引脚单独作为SS信号,这个 GPIO引脚可以随便选择。
6 t9 S9 m2 n4 g4 U$ P$ s5 u5 VSPI时钟时序8 _8 Y; Y7 q# B4 o% y+ q7 N1 J
根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。
j& Z, e+ d# n0 s. J 时钟极性(CPOL)定义了时钟空闲状态电平:# r4 F+ v: m! m% Y) w
CPOL=0为时钟空闲时为低电平, E% `8 v6 _3 z, b) @- ]
CPOL=1为时钟空闲时为高电平% u v* {' Z4 S- f9 S' \
时钟相位(CPHA)定义数据的采集时间。% D( {7 W% _- _) d3 Z" W
CPHA=0:在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。
% Q6 ~6 i, O+ z2 i5 @& qCPHA=1:在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。
7 v4 p; y& v- R% n2 I
8 P4 U8 E. l# }
CubeMX软件配置SPI:7 \! {% _( Z! D4 s: ~& K
下面继续介绍cubeMX软件配置STM32L152的SPI接口方法。
; O& ~2 E- f8 K$ i(1)打开软件,选择对应芯片后,配置好时钟源;
, T9 k- S. k+ W* l(2)勾选SPI1为全双工,硬件NSS关闭,如下图:9 m2 y1 s* H5 ~
: K8 @. G# C! }6 J Y$ |
(3)勾选好后,PA5、PA6、PA7如下图,在配置PA4为普通io口,gpio_output
# i+ Q; \7 Y2 L
; m/ X# ~7 G1 P2 G: M. r* f: Q(4)SPI1的参数配置选择默认,如下图所示
, C/ V3 _6 w9 i; x3 M0 A* s
9 q1 T2 ?, w7 K; C [ n: p r(5)生成代码,保存即可。
0 s) W# q* ]/ m. `! z% m0 W HAL库的SPI函数分析:3 ~2 W2 q; K, Q$ ?. k* ]4 U
下面具体分析下生成的SPI函数和函数调用。) V \5 [- Z7 h/ U L3 l
SPI_HandleTypeDef hspi1; //SPI结构体类定义,下面看其结构体内部的声明。
* l) Q6 F% V: e! i" _5 E
/ e% q5 P) k6 i! C! `# w4 [
Q' B3 R! e# o9 H- m下面分析SPI的初始化函数:9 T m2 C! V* V, J o
5 b: r6 r- \& D2 ]
5 Z- _- d' j/ l( m- void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi). H& }6 x3 y. I- i" @0 Q4 |: N
- {2 X" T! j& y$ v9 k
- GPIO_InitTypeDef GPIO_InitStruct;& T! a7 ` m( V; a/ P5 P$ F
- if(hspi->Instance==SPI1)
% y+ \; \' J, U3 j$ Y# J8 K - {) j8 Q% `3 w* s* A& ~' k% H9 D
- /* USER CODE BEGIN SPI1_MspInit 0 */* ]) d; `$ c4 s/ N- W7 I& _0 P! `
- /* USER CODE END SPI1_MspInit 0 */; k" y' c% \5 |0 X
- /* Peripheral clock enable */
) n L5 r2 _. l - __HAL_RCC_SPI1_CLK_ENABLE();//使能SPI1时钟
# q# z7 O% a# d. G0 ? - /**SPI1 GPIO Configuration" A- h/ w- d+ D, t7 K" D( s8 f' ]
- PA5 ------> SPI1_SCK
/ Z) I; \+ o( H8 y& B - PA6 ------> SPI1_MISO) c w- x @0 r h( G7 u
- PA7 ------> SPI1_MOSI$ X6 g' Y$ c. e) o4 U0 N
- */* K. j+ C9 K7 W/ K, k
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;" F) G5 h$ q. l7 e' @' b1 Y
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;, l7 T2 U: i" C N+ @
- GPIO_InitStruct.Pull = GPIO_NOPULL;
& x% O l' L" W7 } - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;) @* L1 X8 C6 r2 w2 s* q
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
h1 Y( f* |! b$ I! v3 {! G - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//配置SPI的数据线和时钟线 N8 }% G7 @; D0 I T9 t
- /* USER CODE BEGIN SPI1_MspInit 1 */
' ?- W9 Q5 y4 e6 Z" B - /* USER CODE END SPI1_MspInit 1 */
6 m- }' M+ X+ Y. s* A1 l0 M - }! {; L1 ? v0 A; v. X8 U3 x
- 7 V$ u% y# E. D E5 m
- static void MX_SPI1_Init(void)% ^' O- P0 O- q# l( u5 m4 S4 w
- {7 L8 B; X X/ R6 l, k* }
- hspi1.Instance = SPI1;" D4 D$ R7 x, r" H! \
- hspi1.Init.Mode = SPI_MODE_MASTER;//主模式
9 S; I( p$ h: P' I" D3 H - hspi1.Init.Direction = SPI_DIRECTION_2LINES;//全双工
0 \7 G+ @9 z0 I% r) C+ O - hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//数据位为8位1 ]- l1 S$ }& z5 \2 S a! b
- hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;//CPOL=0,low1 }* o% {; M) @/ I" R
- hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//CPHA为数据线的第一个变化沿# O ] h! e" g' _% g# q9 Q
- hspi1.Init.NSS = SPI_NSS_SOFT;//软件控制NSS
0 A3 o; ]4 S5 @ - hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,32M/2=16MHz3 L( h/ D" K) {5 S( Y: I+ f3 `" Z! y) I
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//最高位先发送# D; Z! y2 n# }$ d6 v
- hspi1.Init.TIMode = SPI_TIMODE_DISABLE;//TIMODE模式关闭
# ^! _" Q% S. ~0 `" l - hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC关闭
$ J: g# y& x! {' m u( I) W - hspi1.Init.CRCPolynomial = 10;//默认值,无效& \9 @( ]/ x1 J7 [
- if (HAL_SPI_Init(&hspi1) != HAL_OK)//初始化
u7 |- T9 [3 f, q) ?! ]5 Q# v - {
' \& ?) b8 w* J5 g5 C - _Error_Handler(__FILE__, __LINE__);" T3 k/ s: }7 u t' l# }7 j
- }
9 ?8 i; C! s" G" e- ^ - }
复制代码 , x# ?! D0 w" ~9 Q, O0 ^
. H1 M% @, r, y
利用SPI接口发送和接收数据主要调用以下两个函数:9 d. B6 N9 R6 F$ I, t7 A
" L) `. X, T: D5 ?# R% j: p& w
0 W/ ]* o& g& m4 A9 K' M7 I) q- HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
& z9 v7 A; x( o8 k2 j$ e" i+ v - HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码 : m3 h# v2 h) Q" m+ T; I, |# K; s
6 Y. A6 w; B$ u! Z- p6 X4 W- r M; ^, Y
5 `- t( E# a& d! t$ m
& h4 d. k: x0 p5 Y; b4 M
; X) ~! S' L1 ^& X2 ^0 s1 g$ e8 y. [ t
6 G6 p% \' P" x' d
|