SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。
4 j0 |' q* ~; [; n. T/ R5 P8 Q3 h2 z. U
在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:
8 Y- G) a! Y1 y4 L5 l
" z( z8 t# t3 V: m4 M3 n/ D
& s. h9 N& P; x8 C( G" f* ?% r+ ~" A. K( t% |4 {
Normal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。0 v( z, x1 s' O4 U( o5 _% I4 B
# ~8 n: f3 m: N3 p
SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:
' q, j6 W5 U0 f; C; p- void SPI1_PA5_PA6_PA7_Init(void)# D' Z; T& Z6 G0 ~8 Z
- {# B v$ w3 Q5 t! E3 d2 x
-
# K& g. U7 C$ C; {9 h7 } - __HAL_RCC_GPIOA_CLK_ENABLE();6 s/ o8 g, a" P
- __HAL_RCC_SPI1_CLK_ENABLE();
3 n) u4 i0 F4 ]. [0 t - __HAL_RCC_GPDMA1_CLK_ENABLE();0 f! m) @/ n6 d, f
- ) s- a1 ~/ ?* f( {" t4 ?# N
- RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
$ N* z6 Q2 u* @% e+ @& D7 T n - GPIO_InitTypeDef GPIO_InitStruct = {0};* E* W( H* g5 w3 q( H
- SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};$ }- i Z& F, u) N
- * V9 y6 m) E* E% Q T
- PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;
3 d8 [" [- O% c* _7 h! H3 w - PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;1 U4 T- N8 e. t5 h. P) z4 ?
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
! o" ~ r6 \# J) ]0 X- M6 b% y5 M - ' h4 s7 C% \! M& K
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
) Q" G3 b2 o* j1 s& e - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;7 d2 F# O5 g' M
- GPIO_InitStruct.Pull = GPIO_NOPULL;7 L* S% j5 Z8 g9 }/ S
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
2 R8 A ^& L4 g! q- ?0 P! E+ x8 i - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;- l7 D5 ~7 S$ c% i, I3 l! P
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
8 [- X% O# K7 L - 8 }7 R1 L4 K) v5 V
- hspi1.Instance = SPI1;
& Q' I7 V/ W' k5 f9 P0 t - hspi1.Init.Mode = SPI_MODE_MASTER;
" C" b, u8 l4 L - hspi1.Init.Direction = SPI_DIRECTION_2LINES;
- b T' X7 }: G r$ R - hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
9 q4 o0 T; A2 G: p# K; u7 g+ Y) {) F9 B - hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
: x& v2 g6 d Z w* g3 |" c& I! N - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
8 i0 E2 [* r4 g* l _+ e0 j& d/ N. b - hspi1.Init.NSS = SPI_NSS_SOFT;3 i9 A" N3 z$ l; v/ N% f6 x/ J
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;! v! x: E8 u5 ]% q- q5 j
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;# R% m' k; p$ V. F1 a/ t- v4 n' ~
- hspi1.Init.TIMode = SPI_TIMODE_DISABLE;7 p: _( \* [6 F
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
; h& R; G4 l9 \" y - hspi1.Init.CRCPolynomial = 0x7;
: T* r" `! j/ d - hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
9 s( {7 \9 t7 a) w - hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;# ~* c4 _: U" z7 d% S' {
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
6 J, H+ f/ ^3 I8 N! N D' L( v% Q v9 b - hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;/ B* N2 I2 |6 {3 q
- hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
; z) C+ z+ F0 j - hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
2 W/ ^ x: y" c - hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
J) _4 F. J S* R& q - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
# n' I3 a2 a" s; e2 y - hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
: o. v6 N+ e" S* \( w' T - hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;* X# w, I8 f5 c' z6 N% ]7 O
- HAL_SPI_Init(&hspi1);
8 v1 Y: c5 |2 n6 S- Q- ]- ] - __HAL_SPI_ENABLE(&hspi1);
& c3 H ]5 S% X% K( I - , v; M" V4 _& t: A" w- R6 _2 B
- HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
, ~$ E6 |: P6 U% ?$ f8 E - HAL_NVIC_EnableIRQ(SPI1_IRQn);& {0 u& n8 M3 ]
- ' V& [+ x4 L# Z4 v9 `
- HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;
+ f% `7 Q# R9 | - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;
9 ]" T+ B6 D: S7 y6 h - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;- U8 p+ l* {9 Y: @2 M! Z
- HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct); V0 N! {3 |; r- g
& D' P( a2 w0 e- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
* N1 k v8 n" W: _4 m% A* [9 n+ F! d - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;! g/ C: \* Q4 b. U7 W1 n( M& ?+ g
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;/ e# S1 ?% a) F
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
$ S; I: |: E* g; i; k - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
" E e+ v7 E* [# O - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;: \4 r6 M, M* K" |7 h
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;" S7 c! C- @; K: l
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;7 D+ O" u+ o& }# f- X. e1 s
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;' l# E" B: h9 M# O3 d& J7 n
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
" M" m7 A* W1 s - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;* b8 W5 h, t" F T- F& {
- handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;' H$ B+ O, ?8 }) t1 ]- l2 [5 B
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;0 z1 W8 Y& y0 N9 h
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
1 E! c' x- s8 W' M* [( m. _ - HAL_DMA_Init(&handle_GPDMA1_Channel1);
! d# Z0 k+ W( b5 G - 4 r* B6 y% J" K, j% z; ]( v
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
$ T/ ^: _8 b0 S; n; U3 e6 p1 H/ b
o5 z* k8 C! t% |' @2 H0 R9 h- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
8 ^) F4 Y% K/ |6 c8 o! O E, b -
3 V1 `; r$ z$ s# e- k4 f - HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);
+ p' g3 K# }0 f+ l C7 g - HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
: `% ]6 I+ }$ g
5 S% Z) [1 Z) M4 T5 s8 q0 x- W- }
% J h+ U4 k5 G
& f# e# V7 v8 q- void GPDMA1_Channel1_IRQHandler(void)% p4 g/ N2 v0 L/ j" L# I- b. }
- {3 {6 O% B% k/ b
- HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);
, ]7 \. _# p3 p5 K- T3 T, ?$ l1 V - }/ k+ }5 j, Y( X- n4 Q. o5 ^+ {
- , \5 E+ s+ C3 O' X6 e) r: ?" T
- void SPI1_IRQHandler(void)
3 S, ?& T, p- k' E M2 | - {, f! T# s; E8 h' h r- a$ @
- HAL_SPI_IRQHandler(&hspi1);1 }4 V( ?* d9 J) R# ]0 N
- }
复制代码 ; O6 e+ g5 s! H8 P* S" S6 R
* d1 o- h4 H4 P+ }# J
SPI发送或者发送+接收代码如下:
1 m4 b$ T4 A7 q2 U+ k" k- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)! y+ F+ V6 S5 U# Y
- {
* b$ {; \/ m n - uint8_t Rxdata;+ L6 Z7 J% D9 e4 |4 C6 z. i* \7 o; V
- HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000);
. [$ Y/ Z1 H) W! P- I6 k/ C/ u - return Rxdata;
+ x$ n5 v9 K6 l6 k. X8 T - }; b; U2 b6 }! T! ~0 y
/ w x' J F2 m v. b- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData)* \- i, P: M" [3 u$ G7 h
- {
7 x7 G3 F" e$ E: U' a0 M7 H - HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000);, ]9 @& K, H% w5 [, [1 u
- }
复制代码
8 x& r& K J; l! j# u1 ?! }/ p/ m, D7 x# G
8 B4 `3 R" {5 M: v
SPI随时需要调整数据位数为16位或者改回8位:
2 l% j" H7 W8 y& b0 n- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;$ H M$ t# z% P; _
- HAL_SPI_Init(&hspi1);
$ Y, M) W( ~( B: ]: ^9 [ c2 n - 9 y/ m2 n) y- Q3 W, l1 \8 [
- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;
1 {' Z4 R% B G - HAL_SPI_Init(&hspi1);
复制代码 $ W( c4 n& ?* A# y4 g
在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:; Q+ I/ x; k8 y' x: n5 K( b
- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()9 q6 F3 K% }" t6 H
- {; \6 y" j% K: ? s' w) M: P
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
) F" e/ X) t3 U- L3 v6 P+ n - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;. W. ]. k& R9 {1 F4 U
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;$ R, b3 X' K# E
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
`$ s. i, Q- F9 Y5 Y8 \& n - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;: |3 a& k; B% V. y0 N7 ]
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;) V4 U0 c7 u+ Q6 d3 j! P
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
" @: d/ K1 l9 a8 [ - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;$ o9 F( a: s, }6 g
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
7 L) b$ j J% K b2 s( u, m* w - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
# l5 K) a" D" J7 ]$ H- y# f - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
% n- S7 A" ~4 u& ]& c. ] - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;+ p" ~# z7 L. {. n! [* G
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;, F5 D- ?, t! z5 p
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;9 v% D2 T4 B; R
- " @7 I( `* _5 b, x+ j
- HAL_DMA_Init(&handle_GPDMA1_Channel1);# q/ A) [" i5 t; k4 x
-
: K7 u" G+ ~1 o - __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
4 u- \/ ~* G6 U* ~- a) H' t4 @ - " I6 j( B3 |* M" I! O5 b
- }% E5 q( C& @5 F, z
9 i: o; Q# t& o; D1 {- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal()
" {; j) y7 \1 q6 Z8 a - {
1 y6 h; Z; ~- A! B7 m" w$ \, | - handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;$ |( Q. o% I6 m
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
8 l- Q, k% b l - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;8 V7 n2 z w2 |3 S G
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
. i. l% J$ q7 ~ Y5 u - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
" h& t2 ^1 B5 @, v, h e9 N - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;" W" |8 I' X& D
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
2 m* d- O: S) D) S - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
0 R: b8 v1 I; V( r) S# H" V - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
1 _) t* S7 Q: q9 E9 Q1 i - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
& ?# Q* I$ {1 |, f" }9 i5 i - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
0 }# V3 L4 t: p' a - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;6 s0 c# }( ^ Z, e: J ?
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
# _4 x0 h9 D8 v" {# h3 n0 g5 n3 X - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
" B- s& ^, ~6 f - . `6 z! W: @ o! z p& ~) G+ s# o
- HAL_DMA_Init(&handle_GPDMA1_Channel1);
7 x* R5 Y. H# r3 u, S9 u - 7 c* ^# z# H; }4 a/ _
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);; [" [9 g! _$ t4 l2 A" q- }2 p# L
- % Q9 S; O) P4 L0 |! q
- }
复制代码
/ h4 ^$ m2 k) R4 C我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:7 h4 X% X2 Q1 Q' g3 A
- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码
, f3 X" V# \5 O& C+ j! JSPI不使用DMA的波形如图:) V( l8 l! C+ L6 X
! W* Q% m7 D% _9 C
这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。, Q. @& D, [" }& o. S
9 k1 x9 i+ v" g, R: y2 |* x
SPI使用DMA的波形如图:) r3 S) S) \; ?' f) c
- `$ ?9 Z4 I# x: _ b4 V2 `9 i& N
7 M, ^! Q% @% M' l1 h
使用SPI引脚驱动ST7789液晶彩屏:
, i. H* E9 X5 O+ A6 a
! n6 C4 T0 x- l, f8 Z7 v4 G4 P
2 {% D2 d: `& c) T) G* o8 R( {
' z- I5 B, N% h3 J
|