SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。
/ m! I" }& \: H8 f$ s& S! @& v+ k. H
在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:+ A `, I2 D8 t# F# B
* a! t' {4 S* r: O9 T+ }5 e
]# @* S( W$ I7 H* |& ]( {7 n
3 A; \& p( g5 S D5 FNormal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。
% [ D! s" d; U8 Q4 X+ H7 e4 g. r! p4 W8 F8 s3 [
SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:
5 @: d) t3 L* ?/ n- void SPI1_PA5_PA6_PA7_Init(void)4 J! G% d2 e* N4 b
- {
; G/ [1 { u6 h* O& } -
) |# V, ?; `& J p2 F4 z - __HAL_RCC_GPIOA_CLK_ENABLE();1 ]# P. d# `- |# f
- __HAL_RCC_SPI1_CLK_ENABLE();
3 j5 L3 R3 }- P! Q6 l - __HAL_RCC_GPDMA1_CLK_ENABLE();
0 J. e0 [/ _( B. L2 o0 ^( T -
* L7 W& T+ E- h- G/ W: P: [. ^ - RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};. Y5 D+ _& _; j3 D' U: @) I, R& y
- GPIO_InitTypeDef GPIO_InitStruct = {0};% ]* _/ z+ K# U
- SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};
* o, T, W. H$ a, U5 o$ F -
9 G/ L9 a8 E% L% J9 ] - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;9 p% F% n, Z, T( a1 G, u
- PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;
. n& H. a9 U& q% O5 f0 J! K* I - HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);! |8 m: u/ v' V/ G8 F% D, t* j
- 6 t! y$ y/ K; u/ k6 y( e7 i
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
6 |/ r7 M) z* S - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
' _8 ^' T5 r. B4 E u- C - GPIO_InitStruct.Pull = GPIO_NOPULL;
* d, |" `% b% E* |# U t - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
8 n* n. R d* ]- m - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
) ? D3 i" T$ u5 w$ w2 b/ d/ Y - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);5 l( Q& H$ s; k; r$ v& y( _
- # Q' [4 z& u# C5 C, k" t$ G
- hspi1.Instance = SPI1;% P2 }! I; s; A' i$ q( s
- hspi1.Init.Mode = SPI_MODE_MASTER;9 F1 A6 ]( S9 ~( p6 a8 c2 n' b
- hspi1.Init.Direction = SPI_DIRECTION_2LINES;' G5 X2 a6 ` P9 h A) o3 l( _
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
3 X( B. _0 e' l1 ~) P, [5 b - hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
( \' t1 H0 V9 c/ G9 h - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
& x/ H- x0 I9 I5 ~6 a/ J" H3 }0 z - hspi1.Init.NSS = SPI_NSS_SOFT;* S( E- R# Y) a8 `
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;% i1 E T( J1 Y& e7 b
- hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
3 s- o! k, Q8 U& Q. a - hspi1.Init.TIMode = SPI_TIMODE_DISABLE;% x9 c4 B1 m# ]4 R
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
* q" j: N8 M+ v" T - hspi1.Init.CRCPolynomial = 0x7;9 k- T+ [% U0 v6 S( ^( [/ L
- hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
6 m5 C! Z& K7 M; f8 @' f - hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;( f: b u+ e/ p D$ g2 i" V
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
5 C* V/ i0 h( L& P2 x$ X - hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;4 |5 ?1 p+ Q4 g4 b2 E' U
- hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;$ Z' C" E$ g) X/ |0 X# x3 G- X
- hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;( Y( b. M' U! X& ~! d
- hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
) m2 I) J' h' V; \ - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
3 y7 _/ R4 [) e" k - hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
+ S0 `: c1 P6 R' K! i( j: C% j* f - hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;5 d7 G" z; [% i6 u; x1 x
- HAL_SPI_Init(&hspi1);/ a4 ?& s& f7 H4 d6 R4 i
- __HAL_SPI_ENABLE(&hspi1);
7 w5 ]# g8 [+ Y) d' r( y" S - 3 m- W6 M t( j' K" G& Y
- HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);' e- W/ _$ F, h. ^+ g& B5 w
- HAL_NVIC_EnableIRQ(SPI1_IRQn);5 b8 ?: Y* i2 L1 v& o2 L, {5 d
-
W6 h1 p( M/ d" j* n7 Z0 } - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;$ u# E( Y2 k8 a8 m) \
- HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;
4 a9 d3 c1 l! G - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;
! q5 U. Z7 X9 U+ b g0 s: e! [) y - HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct);4 s$ n# v! a1 C6 l/ J& L- H
- * P' [; e" k' d8 y/ p/ y1 e6 M, U
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
: J$ f+ N, h7 G- m7 z8 u0 _ - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;# K L$ T+ e1 m3 `, q
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
# @0 {2 n1 |+ o' y9 w5 z - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
5 |& e, J( g- G3 Z% t$ V s( ? - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
7 U1 ^0 H' K3 M( T5 o7 h- ]$ R - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;% Q3 m& e2 y7 Z. s/ \8 ~9 y
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
o3 B; ?# r3 r8 G' P4 v - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
$ }" N7 z" P, h( R& }6 b" Z - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
* G5 y. K- Y+ E7 F# X2 s& f& r& J - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1; z3 G H* X0 g$ i1 H2 g, o4 M; C" g
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
3 Y5 T' D7 @$ U ~ - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
/ `9 _9 N" R6 g8 ^6 j* X, m - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;: @; k* W) g/ N( R8 O* C
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;( B# g9 N, n7 u+ j
- HAL_DMA_Init(&handle_GPDMA1_Channel1);
! {5 M4 w. Q# d4 g8 ~0 Z
- @3 ^# ^6 |2 p3 b5 ^+ o6 Q- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);* T: }% X9 J0 |( x: y' R
- 6 t& P4 i7 S \; f
- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
% d' L" H) M5 n7 {! R; L -
. r3 A k0 J- w8 H, X4 {, p* l6 ~1 x4 n - HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);' L" M/ ]; ~: X+ X J
- HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
+ U9 J" O, {% L
* O: V9 v9 P& W9 _- }
" K5 z0 @. L. e9 k
" r' T& S+ s7 k; x2 _- void GPDMA1_Channel1_IRQHandler(void). s- s0 v; o+ k+ ?6 g% ]
- {- f# A0 V9 b% y8 r$ F G4 g
- HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);
, Q8 k* J- P/ K' ~# Z - }
7 h) v% [$ [( F1 }+ i, A: q - : B0 R+ U# J* m9 }$ N3 D" h6 L
- void SPI1_IRQHandler(void)0 Q$ k* d9 z8 U8 N i" m) K
- {! }$ J& f# S0 o. Z k$ ]
- HAL_SPI_IRQHandler(&hspi1);
* o* ?! K$ S4 M, ] - }
复制代码
3 z) U. Z2 r) R. x) j4 c8 l# g; g- Z7 f
SPI发送或者发送+接收代码如下:
, b5 S2 ]" M7 Q! x5 {" F- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)3 ~! |/ u+ p3 T* i9 O( z' P
- {
- [- s+ @! N/ U' G - uint8_t Rxdata;
0 }5 `+ n! G& g1 Y - HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000);
& {5 z3 u1 `, s X - return Rxdata;
0 d' i& k; b$ x5 c# G/ R% n - }. m) U: M6 X) G$ E6 B
- ; k- d' m2 ~, S% u+ g
- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData)
4 F8 a3 }+ z3 G1 ~9 P! U0 a - {" I& d! z2 X A
- HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000);( Z7 B/ Y( A% x6 [6 \) f& Z. e8 I
- }
复制代码 4 o$ B7 K4 j& d8 u; d
1 h3 b; O0 i" n, e$ J3 a. t- n
" u f) V8 w( @8 U, l( ^; j
SPI随时需要调整数据位数为16位或者改回8位:% D6 S. C. A# B; n8 {
- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;
5 \: T$ Z' z+ w) v - HAL_SPI_Init(&hspi1);# G4 ]" T! ]* D/ m
& g W3 b/ e' u6 T; E- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;
0 I+ I1 W5 |- d6 ?" g3 G - HAL_SPI_Init(&hspi1);
复制代码 % Y6 {$ I h5 r. D1 u5 R" _& `' R
在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:! p; t3 q- e. }) W
- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()
4 ] F; ?+ Z9 j' s4 ~: n - {
5 ^( |3 X: F+ |0 @5 f - handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
. W* u, R7 [4 W. e8 Q2 t* { - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;& V% l0 N8 F8 d$ j- l. v
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
F! C+ e2 ^1 f) b! f% q - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;& F) f' x- }7 _! N1 r7 d0 r' |
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;* y$ `' @) I5 K' I% V: }6 j, z
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;2 o+ n, [8 G# [- k) Y- ~8 z
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;1 ?; @- x! m. f% h- o2 g; T$ V
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
/ n- Y% m0 c! n" M5 }, K/ M - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
6 K6 \ ~, |1 r0 ^9 K5 u# F - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
. C8 x3 \0 d1 _: |( R - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
6 Q; J; t% t. [$ _ - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;6 x5 Y( T. J2 Q& `* c2 T. A- i. \
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
1 H/ u" W; [6 H& Y+ K - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;) X% x( l/ b; e$ H" D1 l# s
-
& V$ N, Z% N/ n8 r/ v" W1 Z - HAL_DMA_Init(&handle_GPDMA1_Channel1);
$ T$ b$ D# }, v" X/ h; i& ] -
6 a$ ^% T9 c0 ^' y0 X6 @, G - __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);* a7 t1 b0 g$ S/ p4 j1 J: E
-
4 E Q' z! c+ q. j4 f9 g- N - }, y; n0 K5 z+ V/ P
( G& Z( }& w; {, E- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal()( P+ F. R! }" }) H! Q
- {* W% k6 X- {, O7 F" t+ @+ }0 v
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
( o t6 j9 |- k; M7 x( b - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;$ q |3 X7 O1 O, h- Y+ \
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
/ X- W A9 i& m. V: c* y - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
4 B1 n9 y& w, q3 E - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;' v% N# T9 H. k% ]! n
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;
5 b' E) j- f* I3 y0 N - handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
; j6 X; I# v. M+ y. } - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
9 }8 r E( o7 H& c4 S# F - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
1 C2 ^# \" p$ n+ b - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;0 e3 F! @8 n- c$ C8 U. A5 i2 l9 Y
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;' e- h) g4 Z* O% e) b7 O- |/ ~
- handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
, Z8 g$ m0 d) F - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;- O# Z1 G; N$ z$ t: j9 K! u6 g
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
& w! ?# G. H2 k3 Y N - ; j t9 e3 H+ K! }5 f
- HAL_DMA_Init(&handle_GPDMA1_Channel1);, z$ r7 ~ z/ [; b0 t W
- 9 ~1 Y6 ?4 k* T- b9 T2 i/ C
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);9 \( k1 v# ?7 ~
-
. W. G/ A0 @& K) B3 r - }
复制代码 |6 s* ?# Y5 d' K4 U8 r
我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:) C n- i: Y& Z$ Q6 i' h
- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码 2 w+ u7 V7 d- J4 ~) I
SPI不使用DMA的波形如图:( j4 i; @+ Z0 q2 Z5 M8 ]
% Y7 V: Q9 o& @% C+ x8 Y
这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。
, W& n- @) k0 o! _0 h0 M6 J {% ^+ m4 q) q0 {; x8 \
SPI使用DMA的波形如图:
6 c- A- `9 U3 m$ P
- I. c: U# J2 C) `4 A( s( x5 `8 \( L c5 k3 l3 U
使用SPI引脚驱动ST7789液晶彩屏:7 d. x: M" V* C5 L1 h1 z
. L4 ^! y$ C/ ?( @; w2 U9 V
4 Y( @6 ^) b" J
6 X; k: @ c+ X7 S0 _+ n
|