SPI协议的原理,网上大把的资料可以找到,这里记录一下SPI的初始化过程,以即以读取W25Q16型号为例的一个简单的SPI读写过程。
6 J/ W+ n9 g/ T$ _. [CubeMX配置:
0 S1 K6 Z/ }) g' h6 K# B
! g/ G8 U. U7 J6 ~' b. D2 A5 U5 I
. k. a1 ?2 z, f% y: S6 l% N8 tSPI模式
* _7 }& e( ?% B. `2 s有只发送、只接收、半双工和全双工模式;2 Y. `$ n: s% I9 Q) A
根据自己的需求,和SPI设备支持的类型,这里选择全双工的SPI;) u; k( x4 x# C0 h, X% `) Z
7 b' n. r- {0 S. M& g7 {8 o片选信号
% ^ g5 G, h; Y, M: u/ }+ h因为我们这个SPI总线上会挂载多个从设备,而且只有一个主机,所以,这里禁用硬件片选,程序里面读写之前使用软件进行片选;2 ]& o. a/ S. j# [2 A- ?
2 i* Q7 O1 N, d7 s
帧格式
$ }% h) e1 w+ ] n6 e# y. m% i这里选择Motorola帧格式;- s. [3 }: b1 Q8 v
选择TI格式不能设置CPOL和CPHA,而且必须要开启硬件NSS这里才能选择TI模式。
/ Q2 o' D- l. d& E博主在网上搜了一些关于TI的SSP协议相关的资料,只有简简单单的一句:SSP总线兼容SPI,SSI 和Microwire 总线的接口。
( b8 q6 v+ ~" J. A0 o1 Y/ j. @博主又搜了以下Microwire协议,和SPI一样,只不过片选信号是高电平有效。
9 p5 ?! ]) K, b. y! O& Q5 ^/ }这是手册里关于TI模式的说明部分和波形图:: N2 h6 T# H4 P# \6 ]; J
3 O% ~. r$ ]7 H1 l; m
) z7 X; N6 {, P" P% |- S5 |7 X
( D. A* L2 Q$ y9 Y, f& S: f _
$ I, ^! _/ J. @+ ^
5 t& m; R( j6 P也不清楚这个SCK的触发信号是干嘛的,所以这里老老实实选择Motorola就行了。: J; w* J6 W9 }0 j5 {$ y0 L1 B
2 e# U a6 O0 S0 x
数据长度4 l& C7 m9 P% I+ n: ] Q
4-16bit可选,根据实际,选择8bit;) c1 q+ K+ o3 E
, l4 u2 `9 @9 U2 \' c数据传输模式3 U7 w! `! K+ a( H$ `, W$ q
第一个传输的bit是高位还是低位,这里根据设备来选择,好像MSB(先发送高位)的比较多,注意这里的会同时设置发送和接收,如果设置错了,发送的指令对方就无法识别,可能收到的就是0xFF这种值。
! O% O( Z6 i2 T8 R( C+ W$ h7 S5 `+ l. z; F5 p6 b
SPI速率7 L( b& _+ t1 `3 M5 H
SPI的波特率是由时钟分频得到的,CubeMX很方便的还帮我们计算出来了时钟速率,可以根据自己的需求设置。
: ^0 ? C8 O2 F. n, D0 G, b* a6 g1 L2 i, F# M, T
时钟极性和时钟相位
% p2 o4 K% V/ q0 S$ \, C根据极性和相位的不同,可以有4种不同的工作模式,这个根据芯片手册支持的模式选择,注意,一条总线上尽量挂载支持相通模式的设备,这样就不用每次在设备切换之前重新配置SPI外设。
6 X& m9 ]! H! ?) o y m) _6 _5 e6 ]6 d
CRC校验( m: g" F0 s9 n! A8 l8 f
暂时还没研究到这里……4 X6 _% P8 g. ^
) m& q3 w" L+ D
NSS脉冲
! a/ _4 E1 W) D T) _% A参考SPI的NSS 脉冲模式的作用,这里用不到;. k: x0 \! `, Z: E& {4 Y8 R
. Z% a% t4 v% W0 ~& @+ [3 P P
NSS信号类型
) F) Z+ z" R6 D) f! c因为前面禁用了硬件片选,所以这里只能设置位软件模式。' E8 o( q6 s# l6 s0 g1 f/ U" Z
0 |7 m. E0 T u) n' {/ E% J) \除此意外,还需要把连接硬件片选信号的GPIO,设置为输出模式。8 V( ^9 a3 `0 B' p9 Q, a
CubeMX生成的代码如下:
0 E# ^2 Q# u! ]# o. U' ?- ]* K
- SPI_HandleTypeDef SPI3_Handler; //SPI2句柄
2 {& J: K7 { m3 v - " t6 p& g: B- n
- void SPI3_Init(void)
! q+ Y2 Q: c! z2 k! M8 ^3 K - {
4 U& @- D; ?; E9 J! L - SPI3_Handler.Instance = SPI3;
1 t0 i& N; `$ j: u - SPI3_Handler.Init.Mode = SPI_MODE_MASTER;% e( `- L9 R& J1 y! H. V9 M5 o
- SPI3_Handler.Init.Direction = SPI_DIRECTION_2LINES;
8 K4 ^$ M5 O0 q$ s# f% | - SPI3_Handler.Init.DataSize = SPI_DATASIZE_8BIT;
6 F _; J* d( k ? - SPI3_Handler.Init.CLKPolarity = SPI_POLARITY_LOW;6 [& T- j" }5 T2 ]
- SPI3_Handler.Init.CLKPhase = SPI_PHASE_1EDGE;
) O7 |6 }/ d4 }, _* ^ - SPI3_Handler.Init.NSS = SPI_NSS_SOFT;
& F% T- v4 q9 [6 R4 o - SPI3_Handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;8 w6 R$ m' _$ `0 T4 A Z a
- SPI3_Handler.Init.FirstBit = SPI_FIRSTBIT_MSB;* k: `* J/ d% X
- SPI3_Handler.Init.TIMode = SPI_TIMODE_DISABLE;+ @# M+ Y# n% P- ^" P& p
- SPI3_Handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
7 j2 @# j; c( E, o' a3 x" u9 v - SPI3_Handler.Init.CRCPolynomial = 7;
9 S$ |% Y J; f - SPI3_Handler.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
5 w% C" z f+ P5 S( h - SPI3_Handler.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
- U+ u, |. l2 b - HAL_SPI_Init(&SPI3_Handler);0 v) y8 \ p' C
- 1 ~, ]- `- {6 I9 G* J
- __HAL_SPI_ENABLE(&SPI3_Handler);
/ G8 d6 E. U& J1 H" s - }: Q) W( U0 e, Q+ S9 L2 r
$ X X; ~; u6 V- S6 L: v- void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
2 l4 B; r8 W; F3 D+ c+ c0 i - {
7 Q9 b& g( l" @* a - GPIO_InitTypeDef GPIO_InitStruct = {0};: ~9 D/ A1 `6 k2 ^1 L5 s2 n4 Q4 \
- if(hspi->Instance==SPI3)' v1 N' M% A! S, H# ?! U y
- {
' A- {& q4 L9 @; z) x" T
5 [1 j6 U# g8 q- __HAL_RCC_SPI3_CLK_ENABLE();
: a& k8 w6 f) r2 N, P
$ r/ k c# s8 ~" ], P' u- __HAL_RCC_GPIOB_CLK_ENABLE();9 O5 N* F/ ?4 h+ b) Q6 e
- ; X1 }) P) \/ }' M- x
- GPIO_InitStruct.Pin = GPIO_PIN_2;
$ ~0 a/ n: q% g- d) L5 z - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
; m1 D# N: `3 z. A - GPIO_InitStruct.Pull = GPIO_NOPULL;
$ s' m9 s5 Q6 Z8 f9 R8 n3 n - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;5 H. g" Y# j8 y7 o% j' ^
- GPIO_InitStruct.Alternate = GPIO_AF7_SPI3;
+ d/ Z+ _4 P4 k8 Z* Z( P - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);" A v+ ^- t0 l8 k9 W
- : K$ ~0 ?1 I, s$ K9 Z
- GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;
5 l4 I8 P3 Y5 o, v1 t/ A, e' v( d2 R - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
' Z; L1 D6 ^+ n) D6 j& ?' N - GPIO_InitStruct.Pull = GPIO_NOPULL;
! K* Q) f: m8 J- h/ f' z - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;( d$ Y5 F. O8 V0 D3 [* G; L' M, T( U
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
+ c7 i" X8 l5 q! m! M - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);& r* E* u. H* n3 s2 C
- }
, I6 W0 J/ |* U0 s( P - }
3 W( l, R B3 [1 u- ]! ^1 R
" h$ O, I" k! A6 ?( p: F- - R: B- {5 Z3 Y w y% O; @
- static void MX_GPIO_Init(void)
1 ~/ e' T8 E1 u) ^4 d - {5 \! D- f% z4 s1 K- i- y$ i2 r
- GPIO_InitTypeDef GPIO_InitStruct = {0};
% E$ J$ V3 s; Z9 K - 7 |3 v0 R% E& n; J0 J4 d( \
- /* GPIO Ports Clock Enable */* e( \ T' \8 w1 j+ f* v, z
- __HAL_RCC_GPIOA_CLK_ENABLE();8 F/ Z1 Q5 E7 g8 v
- % { U3 R# e+ d4 x; [8 h/ g- s
- /*Configure GPIO pin Output Level */
9 C' T! {4 F9 n4 o1 s - HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
5 s9 [! {+ d& }7 D; v+ { - ; g8 s2 T, {* I- p2 S9 F
- /*Configure GPIO pin : PA4 */; q" e2 E4 Z$ K4 Z; A8 t/ w
- GPIO_InitStruct.Pin = GPIO_PIN_4;6 _( {( ?0 B! r5 B* @
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;+ c2 C* T2 a4 D
- GPIO_InitStruct.Pull = GPIO_NOPULL;7 X: X6 f6 j+ S: M
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
& B7 r6 [% D6 T1 e5 i9 T' j7 K - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);7 g% L' v5 _, Z; p6 ^
- 8 G7 U" a) \: F* @4 U0 e
- }
复制代码
3 c4 B, x) ?! E8 Q$ i `% a同一总线上不同设备,工作在不同模式的时候,速率可能不一样,需要一个重新设置速率的函数:
! D. X$ N* y. W" F5 u4 E" `8 V! ^2 Q7 q# C8 \: N' K/ `7 Q2 j# z8 R
- void SPI3_SetSpeed(u8 SPI_BaudRatePrescaler)
3 F6 B8 i2 l$ @# C7 j - {
2 }/ N* h$ y2 E: M7 }' W! j - assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性+ W# `& `- d9 @; h }+ @) G2 g
- __HAL_SPI_DISABLE(&SPI3_Handler); //关闭SPI; U% b* T0 P! X) T4 g0 q
- SPI3_Handler.Instance->CR1 &= 0XFFC7; //位3-5清零,用来设置波特率
9 d2 z% x; _# n% c, c5 N5 y6 P - SPI3_Handler.Instance->CR1 |= SPI_BaudRatePrescaler; //设置SPI速度* M8 D" L8 O$ y$ a' Q5 H
- __HAL_SPI_ENABLE(&SPI3_Handler); //使能SPI
$ o- } v8 k, k3 w# m - # {" P4 u& n7 R1 r& @! L% ]: _& h! u! K
- }
复制代码
4 F4 c/ w* o0 p9 L' l因为全双工的读写是同时的,封装一个读写函数:
; s3 d# U4 s" X8 m" R Z) f' z: f1 f! F5 ]4 }. E0 F7 K0 V) }
- //SPI2 读写一个字节, w8 f8 y' r1 U/ V+ l+ u
- //TxData:要写入的字节. [" U2 j1 R8 l7 Z
- //返回值:读取到的字节
% d. M6 e2 C T4 }7 e, g5 C - u8 SPI3_ReadWriteByte(u8 TxData); q, b& o6 w1 y
- {0 {' \. B9 S/ \( g
- u8 Rxdata;
6 }+ d( j9 d" Q. v. U - HAL_SPI_TransmitReceive(&SPI3_Handler, &TxData, &Rxdata, 1, 1000);/ f8 B. Y2 @2 \) ?, D1 y i# H
- return Rxdata; //返回收到的数据
! s1 n7 `* q: m8 |, C8 k - }
复制代码 9 `: \: l7 Z& u. n6 J3 I
根据W25QXX的数据手册,写一个读ID的函数:
3 z6 c7 _' p0 q* F5 a" t& T- //读取芯片ID$ I( f" I: L* {& w3 V1 {# L
- //返回值如下:
7 w: i7 w$ ?9 H7 T5 e - //0XEF13,表示芯片型号为W25Q80 & s3 h! L, |0 ^; [+ _5 q
- //0XEF14,表示芯片型号为W25Q16 % W- y" A0 N: _ _6 ? u" r
- //0XEF15,表示芯片型号为W25Q32 ; `) t0 i1 N* K! L# T# @( e
- //0XEF16,表示芯片型号为W25Q64
3 C9 Z8 o+ n& p - //0XEF17,表示芯片型号为W25Q128
0 E' K. _! q0 @ - u16 W25QXX_ReadID(void)3 z8 t: r9 u2 I+ f3 ]6 J; J" t
- {
; K6 e% X" J3 U7 b - u16 Temp = 0; + ~& s, \' o; a8 {7 C+ R9 }1 L
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET); 3 ~; L; r l4 v" X5 D3 Q
- SPI3_ReadWriteByte(0x90);//发送读取ID命令 , L2 U1 t8 I+ V; l
- SPI3_ReadWriteByte(0x00);
" l3 y/ K5 s5 k4 p+ Y: I - SPI3_ReadWriteByte(0x00);
3 h! I5 O4 v" C# } z, {& p. c' J - SPI3_ReadWriteByte(0x00); - ]# n$ \; I; k! E9 Z3 @$ C
- Temp|=SPI3_ReadWriteByte(0xFF)<<8; # S2 P: i, i4 e+ m8 a, R8 D& |
- Temp|=SPI3_ReadWriteByte(0xFF);
7 V$ u7 E' Y4 G* p% { - HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET); . g# u3 Q* I, {2 g' T
- return Temp;& i4 Q' O) ~. h, d5 ?- R
- }
复制代码
+ g- v7 p9 A3 \, I3 _ |最后写一个在主函数中调用的接口函数。6 X( _8 k W0 a. Q. @
1 I6 ]7 k+ A/ y6 W/ I: w' O. c- void W25QXX_Init(void)
3 J* }2 t" ?" Y& t; V - {2 L7 @0 g ?& K4 P
- MX_GPIO_Init();
1 z; P2 A% H8 ~. \ - HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);% j! J1 O+ Y, v+ E
- SPI3_Init();! H* B$ T5 k( P% `
- SPI3_SetSpeed(SPI_BAUDRATEPRESCALER_4);
3 x) p# \' T, F0 h; N - uint16_t W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID.
* U# B& L; [/ F$ N3 ]0 ~* x! b! C - printf("W25QXX ID:0x%4x\r\n",W25QXX_TYPE);
0 ` u3 t0 l. \ - }
复制代码 % e- J% a' I7 C' z7 B: Y8 ^0 r
下载运行,读取到了芯片ID。
# i2 ~! h( ^- z7 g" ], Z3 v- O3 G, z( i2 U
4 P( B: n2 Y7 D- m0 I9 G/ K7 t j9 {
4 [8 |7 s7 V. i* M& z. N1 r
|