SPI协议的原理,网上大把的资料可以找到,这里记录一下SPI的初始化过程,以即以读取W25Q16型号为例的一个简单的SPI读写过程。
9 X* O: m# `/ |( r9 Y8 rCubeMX配置:
6 A& M6 S+ Q/ h/ y$ b( Z
5 s! u% E' \) V/ K: t. i9 W
* C J9 K( W4 Z, O) P& m
3 H2 E) G+ ~% V: r, d l# `SPI模式
6 ^5 E6 L( Z, w! }9 z有只发送、只接收、半双工和全双工模式;! c# m) @5 G9 M2 ^: y- a
根据自己的需求,和SPI设备支持的类型,这里选择全双工的SPI;
F: g5 n! Z/ j. ^2 {3 |3 M' O0 p& G* y u7 n
片选信号
, b5 o' W b; o因为我们这个SPI总线上会挂载多个从设备,而且只有一个主机,所以,这里禁用硬件片选,程序里面读写之前使用软件进行片选;" ` Z7 ] @( l' `8 J
" _; T" i: [/ r0 i5 R2 H帧格式
7 J! t* |/ v& M* |这里选择Motorola帧格式;/ P% D0 E1 g, P1 P' ?
选择TI格式不能设置CPOL和CPHA,而且必须要开启硬件NSS这里才能选择TI模式。
# N! @4 R8 c# A: f8 |博主在网上搜了一些关于TI的SSP协议相关的资料,只有简简单单的一句:SSP总线兼容SPI,SSI 和Microwire 总线的接口。
& d2 ^& r9 F7 A9 q博主又搜了以下Microwire协议,和SPI一样,只不过片选信号是高电平有效。2 c s! P M& }4 I" L( Y, R4 D! I
这是手册里关于TI模式的说明部分和波形图:
6 q' y; I+ i! m& m! v+ M+ @0 f$ V' w
$ f. d, w5 p2 i$ y
, u$ e* M1 z# Z
( ^3 k) \- P: r7 f
6 _. q3 l9 y% A1 }也不清楚这个SCK的触发信号是干嘛的,所以这里老老实实选择Motorola就行了。9 D' o* f! l! ?! Y; a* O
/ e g4 x# n! ~) `, O& q5 Y! r# N数据长度: g+ T* z1 j9 r" L* j8 G& h. f
4-16bit可选,根据实际,选择8bit;
1 s# }. l2 \: ~; F ]1 A2 Y5 W. A5 c2 t ]% z" s; z
数据传输模式
4 y! y! H+ J; o+ ?) l第一个传输的bit是高位还是低位,这里根据设备来选择,好像MSB(先发送高位)的比较多,注意这里的会同时设置发送和接收,如果设置错了,发送的指令对方就无法识别,可能收到的就是0xFF这种值。: N8 j) B. f3 J. X: G4 `5 o/ K
" K% i* X P, M" f" R3 p; [SPI速率* b L4 g6 ^7 q! M3 C" X- S
SPI的波特率是由时钟分频得到的,CubeMX很方便的还帮我们计算出来了时钟速率,可以根据自己的需求设置。
# j2 U1 o( G+ B% ?1 v) o, h# _- Z8 Z/ _
时钟极性和时钟相位/ c- i" q) r& [+ } e, j1 Z
根据极性和相位的不同,可以有4种不同的工作模式,这个根据芯片手册支持的模式选择,注意,一条总线上尽量挂载支持相通模式的设备,这样就不用每次在设备切换之前重新配置SPI外设。
9 j9 t+ t6 ?/ Z- Q- ~- R- Q# N& [! j3 p, g
CRC校验5 R' D, j& ]: W; t# {
暂时还没研究到这里……
. P* C: N% X7 w( g+ ^( B3 G9 X9 _5 R; u
NSS脉冲5 h1 z7 }9 k5 `& J7 Q9 f
参考SPI的NSS 脉冲模式的作用,这里用不到;
" t( ], |2 q+ r2 _+ a1 t1 K7 _$ X& `, ^
NSS信号类型# |7 t* ]4 j; g- J8 E8 L& }0 l
因为前面禁用了硬件片选,所以这里只能设置位软件模式。6 L; r( s) Y3 Z, n9 l
) C$ D# H, _+ r, D" g) m6 w除此意外,还需要把连接硬件片选信号的GPIO,设置为输出模式。
( S* O& J+ V* N( [; R/ o" {; H/ PCubeMX生成的代码如下:
* a0 d2 i3 z0 v, r0 W
9 }, `7 \ D! u9 X- SPI_HandleTypeDef SPI3_Handler; //SPI2句柄
6 U4 s6 X+ D3 m6 [3 @% Q5 {
+ q' G( P4 p/ u. P$ ?; |- void SPI3_Init(void)
; r7 X4 u5 a- `( u1 k9 T - {
* P9 R8 X$ h! y- E* i, E+ F Z - SPI3_Handler.Instance = SPI3;
: I) a$ E& L, J1 O7 ~( V4 i" C, q& l - SPI3_Handler.Init.Mode = SPI_MODE_MASTER;' C" }6 O' V. T* q
- SPI3_Handler.Init.Direction = SPI_DIRECTION_2LINES;
: e* |- n8 `, j* P( b+ Q2 D - SPI3_Handler.Init.DataSize = SPI_DATASIZE_8BIT;
1 |6 Z/ J* j* ~5 K* u. m" l5 b% t - SPI3_Handler.Init.CLKPolarity = SPI_POLARITY_LOW;
" ^* \# c* U; q Q - SPI3_Handler.Init.CLKPhase = SPI_PHASE_1EDGE;" I- m8 y5 G9 C2 Y
- SPI3_Handler.Init.NSS = SPI_NSS_SOFT;! d9 {& `; E1 K* x1 Y- n- ~
- SPI3_Handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;- I/ C5 r7 u3 o4 ?- n9 d
- SPI3_Handler.Init.FirstBit = SPI_FIRSTBIT_MSB;
1 s6 D- B; ^9 w8 Y - SPI3_Handler.Init.TIMode = SPI_TIMODE_DISABLE;
0 t% @4 P# a8 @- g: v6 ^ - SPI3_Handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;9 u: M: C" r! `( r; F; l
- SPI3_Handler.Init.CRCPolynomial = 7;. c0 s5 W( @$ N5 T) K
- SPI3_Handler.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
5 Z. e ?6 d# Q# d" j4 o* b - SPI3_Handler.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;) m% Q% v2 ]3 Q. `8 q8 O
- HAL_SPI_Init(&SPI3_Handler);8 H6 v" c/ u: g* g
. V# t4 @3 Y2 W( ]; z5 u7 l" m- __HAL_SPI_ENABLE(&SPI3_Handler);
- i( H" X( y" N# ^/ d9 \# k - }
@3 A7 Z3 f! s - * p+ m4 V* R0 ?$ M" R% k" O2 ]0 }
- void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)' F. h3 C- o5 X5 _
- {
Q* N* r# M; p. ]! g - GPIO_InitTypeDef GPIO_InitStruct = {0};
& t8 O; T/ e" [, z - if(hspi->Instance==SPI3) ~# Q9 v/ u7 A# `6 o% s5 d
- {) o/ @ |' O+ E
- 5 U$ p$ k- b4 P0 K i
- __HAL_RCC_SPI3_CLK_ENABLE();! F+ \. a5 L2 k6 c p! a6 y
- ! a: g4 ]/ q+ J1 v; Y9 H3 i
- __HAL_RCC_GPIOB_CLK_ENABLE();1 w; {2 O0 V: X. L
+ B. ^8 {- C* Q* G! ~/ d8 z5 E- GPIO_InitStruct.Pin = GPIO_PIN_2;' Q) z' L1 h3 @0 y. ~! q5 K
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;5 A) S! w& F& f4 o# s: s+ D
- GPIO_InitStruct.Pull = GPIO_NOPULL;
' v1 t" ^7 Y7 P& t8 B8 k3 \ - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
7 L6 h( l7 J. T( {8 C* \( z ^ - GPIO_InitStruct.Alternate = GPIO_AF7_SPI3;
& |- Q$ ]( |$ B - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);! @" n3 _! t4 K, f
- 1 L: e9 [# H6 |1 l& u. v
- GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_4;3 q1 N1 M- J' V
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;; u- G' S! P+ v1 ]$ x
- GPIO_InitStruct.Pull = GPIO_NOPULL;
7 y3 }& _' L4 I& m, O - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;; @( q& u! G7 A6 h
- GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
% C. Y6 {, _3 f$ B# y! [, t6 B r - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);5 A0 @9 s$ F4 y- f7 ^8 I! K
- }
& |, z" Y& j6 | `' m% s# ~& R3 R - }3 O4 H0 `# E5 n" {
- 2 x" F* [. Q7 M/ Q3 a8 {
: z9 H2 `- B! x$ o7 i( J/ S t5 b- static void MX_GPIO_Init(void). Q8 }$ y7 g* o' s" ?0 C
- {7 _3 F1 h" W" X9 Z' k8 N0 @' |
- GPIO_InitTypeDef GPIO_InitStruct = {0};
+ o- V7 w& f8 B+ E6 j
- z# p# J: ], | p3 P* M- /* GPIO Ports Clock Enable */
7 T8 L; z# O3 Q% k+ Q- k - __HAL_RCC_GPIOA_CLK_ENABLE();
& b9 v3 \' Q( B4 u+ {: G: E) Q" D
I( M( W' N3 T# y h+ l, C- /*Configure GPIO pin Output Level */
3 F' e3 i4 U8 P p$ s, f, G - HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
( H& `4 r' [7 E( b
# n4 F6 Q/ Q, ]0 \- L- /*Configure GPIO pin : PA4 */9 j$ d' |. [! ?. ?' b4 j' T
- GPIO_InitStruct.Pin = GPIO_PIN_4;7 J+ h4 q- Z$ I9 d7 g0 G
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
. j5 c8 T2 E+ F - GPIO_InitStruct.Pull = GPIO_NOPULL;
# ^0 A0 k7 P9 h/ { - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
8 q. A. _9 |! J- J- M9 a - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
$ R. j$ U+ u) F. P9 k+ X: `" B
4 F* U; x# v9 c1 }- }
复制代码
; O8 k$ Y' g4 S, K) d5 h) |同一总线上不同设备,工作在不同模式的时候,速率可能不一样,需要一个重新设置速率的函数:
: q! r( [1 m% f* ~' j
5 e Y( v: u T. t- void SPI3_SetSpeed(u8 SPI_BaudRatePrescaler)9 O( I+ y$ X5 [/ t! l, x
- {
% B9 L, i& i5 q# ^1 F2 A' I3 g2 S - assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性5 _* B2 T$ p) o* j- @
- __HAL_SPI_DISABLE(&SPI3_Handler); //关闭SPI
$ {) _$ K9 w! ~+ G% ?8 S7 b - SPI3_Handler.Instance->CR1 &= 0XFFC7; //位3-5清零,用来设置波特率8 P" M* V( p2 n: G# F9 S4 `& k5 F# f4 p
- SPI3_Handler.Instance->CR1 |= SPI_BaudRatePrescaler; //设置SPI速度9 E# q5 f" u6 }9 ?: j) J3 u5 A
- __HAL_SPI_ENABLE(&SPI3_Handler); //使能SPI/ |2 n' m" c: @
- - B9 d- A* ]1 B) ^7 g4 Y/ C! C4 _
- }
复制代码
9 l" d. m% T& Q6 `因为全双工的读写是同时的,封装一个读写函数:
! a) N+ z7 `8 W7 x/ N4 x7 B' L; k+ u; h: i8 L0 C* M
- //SPI2 读写一个字节5 z$ q B H- h' b1 l7 K! j
- //TxData:要写入的字节
. `/ l8 M8 r: h: n - //返回值:读取到的字节( ^& \* A" o- p# @- [ O
- u8 SPI3_ReadWriteByte(u8 TxData)
, M0 G2 n4 z' E4 t j' @% [ - {
_4 I" E2 p1 i& h& `; C$ {3 a - u8 Rxdata;2 Y5 R1 ~# { s2 U* p
- HAL_SPI_TransmitReceive(&SPI3_Handler, &TxData, &Rxdata, 1, 1000);' ~$ F! F" O& O6 X+ a# s
- return Rxdata; //返回收到的数据
1 d7 h& m" u0 `7 z - }
复制代码 ! `" g) G; [7 @& H9 ~' u. i) W- v& W
根据W25QXX的数据手册,写一个读ID的函数:# }9 s7 s9 c% D+ W4 x+ d
- //读取芯片ID0 m- P- R3 w6 n. l" Q! w- Z5 `( N
- //返回值如下:
8 J6 C0 n5 j+ Z# s/ \ - //0XEF13,表示芯片型号为W25Q80 2 c5 x+ Q9 ]' a0 {6 Z
- //0XEF14,表示芯片型号为W25Q16
3 D! w" R F8 r - //0XEF15,表示芯片型号为W25Q32
6 R& f- T! h! H1 N+ ]3 M8 X - //0XEF16,表示芯片型号为W25Q64
( u7 y4 N& t, I - //0XEF17,表示芯片型号为W25Q128
* F) q+ v6 M5 w# S - u16 W25QXX_ReadID(void)
" P! n& S5 H8 m- o( [! s0 H6 J - {
* y: H/ p6 Q* u* h& G# U$ } - u16 Temp = 0; + H2 F( L: ]1 W( \( ]7 R
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET); / a9 {" T' x4 x7 S2 F" h% ~8 B
- SPI3_ReadWriteByte(0x90);//发送读取ID命令
3 p( y; E- A3 W+ @: R - SPI3_ReadWriteByte(0x00); 4 T; v2 O: H/ y+ v4 B5 n
- SPI3_ReadWriteByte(0x00);
, ~4 L* T. E, F$ [8 r$ p - SPI3_ReadWriteByte(0x00);
& |+ \5 {) a& A+ | - Temp|=SPI3_ReadWriteByte(0xFF)<<8; 2 N1 x, e# i% A/ t& i
- Temp|=SPI3_ReadWriteByte(0xFF);
4 K5 M* \. ~) p5 ^& ^7 ^0 U - HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET); # W! ? A1 Z o+ m
- return Temp;" k. @! `3 f, s
- }
复制代码
! }+ I+ d! C5 X最后写一个在主函数中调用的接口函数。 P! a' i- b4 G& F& X
1 ]4 F. L1 f0 s7 L8 z8 ^ F0 |- L
- void W25QXX_Init(void)
! v8 B) z& z$ Q" P - {" b/ m7 N( t+ r( R
- MX_GPIO_Init();2 @+ K5 S! f, p7 S# K6 _6 s
- HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
; x' L+ M5 E" X. r, t - SPI3_Init();
8 Z6 ?0 w+ O. Y% N# q i - SPI3_SetSpeed(SPI_BAUDRATEPRESCALER_4);* Q. A9 A( O- I l
- uint16_t W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID.
0 z" C4 M2 G- F* y6 @8 | - printf("W25QXX ID:0x%4x\r\n",W25QXX_TYPE);
3 W5 g3 g9 ], d' H1 e/ Q* l - }
复制代码
/ i. L+ m6 `4 d下载运行,读取到了芯片ID。
; q; A$ ^# G1 S2 ~/ z# e4 z# L& B5 j$ J# ^6 v, U4 G7 n0 l
, y; [8 k0 H7 Y% S7 f0 J3 d7 {$ a
?+ g! z' O2 m3 W |