SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。
- q( D! o5 H7 p1 j' @
6 o, Y, Z/ w, u! h( u 在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:
1 B2 M$ B: p5 N: q, p
4 K& r# f+ A4 o& U7 Y3 {$ T# Y
+ r4 p" o7 w- z
q: Z9 w0 {0 I8 v6 TNormal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。
9 D: O$ |6 e* P% ?, q# @% Y+ q
( \: g8 ^% U# \. G+ \9 u1 L( v! K7 ]SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:
5 P" i1 T4 p! v6 G6 h. N% ]- void SPI1_PA5_PA6_PA7_Init(void)( L9 Q% A7 U1 {8 e$ A
- {* M; f1 f8 O6 b
- ; p$ e9 i6 Q" V9 D+ N. T
- __HAL_RCC_GPIOA_CLK_ENABLE();
1 G% c; q3 }4 u$ v$ d - __HAL_RCC_SPI1_CLK_ENABLE();! c5 N: a! r2 J( S- k
- __HAL_RCC_GPDMA1_CLK_ENABLE();
9 w: \1 g8 w3 {2 q% i+ S -
% q% h, u& L( t4 K9 I# p - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};/ D& \" V/ E$ x+ [
- GPIO_InitTypeDef GPIO_InitStruct = {0};
) k4 x& F' k: D8 h - SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};, l4 m$ C9 V! F
- # m, x1 J2 _! ?: w
- PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;. p: l @3 n" N p) b4 l
- PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;3 X: i; j# E! w
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
' U0 U% K: H0 Q. l - % K0 S; F& I( e4 q0 Y2 |8 ]8 y( ?
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;* w6 I& r+ X2 g$ ~; L
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
4 y. y/ }" k* q; f# n8 C* W - GPIO_InitStruct.Pull = GPIO_NOPULL;- @ |' m: X& R a7 J; r
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
+ k2 U( A' @! G - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;7 L" d q3 U- u M
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);5 P% R& D3 [& L8 v" C- N
U7 r: v3 O. f: ?, ?; L( w l3 K- hspi1.Instance = SPI1;
7 T5 M3 i5 A; h8 `6 D# a y - hspi1.Init.Mode = SPI_MODE_MASTER;* G' z) n! V) S! y% G5 O
- hspi1.Init.Direction = SPI_DIRECTION_2LINES;
' C8 i( v* r& i- x# F( N - hspi1.Init.DataSize = SPI_DATASIZE_8BIT;1 V$ B# Y9 C p$ T k) z' Q. c
- hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
7 f; z0 f3 l. t6 w - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
6 B! ~) F) G. F$ c8 F - hspi1.Init.NSS = SPI_NSS_SOFT;9 j. I' g3 n* [4 b
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;" y2 D _" E, }+ l, q9 p, X+ M
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
+ I5 }& ~: `9 N8 B t - hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
& {% ~, Q' N+ y! {9 E7 F - hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
! E( M4 z" n/ p- H { - hspi1.Init.CRCPolynomial = 0x7;% I" a, z1 A: Q# I" d
- hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
7 ?% K5 |5 R' W: N% K! r - hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;7 v0 G, }8 L: [0 O2 n8 C
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
- [/ n, z- B* w8 f W - hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
|5 g: h# U/ D* J0 L! |' w - hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
1 c+ P6 o ]" s) w - hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;3 j- @& r% [ {6 j" B
- hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
9 m n3 I& g+ U# s0 X - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
4 x+ [2 c) T2 } - hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;/ U7 T) H) t7 a; ^% P& }# R
- hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;! q! o( M5 C' d' ^% a( k! e8 P
- HAL_SPI_Init(&hspi1);
" o8 U! p: b6 n1 h$ Q3 G - __HAL_SPI_ENABLE(&hspi1);
' O5 f5 @; z8 C p6 {) d: I -
, r+ \4 b; |+ |2 Q. d. ~" Z* @ - HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
' A; [% U m' X8 R5 d - HAL_NVIC_EnableIRQ(SPI1_IRQn);! Z+ Z0 ~5 \* n2 _$ q- P5 Y: F
-
* P1 h. o7 N: E - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;
& C( c+ w2 P5 S4 T - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;
* y! F2 i) ^. M - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;
2 z: _3 M0 D4 ? - HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct);
& {/ y' N4 p; T. B, K* H6 u+ l, n - ! T8 j, A b- b# E1 G. e
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;! e$ m! |( ^* B( n q- [8 S2 |, @* h6 F
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;, o3 `% N! o( S+ x& M1 i# O9 X
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;; y/ V1 ?5 S* F# h$ ~
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;# M' U1 n+ B' Y+ E
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;' G+ S1 S% f8 q- j
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;9 Z/ A' W0 ?1 Q" m; ]4 a
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;+ @: ~/ I7 B) L, c8 G
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;+ t& a! _+ J2 L# E7 @# c% `
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;7 y m% [/ A1 L) Z# C
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;' B% w( V! X: J
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
, h: W" J8 l6 x/ C( q0 ~ - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
$ `, ~5 q' o9 W+ S8 s4 a5 M - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
/ |7 j/ H" i& p - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
4 {5 l0 V. h2 }7 i1 G$ Q) N - HAL_DMA_Init(&handle_GPDMA1_Channel1);. u- S% I/ R P+ o# v
- , o# k8 q3 U4 T. A0 J- g7 G
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
6 @1 | t' J* U M+ h$ ]6 ^1 E
$ w( I, R: P8 V- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
4 c9 i: P' b# v5 C" j( S; J - 9 k; k; F( Y1 [9 u8 l" s, d
- HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);
6 O4 o! L1 _& b$ {9 {& H1 j" V - HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
8 i6 d2 g: _4 z9 Y; _
9 T5 s% ^2 y1 H) N {6 r; k. W% N- }
& _6 K+ Y5 Z8 ?7 E( b - 1 Z5 A- r4 f5 k. R9 e
- void GPDMA1_Channel1_IRQHandler(void)9 M8 ~6 q+ y% y% ?. q
- {/ Q3 I* k- D( Q% i
- HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);* F; F7 L) Y) s. I
- }
* ~ B# j* }9 C3 B B - ! l3 l5 ]5 K5 C5 p! x" K- Y
- void SPI1_IRQHandler(void)3 U- u& w' |4 _. E! A
- { L$ y* P) e; p/ q J4 ^4 u! r6 N
- HAL_SPI_IRQHandler(&hspi1);
+ b3 a: [+ m2 Y0 J3 v - }
复制代码
( B3 t1 E' }9 T4 k
- D+ h! }' c( y; aSPI发送或者发送+接收代码如下:' L' g: j+ {5 ]! B
- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)
8 I1 M. R' x0 h - {
* Y: Q) h% N* K0 y - uint8_t Rxdata;' I2 V& Q4 Q- s7 s" D: F& W' `: Q
- HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000);
4 k" \ P9 a( D- j* B - return Rxdata;
9 W+ x6 v! s+ O# g/ \2 V' r - }3 Y* [* ]# U; ^$ z8 d
- 3 o5 W2 N3 @0 n/ P2 y
- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData)
0 a+ S* Z4 @' m - {
- h( I; }7 ~& I- G* \. V! H( d* N - HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000); c3 a: \/ J7 e- [+ G. z1 R& Y
- }
复制代码
4 t- }- p7 _# R ~; n. }: T) a% h4 ]4 ^. m1 `3 R+ D# @* R. }! M
6 Z) M6 L& |3 [
SPI随时需要调整数据位数为16位或者改回8位:. R, }3 Q: @- v* [; ~& I3 ^. j5 V" A
- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;1 B0 ]! h( K+ ~8 K- O
- HAL_SPI_Init(&hspi1);
( T9 M# @! y0 L! _& q G2 a8 N - 2 |- @; ^ r- }) v V& z' J
- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;
2 R/ l$ d ?4 y: o$ ]0 M5 i6 A: B2 z - HAL_SPI_Init(&hspi1);
复制代码 : z' h5 O# G8 d3 g; l$ ]
在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:
2 h* f$ g( q+ Y% J- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()
7 T5 U k8 k: R( ~- w* ]& [ - {' ^9 J9 q) i. ~; V; b( Z0 C9 r% t. b
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;$ ^( ?3 ^. ^( u$ U. ]
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;2 Y$ n! L2 k& C6 s$ b# K
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
1 G* }/ C0 g4 V! y - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
. B0 \$ | O* { - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;( O$ n! A& z6 F
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;
, \+ o8 Y6 Z0 s6 K& i - handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;1 F" S# \0 K) f4 P
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
5 R2 b" h8 Y( ^ - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
% ^% U- _* m8 J8 B! z1 j - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;* Q0 D% t9 {+ V$ n
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
. ~, L9 t% y/ a# |: M' n: ]: N - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;1 D% |$ h0 v7 W5 I$ x' K9 G/ j
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
. b3 d# @$ a0 v& w5 E - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
3 A; G8 `4 [2 B4 W' w3 H5 ?, ` - % L ?" E$ }$ ~
- HAL_DMA_Init(&handle_GPDMA1_Channel1);
' m' m- l1 B& e( j9 E, u8 ?2 q3 a - 3 ~# B$ O0 ^1 Z; Y
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);; c+ ]+ L2 h2 t! A! S
- 9 m2 a' |( L2 E" i
- }
" T2 a0 c e! W5 U1 f - ) q" g1 s- F/ q4 _% E
- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal()
4 `0 l1 N, e* M5 G" g - {; ^0 ?( o/ S4 w% w+ A
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
1 ^7 f9 Z& w: v2 C; U - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
+ E! H6 }# \( e* w - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;3 r/ a4 I2 Z4 e, X3 Q! |
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;1 }2 c4 a# U" w6 S% w
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
: B1 L) U" |' C - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;5 A4 `, W* \ \* d ?3 h& L
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
; D+ e% Y2 R% |/ c - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;2 C: i+ x0 J8 e" ~6 g1 M) A8 C# O3 {6 F
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;3 R: `/ z2 A0 F$ L7 S0 l
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
; t) H/ p. A* x' E6 w - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
0 C1 q5 O- N: M! ? - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
+ o. I& }3 L! h4 ~ - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
9 i. [% l' L6 e# O" b l% ] - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;6 F9 ]7 I$ h" ^
-
( C; q% r3 Q) l - HAL_DMA_Init(&handle_GPDMA1_Channel1);7 n! t3 g9 L* n4 O% R
-
@: y C7 R9 `% S @" }. H7 z1 [ - __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
. \! \0 b/ t; z0 ~- [. G -
- B' t4 b2 A0 T2 \ - }
复制代码 . C- o! d) c* E6 q i9 l
我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:
3 b/ M% ?6 y/ C0 `1 d& n5 ^+ N- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码
* y$ A$ ?8 F1 i' o) S' S6 ZSPI不使用DMA的波形如图:* U- m9 T$ q. y" z1 T4 `2 y
5 _- t7 V, b) @4 W3 n7 ~
这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。
% G4 d1 C+ Q0 F9 ~
+ z# m0 k- {4 X3 u2 d3 y: VSPI使用DMA的波形如图:
9 c# ^. Q% \3 L" z# C
2 F! K1 \% ~% @
9 O9 W- x& r7 B+ U' o( L# |使用SPI引脚驱动ST7789液晶彩屏:; r S# a9 q j- E2 r$ |0 ^
8 x" p b* `! t9 I" S6 Q( p( r% k
- e: |2 z( f) g8 Z6 N& z) g- Z
0 G7 j* C. o: }9 R& ]9 K/ J8 ] |