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 至少可以得到一半的系统总线带宽。 ( k7 X; M8 k2 b% |, j
: ^4 Q7 x' _+ h; H- U E' m
- DMA 处理, k% v- i2 J6 d) i5 [" p% W7 `5 q2 t6 }- P
在发送一个事件后,外设向 DMA 发送一个请求信号 的箭头。DMA 控制器根据通道的优先权处理请求。当 DMA 需 送请求的外设时, DMA 控制器立即发送给它一个应答信号。外 号后,立即释放它的请求, 同时 DMA 控制器撤销应答信号 7 x% Q. V+ Z L o4 e
- 仲裁器" J0 L8 A; t0 c7 {& Y( y1 n3 [3 [1 g
一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数据等信息。如果我们需要同时使用同一个 DMA 控制器多个外设请求时,那必然需要同时使用多个数据流,其中哪个数据流优先, 此时由仲裁器来选定。 仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时可以通过寄存器设定它的优先级别,可以在 DMA_CCRx 寄存器中设置, 有最高优先级、高优先级、中等优先级和低优先级四个等级。第二阶段是硬件,如果两个请求有相同的软件优先级,则较低编号的通道比高编号的通道有较高的优先权。例如:通道 2 优先于通道 4。
. ?+ m* w9 p- t6 d h, m
- DMA 通道' h4 q9 u. ~& W. T% m# k$ i
每个通道都可以在由固定地址的外设寄存器和存储器之间执行DMA 传输。DMA 的传输数据量是可编程, 可以通过 DMA_CCRx 寄存器中的PSIZE 和 MSIZE 位来进行编程,数据量最大可以达到 65535。DMA 的外设繁多, 例如 DMA1 控制器,从外设产生 7 个请求,通过逻辑或(例如通道 1 的三个 DMA 请求,这几个是通过逻辑或到通道 1 的,这样我们在同一时间,就只能使用其中的一个)输入到 DMA1 控制器,此时只有一个请求有效。
9 C3 [4 T1 m: I/ P& |
5 q# e: K) y8 a! w! M6 w! }7 Y8 ]( r% f
STM32 的 DMA 有以下一些特性: ●每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能 通过软件来配置。 ● 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如 在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 。 ● 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和 目标地址必须按数据传输宽度对齐。 ● 支持循环的缓冲器管理 ● 每个通道都有 3 个事件标志(DMA 半传输, DMA 传输完成和 DMA 传输出错),这 3 个 事件标志逻辑或成为一个单独的中断请求。 ● 存储器和存储器间的传输 ● 外设和存储器,存储器和外设的传输 ● 闪存、 SRAM、外设的 SRAM、 APB1 APB2 和 AHB 外设均可作为访问的源和目标。 ● 可编程的数据传输数目:最大为 65536
8 P6 I; E, o+ n( N' B i/ e STM32串口DMA使用详解本次我们使用的硬件环境是之前开源的板子,falling-star board,使用串口1。 3 x* }1 U% I7 m3 ~- l. o
cubemx配置关于时钟配置、串口基本配置请参看:cubemx的正确打开方式一文 接下来直接进入配置串口DMA: 选择串口1,基本参数如图,都是老生常谈了,easy~ # ^8 S& @& U* ?7 Z% Q
' w3 U- J( v$ t' y/ S
+ ~3 l5 o3 g9 c8 P9 s2 X/ U, m选择DMA Settings,主要有一下几个地方,基本上不需要改动,根据自己的使用情况确认即可,需要注意的是,发送和接收并不是一定要成对出现的,可以只选择DMA发送或者DMA接收 , o" S5 b i" [5 q' o3 o4 T4 z: D* r
& ~+ j! A' w7 c+ [4 J
0 s+ D& f1 ?1 y3 Y! |
中断设置,DMA中断可以配置,可以不配置,同样也是根据自己的实际需求情况,串口中断需要配置,下面会用到,优先级根据自己任务的优先级确定,分好“轻重缓急”即可 + N5 h" u, n$ n1 U4 ]
' E# _ A5 w; O& \
6 d7 [( t; S2 P配置非常简单,主要是在此前串口功能基础上添加DMA功能,over # o; \* k3 F! V7 e7 p* g5 t4 A
串口DMA代码设计串口DMA源码API介绍上面提到的配置项,都封装在一个结构体里面 - /** @defgroup DMA_Exported_Types DMA Exported Types U) O, d8 f/ L* I* T, [
- * @{
/ y# f* D: H( u5 F+ ~ - */ o0 L" m" W8 [9 r
- - k/ Y9 Q: ~: Y
- /**
, k1 _$ p, Y! q6 o G - * @brief DMA Configuration Structure definition4 g( g- u0 x8 R3 w" S
- */4 c, V4 j a8 T4 m, @3 b
- typedef struct
6 T* B/ D9 G6 \% g - {+ |, J. k: c4 @
- uint32_t Direction; /*!< Specifies if the data will be transferred from memory to peripheral,
4 A6 h H" K9 y1 j! F - from memory to memory or from peripheral to memory.) Q0 G3 u6 c, d6 {. q V
- This parameter can be a value of @ref DMA_Data_transfer_direction */& `: b$ W) _! e: [* }: p
4 _, o! U* E* i; R3 q$ Y$ m- uint32_t PeriphInc; /*!< Specifies whether the Peripheral address register should be incremented or not.
- n6 x0 ] r9 A' ?& F) j$ ?6 O - This parameter can be a value of @ref DMA_Peripheral_incremented_mode */
3 u/ ~$ I/ L2 m! m; R; Q - 0 u" @, ^6 x9 {
- uint32_t MemInc; /*!< Specifies whether the memory address register should be incremented or not.
+ _2 e* m$ Y: ~/ r; C% w8 s' O/ C - This parameter can be a value of @ref DMA_Memory_incremented_mode */
8 H% D5 c9 H' \; o) Z- {
: K" }# n+ z1 R0 g% L- uint32_t PeriphDataAlignment; /*!< Specifies the Peripheral data width.
2 ^! Q3 ?; F" d: R, R - This parameter can be a value of @ref DMA_Peripheral_data_size */8 L7 A) r$ \$ f( I1 I4 o- M
- {4 o6 f: M$ W. R1 q
- uint32_t MemDataAlignment; /*!< Specifies the Memory data width.: Z \( C# z# a
- This parameter can be a value of @ref DMA_Memory_data_size */8 v' o- w6 G7 |# B& ~
3 ~% C% x3 H3 ]8 x6 ?1 X- uint32_t Mode; /*!< Specifies the operation mode of the DMAy Channelx.3 K0 I/ E+ g/ n( J3 Q
- This parameter can be a value of @ref DMA_mode
, A: E& a& @4 T: h S* u) h - @note The circular buffer mode cannot be used if the memory-to-memory% O2 ?' y' S/ i! }
- data transfer is configured on the selected Channel */
. x, `: ^: J' Z5 g6 E. Z0 o: u - 0 e' {' C+ Q1 J
- uint32_t Priority; /*!< Specifies the software priority for the DMAy Channelx.
2 U) Z% K/ i# f% m: ` - This parameter can be a value of @ref DMA_Priority_level */
8 ` T& u( c8 o3 ?/ ]4 ?7 H - } DMA_InitTypeDef;
复制代码 $ H8 ?, \- j. }$ e# L; Y8 t
关于DMA的函数不多,本次用到DMA的初始化,开始发送函数
& h) |* [3 V7 W$ ?; B! ?# U
& P$ v9 z; t- i( ?+ x3 U2 s) P
y! x8 K: p' Y* l" C: q
串口中关于DMA的部分主要有这几个函数,还有一些关于中断、DMA标志等的一些宏定义,就不在一一列举了,需要用的时候大家知道去库函数中去找就可以了
! ~! f9 j% e% C& J2 N
/ v4 k" S8 Q8 \/ K9 }, h, {- v
 串口DMA初始化部分: - void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
% y4 w0 y3 a# P( U7 Y - {7 g v; A) h& ?( @3 H
: W/ j1 }7 C8 P" G. T0 Q* [- GPIO_InitTypeDef GPIO_InitStruct = {0};6 _! C% W3 ~7 u
- if(uartHandle->Instance==USART1)1 a! e/ X. o3 \! C; K8 Z5 K
- {
6 G) ~) T) O) k2 N4 r9 K - /* USER CODE BEGIN USART1_MspInit 0 */
" y/ i3 M+ U/ ^; v0 R$ @ - ) ^* W/ Q/ ]7 q' j8 K9 A; B
- /* USER CODE END USART1_MspInit 0 */
) f& S6 {$ g: J" U1 @ - /* USART1 clock enable */
0 H' m: M( a( ~ - __HAL_RCC_USART1_CLK_ENABLE();
9 w5 q" u' i' L. ~& N8 U - ' {( ]4 G1 Q; i, S" W$ ^
- __HAL_RCC_GPIOA_CLK_ENABLE();
8 K' W, r8 `1 K& M9 t - /**USART1 GPIO Configuration
' B! Y$ ~: z" T - PA9 ------> USART1_TX8 l) e' S- k8 j% x' z% N1 i* R
- PA10 ------> USART1_RX
1 z+ d& @1 g. f* `: ^- c5 L - */
; u4 b1 G/ \# Z% i8 O - GPIO_InitStruct.Pin = User_UART_TX_Pin;3 r9 u: w# a$ |# X$ I
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
% h% R6 X' \2 i$ V2 v; }1 E# ^ - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;* C9 \. t2 i8 \) v- A
- HAL_GPIO_Init(User_UART_TX_GPIO_Port, &GPIO_InitStruct);. o- K: C0 G- v j9 o8 Y4 P2 U9 K* _
* U) A/ H& F* ]: l$ x& U Y" Y: |3 _- GPIO_InitStruct.Pin = User_UART_RX_Pin;, R' k4 i* i7 w' ^5 b
- GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
0 h% C/ m* h$ [2 u3 b% {; w3 Y3 T9 \ - GPIO_InitStruct.Pull = GPIO_NOPULL;
9 j8 b8 N/ ~; @6 {0 h; d - HAL_GPIO_Init(User_UART_RX_GPIO_Port, &GPIO_InitStruct);
# w" i& q0 A0 ^9 \- C! C - * \1 ~+ a1 P# k" t: Q
- /* USART1 DMA Init */$ P1 J% x. U `) X% q: l# Y
- /* USART1_TX Init */! h4 `7 `% g' n
- hdma_usart1_tx.Instance = DMA1_Channel4;
! ~; o1 L( ?0 p7 M0 S# G - hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;9 j2 Q. `. w3 H. u6 j
- hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;* X) H" J: }# X' g
- hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
5 \& o. J6 S% y" Q$ s - hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;# J. S3 D+ d) q/ t4 W6 W
- hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
6 C* E& o" q7 @" B8 | - hdma_usart1_tx.Init.Mode = DMA_CIRCULAR;
0 W' o! x: W+ a2 V% r - hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
, ]* m+ `3 C* [* Z; ~ - if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)! H( u; z; Q8 m- r1 @5 b
- {
0 a* Z- E# B- k! S - Error_Handler();$ m, A3 H3 J$ ^2 b, y
- }" s! y5 C. @& u6 l
- + j4 P0 H- ~, @+ p* [ @( C( f
- __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);: d( r2 G5 Z8 O% T/ ?) m0 k
- - O$ [8 E+ n4 }1 Y
- /* USART1_RX Init */
5 t3 p/ j: w& v2 c. _! Q8 F" z! N, }( X - hdma_usart1_rx.Instance = DMA1_Channel5;
1 Y0 N& H/ F) z. `' { - hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;" e/ K! B W1 @1 F
- hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
" ]: @# y/ {% \+ g - hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;# n& v+ @/ A- z+ P' K& R& w
- hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
% b8 z A' O1 q9 x - hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
9 O5 a" V! @4 W* A9 ?, U4 y1 T - hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
: g1 i8 ^, ^0 Z9 M2 M( l8 z u - hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;9 K7 Q4 l+ U0 _
- if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
/ X0 ^8 h/ G J$ s' _. f - {' g& T# N. A2 @
- Error_Handler();* j5 j+ v- f& f
- }6 U) ~- U6 R* ]) F& r
8 c" K5 p! X, \9 F4 }- __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);& |/ K( G9 s L! o2 U* t* f
~0 m2 O5 D. g" O; E- /* USART1 interrupt Init */, ]( {- n4 l- X- H( L, E G( J
- HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
/ i) ^/ z" k. N0 V! } - HAL_NVIC_EnableIRQ(USART1_IRQn);
0 b9 r+ L5 K+ c& c5 C$ |/ g - /* USER CODE BEGIN USART1_MspInit 1 */
. r' L5 K B+ c6 X( ~& n
$ Q+ ^9 N+ |# u) V* W- /* USER CODE END USART1_MspInit 1 */
# \, I: w3 X+ _ - }9 L" b; k: o' q$ i* {; {3 n
- }
复制代码
4 k* }. ^0 R( ~8 f, o8 n3 KDMA发送设计发送非常简单,都不知道该怎么介绍...发送对应DMA的方向是内存到外设,我们只需要把数据装入相应的内存中,调用发送的API即可: - /******************************************************
e! Y$ j5 c: x6 c8 V - * Brief : DMA传输函数
$ ?" n8 c+ i# O1 s8 F) U+ X - * Parameter :
$ }; [0 x! f- \# ]$ U - * *pData: 要传输的数据
8 c _# E4 }4 l - * Return : None.
# {1 g. N/ t5 G- K# _( }% N# X. h - *******************************************************/# K6 T4 U$ d! V7 A z
- void User_uartdma_Transmit(const char *pData)6 K( W' D/ y/ F
- { % ]6 y' r) ~7 @- k
- HAL_UART_Transmit_DMA(&huart1,(uint8_t *)pData,strlen(pData));
/ ?$ m3 ?; ^4 K7 g# {: M8 T9 F - }
复制代码
1 U! D1 J, T1 A2 c- L8 n根据DMA的原理,我们发送的时候是不影响CPU的正常工作的,效果应该类似于操作系统的多任务执行,现在我们只需要在初始化时候打开DMA发送数据,串口助手监控,主函数什么都不干,数据不断在发送 此时如果添加两个LED流水灯,是完全不受发送数据影响的,效果就不放了,相信小飞哥...
; s. U( g0 R8 ^( }! D' u5 a
9 g7 b( ^( |; r: A& f0 ]
4 d, h' A4 D) O2 F3 Z6 p/ H不信你看~ + Z1 H R3 w' w, f6 O
: v* i2 ?$ l: t! }- s; ]1 G
 发送的是非常简单的,好像这里也没体现出来,使用DMA发送有什么好处,其实在LCD驱动的时候,当有图片等大数据量的数据需要传输的时候,使用DMA是种非常好的方式
* |( Q$ q |4 B, \ DMA接收设计聊起串口数据接收,我们最先接触的可能是,中断接收,来一字节进一次中断,通过定时器超时判断一帧数据结束。 但是小伙伴们有没有考虑过一个事情,数据量很大,串口一直进中断,对MCU带来的负荷是非常大的,这种方式就显得不那么美好了。 哲学上讲,矛盾是推动社会进步的源泉,没错,鉴于此种情况,我们换一种方式来处理,DMA+串口空闲中断的方式,我相信,这种方式你一用就会喜欢上~ 具体的设计思路是: 1、开启串口1中断 2、开启串口1空闲中断 3、打开串口DMA接收 4、判断空闲中断标志是否置位 5、数据接收完成,主函数打印接收到的数据 先来封装几个函数: - /******************************************************
# {) I/ S: t: H0 Z# t! u* X - * Brief : 串口DMA 初始化,初始化除了cubemx配置之外的部分
- [% K) }/ X5 w - * Parameter :
& ?" C# v7 H, ]) u O - * : & m1 |& `! T9 W) J: | ^0 b' E6 _$ q
- * Return : None.# T" L& s$ I3 ?' t0 i# K
- *******************************************************/1 W, d, _" a% M1 V
- void User_uartdma_Init(void)
! G2 H4 J3 N: n' L - {
4 J, }# F& q* h9 D/ H. @ - //失能串口DMA传输
8 M$ j, |/ l2 L - HAL_UART_DMAStop(&huart1);- h" T* w: j7 A9 Y% O7 K" t
- //使能串口1接收中断
2 P/ ^7 c+ E5 }$ z - __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);4 t" P3 f' M# [- d% v4 F
- //使能串口1空闲中断
5 g4 [& ]/ l. ~ - __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);; e! Z' o* A& y
- //使能串口DMA接收" U. A; B. N' \2 U) H' z
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
# k1 q& f4 |) D. E7 q* d% ?8 J# X - }
复制代码- /**6 X" t: W' u/ V
- * @brief DMA接收函数) u; \$ B. j% H8 u1 @/ Y9 w
- * @param htim TIM Base handle
: X) C6 c, p! M9 Q- h" R - * @retval HAL status" ]) ^% I6 K$ I+ ~$ {$ P* n4 {% t6 \
- */
! e& Q1 @; A; Y. w - void User_uartdma_Receive(uint8_t *recData,uint16_t rec_len)
$ o# c3 N* z/ K% w8 I) G - {2 Q, q1 u) b' v' `
- HAL_UART_Receive_DMA(&huart1,recData,rec_len);
. R, ^7 j0 m0 G" M - }
) w0 d* w. B+ ?9 U9 B) T1 F* s: }
* x9 b. Z z8 i( W, D/ e: E
复制代码 * v' L8 q! s) K6 s5 O, k
主函数: - /**
6 v5 ^( n9 F. }& ?" M3 c - * @brief This function handles USART1 global interrupt.
: y. U1 o3 U! y9 i9 \- ~ - */+ J% U6 L+ l- b: S2 L
- void USART1_IRQHandler(void)$ X* [ n+ _4 S8 B
- {
2 L, r( ~; ^) G Q, q( _/ b7 Z; J: ^ - /* USER CODE BEGIN USART1_IRQn 0 */
$ h& b/ ]7 i! W* [! G h$ A - uint32_t idle_flag_temp = 0;
( Z/ e- J) o; Z4 M$ Z, I - uint16_t len_temp = 0;
6 O# t: ]3 E, j- X8 C7 N5 G - /* USER CODE END USART1_IRQn 0 */) B+ C: I8 ?* D7 K9 F/ c
- HAL_UART_IRQHandler(&huart1);; B( L* a( _; W n/ b: f
- /* USER CODE BEGIN USART1_IRQn 1 */5 {! l7 o! U0 k/ C& e8 ~
- idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
, Q$ h8 J: F/ g$ A6 ?" C - & W: u+ y9 H5 p& U a; F6 c4 o
- if(idle_flag_temp)
& D" [, F' l9 L O4 S+ J, m - {
& w% ~9 [8 Z1 o( S) F - __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
& K, m6 h8 g: \4 M - HAL_UART_DMAStop(&huart1);6 Q# a: o) O l6 Q) C
-
/ F5 a; M6 C4 p) X8 L - len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);( [! l0 c) D- }: q4 l
-
7 p, Q) x* [& S. ~! y - UserUartDma.RecDat_len = Max_RecLen - len_temp;
; m$ v2 {& q% L' Z& g. B - UserUartDma.rec_endFlag = 1;
# g, M. G& e, }2 m, ?( _ - 0 g5 A }" L* ]6 Q
- }
/ q: D& ~" y- P( z$ Z( K- y' N' B - __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
6 v* C9 _ z! |3 K& Z3 t - HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen); $ ~) |/ ]# j# d, a; l: H& w. k
- /* USER CODE END USART1_IRQn 1 */
: {$ p) j9 {7 Y3 d) B4 U8 `: M - }
复制代码
2 g" B+ ?; a# z0 j6 b' ]5 I" ?演示效果:
# k6 b+ f8 t- {. K8 Q# h$ T
+ }1 d8 h0 ]) c
! [% u4 q \8 H
, S' V4 d" y- g" v
转载自: Embedded小飞哥
4 ]; B; X; z3 J! S$ d如有侵权请联系删除
3 s5 @5 o, c3 x, c6 F
1 s. e5 @2 k3 h. H1 n l
. y; K# Q8 G. G* o, B; D6 h+ O: G! T/ v: t
|