SPI协议的原理,网上大把的资料可以找到,这里记录一下SPI的初始化过程,以即以读取W25Q16型号为例的一个简单的SPI读写过程。
& R: T9 K* T/ y0 q. m# x4 sCubeMX配置: o1 }8 c j6 p3 o e* W4 f
: s) ~( H, s; E+ Z$ ^6 y5 U' s& Z. q& B
& y7 V$ |& L* q4 f) w: l3 e
SPI模式* p! V+ m: v. c
有只发送、只接收、半双工和全双工模式; g; R# d: i9 b& A! \8 Q
根据自己的需求,和SPI设备支持的类型,这里选择全双工的SPI;" k! j4 g* s3 m6 {( f$ H7 l. Q
9 S1 [3 V8 w' P, r [$ F& a( U片选信号
b: A- u5 m+ H# Z3 q因为我们这个SPI总线上会挂载多个从设备,而且只有一个主机,所以,这里禁用硬件片选,程序里面读写之前使用软件进行片选;2 { T9 M/ J. m0 L8 j
: l. k5 `$ _/ y帧格式
% D- d1 p! Q2 L# ?$ p; {: e5 V4 I这里选择Motorola帧格式;
3 |# |3 L+ M; H/ p$ \6 E& }( P选择TI格式不能设置CPOL和CPHA,而且必须要开启硬件NSS这里才能选择TI模式。+ ^6 a2 a, E( ^" A: V9 c! D7 S
博主在网上搜了一些关于TI的SSP协议相关的资料,只有简简单单的一句:SSP总线兼容SPI,SSI 和Microwire 总线的接口。
9 F, w8 r5 x9 |* `6 R博主又搜了以下Microwire协议,和SPI一样,只不过片选信号是高电平有效。
4 a" ^' i+ h% O0 n/ h( ]3 T6 E这是手册里关于TI模式的说明部分和波形图:. q3 \3 u) `- A
2 C( A- h+ z$ P7 q
# L: R5 F! L6 k& j- s/ _8 W5 ~& Z g
9 s8 l' h8 O2 h2 X0 g
) d2 Y/ k1 v7 [8 i也不清楚这个SCK的触发信号是干嘛的,所以这里老老实实选择Motorola就行了。3 w a8 e8 N0 o2 C Y8 S) s: B
+ s9 X; J4 m X: }, v! y4 i: w j# t数据长度" p3 B. h, e' ]
4-16bit可选,根据实际,选择8bit;
: h: O7 y7 F! c! ]" r- X V2 u% s c1 S) b$ \+ X4 k' X
数据传输模式
3 n" ~2 ~9 R$ I6 p' o2 e9 H; l0 Q) u第一个传输的bit是高位还是低位,这里根据设备来选择,好像MSB(先发送高位)的比较多,注意这里的会同时设置发送和接收,如果设置错了,发送的指令对方就无法识别,可能收到的就是0xFF这种值。
; K! _3 \9 c# `$ w% s D' m
7 ?- N/ ?% L% Z `9 RSPI速率
6 w. A; d$ b- l% ~- H0 a& JSPI的波特率是由时钟分频得到的,CubeMX很方便的还帮我们计算出来了时钟速率,可以根据自己的需求设置。
5 ? K% i! `$ y3 g& O) A( q" k$ D4 z! e9 L1 p3 q5 v
时钟极性和时钟相位! O1 M; D5 _5 o P8 Q7 e4 v( E
根据极性和相位的不同,可以有4种不同的工作模式,这个根据芯片手册支持的模式选择,注意,一条总线上尽量挂载支持相通模式的设备,这样就不用每次在设备切换之前重新配置SPI外设。
8 R$ V. u9 Z( w$ I" J0 ]' t' f
2 @$ m1 f# D. D. K# ^; G L* I4 dCRC校验; F, K' F1 Y1 Y# U; y) `" a9 e
暂时还没研究到这里……
. o; V. K' p9 R: S2 [: ~/ T3 x5 B2 `
& n/ C1 A; J& J) k8 ONSS脉冲
- V. u$ {% L8 O参考SPI的NSS 脉冲模式的作用,这里用不到;
- T( e6 x9 c! M+ j. {. M2 g! H! t, x8 T
NSS信号类型
, s5 H7 b6 d( P, q; p. U( D7 c' k因为前面禁用了硬件片选,所以这里只能设置位软件模式。4 W/ y- U! g5 g* ]4 [$ k6 l8 r8 [
- Y$ ~9 a+ q$ T% h
除此意外,还需要把连接硬件片选信号的GPIO,设置为输出模式。
. C, Y ^, O; J( S( X" ]/ `6 `; K$ m vCubeMX生成的代码如下:5 { H+ a4 [5 ~+ L1 `5 B
8 D; M9 X! q+ |
- SPI_HandleTypeDef SPI3_Handler; //SPI2句柄
+ u2 S$ i: c6 r
# }& p4 H) _. K9 V! _6 V- void SPI3_Init(void)
# p- d2 o2 {$ s9 ?: I - {
8 y& g7 x* P% \( D - SPI3_Handler.Instance = SPI3;
) V9 `1 c' f4 }5 P0 ]" O, s) \ - SPI3_Handler.Init.Mode = SPI_MODE_MASTER;
2 f: C5 d1 P2 w; v' {1 E - SPI3_Handler.Init.Direction = SPI_DIRECTION_2LINES;: U8 R0 L6 m: G1 }4 z1 l
- SPI3_Handler.Init.DataSize = SPI_DATASIZE_8BIT;
9 M7 G. Q( _ Q8 q K - SPI3_Handler.Init.CLKPolarity = SPI_POLARITY_LOW;
) `: b9 o" z4 A1 c3 l) Y5 W# o - SPI3_Handler.Init.CLKPhase = SPI_PHASE_1EDGE;
3 d8 u9 K0 l: }9 E* B9 E& t - SPI3_Handler.Init.NSS = SPI_NSS_SOFT;. l' `9 o1 k" {: R' l4 Z3 U
- SPI3_Handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;- r3 p! P9 z1 q0 v5 n. q/ G
- SPI3_Handler.Init.FirstBit = SPI_FIRSTBIT_MSB;
1 Q% v4 ?4 u6 Q( A7 |7 { - SPI3_Handler.Init.TIMode = SPI_TIMODE_DISABLE;8 |4 c# Q3 T( o2 O% [( ?
- SPI3_Handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;' f: c1 M2 l, ]) n+ M
- SPI3_Handler.Init.CRCPolynomial = 7;" \# N) p4 P# H1 |& d1 X9 j6 R
- SPI3_Handler.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
! ?6 {1 U D' w0 \ - SPI3_Handler.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;( e$ b; Y, A' M% z, ~5 I; C
- HAL_SPI_Init(&SPI3_Handler);3 d8 t8 c6 ~4 N& p. x
, k& A& u( i6 o- __HAL_SPI_ENABLE(&SPI3_Handler);
- `$ q, g' Q2 a3 A. y - }3 l7 b6 b' u4 T4 O" t+ B$ A
- 4 Y+ [3 H& q8 m. Z! w& t& y
- void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)* V: }- }! k K5 w. Z9 T; ?
- {
5 `- ^- ^$ e8 Z% g# f3 ~9 K - GPIO_InitTypeDef GPIO_InitStruct = {0};
$ \1 `7 _& }1 X7 w- S - if(hspi->Instance==SPI3)9 I9 Y: F" q: `0 r6 Z
- {/ F/ e3 S# n* P' K9 N% t) N
- h* e6 m% m+ W/ V
- __HAL_RCC_SPI3_CLK_ENABLE();/ v. M: K$ w# W" W6 o* E
- " u* K, O. O9 k0 \
- __HAL_RCC_GPIOB_CLK_ENABLE();7 R7 w3 B' ~5 R4 c6 A \
- ( O9 F6 E; J* n
- GPIO_InitStruct.Pin = GPIO_PIN_2;
$ D( b9 a8 u& a - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;; d* j1 X% A; y: H. [, J0 ~4 V
- GPIO_InitStruct.Pull = GPIO_NOPULL;3 ]5 p. E2 F7 [8 A& T+ u1 `' h* Z
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
1 V) Q. P8 b9 k! @# J4 C - GPIO_InitStruct.Alternate = GPIO_AF7_SPI3;% x; E# K, W' c8 ?( H
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
% k6 P- J' S: A* ] - # G; h e4 X1 a2 d. f9 S6 J
- GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;( K1 e$ S5 I6 O& V
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;0 W1 u0 ^, v5 j
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- p' }. q: n$ E; P% E' { - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;$ z/ f+ f& Z5 g. O' n7 q8 u* E
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;9 p$ m1 j6 S7 b
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);+ a: C4 k! M, ~$ u" Q
- }
) ?( p& f% o" N5 B; K" O2 c8 V; r - }, w) N; v1 W2 V; m$ E0 Y
/ A4 G" y& w* h: w: H
/ Q7 U. A' T& m4 r9 c- static void MX_GPIO_Init(void)8 H8 {& u8 u$ x$ q3 D
- {
/ x8 t# \% p# I2 @ - GPIO_InitTypeDef GPIO_InitStruct = {0};
) O$ b/ }* X U1 z w+ l
7 A ]$ x! j3 \- /* GPIO Ports Clock Enable */. }: u9 z& ?7 ?: A
- __HAL_RCC_GPIOA_CLK_ENABLE();# | ^! A" g- C1 z, ]
- * k5 U" d7 t4 r
- /*Configure GPIO pin Output Level */
/ c D0 {6 C7 d, _. u - HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
; `) x; J5 e* ] @* M - $ c2 y: M s3 k
- /*Configure GPIO pin : PA4 */# x2 K# b& t, g$ ^; c
- GPIO_InitStruct.Pin = GPIO_PIN_4;
* H5 ^* E3 A$ F9 D; } Z( ~ - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;; r% N! s5 K: o( ? f
- GPIO_InitStruct.Pull = GPIO_NOPULL;
7 r8 K% O- p2 ]4 N6 F - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;) V2 M& A6 ?/ w& ?) _$ N6 Z
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
( q9 o8 b) A4 \9 W3 m4 i
5 ?" x2 A# `! a+ r: [6 e- }
复制代码
* R% ^5 h7 ?8 A( ^" A同一总线上不同设备,工作在不同模式的时候,速率可能不一样,需要一个重新设置速率的函数:% B6 J" h: q; W1 q
$ M* Z$ T6 ?* Z, E6 B. |% t( M
- void SPI3_SetSpeed(u8 SPI_BaudRatePrescaler)4 k' G0 q4 A) L/ u
- {+ U: x1 E! X$ @) v6 W. Z1 t1 A
- assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
8 q* J/ M7 o; Z% C# n$ w: M - __HAL_SPI_DISABLE(&SPI3_Handler); //关闭SPI
4 K8 w; o" Q% R o9 \) h. B2 I - SPI3_Handler.Instance->CR1 &= 0XFFC7; //位3-5清零,用来设置波特率
2 B/ c* X; r, D$ s3 q& A - SPI3_Handler.Instance->CR1 |= SPI_BaudRatePrescaler; //设置SPI速度0 G, J# X) ?3 `. u* ?- V; Z" `
- __HAL_SPI_ENABLE(&SPI3_Handler); //使能SPI- x9 ^, L8 t& w( L5 s! I P
6 D* U8 F+ O( G" l- }
复制代码
% K; v# _/ u, X h' T) |因为全双工的读写是同时的,封装一个读写函数:
% _+ {) Y' C- C$ E a4 D, N( S. G3 h9 H# S
- //SPI2 读写一个字节% U* Y# ]$ D; s$ W, I
- //TxData:要写入的字节+ V; \ n; a+ p Q
- //返回值:读取到的字节
; V) h1 g m) J& V" t: k7 t# \; z; h( W" w - u8 SPI3_ReadWriteByte(u8 TxData)3 s7 s6 }+ g" M
- {+ i' j/ m& j* d. H" o& H
- u8 Rxdata;) P4 S2 L" P5 }' s4 }
- HAL_SPI_TransmitReceive(&SPI3_Handler, &TxData, &Rxdata, 1, 1000);
9 }0 g# m J9 M2 G/ r( h - return Rxdata; //返回收到的数据
2 M: T% p4 i! F: p+ j: | - }
复制代码 ) B$ B2 D/ ~( }* z
根据W25QXX的数据手册,写一个读ID的函数:
. A8 `9 H4 Z! l0 N$ i# Q# O/ Q- //读取芯片ID- J! h( f4 P+ h+ i
- //返回值如下: 5 L6 X; l8 J! T6 M
- //0XEF13,表示芯片型号为W25Q80 & j( g0 G3 Y* {! a1 Y! a+ |
- //0XEF14,表示芯片型号为W25Q16 & U/ A, f; Y) Y- h
- //0XEF15,表示芯片型号为W25Q32 0 i; m; [: U% G) N6 B2 }! H
- //0XEF16,表示芯片型号为W25Q64
; R* |; k6 e! [$ t/ q" \0 U7 L! _8 m - //0XEF17,表示芯片型号为W25Q128
& ~ P3 k/ l( R+ x - u16 W25QXX_ReadID(void)+ h6 M b3 ~: M
- {
1 t+ A6 J0 J% ~! ] - u16 Temp = 0; ! c- t' f/ Z4 k
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
! b3 s2 n% D% q/ A - SPI3_ReadWriteByte(0x90);//发送读取ID命令
6 g. z+ |7 Y1 p3 E% | - SPI3_ReadWriteByte(0x00);
( \5 f. ~ |0 c) o; ]' r" _/ ~ - SPI3_ReadWriteByte(0x00); 4 ]* y3 u$ j: W# @' a
- SPI3_ReadWriteByte(0x00); * `* T; M" @$ Z; s
- Temp|=SPI3_ReadWriteByte(0xFF)<<8;
) n& L" G% W3 O+ a# o, B' G1 ~ - Temp|=SPI3_ReadWriteByte(0xFF); 3 x: \- O1 [- t! c2 v
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
A5 @- {* O/ K - return Temp;
6 |! d8 o( a, ?- E - }
复制代码 ( ]( f6 g; X! ~$ p" D& Y% w0 y) C
最后写一个在主函数中调用的接口函数。
3 j7 @2 T" n. }/ K0 L. V
! Y2 d* W8 F4 g% y& k- void W25QXX_Init(void)
# P' d% R0 L0 G - {2 n+ H" z G, J" D- j
- MX_GPIO_Init();- H& n1 a+ R9 E
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);0 S# ^1 y3 i7 {
- SPI3_Init();
3 x- p) A8 g0 g6 B - SPI3_SetSpeed(SPI_BAUDRATEPRESCALER_4);
) J) R, E7 V5 \9 g2 u# W0 w3 w - uint16_t W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID.
2 i. D" p. R3 ^. x' W* d j, V - printf("W25QXX ID:0x%4x\r\n",W25QXX_TYPE);7 ~1 H6 T+ |2 P1 B
- }
复制代码 - J# `8 M( @0 G: t
下载运行,读取到了芯片ID。" P' M* V+ R$ U( |9 f. X
5 F' N: H8 j" I9 P7 B j+ F
f- _1 Q- K- r$ |$ Y! C4 M
5 y7 Z o) H' ] |