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 至少可以得到一半的系统总线带宽。
j) z3 C7 b8 [/ w$ C7 ~
% Z5 z3 a8 ?5 r" `
- DMA 处理
8 Z6 p+ ]7 V+ ?7 k! V
在发送一个事件后,外设向 DMA 发送一个请求信号 的箭头。DMA 控制器根据通道的优先权处理请求。当 DMA 需 送请求的外设时, DMA 控制器立即发送给它一个应答信号。外 号后,立即释放它的请求, 同时 DMA 控制器撤销应答信号
. k3 S# m2 C }0 q) d
- 仲裁器# w& k3 r1 q0 _! t P Q% a- D/ i
一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等信息。如果我们需要同时使用同一个 DMA 控制器多个外设请求时,那必然需要同时使用多个数据流,其中哪个数据流优先, 此时由仲裁器来选定。 仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时可以通过寄存器设定它的优先级别,可以在 DMA_CCRx 寄存器中设置, 有最高优先级、高优先级、中等优先级和低优先级四个等级。第二阶段是硬件,如果两个请求有相同的软件优先级,则较低编号的通道比高编号的通道有较高的优先权。例如:通道 2 优先于通道 4。 ; [7 q5 s4 N+ q) p1 M
- DMA 通道
7 N! E; t9 ~& ^' y r4 e2 [
每个通道都可以在由固定地址的外设寄存器和存储器之间执行DMA 传输。DMA 的传输数据量是可编程, 可以通过 DMA_CCRx 寄存器中的PSIZE 和 MSIZE 位来进行编程,数据量最大可以达到 65535。DMA 的外设繁多, 例如 DMA1 控制器,从外设产生 7 个请求,通过逻辑或(例如通道 1 的三个 DMA 请求,这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个)输入到 DMA1 控制器,此时只有一个请求有效。 8 p+ j7 a! Z# ~0 U
% ]) n& B+ B9 S& z
9 [7 ^. b6 v i7 PSTM32 的 DMA 有以下一些特性: ●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。 ● 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。 ● 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。 ● 支持循环的缓冲器管理 ● 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。 ● 存储器和存储器间的传输 ● 外设和存储器,存储器和外设的传输 ● 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。 ● 可编程的数据传输数目:最大为 65536 $ m1 u. B' l) {1 B$ [7 }0 B& {% C$ }
STM32串口DMA使用详解本次我们使用的硬件环境是之前开源的板子,falling-star board,使用串口1。
4 v) g& t: p4 F3 R, W$ p E: v cubemx配置关于时钟配置、串口基本配置请参看:cubemx的正确打开方式一文 接下来直接进入配置串口DMA: 选择串口1,基本参数如图,都是老生常谈了,easy~ # O6 d& ~; \' e( }, M( o+ n. M; N
2 f! W' r7 @: X5 t' C. @6 q3 t; u) V" u& K8 O
选择DMA Settings,主要有一下几个地方,基本上不需要改动,根据自己的使用情况确认即可,需要注意的是,发送和接收并不是一定要成对出现的,可以只选择DMA发送或者DMA接收 4 a. J# |2 u8 L2 m
5 q# k! j1 g7 @" ~: f, G$ ?
6 ` }. \/ U) P' D w. j6 v
中断设置,DMA中断可以配置,可以不配置,同样也是根据自己的实际需求情况,串口中断需要配置,下面会用到,优先级根据自己任务的优先级确定,分好“轻重缓急”即可
% \& _& A0 Q& \& a
9 e* V( x c4 M! \$ S
- P. r( v; U+ t配置非常简单,主要是在此前串口功能基础上添加DMA功能,over 3 n& l, ?) ^8 C
串口DMA代码设计串口DMA源码API介绍上面提到的配置项,都封装在一个结构体里面 - /** @defgroup DMA_Exported_Types DMA Exported Types6 b# W$ ^/ H' }9 T! Q$ v0 Z
- * @{
5 _' t# E& ] R% r% _& K - */8 d) e! G6 ?4 z! ?# d
) R6 [3 C+ v* ?- /**
, H5 ]9 g# t6 e1 L+ k9 U - * @brief DMA Configuration Structure definition+ U# M( x+ D; D% a
- */8 F& A {$ ~; i7 ]2 a& p! o$ u
- typedef struct
8 @6 r& L0 f/ _ - {7 Z! z7 [+ N8 N* R# ^: h4 ^
- uint32_t Direction; /*!< Specifies if the data will be transferred from memory to peripheral,
/ W$ K/ {; Z" j( T: s - from memory to memory or from peripheral to memory.
% g# v: B: Q% @' @1 M) X" {' C) ~ - This parameter can be a value of @ref DMA_Data_transfer_direction */
- V9 G: ~' _, G$ W - $ |) }! r' L2 m7 W# r. {0 {
- uint32_t PeriphInc; /*!< Specifies whether the Peripheral address register should be incremented or not.
/ p, Q% C9 _4 V2 E8 R1 r+ z) [ - This parameter can be a value of @ref DMA_Peripheral_incremented_mode */
, c4 X: K6 G3 V2 N+ w, a3 T
% Y1 m) j) k& ?& n1 X3 a- uint32_t MemInc; /*!< Specifies whether the memory address register should be incremented or not.5 ~4 u( h( j+ ?% {5 V: M
- This parameter can be a value of @ref DMA_Memory_incremented_mode */& Q7 u+ T( ~* ]6 d
- . a s+ ^4 f. W
- uint32_t PeriphDataAlignment; /*!< Specifies the Peripheral data width.8 }) c, ~" a( u
- This parameter can be a value of @ref DMA_Peripheral_data_size */
! \# w3 X7 N2 s3 U8 n
4 [) i) u! l: b0 b% @- uint32_t MemDataAlignment; /*!< Specifies the Memory data width.0 G/ \* |+ x# I' D$ u) ~3 v7 h& f
- This parameter can be a value of @ref DMA_Memory_data_size */: {: h" e2 q3 A$ x3 V8 h4 U
- - V' g8 Q; d5 K
- uint32_t Mode; /*!< Specifies the operation mode of the DMAy Channelx.5 e* M$ I0 e4 D6 z6 L0 W* f+ d, `( q3 ~
- This parameter can be a value of @ref DMA_mode" L" |* Q$ x' e Y( a+ j/ ]
- @note The circular buffer mode cannot be used if the memory-to-memory
! X3 Q) _0 V ? p0 B9 f5 g - data transfer is configured on the selected Channel *// A2 E) i* r/ k- h$ S
- 5 E$ m! D, c- o7 } { O5 _
- uint32_t Priority; /*!< Specifies the software priority for the DMAy Channelx.
3 Y: h3 L, }, K - This parameter can be a value of @ref DMA_Priority_level */
2 M" R2 q0 T5 ^6 ^7 L# m) C9 R) K( u" I$ A - } DMA_InitTypeDef;
复制代码
1 G1 D, G; G- [4 q9 [关于DMA的函数不多,本次用到DMA的初始化,开始发送函数 7 l5 q& O% i! H+ }$ ?' j. c7 E
. _1 Y G. b: F4 a6 A) \
0 A8 S2 _5 m) W. d
串口中关于DMA的部分主要有这几个函数,还有一些关于中断、DMA标志等的一些宏定义,就不在一一列举了,需要用的时候大家知道去库函数中去找就可以了
6 A, {. K+ V5 R$ }0 c
, K- u9 u6 R7 j1 D2 I6 l; X
 串口DMA初始化部分: - void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)2 i/ j2 m# n2 Q. d9 g
- {
/ K& ^8 d# ?# i5 v" y
! b" d( t E' H- GPIO_InitTypeDef GPIO_InitStruct = {0};
6 N* f" h0 e8 H) B& B% H" a/ M9 t - if(uartHandle->Instance==USART1)
" y6 r- X0 Y' s/ J6 Q - {3 ?4 P. }: D6 z# s' W! q
- /* USER CODE BEGIN USART1_MspInit 0 */
# C2 f2 M) @3 a# ]' S/ e3 `/ v: \
7 \% q+ y; A5 t! I Z/ e. @2 \- /* USER CODE END USART1_MspInit 0 */$ y# H: C) s4 A6 Y- P1 u4 T+ P% w! O
- /* USART1 clock enable */
, ^! e$ U9 G% O |& Q7 [) r - __HAL_RCC_USART1_CLK_ENABLE();
/ H6 \8 S2 K% a |0 B& ]) z
9 v' b* T% i1 I' T3 {7 x' i- __HAL_RCC_GPIOA_CLK_ENABLE();4 G& m* P4 X" k4 }6 I' f+ y8 ^
- /**USART1 GPIO Configuration8 T7 G: j" h( ^) P* C) v" T
- PA9 ------> USART1_TX
9 T* {4 ]* I" A( X' `5 o- M - PA10 ------> USART1_RX: g" f9 h! \* H1 R- q3 ^
- */
" l0 q! i7 O% D: ]* {- Y - GPIO_InitStruct.Pin = User_UART_TX_Pin;! I. {* l, R9 ?. a( L4 a
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;' q2 R: V# V7 f5 J( _' |( z# ^2 H: [, a9 R
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
( S! u8 b9 T1 c1 v - HAL_GPIO_Init(User_UART_TX_GPIO_Port, &GPIO_InitStruct);* N% b6 m9 |9 e8 u
9 L2 n |. a2 w7 q- GPIO_InitStruct.Pin = User_UART_RX_Pin;9 u/ A. d3 x, A8 }% X. r! D; E6 O
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;7 S# y( |% {% U) S# Z, U
- GPIO_InitStruct.Pull = GPIO_NOPULL;) j" C" Y ~& ?# a" @4 c8 a. o2 _
- HAL_GPIO_Init(User_UART_RX_GPIO_Port, &GPIO_InitStruct);
/ r, {; f3 ^0 ~) j: ]
* [) {9 y6 _4 k- /* USART1 DMA Init */: F; k0 q& E$ N& d+ ]
- /* USART1_TX Init */, R) Z# _' ]; d7 ^4 b# K# K) G/ Q
- hdma_usart1_tx.Instance = DMA1_Channel4;
" ?9 K! B; A* M/ |5 R8 x - hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
3 B& y6 i/ T3 i. x6 z" Z6 ` - hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;4 u v9 X) f9 K( c2 {( E) u
- hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;9 M! G F6 f8 Y8 ~" w/ Y
- hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;! ?9 o4 U$ F- F1 C
- hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
" S+ G# o- b1 x* o8 q1 t - hdma_usart1_tx.Init.Mode = DMA_CIRCULAR; R: ?0 |0 f% Q3 B4 J U$ v) O
- hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
- S/ n5 [1 [6 c3 l" t - if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK), B( ^& u- X! ?! Y* j
- {
4 ~) b/ n9 t- t g( U' C* x - Error_Handler();
; \/ Z1 ]' X% S - }
4 ?0 B- s! n6 d( Z - 9 u+ E0 y6 l: A6 Y( \8 Q7 I6 X
- __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
9 w5 f) t* z; K
$ P' `% L+ i. {/ S* `* l8 W# n& d( M- /* USART1_RX Init */
7 l3 H, V$ {8 S7 g1 h5 I) G - hdma_usart1_rx.Instance = DMA1_Channel5;
" u' c/ t1 z, _9 o - hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;. u) e! y: d0 `; u. y
- hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;" s. V9 a6 {$ @! s" M3 K
- hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
* i7 }% J+ I6 R$ O; v3 V! K, x - hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
" i( U* H; x3 c- D3 f" {7 f - hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
* Z A @; u; D" I% l5 L2 C: ` - hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;; @* N7 t" E5 p9 N
- hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
/ ]/ L9 ], `; [ - if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) Q4 B2 [8 |4 Z' e$ j- H0 h
- {! u2 r/ a% W& Y( P( ~$ Q8 b" h
- Error_Handler();
( C0 B2 T6 P* U0 p" q' ] - }8 }, m4 a, P5 o( K1 k
- 6 [& S! I4 E: `3 c) _. N
- __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
5 K) ~% u( z' D) b: e& N
4 x' D c. y, w. }1 |- /* USART1 interrupt Init */
. h1 i9 s' j* S8 W - HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
9 X$ D' |$ ]% [: l1 M - HAL_NVIC_EnableIRQ(USART1_IRQn);
! _& |! ~2 m3 F$ N7 @4 K( s7 a/ Y - /* USER CODE BEGIN USART1_MspInit 1 */& p# Z" i8 t2 z9 c
- & S, M. B* \* o5 r9 d( D
- /* USER CODE END USART1_MspInit 1 */
0 m, H$ _9 n6 e - }
9 G& g* g% V8 _) C9 z. u, @8 g - }
复制代码
6 y! c9 m% \8 y; H. y; pDMA发送设计发送非常简单,都不知道该怎么介绍...发送对应DMA的方向是内存到外设,我们只需要把数据装入相应的内存中,调用发送的API即可: - /******************************************************
( Z1 [+ F6 b3 q1 ?) G- H1 M+ g) v - * Brief : DMA传输函数
- r* ]) F5 n1 Z; ^1 L - * Parameter :
$ n$ B s3 y9 K% p - * *pData: 要传输的数据" ]2 l4 s- A/ t4 O9 G
- * Return : None.
0 h* n; J; S! l - *******************************************************/
3 Z7 r% c1 [5 F - void User_uartdma_Transmit(const char *pData)' u$ A3 {! ~, ^! U
- {
9 E9 L9 `$ B/ ^/ z* I. Q# U - HAL_UART_Transmit_DMA(&huart1,(uint8_t *)pData,strlen(pData));0 l0 {+ A. y E8 t X1 D$ S; t
- }
复制代码 # A9 p2 w& |3 k+ c6 I0 }8 Q: b
根据DMA的原理,我们发送的时候是不影响CPU的正常工作的,效果应该类似于操作系统的多任务执行,现在我们只需要在初始化时候打开DMA发送数据,串口助手监控,主函数什么都不干,数据不断在发送 此时如果添加两个LED流水灯,是完全不受发送数据影响的,效果就不放了,相信小飞哥... & m! q: M, i o. Z' T* W: O
2 A( j" V" b7 `6 C5 d$ ]7 Q" B% j4 z
! n5 i R7 Q/ p G$ O$ ?$ z' y9 }' y不信你看~
9 ~7 Y; Y8 O' g! t! P# n
4 E9 n0 V5 K! ]2 D4 ?/ k
 发送的是非常简单的,好像这里也没体现出来,使用DMA发送有什么好处,其实在LCD驱动的时候,当有图片等大数据量的数据需要传输的时候,使用DMA是种非常好的方式 4 q2 K/ j& W8 D2 A- w
DMA接收设计聊起串口数据接收,我们最先接触的可能是,中断接收,来一字节进一次中断,通过定时器超时判断一帧数据结束。 但是小伙伴们有没有考虑过一个事情,数据量很大,串口一直进中断,对MCU带来的负荷是非常大的,这种方式就显得不那么美好了。 哲学上讲,矛盾是推动社会进步的源泉,没错,鉴于此种情况,我们换一种方式来处理,DMA+串口空闲中断的方式,我相信,这种方式你一用就会喜欢上~ 具体的设计思路是: 1、开启串口1中断 2、开启串口1空闲中断 3、打开串口DMA接收 4、判断空闲中断标志是否置位 5、数据接收完成,主函数打印接收到的数据 先来封装几个函数: - /******************************************************1 P+ ]" |7 ]# j6 R" J
- * Brief : 串口DMA 初始化,初始化除了cubemx配置之外的部分5 t- }/ B! V, n! w
- * Parameter :
1 o, d; |- r$ @, c) W - * : 9 }1 u1 s9 _- Y& b: U# J: w
- * Return : None.
3 ^8 p4 s% X+ z3 h, I c# F N - *******************************************************/ }' S$ r# @1 w% |: |4 J
- void User_uartdma_Init(void)
# C" U8 K" {2 L* n/ ^5 _( ^7 a - {* C% Y7 ^3 Z& I$ n! r8 H
- //失能串口DMA传输
0 T& m+ y S; Q6 z+ R - HAL_UART_DMAStop(&huart1);
# F" ?! z5 d0 ?4 h( x4 ` - //使能串口1接收中断; K3 G$ ~6 Q9 E! O) ]
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);' T9 o+ r6 _' U6 M
- //使能串口1空闲中断( T3 S2 W8 U P/ z/ z9 n: [
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);9 K, J! G, I, C w
- //使能串口DMA接收
2 _% z) i6 q7 V& L1 a: k - HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
' A0 V; n3 ~2 L0 n: {+ P, j4 I+ t - }
复制代码- /**7 u2 p2 J& [3 R Y, g
- * @brief DMA接收函数
5 _: s1 }9 D% C- c& A. ~ - * @param htim TIM Base handle
9 u, O; f6 F; s8 n+ ~0 L0 _) K4 O4 p - * @retval HAL status
$ M9 P) K6 F9 V: i5 u9 @, J - */8 J8 u. V4 [3 v3 S
- void User_uartdma_Receive(uint8_t *recData,uint16_t rec_len). v6 T: d# {+ \ ?4 H% A
- {; Q6 h( p8 y! h
- HAL_UART_Receive_DMA(&huart1,recData,rec_len); {+ c; A2 z# `' p" V
- }+ i# A* e. a- r5 d+ p; ?
8 s# z# z1 R: v" ^1 C& Q1 H, V
复制代码
# h& v1 L' A7 e4 {5 P+ |主函数: - /**
4 H& f: x% `) r9 p - * @brief This function handles USART1 global interrupt.
: [+ R' n) `% P) Y; s! ?$ o - */; L" Z- T' Q7 o/ i6 Q# u- A
- void USART1_IRQHandler(void)7 X' [5 b- ~. Q0 f0 ~" j
- {$ R. y$ N3 X, m; E* W b" f, O4 w$ _) N
- /* USER CODE BEGIN USART1_IRQn 0 */2 b6 Z4 p+ D1 Y6 z! y
- uint32_t idle_flag_temp = 0;$ X. @0 F; w x) j8 x1 z$ w4 F
- uint16_t len_temp = 0;) W/ W Z6 G ^, h4 f% ~
- /* USER CODE END USART1_IRQn 0 */0 s/ M) H# c/ a& C8 I
- HAL_UART_IRQHandler(&huart1);
( L! A7 C& Q; K) A; q - /* USER CODE BEGIN USART1_IRQn 1 */
$ j$ b. Y9 {3 j w, v- ]" E. d5 r4 K2 k - idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);. S% A- w* O" @! c
- z$ l Y3 S! D$ v
- if(idle_flag_temp)
6 v N- Q# ?2 l: A# J; r+ O - {
3 {8 ?" S& Q3 K$ @) t3 \- ?# U3 D - __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);4 M5 ]: Y! ] [% U6 t$ I
- HAL_UART_DMAStop(&huart1);
+ {* E9 M" U- M# U* d* O - ( L3 V/ z7 @1 _. h2 t
- len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
, M2 m" D$ i& |0 t/ z1 X - # [" w w8 \% Q! @5 w
- UserUartDma.RecDat_len = Max_RecLen - len_temp;
$ q8 b! X) I* c: H; d0 a - UserUartDma.rec_endFlag = 1;# U& \0 b# o& u9 U# q5 h
-
% W" ?0 S* {( w. f# B5 }4 L - }: M: F6 F; _- \; @. p$ u4 M
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);! b# B0 L; ?4 I+ {$ D) I
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen); 8 _; w! H3 Q( ]& P; p
- /* USER CODE END USART1_IRQn 1 */* S# g/ P k, L# F0 _( A; Y8 N6 D
- }
复制代码
! R6 ~$ \7 ^: Q演示效果:
5 R: A) D) F5 j1 B L( R7 q' a/ J$ `2 J7 o, m1 H7 ?
g# |' y: e# s* j) t
2 ^0 P/ w r, S' u& K8 N! R7 q转载自: Embedded小飞哥
9 ]( \( b# S# Z* R/ t. ~% A如有侵权请联系删除! x5 U9 P3 F/ R. Y
+ v1 C, F# B$ l" [0 H( a. m2 [) F1 R
7 Z1 c1 r: @5 u
9 e E6 g2 q/ i) t3 C% g |