SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。: l: B" [( C+ M; m9 t; S" w
) H( W' c/ z2 e 在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:5 g2 I2 W9 [4 r& {
6 `7 p) c {0 q; v
; t9 Z- j2 E/ [: H: V8 H8 z: t
4 r+ K; z" A+ N3 _& j; J4 YNormal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。
" U, ^& r" F9 A* J/ a2 X( a+ T: i N) Y7 e7 H" @$ r7 ^
SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:
; _5 i7 U5 i8 y! v8 A5 i# m1 F- void SPI1_PA5_PA6_PA7_Init(void)- h2 \) _' ?( N& q- n7 ^8 P8 a$ W
- {
# F9 r1 J, v" s- O4 K" I6 o -
$ W+ C2 n# t8 _' m# U" u n& \5 ?0 H - __HAL_RCC_GPIOA_CLK_ENABLE();
0 J# D, f& i4 U! ~' n6 h, g - __HAL_RCC_SPI1_CLK_ENABLE();
) T& o5 Q1 m, ^+ N - __HAL_RCC_GPDMA1_CLK_ENABLE();# r2 t/ ?& v. T% F9 ~/ \' N( I
- ' C [$ Z- c, q
- RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};" f+ _* A5 U, K9 C5 f2 {
- GPIO_InitTypeDef GPIO_InitStruct = {0};; c2 y+ W1 u# z) }% v. d) k
- SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};
' x6 R. \% g. @! M( G$ n: J: a - - q6 Z, c+ \9 |
- PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;
# Y4 g+ I1 X0 Z5 \7 l - PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;
s ~8 h( G6 t$ `5 S9 ^/ l t7 O9 D - HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);3 X/ E; b+ f7 F1 M8 X
- + |& ?+ X$ t& v2 J9 L& i1 X
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;' s% V4 X. T1 E; W& R
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
' Y* e9 s1 e2 c% M - GPIO_InitStruct.Pull = GPIO_NOPULL;
1 W0 i: |2 r* l/ u$ o- b - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
6 k; k$ L: f2 S( V' k" p7 g - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;! F4 o: B6 _9 }* u6 X
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
; n5 w6 l: z4 c
, j( ~0 a. }3 u* l5 e: j2 I- hspi1.Instance = SPI1;
, P0 M7 g' K8 l, f6 q- ? - hspi1.Init.Mode = SPI_MODE_MASTER;
7 }1 U' J, C/ s - hspi1.Init.Direction = SPI_DIRECTION_2LINES;6 A' F7 b* G3 }" p& Q' @
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
h5 k; Z) T! [3 _2 O+ e. h* h/ y - hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
3 I3 b$ a% Z! @* V- a - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;. X$ i4 P8 E; l' R
- hspi1.Init.NSS = SPI_NSS_SOFT;7 N* y1 _ _2 c; P& \' K
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
) w2 c- Y2 s+ T Y I8 | - hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
7 a- v5 Y3 N! B0 b9 G+ G7 v - hspi1.Init.TIMode = SPI_TIMODE_DISABLE;" r6 q8 [6 t, l6 W$ [3 @
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- L9 y; L# _3 j4 D; ^* w& O+ F' { - hspi1.Init.CRCPolynomial = 0x7;9 w0 m3 R2 ?9 U3 S! L5 Q
- hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;3 Y$ u( _" _) S6 {2 n
- hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;+ Q5 Z. ^7 q3 o+ q& H& o
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
7 e& K8 P/ M: f9 u2 N - hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;1 y( w) y! [ T+ ] u! H4 y9 N9 h
- hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
; _3 n6 @) w* E5 h/ O- H - hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;, _% _! d `" m& ?
- hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
2 A8 _3 r5 n* o. i P3 {% m - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;: K2 w" w5 _ t7 y- v) r
- hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;& [# J$ g8 e- b( X& d
- hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;6 J2 p& p4 x( I3 w8 ~' t
- HAL_SPI_Init(&hspi1);; J% _8 I# Q h2 p. D, E& [
- __HAL_SPI_ENABLE(&hspi1);/ G# d+ ?( j( Q O3 z* f- {$ ]& G* J
- $ j) V1 e, [6 I9 F, c
- HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);: D0 P9 S# ?/ k9 y7 v a
- HAL_NVIC_EnableIRQ(SPI1_IRQn);
' a' l E" J: @+ \9 v -
; k1 B2 Z5 l0 J6 r' [# z - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;$ E- W( ~9 {7 E w, e+ n2 R
- HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;# L2 X$ y: ~$ o1 j) V7 `
- HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;, T5 b8 ]( `0 e+ U2 U/ Z8 B7 K
- HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct); U" \/ l7 J: }2 e
- , H8 N+ d9 L5 _# s- q2 A" F& G8 ^
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;9 J$ w( I* @7 _ O/ ^ i
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
t: X7 l8 f2 j& L - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;1 F8 W( Y5 u- ]
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
% y! x! }, k4 ~9 ~4 d9 { - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
1 ~; L+ w- a3 H) L6 q" g" }1 ~ - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;
1 W- v" k% _& X- z( c - handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
6 N; \8 T" I& U0 {3 `2 E - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;% h; ?0 e+ H- f
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
* \4 z5 M( [# Z' R& V6 c7 J - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;- D! q2 j% g. ]% x) y# m
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
0 Y a8 m- {+ m9 R - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
; W, T' d- T8 A& j! F! g* l( x - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
3 K* V$ B" d5 r8 H, N# H V - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
! }" p3 s, r! z; A ~ - HAL_DMA_Init(&handle_GPDMA1_Channel1);
" F* k9 j v# a
: [+ o u1 T. l: V- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);% w a6 B# D: g* n2 ~: E. j! c
- + n9 a, l& ?0 k) a. S
- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
6 v, M/ B1 F" u* j" z: x -
, c8 a* m: v9 I/ p+ }5 x; O0 Y - HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);) T4 B+ L- G2 ~ l; I3 Y" B
- HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
# B( W4 |! `3 y M, N2 K
& ?2 Z* m0 t! Q& e( x- }
& q2 _# a& m+ ~. D, Z" ?
W* j+ @- o, M0 \- void GPDMA1_Channel1_IRQHandler(void)* `* {" j/ ^- w0 D
- {$ y1 w x" X$ A" }( R9 P0 w8 {
- HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);! u0 m/ v) ~, s8 I& f- D
- }
8 J5 i5 o& Z. }3 m7 q/ ^" c# D - " k2 b) t4 w' E% j3 n
- void SPI1_IRQHandler(void)! f* O! H7 L- L" O7 I6 t
- {
; k+ Q0 s: D; M3 _" C" C/ W - HAL_SPI_IRQHandler(&hspi1);6 K2 O) T$ n: D3 E$ ~
- }
复制代码 6 }& g9 ^6 y. R! y
; K3 A' h2 |" Q# TSPI发送或者发送+接收代码如下:
) x0 _$ y! ^: V/ p. @) V/ z ?- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)3 m, z$ i% I% N; P
- {$ B* b q/ Y! ^) u
- uint8_t Rxdata;. u: s8 ^9 }1 r: ?
- HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000);
) M, n$ x! \, I# {0 p* i$ ^* Z. b: T - return Rxdata;9 ^/ A5 V! d6 K$ I' B* X5 v1 w: c& U
- }! W S& b" G" z
. p' I% V4 s: {" z4 v& a- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData), M1 d) p# r+ e8 L% c2 t
- {+ e5 T8 H/ N) H/ [$ \4 S6 p
- HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000);4 a5 Y0 P0 H- W1 ]0 N+ S
- }
复制代码
- a) d3 r$ T1 A) t# y" y6 r/ _# k3 D! H! D+ p( _4 s* f/ G8 P
3 C7 y) u: M7 T4 ?* Z! b9 K% O9 Y
SPI随时需要调整数据位数为16位或者改回8位:; |: @* {0 S5 P& V3 F
- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;
' t7 V+ j' B9 \# q6 C% S% ]4 [$ V - HAL_SPI_Init(&hspi1);0 u# S4 H, ]2 ?7 P- g: P& v
- 9 Y( V% k6 Z7 O5 D! [7 S0 f4 {
- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;
3 o: L; d) x0 P- B - HAL_SPI_Init(&hspi1);
复制代码
! Y! L4 V8 @, H; V) k在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:! S; F3 V& ~) }1 u0 p# y; P
- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()
( g, j' x0 P; r) ~ - {
! s8 u4 o5 `! f - handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
0 S; M+ V# P3 c1 X: A l# @1 z8 E - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;9 S! s6 i Z, s) l2 p+ r& a) I) r, d
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
7 L% G2 ]7 F# z* y. ^& { - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;& N+ {) T/ J$ [/ L5 o) b! k1 g- c
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;- b! p2 u+ u; b/ K: i; e7 d
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;! ^6 @2 a( ` i' Y! r) v
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
- X) L8 l9 k7 q - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
6 i# e; O: [6 q1 _ - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;4 \$ p& e2 z* J
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
- z/ `5 \; C/ [; _0 o2 { - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
% I$ L% g- j3 ]5 [! t+ g - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
7 R: I1 g* d5 h7 R - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
$ X% C8 a4 }7 H7 v1 r5 O' } - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
/ `* U5 \" ^1 B7 E" B) e4 C - " r2 v' w2 s/ O1 G
- HAL_DMA_Init(&handle_GPDMA1_Channel1);
# `- p% y8 I. w* E- B - # W3 q6 Z2 E- |# R' t7 x) {; J
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
4 W# i' J( `* M e4 Q6 v6 D -
: Q; J" l' r" D+ u - }2 B, g9 d4 x8 Q. w
. \+ ~+ a) V7 k. F- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal()" f1 Q2 D2 y& X0 a; W1 @: O
- {* S0 |4 H/ E3 U
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
2 q% q. ]. W4 f n& I6 H - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
% G. z. c+ U$ ^( ?+ j/ ^2 X - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
+ F9 _7 u4 q7 X9 O - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
! w6 J( \/ G9 _3 P - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;. S/ P$ k' B7 l( ]2 D1 u$ v
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;8 y1 h- R# K# ]: M/ X
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
' q0 [5 X1 y% @/ }6 v - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;2 n4 `$ @/ G; \
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;& Y: \2 }8 u0 o* j
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
4 D4 P% Z, v5 Z& J8 \& z7 p - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;) x/ h" M) o; p; a0 @) w
- handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;! |+ y& ~6 F$ i& \" q" w
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
, ]6 ]. |* y5 `. W- u. I* h - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
! T7 H/ h( ]2 W P1 s -
8 I) {/ ^/ r/ |- e( g - HAL_DMA_Init(&handle_GPDMA1_Channel1);
8 i" R2 e3 g8 x1 ?* x - 9 O6 A3 t: n8 h" D+ o8 U4 f
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
0 E) T5 E& l+ v - : }3 R; f/ e+ C3 e( X, {
- }
复制代码 6 }7 d6 M! R7 L7 c" N
我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:
+ s- F4 |& I& t5 W% N' u# C- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码 0 V( R* f( i! ]7 {
SPI不使用DMA的波形如图:
% e& N" p4 G" U( k8 G
& L+ [' g, Y- e
这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。
* D& |/ R: v0 r. O8 Z
# ` e( I% J! A( a9 H0 D+ WSPI使用DMA的波形如图:5 @9 c: G G$ N2 @ f: `; W! Z
/ ^) S+ | U" _7 z6 ^5 I% h4 O5 k: O: b: V- W5 s+ G: B6 ~! J, W& X
使用SPI引脚驱动ST7789液晶彩屏:6 P O$ [7 L6 G# a6 [+ r
8 i" d3 P0 s2 f {2 J0 ~( o
& p' W5 g" ~4 S9 S
3 j: R9 v- F' Q- A! ^ |