SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。
, Q% ^3 o- H, V# l9 ?" ]: I2 K$ t; w$ ?
在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:
5 B7 M& B' l. q' L; Y* }$ @9 _2 ]
, M7 z$ r& ` W ^
h) {0 H; E3 y7 u* C; f6 C. c0 r9 M* U
* A7 N) ~! p* P. ]( F( W9 ]. {
Normal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。
8 e, i" {( a1 V7 C6 }2 j; M
/ D1 m5 m& \% D7 E( D$ ?SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:" ~1 A, w; H7 ]
- void SPI1_PA5_PA6_PA7_Init(void)
0 Y2 W- E1 H* v5 d+ N - {" ? C' y/ T* C
-
* {5 \" O+ w# y$ o& L* n - __HAL_RCC_GPIOA_CLK_ENABLE();
' [" K7 `0 k8 y. c* @4 f! Z6 P - __HAL_RCC_SPI1_CLK_ENABLE();; j# s* e) z( b- S7 E: V
- __HAL_RCC_GPDMA1_CLK_ENABLE();% U) S1 {" ?* b1 [
- 4 ]) X$ I9 B, Z8 M) g' r% O
- RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
+ J* o" x0 i% r - GPIO_InitTypeDef GPIO_InitStruct = {0};5 K$ B6 D F9 e8 h+ A5 C
- SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};
" X. }; H/ u. o! N4 O. F -
7 y9 U. x. T6 } a/ e1 Z- X - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;* C, A; N9 I. ?! H, F, R$ a
- PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;. K8 l. X q* [1 Y
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
Y/ a+ o( J1 N0 a) h - , y h7 w- |0 f/ N/ ?+ b
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;. v, k" S0 t; g/ \1 d
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
6 e( c. K' ^0 Q. u: @7 f1 K/ q - GPIO_InitStruct.Pull = GPIO_NOPULL;
2 }5 e# ^5 P7 m: d. b# w* L- X - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;6 ~/ V9 w( C+ T4 P4 @
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;+ @. @, s' f& f" \3 ~
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
! M9 X6 X. a! L
% v( T7 A6 _9 C2 ^- hspi1.Instance = SPI1;
/ H+ i: u. O2 @) p7 ? c8 U - hspi1.Init.Mode = SPI_MODE_MASTER;
% H, Q7 J! C, d% I - hspi1.Init.Direction = SPI_DIRECTION_2LINES;! S7 n/ k' {" X0 F5 Z5 R/ S
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
- `6 N0 B& ?/ d) ^7 F9 Y - hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;, u" u, I4 a4 S. D
- hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
$ W8 H3 P, v2 `: c5 C - hspi1.Init.NSS = SPI_NSS_SOFT;( q; _, x6 S) R) J: a
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
! h+ O; L: i2 U5 G' D J" H. Y - hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
* p- p r6 g, o - hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
B7 ]! x) N8 K5 m - hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
: y2 [+ c2 }7 J; \3 D" [1 K - hspi1.Init.CRCPolynomial = 0x7;
9 i+ r$ s/ q: U% H: l7 N H - hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
. ` `0 n5 |1 T R7 C( C - hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;+ |" Y7 C/ Z# T: z( p5 ^7 P3 R
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;! q. b" B6 _+ q2 [ B6 E6 @
- hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
6 [! Y a3 b2 C7 A: ~ - hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
E& k |9 z' y+ E. B: t( L - hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;& j! ^0 F5 K; a* S/ `
- hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
3 S, e% {& B# W - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
: }; D0 k2 a5 e% S% ` - hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
# G4 }' ~6 n& u& H7 n/ X! I- V1 u - hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
2 j& G0 y4 _- R. ?, h7 I5 J) G - HAL_SPI_Init(&hspi1);
: y. D0 E: O/ O5 M( G - __HAL_SPI_ENABLE(&hspi1);/ X: r0 w* l7 C0 _7 @' N: U
- & J; X3 O+ f+ V& y+ v4 M
- HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);& N) B m- r" I. x; D8 Z
- HAL_NVIC_EnableIRQ(SPI1_IRQn);5 c6 D) T, F$ l; @( x4 `0 \4 J
-
2 T% j q" g c' e$ C& v - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;
0 T, p7 R8 |. p/ E7 a- T - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;
; r9 r# u- ?: V- A! f - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;) v0 t9 O" B( x+ B
- HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct);
5 l, Y4 B" W; u: C! i
% Q$ D5 \- Y' K+ f, E. T6 b- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;7 w5 _1 ^, R6 J
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
$ j ] [& [% j& @! S" F9 a - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;$ {6 }. e) q( o
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;7 _' s% P: t, s( p# C/ `
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;
) h- Z3 C. E! u8 S/ f) v - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;- e7 C6 l" @; Y- w4 ?9 R
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;( S4 |0 m, d: c$ z
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;( ~+ g9 f+ F8 n0 V& V
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;' M% Q; P* H' c7 @6 a0 \% l
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
8 |# {- x6 j4 `: U6 N q - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
" A% S( l$ a; f, ]* g3 T8 T - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;% G) Z6 V, A2 F* R$ d% ]% o4 w
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
2 {" y1 b2 N' u6 ^9 x2 d9 C# h9 } - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;; g* @- T+ E: I* V6 a" X0 u9 i3 ?
- HAL_DMA_Init(&handle_GPDMA1_Channel1);* Y+ n2 j- C2 V) I* l$ l% @
; ]6 R& V( I# M; V- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);; ]8 |2 n8 _1 m, z8 E& n
- ( e7 @0 Q/ {5 b2 M5 E- l5 c" |2 [
- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
5 Y* K/ s4 Y& ?7 j/ t -
5 L* O8 b- V% e% d9 `( c. _ - HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);
& W3 k+ ^6 ~# p( x# I. {- n; ^ - HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
0 t0 N0 I- ^5 D; H1 B/ ~- b - . v; a/ o/ L" u9 F( @; L, t) j
- }! w' E% N1 v& \- M/ t. ?
- 3 J6 U! z9 ? W9 A; l
- void GPDMA1_Channel1_IRQHandler(void)
, Y0 i& w4 ]+ U' Y! N5 J - {
! w7 V1 n7 _ Y: s \ - HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);
2 M3 L- G) L; @# \. o0 g - }
6 ]) F* K7 U- x6 m g
7 v0 I" q+ m5 J- void SPI1_IRQHandler(void)
% q& m8 U0 K! H5 V2 X - {
, H! s9 j1 f D - HAL_SPI_IRQHandler(&hspi1);
1 M- s4 z5 F- I" c) J - }
复制代码 - ^3 c1 ~7 m$ o3 x; R
: x/ g+ g4 }( I! ?& p
SPI发送或者发送+接收代码如下:
9 e' A( C" \1 a' o; Y3 {- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)! Z+ @( w3 o: | q7 ^
- {5 j7 i& ]" S5 Z- y) T
- uint8_t Rxdata;, ?/ B0 q: `$ y* ?/ U% n
- HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000); , h: g( o, Z: p5 Z3 q
- return Rxdata;
& R. X8 E- p: M! j9 ? - }
; @, w. c5 x, x* c. {3 K - + f3 _! r, V8 c9 s4 P/ A4 W3 e
- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData)8 V L+ k; s8 p/ H6 B/ M# S
- {, e; A# {/ R, m
- HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000);
/ L* _. x5 H5 c0 m/ |+ d& x% N - }
复制代码
, M9 s+ d( H) Q% S4 x1 Y
6 a6 I. _$ V0 q) o6 G g3 V' g) [ Y6 R1 M' D- c# _4 w; a
SPI随时需要调整数据位数为16位或者改回8位:
5 a1 ]/ e& [$ T2 N [- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;
# c3 C; G# E8 j* `+ t7 h8 h - HAL_SPI_Init(&hspi1); i; U! W+ O" a) o, W
4 L+ F4 J0 a. Z- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;( _# N- K4 B! ~; T* T% U& [6 j
- HAL_SPI_Init(&hspi1);
复制代码 ' O `; J8 V$ Y5 R
在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:
2 ~! }( g- j0 L2 L7 s- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()
& a% o: S$ I3 G2 Y - {6 P$ V7 T% R; p" E7 \9 l6 j
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
" w- q* _2 j4 a+ Y - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;4 Q' ]# {# s& u; ^% _' Q
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
4 H% Q: m! ]$ l6 j* G1 q) v: y - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
7 k. A2 T! h: b" @ - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;
9 M/ T6 ?6 ^# `- r - handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;. _' _) L! K6 g2 z- X
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;5 G+ g f- E7 ~! z# I6 O; T* T
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;" A# c) M' J0 x: I( _2 Y; O8 }
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
/ _. F9 ]8 F! c1 ^+ G8 z4 n0 g - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;. C3 g% d# L* t2 ?0 V# q
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;0 E; _, K( c0 q e" L5 @% n
- handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
6 }; q# w ]( h! Q! p4 ^ - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;& A: u* }: D' v0 [# w
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
, g; v( @7 e$ t$ ~% T/ f2 N -
( F0 o/ V# K! o/ W8 P5 F - HAL_DMA_Init(&handle_GPDMA1_Channel1);
* K x: o, Z4 g* r0 o7 X$ n5 G& e0 u - ( W7 m0 M9 L4 N: t* U' Z' s, ^- T
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
) f3 Y' s J( A% J% ~ -
- A! b5 z& m4 B' D/ B$ |4 j2 _ - }
* z# E& N0 V3 Z! v+ t3 ~# q - " ~- i+ b8 s8 P( Y# c
- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal() h3 ^2 n5 I; }3 p$ |8 ^
- {. x& `& _5 u( p( R
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
2 V) F/ ?% r. K. L/ `: u - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;$ U. U+ ~/ F, \( q: r# `3 W" } l
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
' R3 \0 R! S0 q$ a7 w - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
6 [: J' ^5 q& ] T$ F - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;$ V+ A' `7 x0 u1 y0 h
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;* f, `; z3 ^0 _; W% J/ C4 @
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
" u& ~, \; g2 M! U% o - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;& @- \& s% z+ e3 }+ d( |0 {
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
) w6 F% ?5 g1 v$ | - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
6 x O J4 E; h; }. ^9 B - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
4 t* v% ~! B6 ?' g. W8 b - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;/ _; f/ D; Y6 V: l/ f
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
2 j( I. r( {2 g' T. j. s4 F - handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
& w9 ` ?; c" ^ -
# g/ Q8 v# k/ ?& e2 ? - HAL_DMA_Init(&handle_GPDMA1_Channel1);
+ X. d: \3 p0 H4 w/ ? |0 F - & U4 ~7 v# U, _/ ^+ Y
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
Y( a1 e8 H! F; y' @ - 9 c& s4 e' i4 S. ]- c7 q
- }
复制代码 & Q4 [4 l; ]8 R" t& }3 u2 Y' i; v
我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:
; X& s) c# e5 k6 c# D- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码 , l0 f# ?. p" x4 b1 {
SPI不使用DMA的波形如图:
, f# ^! ?7 `6 x( A
Z, M! c, K$ H4 Y. e$ U这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。
% g8 q- p! z, ^8 J# z- a
7 M+ O1 y2 B4 Q# Y' KSPI使用DMA的波形如图:/ z9 l& Z- i& y: B' A$ K8 _
; R4 Y2 ? T; u& L4 l6 V1 t
& _# \( R5 s( r2 W4 k0 ]使用SPI引脚驱动ST7789液晶彩屏: \& g0 w" Q b9 v6 \
1 `6 s& {, d+ ]" A6 k+ e( W! F" m
' m- q" q% M3 E
; q! R8 u& P' W |