STM32 DMA简介 DMA,全称为:Direct Memory Access,即直接存储器访问, DMA 传输将数据从一个 地址空间复制到另外一个地址空间。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的 内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工 作。 DMA 传输对于高效能嵌入式系统算法和网络是很重要的。DMA 传输方式无需 CPU 直接 控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备 开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。 如图 所示, 两个 DMA 控制器有 12 个通道(DMA1 有 7 个通道, DMA2有5个通道)。DMA 控制器和 Cortex-M3 核心共享系统数据总线,执行直接存储器数据传输。当 CPU 和 DMA 同时访问相同的目标(RAM 或外设) 时, DMA 请求会暂停 CPU 访问系统总线达若干个周期,总线仲裁器执行循环跳读,以保证CPU 至少可以得到一半的系统总线带宽。
6 V% N& x8 B( \: L
7 z1 t' t" \+ H* C6 D* l
- DMA 处理
; ] l! M9 {* E% f6 B. M0 b# n d
在发送一个事件后,外设向 DMA 发送一个请求信号 的箭头。DMA 控制器根据通道的优先权处理请求。当 DMA 需 送请求的外设时, DMA 控制器立即发送给它一个应答信号。外 号后,立即释放它的请求, 同时 DMA 控制器撤销应答信号 : L& |" K7 u3 U" B/ ^) W( y: }1 V
- 仲裁器
/ _% _8 y. d. c: P. }8 G3 ]# p
一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等信息。如果我们需要同时使用同一个 DMA 控制器多个外设请求时,那必然需要同时使用多个数据流,其中哪个数据流优先, 此时由仲裁器来选定。 仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时可以通过寄存器设定它的优先级别,可以在 DMA_CCRx 寄存器中设置, 有最高优先级、高优先级、中等优先级和低优先级四个等级。第二阶段是硬件,如果两个请求有相同的软件优先级,则较低编号的通道比高编号的通道有较高的优先权。例如:通道 2 优先于通道 4。 & R) b& T) w/ U% }
每个通道都可以在由固定地址的外设寄存器和存储器之间执行DMA 传输。DMA 的传输数据量是可编程, 可以通过 DMA_CCRx 寄存器中的PSIZE 和 MSIZE 位来进行编程,数据量最大可以达到 65535。DMA 的外设繁多, 例如 DMA1 控制器,从外设产生 7 个请求,通过逻辑或(例如通道 1 的三个 DMA 请求,这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个)输入到 DMA1 控制器,此时只有一个请求有效。
0 X4 n2 j+ U) p. d5 l
8 ~, e0 I2 C# Q1 Q/ O
- }4 P( b! [9 k& D% m% VSTM32 的 DMA 有以下一些特性: ●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。 ● 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。 ● 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。 ● 支持循环的缓冲器管理 ● 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。 ● 存储器和存储器间的传输 ● 外设和存储器,存储器和外设的传输 ● 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。 ● 可编程的数据传输数目:最大为 65536 - J, ~8 O% K7 w! W" e
STM32串口DMA使用详解本次我们使用的硬件环境是之前开源的板子,falling-star board,使用串口1。 9 i4 M6 L$ F/ b8 R
cubemx配置关于时钟配置、串口基本配置请参看:cubemx的正确打开方式一文 接下来直接进入配置串口DMA: 选择串口1,基本参数如图,都是老生常谈了,easy~ % u" c9 o6 z3 D/ P1 o
( \: T6 X# N' ^, \% C5 k8 {( H" y8 |8 \ E: J% d- K9 @- C
选择DMA Settings,主要有一下几个地方,基本上不需要改动,根据自己的使用情况确认即可,需要注意的是,发送和接收并不是一定要成对出现的,可以只选择DMA发送或者DMA接收 6 v* z* S4 n# c+ W; C8 [5 _& @
# }7 J% f d# c
$ G+ e5 \: N1 w% N中断设置,DMA中断可以配置,可以不配置,同样也是根据自己的实际需求情况,串口中断需要配置,下面会用到,优先级根据自己任务的优先级确定,分好“轻重缓急”即可 8 N7 _% }! c& n. l- A
. l7 a5 o8 H: K G/ ^ I H$ p" q+ Y9 ]0 N; D: v6 i. P: d
配置非常简单,主要是在此前串口功能基础上添加DMA功能,over % ^2 _& g/ K1 ^- g: o* v1 q
串口DMA代码设计串口DMA源码API介绍上面提到的配置项,都封装在一个结构体里面 - /** @defgroup DMA_Exported_Types DMA Exported Types1 S6 e( T$ T5 w4 i, I
- * @{
& s: X' b0 o8 f; L3 z2 g: R" V - */% d+ @0 s, Z( A# n6 j* g
- & m% n( } g1 D* I3 S4 j# d
- /**8 {" {: z2 {) b, B# P
- * @brief DMA Configuration Structure definition
5 h8 o( u' w# Q# C - */
+ X( w3 y& I* W6 G4 f - typedef struct8 Z# H3 s I* [; @
- {
' \7 S4 a2 L. r2 [ - uint32_t Direction; /*!< Specifies if the data will be transferred from memory to peripheral,
% b p% P. @" u. I8 k - from memory to memory or from peripheral to memory.
; U# y/ W4 t8 N3 e, T4 V0 X1 M7 | - This parameter can be a value of @ref DMA_Data_transfer_direction */
% k; E$ i- ], |- i6 A8 v7 y
4 u' B, p, ]! h: C/ f. ~! Y- uint32_t PeriphInc; /*!< Specifies whether the Peripheral address register should be incremented or not.# N0 X, W6 P7 U. j9 }+ i' d
- This parameter can be a value of @ref DMA_Peripheral_incremented_mode */1 P1 M# ]' Y2 x( [* ~4 l) P4 e% V$ @
- 4 m+ X" s1 f" d$ Q; l
- uint32_t MemInc; /*!< Specifies whether the memory address register should be incremented or not.. s9 Q/ k/ V7 C$ G; J' r: U
- This parameter can be a value of @ref DMA_Memory_incremented_mode */
( I9 R4 \ h. o& a - ; K8 r- m: y+ U" b. I. X3 |5 w5 S
- uint32_t PeriphDataAlignment; /*!< Specifies the Peripheral data width.
) o6 K/ S3 r( Z7 S) f3 { - This parameter can be a value of @ref DMA_Peripheral_data_size */2 \7 v- R# z6 R' ]* R$ g
- 0 D% [) r% V7 n1 j( d
- uint32_t MemDataAlignment; /*!< Specifies the Memory data width.& l. s, v2 d' h9 Z- @# U2 a
- This parameter can be a value of @ref DMA_Memory_data_size */# L$ k' j- X9 ^. I ^: T
& N7 y2 _& A( p. B5 E. y1 P+ V- uint32_t Mode; /*!< Specifies the operation mode of the DMAy Channelx.
( J' |& t8 `* T5 v6 Q: K5 }( a - This parameter can be a value of @ref DMA_mode6 h8 h1 ?7 T8 j5 ]( @% @
- @note The circular buffer mode cannot be used if the memory-to-memory
9 U) \/ j0 q+ B3 ?( { - data transfer is configured on the selected Channel */- X" [0 M" i: `. s! l/ p6 B
- ' X$ Y+ D% O9 {6 E8 c' i6 n
- uint32_t Priority; /*!< Specifies the software priority for the DMAy Channelx.; V: B8 F* G0 Z' Z1 v5 p! \7 V: Q0 o
- This parameter can be a value of @ref DMA_Priority_level */
, ^* L- f9 w* [7 i+ P. ^ - } DMA_InitTypeDef;
复制代码
$ O) L( s: O3 p( L5 t关于DMA的函数不多,本次用到DMA的初始化,开始发送函数 5 u! `2 ?& G1 ~3 u
9 s" Z5 s2 q1 G4 D
/ L9 _: R3 \( Q4 s串口中关于DMA的部分主要有这几个函数,还有一些关于中断、DMA标志等的一些宏定义,就不在一一列举了,需要用的时候大家知道去库函数中去找就可以了
$ a7 P) J& F( Y q. w3 u- s) N
, Q4 J9 P0 i& `' S" J7 G" N- |
串口DMA初始化部分: - void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
0 c! l# B' M# j! w: X( z - {
) C I# N8 a# V0 B% } - # s4 t* Z: J$ d: q
- GPIO_InitTypeDef GPIO_InitStruct = {0};# h% f7 D* k+ I" g% b' w9 _* r
- if(uartHandle->Instance==USART1)
/ q# a" q R* h8 P - {
3 L L1 G- f/ k* u% N: Q, k - /* USER CODE BEGIN USART1_MspInit 0 */
0 ^! l$ U% s# w0 O8 {' [" e
% h0 T. k# T) L: ^, |- /* USER CODE END USART1_MspInit 0 */! E1 }; J7 B: }
- /* USART1 clock enable */
/ W5 V$ L: P) b# X$ K - __HAL_RCC_USART1_CLK_ENABLE();
; p6 i1 o4 g7 ~; m2 a1 G - 7 }, ]+ }' g, s' y7 l, Z
- __HAL_RCC_GPIOA_CLK_ENABLE();3 k9 X7 o; h" n
- /**USART1 GPIO Configuration3 `" l7 U- U) y/ K: z
- PA9 ------> USART1_TX* q/ u+ Z8 J& R) i. b9 r
- PA10 ------> USART1_RX
) D$ K: ?# n4 m, { - */
8 |% K& |" K4 @; j' u/ I: ]3 t - GPIO_InitStruct.Pin = User_UART_TX_Pin;
z, U2 I+ T7 }/ w/ T4 h - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
1 Y& z& O- s) c* _7 |* r, q - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;* {7 r& k- d, I- @7 F% a
- HAL_GPIO_Init(User_UART_TX_GPIO_Port, &GPIO_InitStruct);- d) C% {, P( p# H# r0 x# t3 f4 B9 {
- # e) P' T; v8 M" P* O% K
- GPIO_InitStruct.Pin = User_UART_RX_Pin;1 S5 B5 T' n$ }: D! W0 n$ g: k, g
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;( X7 H: }- _$ y2 k6 \. v5 F1 e* J
- GPIO_InitStruct.Pull = GPIO_NOPULL;+ B) Y( K1 u2 \/ q, w# n% F5 I
- HAL_GPIO_Init(User_UART_RX_GPIO_Port, &GPIO_InitStruct);
. I2 }; m1 w, {5 K - 0 O) b- `6 E6 e4 D! J+ i
- /* USART1 DMA Init */9 Q& z3 ?- W/ d a5 N0 z
- /* USART1_TX Init */( y( S7 L, X6 |$ _, }, D
- hdma_usart1_tx.Instance = DMA1_Channel4;
3 Y, z/ n2 f/ r# D4 M5 w4 J( o) W - hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
% v3 ~) Z' R7 S7 w - hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
* _+ m5 O9 e, O, k2 i$ s - hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
0 [3 |8 O* z' P/ m5 z - hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;& n' V4 D: n& z, I: C
- hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;4 X- Q6 q! Z2 C: K
- hdma_usart1_tx.Init.Mode = DMA_CIRCULAR;' V. `# t- g5 \) V0 P5 F
- hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
7 V: P; h; J* [' b5 n5 F# |: G - if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)4 X# F( W" X( D1 O4 `5 \3 A$ q6 M9 s
- {0 o* j3 ?/ r) P
- Error_Handler();
& p* }/ k' v- a1 P6 P" P+ V. n - }
+ b2 a. X7 H: H% |! N8 J
5 W5 l5 x5 C. ^6 \3 P1 {3 P, n- __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);" l# g& _4 ]% [/ K( G7 w" H' j
- 8 M# e" T! l) t7 r( h! d
- /* USART1_RX Init */
) B5 w: v- M2 l Y - hdma_usart1_rx.Instance = DMA1_Channel5;
: b m* f8 G& E* b7 h5 h, [ - hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
( U2 Q0 o/ d7 f. A/ p, P - hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
1 F; a9 Q- \) L& F - hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
+ [" K) B" f8 J" H( S* q - hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;5 Z9 ?( ~8 ^8 H* G9 @
- hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;0 W7 ~; T4 D. U6 y; a( I
- hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;# Y$ s7 O3 ]2 n( C
- hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;! ?' o' ]4 |4 e/ @5 ~
- if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)! J N3 M! @( Z, ?+ v/ _& h, c
- {+ X6 l% m9 C# e4 G- t. k' l5 \
- Error_Handler();) d+ s! q6 u* {1 N& ~8 H' Y6 X
- }2 ^8 P7 c9 T- N8 n- G* ]& i+ {
- ' R# M7 ]& L# a2 p) s1 }- e
- __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
, T& W( L$ u4 Q* _4 U( l! ^) r3 l8 S - & w9 Q2 {$ [; ]$ T/ S& U7 `
- /* USART1 interrupt Init */
5 M. y+ {. f. R# y' q - HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
' \5 H8 H- M3 \( f - HAL_NVIC_EnableIRQ(USART1_IRQn);0 X7 m8 i* E: T% s3 p- l( }/ U
- /* USER CODE BEGIN USART1_MspInit 1 */
% h) D0 s$ ?/ J! | - * _3 O6 _ \; O% V
- /* USER CODE END USART1_MspInit 1 */) W* b- H3 ^+ n, D1 V; Z6 x
- }# I% N7 t' l" T6 h" S, v
- }
复制代码 : \" |9 r5 {. |' d) {
DMA发送设计发送非常简单,都不知道该怎么介绍...发送对应DMA的方向是内存到外设,我们只需要把数据装入相应的内存中,调用发送的API即可: - /******************************************************
3 x1 [- q# B% S" @ - * Brief : DMA传输函数
* f7 F1 }6 k1 K$ ^9 `7 B' ` - * Parameter :
' {* ^& w" ~. X$ o3 { - * *pData: 要传输的数据
$ t2 ^) N4 c8 Y9 I8 C3 o - * Return : None.
. P+ U: o* ~$ [- B+ V, \ - *******************************************************/8 H/ g9 k9 n7 |3 f; q- S
- void User_uartdma_Transmit(const char *pData)' x8 K9 u5 T8 L+ J+ K. p4 `2 R
- { # G! T+ A( S0 `' k( @ r
- HAL_UART_Transmit_DMA(&huart1,(uint8_t *)pData,strlen(pData));
9 D% t* l4 ^$ \. D - }
复制代码
9 L9 P* b- }' G5 H B& }4 m根据DMA的原理,我们发送的时候是不影响CPU的正常工作的,效果应该类似于操作系统的多任务执行,现在我们只需要在初始化时候打开DMA发送数据,串口助手监控,主函数什么都不干,数据不断在发送 此时如果添加两个LED流水灯,是完全不受发送数据影响的,效果就不放了,相信小飞哥...
) I9 B+ y$ i X0 ~* a, |
5 ~0 u. \5 ]# n& n% A$ k/ O# L% n4 }1 v- `
不信你看~ $ O/ Z* f+ d0 t% G5 D
4 @2 J$ V- _8 R4 l% C) D
发送的是非常简单的,好像这里也没体现出来,使用DMA发送有什么好处,其实在LCD驱动的时候,当有图片等大数据量的数据需要传输的时候,使用DMA是种非常好的方式 & A) `: V) E5 x9 l0 k7 l1 m h
DMA接收设计聊起串口数据接收,我们最先接触的可能是,中断接收,来一字节进一次中断,通过定时器超时判断一帧数据结束。 但是小伙伴们有没有考虑过一个事情,数据量很大,串口一直进中断,对MCU带来的负荷是非常大的,这种方式就显得不那么美好了。 哲学上讲,矛盾是推动社会进步的源泉,没错,鉴于此种情况,我们换一种方式来处理,DMA+串口空闲中断的方式,我相信,这种方式你一用就会喜欢上~ 具体的设计思路是: 1、开启串口1中断 2、开启串口1空闲中断 3、打开串口DMA接收 4、判断空闲中断标志是否置位 5、数据接收完成,主函数打印接收到的数据 先来封装几个函数: - /******************************************************
' V% h. E8 q; X8 k \* @2 |1 B& w - * Brief : 串口DMA 初始化,初始化除了cubemx配置之外的部分
3 k- D; v+ F2 W) z; k8 H! ? - * Parameter :
( W$ G2 |: X, c A# J - * :
3 A7 R& ^' u7 E, y$ ? - * Return : None.
: A) \! A9 r$ o$ z - *******************************************************/; n5 C* ^! [* P% [2 ^1 M( ]( O/ _8 `9 e
- void User_uartdma_Init(void)
4 y, k- D0 _) v: g5 p. H8 j - {
* W3 q# r h" {! f# ] - //失能串口DMA传输
; h& L) ^& `" t L, L8 P - HAL_UART_DMAStop(&huart1);8 W( ` I5 k* H! Q, t' v B
- //使能串口1接收中断3 U" W5 l* o5 V1 [5 ?4 K
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
2 @; m, i' m4 ~$ A5 n - //使能串口1空闲中断& x/ H% B+ A* ]4 g* c7 N) S/ J
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);) B. P; E$ h, y. Y' Y. N, _. g
- //使能串口DMA接收
6 n1 @: m- z1 C& J1 q - HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
4 U2 z6 I0 G3 C1 G! P - }
复制代码- /**
" K+ j) |( }. h4 o2 X - * @brief DMA接收函数- O: p, L- A7 |4 e! ^! ]% s5 _" a- U
- * @param htim TIM Base handle
2 V- U6 i" h5 z3 a/ |, ] - * @retval HAL status
- n+ Z1 g: J( u - */% g) d) x6 E% X7 A6 O5 K
- void User_uartdma_Receive(uint8_t *recData,uint16_t rec_len)
2 Z7 n/ u, Y0 K& K - {9 l' ]$ Z: o3 `7 G0 v) i8 x/ i
- HAL_UART_Receive_DMA(&huart1,recData,rec_len);
: g* G$ n) i* @ H - }2 r' K7 w: L3 I0 K7 R' @4 D$ C s1 Q3 t
% j6 i7 K& V0 s3 j
复制代码 + R9 I) J9 z# B1 t% f
主函数: - /**0 S E1 e9 w9 x/ j% O
- * @brief This function handles USART1 global interrupt.
0 W. A9 B2 o8 H* A4 i* U - */
3 F1 e' }/ h+ N - void USART1_IRQHandler(void)7 x3 q3 A# n8 J1 f1 k8 N
- {
: _8 k9 m0 h' u8 c5 y5 w" } - /* USER CODE BEGIN USART1_IRQn 0 */
' T' q/ V# ^' V2 f2 e) G$ z - uint32_t idle_flag_temp = 0;
. ^' f9 J U: ]3 z) D, h - uint16_t len_temp = 0;
) n. p, D8 c0 Z* ]& G; [/ n! E7 ~$ m* C - /* USER CODE END USART1_IRQn 0 */% f# V4 E1 x; G: y
- HAL_UART_IRQHandler(&huart1);. B5 w% S# J/ S: V9 m( c1 f
- /* USER CODE BEGIN USART1_IRQn 1 */
7 D, y% o/ V& X' q g - idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
2 ~% Q- i; e1 J1 `' ?9 e+ b9 [ -
$ a! W0 C8 T1 H* H! b - if(idle_flag_temp)6 n- E. l0 \' a* R4 H% D% M0 l% i2 X
- {
. P5 A# _& k# I/ E2 w - __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
. l8 v" `# ]1 ~9 u W7 ], _ - HAL_UART_DMAStop(&huart1);3 D1 ]# D) R( C' N% d0 V
- 0 z+ m, L5 x! {# s( r8 q) ~
- len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
" N' o- @# ]; U$ ?, {0 c4 X( g( i - ; R, C7 ^4 {5 \" T* S" h: G o
- UserUartDma.RecDat_len = Max_RecLen - len_temp;
+ w' y1 \) ]- J4 g - UserUartDma.rec_endFlag = 1;6 U1 A6 @6 K- J! `8 c$ F7 c: x
-
! M( p# y4 ]5 f4 p7 N - }( F- P0 A% |* O
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);9 G4 m# h* h1 H- U9 L- _: H6 h- o
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
; M9 e' L! p/ E# Y - /* USER CODE END USART1_IRQn 1 */. \# U& }3 G; r* O% T2 K, h
- }
复制代码 7 I" M. g5 A: n6 a( K, d
演示效果:
4 H9 d) c3 i) J" h$ J9 _! L" H
0 |9 K! r8 P4 N: E
, S! t2 f1 r8 p6 F1 ~ o* [1 I
7 y0 h4 `7 a% s u- \/ [% g/ |转载自: Embedded小飞哥: i% g6 c- C+ n3 V" K" b, i
如有侵权请联系删除: ^) ^( ~( u% ^
0 a: H" `5 q) b% u- S
$ u) X% E0 O+ F. Y, m; l4 ]4 m; \% ^
1 y, X0 V3 J& f- \# s. U
|