你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

(干货)串口DMA很难?那就一步步带你学会!

[复制链接]
li1229574727 发布时间:2020-5-15 00:15
串口DMA

* z5 D( ^* s& J$ ~! M

DMA利用好无疑会让串口使用起来更加高效,同时CPU还能处理自己的事情,但是DMA的使用却让代码变得更加的复杂,也使串口配置变得更加麻烦!麻烦???也许只是学习的方式不对,代码是变长了,但是思路清晰的话,也就是在原来串口加了点东西而已。本文让你轻轻松松学会串口DMA!定长数据传输与不定长数据传输都教会你!通过此文,希望能让更多人了解和会使用串口DMA,希望大家多多支持!

一、串口DMA的配置

本文针对串口2(USART2)如何进行DMA传输进行讲解,如果采用其他串口,注意要修改相应端口配置以及DMA通道。

1、串口的DMA请求映像

通过我之前的博文《 STM32 | DMA配置和使用如此简单(超详细)》可以知道,串口2(USART2)的接收(USART2_RX)和发送(USART2_TX)分别在DMA1控制器的通道6和通道7。下图为DMA1控制器的请求映像图。

1 U+ T; Y# |5 h6 \% a; w
7 L# X. U  E  i+ T
$ c: z# v/ i! d1 ^, C# B8 }9 d/ f" B

2、资源说明


: m9 j5 f' G* j% S3 J% @, O2 F9 Z: H1 B+ m" C5 r

STM32F1中,USART2使用的是PA2(USART2_TX)和PA3(USART2_RX),为了方便阅读,使用到的资源列在下表。

外设        GPIO口        DMA请求映像通道        备注

USART2_TX        PA2        DMA1通道7        RAM->USART2的数据传输

USART2_RX        PA3        DMA1通道6        USART2->RAM的数据传输

3、DMA初始化配置

USART2的DMA配置在《 STM32 | DMA配置和使用如此简单(超详细)》中分别讲了库函数版和寄存器版两种配置,我这里直接搬用,不理解的可以看看《 STM32 | DMA配置和使用如此简单(超详细)》这篇文章。要注意的是库函数最大优势就是便于阅读,所以在DMA初始化配置中我们使用库函数。

首先,我们要先定义三个缓冲区(作全局定义),一个发送缓冲区,两个接收缓冲区,两个接收缓冲区是为了做双缓冲区,目的是为了防止后一次传输的数据覆盖前一次传输的数据,并且留出足够的时间让CPU处理缓冲区数据。双缓冲在串口DMA中有着很重要的意义并起着很大的作用!

  1. //USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在头文件进行了宏定义,分别指USART2最大发送长度和最大接收长度
    * r' O- z: C9 y
  2. u8 USART2_TX_BUF[USART2_MAX_TX_LEN];         //发送缓冲,最大USART2_MAX_TX_LEN字节
    . C/ w4 h% T/ P( C4 j
  3. u8 u1rxbuf[USART2_MAX_RX_LEN];                      //发送数据缓冲区1# P% l  t7 {& L
  4. u8 u2rxbuf[USART2_MAX_RX_LEN];                      //发送数据缓冲区2  o9 y  I5 }; x
  5. u8 witchbuf=0;                                                    //标记当前使用的是哪个缓冲区,0:使用u1rxbuf;1:使用u2rxbuf
    : [% S# i  K/ v' _% H+ l3 v& _
  6. u8 USART2_TX_FLAG=0;                                      //USART2发送标志,启动发送时置1
    $ P8 P( L# x2 G+ i" ~  f
  7. u8 USART2_RX_FLAG=0;                                      //USART2接收标志,启动接收时置1
    $ ~& {  e1 G! x! l. f, {6 ?. @2 O& b8 c9 J
复制代码

要说明的是,实际上发送缓冲区可能用不上,因为我们要发送的数据内容和大小都不一定每次都是固定的,可以是变化的,我们只需重新指派新的发送缓冲区地址即可,但是在初始化中我们还是要指定一个发送缓冲区地址。
; H, Z4 w$ F0 K% t' ?5 O$ R下面是DMA1_USART2的初始化函数。

  1. void DMA1_USART2_Init(void)! s9 r$ i* n  x. |
  2. {& b  t5 R. E$ I4 Q
  3.         DMA_InitTypeDef DMA1_Init;
    ; C4 {& H) O1 b8 g/ s
  4.         NVIC_InitTypeDef NVIC_InitStructure;+ b# H) \( @8 _% E8 Z% r
  5.         
    ' o& R2 L& V0 E( c* W; @
  6.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);                                                //使能DMA1时钟
    ' Z1 u: Z0 u' L6 O) _

  7. 7 t. u6 z+ t# b3 j- F+ r
  8.         //DMA_USART2_RX  USART2->RAM的数据传输
    ) A) {% s% _+ y3 C
  9.         DMA_DeInit(DMA1_Channel6);                                                                                                //将DMA的通道6寄存器重设为缺省值 2 _) @+ \; _% b: ?, u' h! A
  10.         DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                                        //启动传输前装入实际RAM地址
    ' N( P7 i3 H3 B. H
  11.         DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf;                                            //设置接收缓冲区首地址8 O8 w% G+ M$ Y. l3 U9 l9 _* t
  12.         DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC;                                                                //数据传输方向,从外设读取到内存0 v7 L3 s5 R8 p2 T) v
  13.         DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN;                                                        //DMA通道的DMA缓存的大小4 j. `/ o8 K. n8 _; ~* @$ o) p
  14.         DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                //外设地址寄存器不变8 e; v; b+ j2 E2 O
  15.         DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                                        //内存地址寄存器递增
    * e4 r; \5 S+ E' b
  16.         DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                        //数据宽度为8位
    8 V0 _3 a4 B# Y9 X- x, ?5 k8 N* S
  17.         DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                                        //数据宽度为8位
    5 L3 M4 s0 {! `) y8 g9 T
  18.         DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                                        //工作在正常模式
    & q' b9 f! G1 S. s. `# i5 I
  19.         DMA1_Init.DMA_Priority = DMA_Priority_High;                                                         //DMA通道 x拥有高优先级
    " ]" {4 w* L1 e& b# z
  20.         DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                                        //DMA通道x没有设置为内存到内存传输
    - I9 j7 N, K& r7 A& u( J
  21.          + E) o/ a# _) b- @
  22.         DMA_Init(DMA1_Channel6,&DMA1_Init);                                                                         //对DMA通道6进行初始化
    ( |: O% s7 F* |5 H
  23.         
    ' `, C% J7 _% D
  24.         //DMA_USART2_TX  RAM->USART2的数据传输6 l/ W6 E! R+ j- @( n
  25.         DMA_DeInit(DMA1_Channel7);                                                                                                //将DMA的通道7寄存器重设为缺省值
    0 b: w. k5 j) A; b- l. L
  26.         DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                                        //启动传输前装入实际RAM地址
    * _2 g* _" ^, e
  27.         DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF;                              //设置发送缓冲区首地址
    4 d4 h* J2 F/ H9 T6 ~0 }
  28.         DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST;                                                                 //数据传输方向,从内存发送到外设3 V+ V: f7 ^0 t) n. c
  29.         DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN;                                                        //DMA通道的DMA缓存的大小4 _! a" g  w9 v8 W
  30.         DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                                //外设地址寄存器不变9 X& I! K* P" T
  31.         DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                                        //内存地址寄存器递增
    & D9 v2 v6 h) C  y/ j; d
  32.         DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;                        //数据宽度为8位( F, ]0 B0 j4 z# ?& V
  33.         DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                                        //数据宽度为8位% ^& k  J3 G+ q: Q. G, x; [
  34.         DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                                        //工作在正常模式# n/ y' h& N1 f6 R7 T  g0 y
  35.         DMA1_Init.DMA_Priority = DMA_Priority_High;                                                         //DMA通道 x拥有高优先级
    ; n0 _+ z1 T+ A* w' {5 h/ N
  36.         DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                                        //DMA通道x没有设置为内存到内存传输, ~+ z; S2 G7 _6 f9 z
  37. $ v5 J+ c: j- D! G" V& H- C
  38.         DMA_Init(DMA1_Channel7,&DMA1_Init);                                                                         //对DMA通道7进行初始化  e1 p# R  D3 u) }3 X
  39.         , w5 }2 Q8 M' @8 H4 D
  40.         //DMA1通道6 NVIC 配置
    $ k' @! R: a  Z; [5 d5 v
  41.         NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;                                //NVIC通道设置
      K) x% z2 G- x0 L7 E9 Q
  42.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;                                //抢占优先级
    ) v: Z& W5 s4 W; H
  43.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                                                //子优先级
    ; s* U& y  g4 Z3 K  v
  44.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                        //IRQ通道使能
    # g9 R  V) ]# ]
  45.         NVIC_Init(&NVIC_InitStructure);                                                                                        //根据指定的参数初始化NVIC寄存器
    ' j5 [1 B  ?. M( g& @3 Z! P* ?* s5 ~

  46. " }7 F9 G- b+ W
  47.         //DMA1通道7 NVIC 配置7 c4 U( S# f" I* B
  48.         NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;                                //NVIC通道设置
    4 y0 ]" A% F% a8 {* J. r
  49.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;                                //抢占优先级7 A* t6 i5 t7 U) t8 J, S) C
  50.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                                                //子优先级# W5 l8 y: U( l1 J7 D, n4 y$ A
  51.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                       //IRQ通道使能
    . X5 L/ |4 {9 E8 K
  52.         NVIC_Init(&NVIC_InitStructure);                                                                                        //根据指定的参数初始化NVIC寄存器- k* z; D! L8 e; c4 O% _6 G

  53. 9 w$ k# ^: t. }2 |& c9 U. N
  54.         DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);                                                        //开USART2 Rx DMA中断$ v7 T6 u- @" r. g) s
  55.         DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);                                                        //开USART2 Tx DMA中断: N, [' v( I% H! `% h
  56. # x# r; O% |* f4 K+ Z; D& S  D
  57.         DMA_Cmd(DMA1_Channel6,ENABLE);                                                                           //使DMA通道6停止工作
    6 U! Q8 R5 s$ H; Q) J
  58.         DMA_Cmd(DMA1_Channel7,DISABLE);                                                                           //使DMA通道7停止工作
    ! A: r' j) m) w. n! p; {1 w
  59.          8 l+ l8 a* J, O7 V% o
  60.         USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);                                                //开启串口DMA发送+ @3 U: h# j) N' U3 Z6 }
  61.         USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);                                                //开启串口DMA接收
    0 L6 Q& J. b' L: l% ^
  62. }4 H, p; d& L3 v" |% N1 b
复制代码

相应配置的讲解在《 STM32 | DMA配置和使用如此简单(超详细)》中讲解过,这里不再叙述,要注意的是,我们采用中断方式进行DMA传输。正常情况下,我们传输完数据要进行相应的处理,那我们怎么知道什么时候数据传输完成了呢?这时候就借助DMA传输完成中断。既然打开了中断,那一定要注意中断优先级,这里特别指出串口的中断优先级应低于串口DMA通道的中断优先级。

4、串口配置

前面已经完成了DMA的配置,而DMA是不能单独使用的,所以不要忘了配置要用到的串口USART2。

  1. void Initial_UART2(unsigned long baudrate)3 Z7 S9 P5 W" i% ]
  2. {' S3 p. l, I/ U7 l+ u$ _: P
  3.         //GPIO端口设置0 q5 K' v! \. t, f) N
  4.         GPIO_InitTypeDef GPIO_InitStructure;6 n$ @7 Q6 ?" C- U
  5.         USART_InitTypeDef USART_InitStructure;
    - K0 M9 i4 N# \% z" Y2 x6 z
  6.         NVIC_InitTypeDef NVIC_InitStructure; " O% a2 a1 U. T6 \
  7.        
    6 X# r5 c6 y2 C2 F. j) r7 x
  8.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE);        //使能USART2,GPIOA时钟
    " Y6 W( L1 m4 Y2 N- f( E$ x
  9.        
    1 ]# {  S* G; a* n; ^! n
  10.         //USART2_TX   GPIOA.2初始化9 I: k7 X# g/ |* E
  11.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                                                                                //PA.26 E- q$ E* J- M  a- G
  12.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                                                        //复用推挽输出7 z7 S! C% c3 r+ X& t7 h+ i* a; v
  13.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                                                //GPIO速率50MHz
    1 F# o7 ~% x- W, X1 Q! Z
  14.         GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                        //初始化GPIOA.2
    : F; O% x. x2 A! H
  15.         ( _0 @* W& v/ Q$ l6 o
  16.         //USART2_RX          GPIOA.3初始化
    % i4 J  \% R5 G3 {
  17.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                                                                                //PA.3. D6 g0 A6 u4 P
  18.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                                                        //浮空输入
    4 P$ q- d. Y) w
  19.         GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                        //初始化GPIOA.3
    ' A' j, n! I. m
  20.          - Q# i/ n: T7 R  k8 {) U
  21.         //USART 初始化设置
    8 p3 @$ X. j; A/ ?# b
  22.         USART_InitStructure.USART_BaudRate = baudrate;                                                                        //串口波特率; x4 i; ~$ N& R: R' r8 G* e: Z. N  s
  23.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;                                                //字长为8位数据格式6 t( }- f. D4 {  Y6 [
  24.         USART_InitStructure.USART_StopBits = USART_StopBits_1;                                                        //一个停止位
    ; i" f6 j5 q% n/ H( V  k1 O
  25.         USART_InitStructure.USART_Parity = USART_Parity_No ;                                                        //无奇偶校验位
    # T- o  [1 ^0 S+ |5 T9 \
  26.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;        //无硬件数据流控制
    3 y1 W$ L) K2 v$ G0 t+ `
  27.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                                        //收发模式
    ! @1 d" ?" T. B2 J2 G7 m
  28.         USART_Init(USART2, &USART_InitStructure);                                                                                 //初始化串口22 T3 k4 Q9 r, _& j$ N
  29.         ; e, _1 Q' J: z5 D
  30.         //中断开启设置1 ~7 X9 Q8 f  h6 t1 |/ A$ Q. t% [; T
  31.         USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                                                                        //开启检测串口空闲状态中断
    7 t5 U% x4 u6 @7 K* W" I) \
  32.         USART_ClearFlag(USART2,USART_FLAG_TC);                                                                                        //清除USART2标志位
    ( q3 @& Z# M# j1 Y8 T$ `
  33.        
    4 S1 k% G' ]( z3 ?2 n& S0 w
  34.         USART_Cmd(USART2, ENABLE);                                                                                                                //使能串口2
    4 Y' `% K! f& E$ K# y! b
  35.         ) O8 i0 r7 p  U& p- q) Z4 h# q$ x
  36.         NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;                                                                //NVIC通道设置
    5 f; E# j  e! G* P1 u/ R& I, R
  37.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;                                                //抢占优先级
    ! e3 {' h! b5 c
  38.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                                                                //响应优先级
    : ~# f8 h% w8 F# j# T9 O0 G
  39.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                        //IRQ通道使能
    9 I5 _1 ]. q/ o6 K& X
  40.         NVIC_Init(&NVIC_InitStructure);                                                                                                        //根据指定的参数初始化NVIC寄存器. V1 {; M- U& J9 ]
  41.         1 C: Q. v0 s! |- G
  42.         DMA1_USART2_Init();                                                                                                                                //DMA1_USART2初始化" e: O6 j& e4 h8 H" }+ ?+ g
  43. }; C& D7 |# O2 l3 `: ?1 {
  44.   T  @; G2 ~1 @0 y! @1 L
复制代码

可以注意到,我这里定义的串口中断抢占优先级低于DMA中断的抢占优先级。细心点可以看到,我开启了串口空闲中断。为什么呢?因为通常情况下我们是不知道接收数据的长度的,这样我们是没有办法利用DMA传输完成标志位来判断是否完成接收,所以我们这里采用串口空闲中断来判断数据是否接收完成,接收完成了会进入串口空闲中断。串口配置的最后进行了串口2(USART2)DMA的初始化,这样直接初始化串口也直接包括了串口DMA的初始化,主函数初始化只需一步即可。

二、串口DMA的使用

。。。。。。论坛看起来太不舒服了,有的格式没办法设置,如果喜欢还是去CSDN博客看吧!

$ e5 e8 F% ~1 I3 F2 Q, e

原文链接:http://blog.csdn.net/weixin_44524484/article/details/106029682

5 p" g: ?$ A) g% G3 P7 a: o

以下为原文目录,一步一步教会你串口DMA!


5 J1 z, B/ _2 G- u$ M7 w. e! T3 u0 _( T8 u$ E: r2 D4 b3 H& W4 W# H
收藏 3 评论2 发布时间:2020-5-15 00:15

举报

2个回答
li1229574727 回答时间:2020-5-15 00:21:46
受不了,代码怎么整都是不整齐的!
李康1202 回答时间:2020-5-15 08:37:39
都是干货

所属标签

相似分享

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版