本文主要介绍STM32的SPI接口、cubeMX软件配置SPI接口和分析SPI相关代码。 STM32之SPI简介:SPI协议【Serial Peripheral Interface】 串行外围设备接口,是一种高速全双工的通信总线。主要用在MCU与FLASH\ADC\LCD等模块之间的通信。 SPI信号线 SPI 共包含 4 条总线。 SS(Slave Select):片选信号线,当有多个SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他的 SCK、MOSI、MISO 线则为多个设备并联到相同的 SPI 总线上,低电平有效。 SCK (Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。 MOSI (Master Output Slave Input):主设备输出 / 从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入数据,即这条线上数据的方向为主机到从机。 MISO(Master Input Slave Output):主设备输入 / 从设备输出引脚。主机从这条信号线读入数据,从机的数据则由这条信号线输出,即在这条线上数据的方向为从机到主机。
) |2 j3 ]. C9 t7 A/ ?0 l% ~* i
$ u( g1 e9 T9 g& L2 S" I g
其中SCK,MOSI,MISO是接在一起的,NSS分别接到不同的IO管脚控制。主器件要和从器件通信就先拉低对应从器件的NSS管脚使能。默认状态IO1,IO2,IO3全为高电平,当主器件和从器件1通信时,拉低IO1管脚使能从器件1。而从器件2,3不使能,不作响应。下图是主器件与多个从器件通信图。
6 |, H! r- y; q2 j0 X- y. i7 a
- L7 a% i' c: y& gSPI特性 单次传输可选择为 8 或 16 位。波特率预分频系数(最大为 fPCLK/2) 。时钟极性(CPOL)和相位(CPHA)可编程设置。数据顺序的传输顺序可进行编程选择,MSB 在前或 LSB 在前。 注:MSB(Most Significant Bit)是“最高有效位”,LSB(Least Significant Bit)是“最低有效位”。 可触发中断的专用发送和接收标志。可以使用 DMA 进行数据传输操作。下图是STM32的SPI框架图。 & M. s) X; O. u4 n1 y9 Y
0 k8 Z$ O( F" Q! D. ]
如上图,MISO数据线接收到的信号经移位寄存器处理后把数据转移到接收缓冲区,然后这个数据就可以由我们的软件从接收缓冲区读出了。当要发送数据时,我们把数据写入发送缓冲区,硬件将会把它用移位寄存器处理后输出到 MOSI数据线。SCK 的时钟信号则由波特率发生器产生,我们可以通过波特率控制位(BR)来控制它输出的波特率。 控制寄存器 CR1掌管着主控制电路,STM32的 SPI模块的协议设置(时钟极性、相位等)就是由它来制定的。而控制寄存器 CR2则用于设置各种中断使能。 最后为 NSS引脚,这个引脚扮演着 SPI协议中的SS片选信号线的角色,如果我们把 NSS引脚配置为硬件自动控制,SPI模块能够自动判别它能否成为 SPI的主机,或自动进入 SPI从机模式。但实际上我们用得更多的是由软件控制某些 GPIO引脚单独作为SS信号,这个 GPIO引脚可以随便选择。
% L9 X; P/ I% y& ^0 d+ }
SPI时钟时序 根据时钟极性(CPOL)及相位(CPHA)不同,SPI有四种工作模式。 时钟极性(CPOL)定义了时钟空闲状态电平: CPOL=0为时钟空闲时为低电平 CPOL=1为时钟空闲时为高电平 - [5 Q2 ^, z- q7 H( i% ~8 p
( G! o( E* P0 o
时钟相位(CPHA)定义数据的采集时间。
! ~; K- N9 k0 C. A) F0 E' Q2 gCubeMX软件配置SPI: 下面继续介绍cubeMX软件配置STM32L152的SPI接口方法。 (1)打开软件,选择对应芯片后,配置好时钟源; (2)勾选SPI1为全双工,硬件NSS关闭,如下图: 8 Y8 J! l+ x! W/ K- y$ m
' N/ n5 R/ }. C(3)勾选好后,PA5、PA6、PA7如下图,在配置PA4为普通io口,gpio_output - j+ k# u+ W9 S! q
4 |8 f2 b2 I) g/ ](4)SPI1的参数配置选择默认,如下图所示 : w% y' K& S( R3 u- W2 c: a7 V( D; @
- v3 D/ n! ]8 m0 \% [! p(5)生成代码,保存即可。 HAL库的SPI函数分析: 下面具体分析下生成的SPI函数和函数调用。- SPI_HandleTypeDef hspi1; //SPI结构体类定义,下面看其结构体内部的声明。
复制代码 2 k |1 y) d+ `8 u
% d5 f1 i3 \" l) U2 n
; O6 g% F6 t1 A; D5 d M7 T4 Z3 J4 M b7 I2 `0 e% s
I0 J+ a$ g5 C' @ 下面分析SPI的初始化函数: - void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)0 a7 q7 p1 F! l
- {
2 A% S2 y& x8 `3 Q* ^ - GPIO_InitTypeDef GPIO_InitStruct;* A' m! s" Q: m
- if(hspi->Instance==SPI1)
$ ?: X3 `# ]2 g. ~ - {, C. s) t( v' R4 p' d. p
- /* USER CODE BEGIN SPI1_MspInit 0 */, \) W9 L& q' z$ B) C
- /* USER CODE END SPI1_MspInit 0 */
# U7 F" M0 u7 {7 O" L* J1 {# c: b - /* Peripheral clock enable */1 n" F6 n* _7 w! ^
- __HAL_RCC_SPI1_CLK_ENABLE();//使能SPI1时钟0 n C7 U$ e* u% N3 _7 k, [
- /**SPI1 GPIO Configuration% ?1 P3 r- S4 r2 q
- PA5 ------> SPI1_SCK
2 S7 o( | k5 ~ S1 u - PA6 ------> SPI1_MISO
! w8 [# Y3 D3 ?/ G - PA7 ------> SPI1_MOSI
- W- g s% K1 Y- \ - */ W, T4 u( u$ A" z
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
( N1 _' w( X- G B$ \0 ]7 Q - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;* V8 G* r7 ^/ `+ }' d
- GPIO_InitStruct.Pull = GPIO_NOPULL;- b* V6 Y+ d3 }
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
* R, u: w9 }$ p. O - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;4 x6 W2 z1 \1 M6 H5 F
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//配置SPI的数据线和时钟线0 J0 {* [( V4 F$ u! Z
- /* USER CODE BEGIN SPI1_MspInit 1 */
3 j; t% Y3 a( `, n - /* USER CODE END SPI1_MspInit 1 */, f4 L# `0 x& F: o: l9 d2 E/ K
- }
4 b7 D" E: I2 u+ i' L9 r
W" B3 c9 C3 Y, U, ^* Z' L% i, u- static void MX_SPI1_Init(void)3 \. | t" d" Z; \3 M t
- {
( a: t4 g) I1 r2 w& y8 y% w( t5 k - hspi1.Instance = SPI1;
0 {5 w! K4 s: H: S3 F - hspi1.Init.Mode = SPI_MODE_MASTER;//主模式% r( a/ d; O' }. X' }: I
- hspi1.Init.Direction = SPI_DIRECTION_2LINES;//全双工' U B n8 a/ s) k
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;//数据位为8位3 Y; ]9 C2 _; o
- hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;//CPOL=0,low
$ a& @% |. g/ @ D0 p% p - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;//CPHA为数据线的第一个变化沿
9 m# E- I1 I! j$ E" A! j3 g - hspi1.Init.NSS = SPI_NSS_SOFT;//软件控制NSS8 y2 f: F+ {. N0 [
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;//2分频,32M/2=16MHz+ b: r# y: @/ ~9 G( ]
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;//最高位先发送3 S3 f6 G# m3 o: ?9 Q7 K- z1 T
- hspi1.Init.TIMode = SPI_TIMODE_DISABLE;//TIMODE模式关闭6 N! V1 H+ [; I, M$ M" y6 R0 R
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;//CRC关闭( O7 r2 H9 _3 U! W$ Y: f: Z
- hspi1.Init.CRCPolynomial = 10;//默认值,无效7 U, a; z; H# x$ `
- if (HAL_SPI_Init(&hspi1) != HAL_OK)//初始化
9 e- v, ~7 d, ?' U+ V - {
* X3 b+ Y/ P. w; p0 ? - _Error_Handler(__FILE__, __LINE__);: X2 C; b4 [" ~1 T0 m' N3 ]
- }
' {) s- H& B% Q1 K) n3 } - }
复制代码3 ?, H% u/ `1 `, n! ?' F6 a! A
利用SPI接口发送和接收数据主要调用以下两个函数: - * n3 u5 D. w* V1 e4 ~- c( w2 g
- HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//发送数据
9 r1 H. F5 a- o6 D E - HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);//接收数据
复制代码
! u6 @% p8 [( | |