SPIDMA发送/接收是非常常见的MCU应用,用于与高速SPI器件通信并提高效率,这个帖子是评测STM32U385RG的SPI1接口,并启用SPI1的DMA发送功能。
: u8 z* ~& i9 G- j/ b( ?; [- D) w5 V
在上一个帖子中提到,STM32U3的DMA外设名为GPDMA,在ST新出的系列中,任意支持DMA的外设都可以绑定到DMA的任意通道/流/节点上,不像F103和F4系列那样规定通道号,来看看SPI1DMA的配置:: j: A( l, p# f9 C+ X
* Y' a t1 | I9 D Y F2 o# h* E
" C0 G3 ]& ]) [5 I
C0 |8 P8 o; F) `Normal模式表示每次调用DMA发送函数只会发送一次,Circular模式表示只调用一次DMA发送函数就一直发;在外设所使用的DMA中一般配置外设地址非自增,内存地址可配置为自增或者非自增,内存地址非自增表示DMA发送的数据是同一个数据,也就是内存首地址的数据;源地址和目标地址的数据格式必须一致,都是8位或者16位;只要SPI使用了DMA进行发送或者接收,就必须要把SPI中断和DMA中断都打开,少打开一个都不会正常工作。
0 O2 E% Y& H6 O0 C( E7 [7 K
% a9 I* C8 k$ O) {SPI初始化代码如下,在ST新系列中加入了一些不太常用的SPI机制,如CS片选引脚空闲等待节拍,Ready模式,因为在大部分场景中片选引脚都由GPIO进行控制,比较少用到SPI外设自带的NSS引脚功能,除非是双MCU通信:
& y( e( d/ K4 V5 e5 V9 z- void SPI1_PA5_PA6_PA7_Init(void)
* c: g% e, n8 U9 k$ {( ] - {0 _* q; N$ s G- I! d
- 7 M, h. _4 K, N7 {% o2 I& q
- __HAL_RCC_GPIOA_CLK_ENABLE();
- C. O2 P7 a# C0 |: E; u& c - __HAL_RCC_SPI1_CLK_ENABLE();3 E* X" M* ]. m, q- X3 a" ^
- __HAL_RCC_GPDMA1_CLK_ENABLE();
# o8 O! a# @9 L' \8 ?% f - : E! h$ C2 u Z6 O$ w( J$ |
- RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};3 Y2 {4 e7 D5 l- e. v, V
- GPIO_InitTypeDef GPIO_InitStruct = {0};
& m. g) K: i8 x: B - SPI_AutonomousModeConfTypeDef HAL_SPI_AutonomousMode_Cfg_Struct = {0};( }6 i: |" _- Q% {) J; Q
-
- L7 F7 |( g( u - PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SPI1;
) `1 i2 N' M* _, K - PeriphClkInit.Spi1ClockSelection = RCC_SPI1CLKSOURCE_PCLK2;' }) Z. |1 x& g( K) U
- HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);7 m4 q4 `" W1 K( C
- ( T2 [1 P! q0 p$ j
- GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;3 h+ A. M: i* T* p$ N+ B
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;7 m: T; ]; Q) y- q H6 o
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- w! h( @ ~. _/ I( m( ~- L - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
+ U8 E, u, n4 I: f, Z - GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;- m. t1 W ^3 \9 S0 ~- W# ]. E/ C
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/ X% e+ }* H/ A7 T. @$ N" [6 m& o" {
6 V4 b3 k# y1 v( C- ~- hspi1.Instance = SPI1;' T. G2 X+ C. U5 w! q4 M3 F& f
- hspi1.Init.Mode = SPI_MODE_MASTER;
% S+ Z. \$ g! m" M9 y& b - hspi1.Init.Direction = SPI_DIRECTION_2LINES;! i! M/ @7 G. y; U) }& Z6 m
- hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
+ L: \7 g% M4 O - hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
3 Y, H p0 O8 v' ^. {8 | - hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;, l2 C% f$ H$ S/ V4 G. Y
- hspi1.Init.NSS = SPI_NSS_SOFT;; i5 G, `$ i2 l( e6 f
- hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
; ~. B( Q. P% N1 [8 f - hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
1 I: c' W. M2 d* x - hspi1.Init.TIMode = SPI_TIMODE_DISABLE;8 f, v. a" Z8 P0 f
- hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
, t, s6 C1 X, y7 x7 H8 X4 U - hspi1.Init.CRCPolynomial = 0x7;
8 X' a) o- o/ p/ i7 Y$ O& Y5 m - hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;0 y! ?4 t" h- T; b
- hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;2 }" [1 ~* r/ q( M% z0 X1 F
- hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;- z* S( V* z7 o6 U0 M
- hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;/ r( x1 o _; C! s; m# e1 ?+ ^
- hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
' } ]' w; z$ K- b, m - hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
! x5 y! |# m6 N L# w - hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
& w6 M/ p0 r. v G1 K: r* T - hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE;
/ j% y7 u1 R b1 Q - hspi1.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;2 h( E" w! S+ M, d
- hspi1.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
8 _! y2 K) k# n' ], D N% M3 t! G7 a - HAL_SPI_Init(&hspi1);
+ g+ P4 C ^1 k1 z) s. Z$ H, H - __HAL_SPI_ENABLE(&hspi1);
0 P# r p, v4 |. u0 l -
+ {$ F$ ~ ~, s+ [ J. v - HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
6 Y+ s2 A1 O9 w) t: ?. @ - HAL_NVIC_EnableIRQ(SPI1_IRQn);. F& ~/ H/ k' G1 w. I: c
-
- K* I$ i. ~# t1 L$ j: R - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerState = SPI_AUTO_MODE_DISABLE;: z3 [8 t) O. ^+ } _; w8 t0 h+ B( x" C
- HAL_SPI_AutonomousMode_Cfg_Struct.TriggerSelection = SPI_GRP1_GPDMA_CH0_TCF_TRG;
2 Y1 U5 C/ y( t+ {0 o: G8 c6 j - HAL_SPI_AutonomousMode_Cfg_Struct.TriggerPolarity = SPI_TRIG_POLARITY_RISING;' @1 _/ P; h8 }* B9 z& _: t
- HAL_SPIEx_SetConfigAutonomousMode(&hspi1, &HAL_SPI_AutonomousMode_Cfg_Struct);
. ]- a* D2 v0 @
( ~ E( i2 v/ S* X9 K; {) O- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;; I/ I$ A2 S' x1 D0 g
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;
$ S: l6 G9 E9 q4 o% | - handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;3 O/ e0 d) y; ]7 }- h
- handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;
. {8 C) d2 F0 u4 j - handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;& z$ y9 U- E3 @$ G# E6 Y
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;. h+ n/ C+ G2 {, i! Z
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
) V t- r4 J3 s9 K- W3 J - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;3 T8 E' F* [5 J# i3 O% \ D- n
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
9 n- G& P% q( L; z - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;+ {" B0 T. H8 h
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
. o: Z. H+ v7 F- A5 }- [+ S3 B - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
: ]8 J2 t7 J& v - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;/ @$ A, s% J5 t( n- k
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
6 ^+ C( r( v- M+ y4 b' Y# i0 i - HAL_DMA_Init(&handle_GPDMA1_Channel1);
: V" b& `- s$ b- F$ w - 0 w* {, `6 T6 m0 z
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);5 n5 V2 V" h8 u. R7 P' E
- 1 S# j* U: _! G9 m
- HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel1, DMA_CHANNEL_NPRIV);
4 \4 _% B0 _, B, h# d. N6 B - : R* H: p" N# D9 Y7 \% L; U! s
- HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);5 W: E& ]! H% o1 i9 b
- HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);( ~+ M" m7 { l5 r; a8 e9 I/ ~
- $ D5 p2 H c& w% {3 x" c" c( J$ i
- }! g' M5 d! a9 i3 ?
- 4 A! K# J+ V( _) D+ T# B
- void GPDMA1_Channel1_IRQHandler(void)
+ z e! U* s$ _1 s$ r* x - {
& x o3 x0 A' V" { - HAL_DMA_IRQHandler(&handle_GPDMA1_Channel1);: ]1 v' j0 u, i& ]- {) b- J
- }
2 K8 W3 c+ l. C+ C+ ^5 U: E
3 M3 D; u+ c, Z& o) E- void SPI1_IRQHandler(void)
1 t0 G. J; O, O5 P - {
' r& C, B4 y V) s; E - HAL_SPI_IRQHandler(&hspi1);% A$ b) K) ^; T' ]" @
- }
复制代码 # k% r* A& o+ G" {) f- H
2 `/ B* {0 a$ {- g$ [0 L& OSPI发送或者发送+接收代码如下:8 B% ?% O, R' ~ E5 R
- uint8_t SPI1_PA5_PA6_PA7_Read_Write_Byte(uint8_t TxData)
/ a' x1 k. Q5 x; F. J - {
! y$ \* S* d! e Z+ l6 p4 l - uint8_t Rxdata;
S3 g, G: a" {7 ?/ c' \3 Z, _ - HAL_SPI_TransmitReceive(&hspi1 , &TxData , &Rxdata , 1 , 1000);
+ w( G8 S' ^1 J9 B! } - return Rxdata;
0 Y& s: C, ]( P( s0 k7 Z - }
! s; X: I7 b6 P6 h. e4 M4 u" B& n( e - , {9 F4 o. ]& @4 X0 g1 a I
- void SPI1_PA5_PA7_Write_Byte(uint8_t TxData)1 M3 O, K7 v" _8 L Y2 l
- {
! G3 c& ^. Y& g0 \! t* m& W& l, O* A) {6 K - HAL_SPI_Transmit(&hspi1 , &TxData , 1 , 1000);' H s8 Z& P* V
- }
复制代码 " t9 c9 u( z$ A' {) B9 |$ C, j
3 T* r4 H# | o- _# N0 }7 v4 \. q$ _
0 c* ?- o- \# ESPI随时需要调整数据位数为16位或者改回8位:
& \8 s; F( n/ g9 M& O2 V- hspi1.Init.DataSize=SPI_DATASIZE_16BIT;. U) e, o7 j' N6 R2 \
- HAL_SPI_Init(&hspi1);
6 H% ~# R( a# ~' ^2 X2 P
, T! d' f3 X9 W7 l; O3 G3 b- hspi1.Init.DataSize=SPI_DATASIZE_8BIT;
! P: J7 V- {: w* z; _; A" z - HAL_SPI_Init(&hspi1);
复制代码
0 g( t+ U5 D9 p" q5 {4 i在实际应用中,常常需要修改DMA参数,最常见的就是内存地址自增或者非自增,数据是8位或者16位(Halfword半字)对齐:+ \/ A2 d* w5 ~# F0 P" k8 D
- void SPI1_TX_DMA_Config_PID_MID_HalfWord_Normal()
; G: `( i5 v5 V. _ - {1 F; c7 Y6 Q1 E5 C0 u0 P6 j
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;3 S5 R; A# p! ^; G) D( y* j: h
- handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;/ s( h& U8 d* a6 ]3 Z1 r2 \# T
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
. y; J) B# l6 n/ \, ~3 ?7 O/ K - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;/ k, ]1 p' P |2 C3 q
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_FIXED;( M$ {5 }, l9 ]* B* E7 S* g5 C
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;
9 U o1 K# z! s - handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
8 @4 Z0 K; R0 g v& H; u - handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;0 \, F t0 A% q& {9 q# ]9 x
- handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;5 I3 I. w B8 Z8 ~6 I' E; }
- handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;) ?3 H$ {6 M* b6 G2 R. M: Q8 {" C
- handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
9 W- I( x+ v0 L0 l! l5 E' {. A( l+ u - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0; ?' V) p h8 c! h
- handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;2 G% Y7 ~7 w% E0 {- Q: y
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
; S) y. _# h4 y+ K -
7 \' c& K& m9 A) A' p6 E5 Y: l+ E8 S - HAL_DMA_Init(&handle_GPDMA1_Channel1);
/ Y* T. j, Q+ }- _; }9 M8 j3 I6 d - : l1 Z0 B7 _! [" e- y3 |
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);& y, {; I6 X* ^% }( ?. {* R6 q: ^% t8 k/ |
- 5 X8 V" C0 p1 k& n
- }
/ F3 U5 g; @) d: E2 y. ^1 \
5 F# n2 D& Y. I- e" M* a0 Z8 ?- void SPI1_TX_DMA_Config_PID_MIE_Byte_Normal()
; P8 u" X6 ]9 ], P0 H8 ] - {5 ~, |* v- ?/ y, v
- handle_GPDMA1_Channel1.Instance = GPDMA1_Channel1;
8 J" D# I, m: p8 ` - handle_GPDMA1_Channel1.Init.Request = GPDMA1_REQUEST_SPI1_TX;8 H- d! a' W5 w' |) W+ N; F& \
- handle_GPDMA1_Channel1.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
4 Z' G9 u1 y, l2 c, d - handle_GPDMA1_Channel1.Init.Direction = DMA_MEMORY_TO_PERIPH;' B, w3 h) q9 `
- handle_GPDMA1_Channel1.Init.SrcInc = DMA_SINC_INCREMENTED;6 ~+ p. c* a+ {; ?, l
- handle_GPDMA1_Channel1.Init.DestInc = DMA_DINC_FIXED;8 r+ n; f! Z% [- @( l4 p- Y
- handle_GPDMA1_Channel1.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;2 h0 F# A& l; ]' d! c! e8 m# E
- handle_GPDMA1_Channel1.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
2 O" W* G: Y0 J3 v6 ` - handle_GPDMA1_Channel1.Init.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
% Z" r8 U: m% K9 o6 f - handle_GPDMA1_Channel1.Init.SrcBurstLength = 1;
' T! u5 _: C) |4 e8 m& f - handle_GPDMA1_Channel1.Init.DestBurstLength = 1;
1 u- ^) i% x& {% ~ - handle_GPDMA1_Channel1.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
2 T( T4 S! B3 w* s - handle_GPDMA1_Channel1.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;, I8 r {, T1 A8 e5 a' k
- handle_GPDMA1_Channel1.Init.Mode = DMA_NORMAL;
- O- h0 @# C! A2 i4 n+ H+ { - $ V! Q9 o. p! i0 Z
- HAL_DMA_Init(&handle_GPDMA1_Channel1);' w( T& X7 [# P! a. q D
- 5 Z5 l4 u$ T4 \
- __HAL_LINKDMA(&hspi1 , hdmatx , handle_GPDMA1_Channel1);
2 F+ c3 A0 J/ S - + p" s2 y9 p; Y9 r+ i4 I Q
- }
复制代码
, w/ }" l* k4 x" ~1 l我这里用SPI1去驱动ST7789的屏幕,使用SPI1的DMA发送功能,因此使用DMA传输函数:
6 L1 C) C* E' F2 t- HAL_SPI_Transmit_DMA(&hspi1 , pic , num1);
复制代码
+ f' _* o1 @! o( ? r5 m4 ?# d1 qSPI不使用DMA的波形如图:
$ Z0 ]) E& h* s! g1 x6 N
' C& T; L, a% l W' w
这里有个ST新系列的SPI波形问题,MOSI脚会在发送结束的时候拉低相当长的一段时间才会恢复高电平。
m5 B x! J2 g$ J8 G$ k/ e3 j$ ^6 |. J9 T5 P' D) s4 k
SPI使用DMA的波形如图:: _" @. s" Y) R( B( ~
+ A( y0 n) o2 w: E- x" \2 @4 R# o2 i- J' J5 ?6 ?! n m0 }- X
使用SPI引脚驱动ST7789液晶彩屏:* S6 y4 \0 @/ k5 K% w
6 x/ M% z/ E1 D6 @' {- {
3 Y! D$ |9 A# s' P: s3 _
5 B; P1 s! F8 w* d
|