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

【经验分享】STM32单片机实现DMA+ADC+UART功能

[复制链接]
STMCU小助手 发布时间:2022-3-20 13:43
        突然想测试一下STM32单片机ADC采样速率问题,按照常规方法,可以通过ADC采样,然后将采样值打印出来。但是这种方法在处理和打印数据的时候会占用很多时间,导致处理数据的时间超过了ADC的采样时间。于是想到了ADC采样的数据用DMA功能存储,并通过串口打印。但是串口打印依然要占用单片机时间,那能不能串口数据的输出也采用 DMA功能呢?这样ADC采样的数据通过DMA直接存储,然后串口通过DMA功能直接输出采样到的数据。这样速度程序执行速度不就极大的提升了吗?说干就干,使用STM32F103C8T6单片机,标准库函数,keil5软件,编写一个测试程序。
; |4 P) S: Y5 A. w9 p
7 H0 a8 B9 x$ M6 [3 K( _      首先实现ADC采样并通过DMA存储
, `+ d5 v. G% W9 @6 j4 P, x; F. `% D% O* g; S
  1. #ifndef __ADC_H9 E- w# a7 a% q4 k6 T9 S0 d* x: S
  2. #define        __ADC_H
    + l% G6 b2 J* c" H, C4 }
  3. 2 t  m- }3 r7 ]; {" B" |
  4. #include "stm32f10x.h"
    4 B% D" P$ p7 a5 i( x2 V+ K
  5. // 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响2 @: c1 X5 @$ t. B4 }. i9 F( e9 C
  6. /********************ADC1输入通道(引脚)配置**************************// u( j$ Y2 @% e
  7. #define    ADC_APBxClock_FUN             RCC_APB2PeriphClockCmd
    $ A, Y( z" q1 u+ v3 G" Z
  8. #define    ADC_CLK                       RCC_APB2Periph_ADC15 N0 }" T9 t& N5 F

  9. & |# u4 X9 C- b& O4 ?3 K1 i: g  T+ f
  10. #define    ADC_GPIO_APBxClock_FUN        RCC_APB2PeriphClockCmd. E( @3 i  s4 I
  11. #define    ADC_GPIO_CLK                  RCC_APB2Periph_GPIOA: Q( a1 e1 {8 k& H
  12. #define    ADC_PORT                      GPIOA
    9 h8 \2 H  g2 Y/ [! [6 u

  13. + S5 X; l5 M: J4 }
  14. #define    NOFCHANEL                                         1                                                        //使用一个通道测试' O; f2 Y2 p  W5 n/ c  B
  15. ) c8 G/ M! B. }' X+ @: }2 n
  16. #define    ADC_PIN1                      GPIO_Pin_09 m) q7 f" K7 B' ~+ O
  17. #define    ADC_CHANNEL1                  ADC_Channel_0
    & O) i# g% y! C3 @6 x
  18. # ^! b$ L. U) C3 L# P
  19. // ADC1 对应 DMA1通道1,ADC3对应DMA2通道5,ADC2没有DMA功能
    5 v8 [( \$ _7 W
  20. #define    ADC_x                         ADC1
    & I9 J( \. n. s% ^6 A
  21. #define    ADC_DMA_CHANNEL               DMA1_Channel1
    & \7 u8 n* Y" I
  22. #define    ADC_DMA_CLK                   RCC_AHBPeriph_DMA10 l* e6 e# ^4 F( _% @# w

  23. + d1 A. Q1 X: R9 P8 r
  24. // ADC1转换的电压值通过MDA方式传到SRAM
    - Z6 P* [, y6 e* m7 R
  25. extern __IO uint16_t ADC_ConvertedValue[NOFCHANEL];- E/ U; m) Q$ W8 y: |
  26. ; z/ p  P/ ~# ?* u9 `0 z9 i' y
  27. /**************************函数声明********************************/1 _4 H6 {+ ]* v* Z; s
  28. void               ADCx_Init                               ( void );
    * L/ o! J# w9 Q# |: P
  29. #endif /* __ADC_H */
复制代码

5 u; ]0 x# Z/ g" `; J/ I首先在头文件中定义用到的时钟和端口,如果要修改采样的AD口时,直接在头文件中修改就行,程序中就不需要修改了,方便代码的移植。下面编写ADC代码。7 E- ?, ]" Y% n; m' M6 ^

/ ^' ?. ]; o1 |
  1. #include "bsp_adc.h"
    ; D- p- @2 c5 D* a0 }. i
  2. __IO uint16_t ADC_ConvertedValue[NOFCHANEL] = {1000};
    % j2 L; J, b! b. [( @- E% L
  3. /**
    5 W$ g- u- \& e1 l8 B+ O
  4.   * @brief  ADC GPIO 初始化; @. z: x/ O. B
  5.   * @param  无9 m) _9 K- [8 m6 [! J! _
  6.   * @retval 无
    3 U# j8 O# v8 _* _( [& g
  7.   */4 g2 U9 X) _) W. [2 k! {# ~2 |
  8. static void ADCx_GPIO_Config( void )
    8 w* M+ J+ H) k
  9. {
    . p0 |' t( [0 F
  10.     GPIO_InitTypeDef GPIO_InitStructure;) W4 j- J0 I/ N* I8 s1 O, Z
  11.     // 打开 ADC IO端口时钟8 t- Q7 |6 \) O9 v1 v/ `
  12.     ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
    2 B5 ]  `. x8 z' \
  13.     // 配置 ADC IO 引脚模式* p- n. w, F/ {9 O3 V+ D6 [3 d
  14.     GPIO_InitStructure.GPIO_Pin =         ADC_PIN1;8 M1 ?5 P, L, [5 x1 F
  15.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;7 X, y3 R7 n) C0 Z
  16.     // 初始化 ADC IO
    * F! c0 U5 Y. a$ W: W/ y
  17.     GPIO_Init( ADC_PORT, &GPIO_InitStructure );
    ' c3 L, ^7 V3 v" S
  18. }% s1 }8 p, x0 @. q8 M* h8 ?
  19. /**0 p' c" f6 e2 z' A( E
  20.   * @brief  配置ADC工作模式
    1 h! B1 b  G  U+ E! v8 E) n3 f
  21.   * @param  无
    ! e# E# i  {  r+ \& `9 W) T
  22.   * @retval 无
      F) a& ~# }. L! e
  23.   */
    , f- @) c& k/ ^0 G
  24. static void ADCx_Mode_Config( void )  g5 J, \( C" r) @2 D
  25. {
    ) N8 i3 g5 O( n7 i, r; s8 b  C
  26.     DMA_InitTypeDef DMA_InitStructure;! D7 L. g0 C% [9 M0 y
  27.     ADC_InitTypeDef ADC_InitStructure;% Y( M0 M& u% j& z- D4 f* o

  28. . }- b0 u+ c3 {1 B9 m
  29.     // 打开DMA时钟
    * r, ~& `3 [$ `% Y) _$ R! q. }; R
  30.     RCC_AHBPeriphClockCmd( ADC_DMA_CLK, ENABLE );
    " |0 p! w/ C) z8 l
  31.     // 打开ADC时钟
    9 j' e1 h' u" Z9 \9 z/ J/ \" b
  32.     ADC_APBxClock_FUN ( ADC_CLK, ENABLE );2 l/ u! `9 U% L
  33.     // 复位DMA控制器
    6 Q, L; u4 P4 P! k* [
  34.     DMA_DeInit( ADC_DMA_CHANNEL );
    . q0 j% j( T% x' f+ z
  35.     // 配置 DMA 初始化结构体
    : h0 Q& @# X* h" Y7 ~; g
  36.     // 外设基址为:ADC 数据寄存器地址
    / q& c( W; G% D* i8 F+ a
  37.     DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );8 m- ]4 x& l: r0 Z4 S7 Z
  38.     // 存储器地址9 |' f2 b" O# O9 e. \7 @" B
  39.     DMA_InitStructure.DMA_MemoryBaseAddr = ( u32 )ADC_ConvertedValue;# P* r8 J  g, i+ U/ ]
  40.     // 数据源来自外设
    - |' @" }" q" g# e' \1 h. {
  41.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    % ^) ]# N. T; A+ }  _! Z
  42.     // 缓冲区大小,应该等于数据目的地的大小" m& {' q: l% e7 F8 G0 G
  43.     DMA_InitStructure.DMA_BufferSize = NOFCHANEL;* `4 a% u0 A( R
  44.     // 外设寄存器只有一个,地址不用递增" l5 S1 I! C! h) K( ?% k
  45.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;! ~$ A! z' h, ?9 [. L9 U  ^" K9 x
  46.     // 存储器地址递增, y$ \$ O" S* p  C$ o, z( {8 N: t
  47.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    ) ~" f8 C5 E5 q* e* n& y
  48.     // 外设数据大小为半字,即两个字节+ ]4 l0 A) Z% o) d$ T+ U
  49.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;, l# t7 t) d0 F  q6 X  [/ [
  50.     // 内存数据大小也为半字,跟外设数据大小相同$ A, w) [  k- o, M5 W: t* d
  51.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;& l0 T7 ~5 P4 }# u9 R0 \3 e  |9 ?3 B
  52.     // 循环传输模式! T' h6 a5 K: |) |
  53.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    * O7 m/ p3 y/ u& W6 u
  54.     // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响; D; P" `& r' q7 m  L, \
  55.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;+ z9 d, S4 Z  l& T) r7 [" o
  56.     // 禁止存储器到存储器模式,因为是从外设到存储器# g5 ]8 r4 e1 Y/ B- ^. m0 W) u# _
  57.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;, v  D: r2 B7 i' w$ q* z! X4 ^
  58.     // 初始化DMA
    8 @5 m  X. M6 {# ?0 G
  59.     DMA_Init( ADC_DMA_CHANNEL, &DMA_InitStructure );
    7 V3 R# `1 w% s( v8 M
  60.     // 使能 DMA 通道5 B, K( J' I( @1 f6 S
  61.     DMA_Cmd( ADC_DMA_CHANNEL, ENABLE );+ i6 h& ]  X/ {# q. P$ P7 ^; H
  62.     // ADC 模式配置
    : G2 Q3 H6 i/ B7 q  X$ Y
  63.     // 只使用一个ADC,属于单模式
    0 }- s( ?7 ?4 p& O5 t
  64.     ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;. {: @* R" z% {) J; t  B6 J4 A
  65.     // 扫描模式
    % R: y' B7 E2 F- |8 r0 I
  66.     ADC_InitStructure.ADC_ScanConvMode = ENABLE ;7 s4 n! T  F: F2 s3 ~9 W4 M
  67.     // 连续转换模式, z- y3 y3 V; S' q7 a7 b( G2 @
  68.     ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    / U$ \9 `7 C  b. V$ i
  69.     // 不用外部触发转换,软件开启即可
    " K7 s! B, e9 a6 V# G% Y
  70.     ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    9 j; k# V. H0 p
  71.     // 转换结果右对齐5 _3 t* ^" w1 Q" `
  72.     ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;6 S( F1 C- I9 _1 R. V9 P
  73.     // 转换通道个数
    2 M% M" w& |  ?. |7 `, s
  74.     ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
    7 M+ D$ Q5 C) O+ e4 l5 p
  75.     // 初始化ADC
    + ^$ l* h7 ~$ b6 f3 G" d  L7 r
  76.     ADC_Init( ADC_x, &ADC_InitStructure );3 s- O2 }6 H& U1 G0 g  N0 u
  77.     // 配置ADC时钟N狿CLK2的8分频,即9MHz9 l1 g: b8 X" ?+ g$ M% B2 |
  78.     RCC_ADCCLKConfig( RCC_PCLK2_Div8 );
    6 D% e! w0 m& B0 C5 D1 Q5 @
  79.     // 配置ADC 通道的转换顺序和采样时间: G9 o" b+ a4 m$ M- L
  80.     ADC_RegularChannelConfig( ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5 );
    + I' o: z0 I7 q$ s- G6 D5 C
  81.     // 使能ADC DMA 请求
    3 f/ K' E" ]1 S: R
  82.     ADC_DMACmd( ADC_x, ENABLE );
      b9 s5 ]+ r& V$ w. V, x1 w5 c' n( N
  83.     // 开启ADC ,并开始转换# e( ]% I5 W" C  E
  84.     ADC_Cmd( ADC_x, ENABLE );
    7 H3 {! G2 G8 c
  85.     // 初始化ADC 校准寄存器
    & I2 ]& [. b. l2 c% [
  86.     ADC_ResetCalibration( ADC_x );
    , ~- J8 a- q. }
  87.     // 等待校准寄存器初始化完成
    % R, `! a0 M% p# g
  88.     while( ADC_GetResetCalibrationStatus( ADC_x ) );4 G! s2 d" R/ p" J# P
  89.     // ADC开始校准
    " N! _) E3 v0 T- Y2 V
  90.     ADC_StartCalibration( ADC_x );
    2 e% I3 x& g: G* S
  91.     // 等待校准完成5 ]# n. J; n* J7 f( y3 v
  92.     while( ADC_GetCalibrationStatus( ADC_x ) );. h* f+ ?; a1 J! |+ }( H
  93.     // 由于没有采用外部触发,所以使用软件触发ADC转换
    9 L) K: V' [! r& |
  94.     ADC_SoftwareStartConvCmd( ADC_x, ENABLE );0 R) |! {% b( I  j/ {- e
  95. }+ @& B! J: Q2 Y5 N  |1 `; f3 q
  96. /**
    ' m, D6 I. a+ x; _- G
  97.   * @brief  ADC初始化
    1 h' A" R# N6 {, b; [" K
  98.   * @param  无8 a; i/ O0 q9 Q4 ]8 s
  99.   * @retval 无$ Q0 x' l! F+ s# J9 }) E) p' y
  100.   */* A' B( W& `: \& L) Z# c3 I  E
  101. void ADCx_Init( void )
    " T+ u/ r  z7 {+ z
  102. {8 N3 F6 V  c7 y) r9 y) v6 D  D
  103.     ADCx_GPIO_Config();  V* D) Z, e) o3 v" `$ j: j
  104.     ADCx_Mode_Config();( A6 z+ a* ?) C
  105. }" z1 `+ A. }/ ?
  106. /*********************************************END OF FILE**********************/
复制代码

+ `) r; a% I3 o! ]        设置ADC为DMA传输,将采样的数据由DMA自动存储到 ADC_ConvertedValue[ ] 数组中,这里虽然只使用了一个ADC采样通道,但是定义了一个数组来存放采样结果,如果想要实现多通道采样值,只需要将其他通道的初始化代码添加上,同时将数组长度,也就是通道数修改一下就可以使用了。初始化ADC和DMA后,ADC采样并通过 DMA传输的功能就可使用了。然后串口输出数据的时候直接从ADC的采样结果的数组中取值就可以了。
5 g. g  m$ J: M& F0 T1 ?# f6 i, P
      下面编写串口相关代码7 _% ~, k7 C* E2 I  K' i
' B6 H- `4 ~$ {/ ?/ a! `
  1. #ifndef __USART_H7 I; G1 `+ d! ~/ L$ ?& s8 y
  2. #define        __USART_H# [' N: J' M+ t* y$ l& b( `, a

  3. ; N0 ^: j: _+ j* O
  4. #include "stm32f10x.h"! l  I( s+ ?: k( O1 p+ _9 F1 T/ d8 c0 k* U9 A
  5. #include <stdio.h>
    . u0 m* b2 l) [+ t( A3 I
  6. // 串口1-USART1
    , m4 V* i6 P  q* _" Q
  7. #define  DEBUG_USARTx                   USART17 I9 s$ [8 A9 _, }7 p: H' W
  8. #define  DEBUG_USART_CLK                RCC_APB2Periph_USART14 q! v6 Q3 j0 B& ]5 S5 x
  9. #define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
    . v, {/ q( d) A. H: y" }0 o
  10. #define  DEBUG_USART_BAUDRATE           921600
      \' c7 ?7 i8 e! y& G7 q/ V: S$ u
  11. 3 ]. f4 c) W" _8 \
  12. // USART GPIO 引脚宏定义
    7 j% h7 n" H% D- E" z$ P4 i
  13. #define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
    ) O. q7 _9 @) j! F6 C
  14. #define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd# V" e0 V: @- t- h- d* S
  15. : u, F: ^9 {% K* x% k$ U& f
  16. #define  DEBUG_USART_TX_GPIO_PORT       GPIOA$ b* |  t' j; j. N2 _
  17. #define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9* F2 c# n. |2 q$ R# I# \' v
  18. #define  DEBUG_USART_RX_GPIO_PORT       GPIOA
    5 `% v% X' n; j/ o. |) k
  19. #define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10
复制代码
, v4 n5 w) h, u# n
将串口的端口和时钟也使用宏定义的方式,如果要改为其他串口输出时,直接修改头文件就行。下来初始化串口。3 {: E1 A0 b+ P, g7 B* ?8 l. Z! P

3 F! {5 R4 B) e3 G
  1. void USART_Config( void )
    4 @5 y3 T. j: h, q3 e4 {; z: B: C
  2. {
    . |/ E# s' x6 d  a
  3.     GPIO_InitTypeDef GPIO_InitStructure;0 E' _  d1 X6 L2 ^1 m' ^$ _
  4.     USART_InitTypeDef USART_InitStructure;& A: x) o, z  |

  5. - O# Y+ h7 @$ p$ `
  6.     // 打开串口GPIO的时钟- J2 T0 S4 M, _0 t# m/ Z
  7.     DEBUG_USART_GPIO_APBxClkCmd( DEBUG_USART_GPIO_CLK, ENABLE );
    ! M" D/ P( ~6 g4 \/ ^: {3 F! e& B  j
  8. ' |! Z% C- `4 U
  9.     // 打开串口外设的时钟% c: f* a- B* X' C$ h) N3 j! G! f
  10.     DEBUG_USART_APBxClkCmd( DEBUG_USART_CLK, ENABLE );
    # z8 W8 |: J0 X
  11. & D  v& W/ Z1 e8 p6 \% R2 w$ ?' [7 x
  12.     // 将USART Tx的GPIO配置为推挽复用模式
    & S' a! K& ^* i" Z  U
  13.     GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
    ) `6 e) F+ U3 a$ _8 d
  14.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    * K  \. c8 m* `/ K
  15.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    % ]# o, c' w5 q2 R* ^) ?- y, J7 a  |
  16.     GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure );
    5 u3 Z" k$ |& w; e0 X( A% W

  17. : b4 q0 z5 X! ^- U& N- K
  18.     // 将USART Rx的GPIO配置为浮空输入模式# e- W7 M8 i) P# f) L
  19.     GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
    ) W/ A% C# s: o' S! m, Y
  20.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;+ n( {  L0 w+ p4 A. H3 e3 I. S
  21.     GPIO_Init( DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure );4 ~  s- y* z+ c7 {
  22. 9 g  h- P) |. a' D: z* x
  23.     // 配置串口的工作参数
    6 m% O" \" p) p% ^- E" ?3 J$ w
  24.     // 配置波特率( D( i9 E' R# E; y) _6 d
  25.     USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;  T3 G; i5 c  Q7 P/ B1 r$ A- a
  26.     // 配置 针数据字长
    ( ]$ i- P- }9 s9 u' I
  27.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    2 S( q: U" H" ?; e8 }0 f6 x
  28.     // 配置停止位
    8 J( y( ?9 ]& Z4 w9 Z8 p
  29.     USART_InitStructure.USART_StopBits = USART_StopBits_1;7 o, v8 Q  o. [; ]0 t; K
  30.     // 配置校验位
    1 K( a1 ], Y8 Y/ o
  31.     USART_InitStructure.USART_Parity = USART_Parity_No ;
    & a! r+ U5 ]2 G
  32.     // 配置硬件流控制9 b0 `1 O/ G. N3 c( R; }1 w1 H
  33.     USART_InitStructure.USART_HardwareFlowControl =
    ( X/ s& G/ Y( `+ J0 |: n) ]% |
  34.         USART_HardwareFlowControl_None;
    + e1 L2 x% b( }" q
  35.     // 配置工作模式,收发一起
    ; [/ q3 J) V/ H' J% f
  36.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;6 V: W* u1 Q/ f! j4 T9 u
  37.     // 完成串口的初始化配置
    9 i, G# t. e  @5 H4 e
  38.     USART_Init( DEBUG_USARTx, &USART_InitStructure );+ _7 s7 m+ ~5 b. h" \
  39. ( o5 ]: A3 ]' S1 m( k0 S3 U. b
  40.     // 使能串口
    6 ^& s9 [5 F! Y: E
  41.     USART_Cmd( DEBUG_USARTx, ENABLE );# h# B: T8 L. Q9 Z+ ~9 K( Y; J
  42. }
复制代码

: a* u& V% d- p下来设置串口为DMA输出
- Z' U# G3 e5 r! Q, f" e" V5 z9 b. p# Y
  1. #ifndef __DMA_H
    $ a  _9 _$ x4 O/ }
  2. #define __DMA_H
    9 P* Z* D/ [1 U6 [
  3. #include "stm32f10x.h"
    * C. ]& F/ b! `# t2 \: O6 _) j" {4 t5 v
  4. // 串口对应的DMA请求通道
    ; B4 e" f- A: q5 P3 K# o
  5. #define  USART_TX_DMA_CHANNEL     DMA1_Channel4
    - q: _! u; P5 o) I
  6. // 外设寄存器地址
    8 Z7 ^  I9 u3 t5 l: E* Z2 N
  7. //#define  USART_DR_ADDRESS        (USART1_BASE+0x04)                        //使用地址偏移值
    6 I* }( A& v' T/ V
  8. #define  USART_DR_ADDRESS        ((u32)&USART1->DR)                                //直接使用寄存器地址
    : ?" p& c: |5 V- f
  9. // 一次发送的数据量
    & V! z$ F' ^& u3 {7 O
  10. #define  SENDBUFF_SIZE            63 F' P& }2 ~' v. Q( H# U9 O
  11. extern  uint8_t SendBuff[SENDBUFF_SIZE];
    3 ]  T) [6 S1 n( }( L# c
  12. void USARTx_DMA_Config(void);' P2 K2 C+ v7 P  M! W' d
  13. void USARTx_DMA_Restart( void );
    3 n' D. C: U* i) v4 v5 @' Z
  14. #endif
复制代码

; w6 h1 m* `0 [) \/ i       在头文件中定义串口发送端的 DMA通道,将串口数据寄存器作为DMA源地址,将SendBuff[ ]数组中的内容作为内存地址,数据方向为内存到源,这样就直接将SendBuff[ ]数组中的数据通过DMA直接传输到了串口中。& V" E: i; w) l' T' V

: g' e, ?/ H/ [( w! G4 j. B0 F: i
  1. void USARTx_DMA_Config(void)
    * O/ X4 e/ w; S1 u
  2. {
    1 g0 c. \4 g4 J! _& O5 C
  3.                 DMA_InitTypeDef DMA_InitStructure;        
    % g; j& B3 l" N. V
  4.                 // 开启DMA时钟
    + J3 p: l! k, _. ]
  5.                 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);! m0 {, C3 M4 \/ y* v4 q
  6.                 // 设置DMA源地址:串口数据寄存器地址*/
    8 [( a6 U1 `! l) d3 V; q- U
  7.         DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;, C7 z- u! S* G2 i5 i
  8.                 // 内存地址(要传输的变量的指针)
    2 s3 b( V2 b6 y, B: X  L$ S  `! f3 X8 H
  9.                 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;6 O  s! q5 K: I+ {$ P9 @
  10.                 // 方向:从内存到外设        0 x- {- U& I4 e/ W
  11.                 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    ( V- p" Q( b. |7 D9 h" _
  12.                 // 传输大小         
    ' z2 x7 r* i; Q! \4 y+ j
  13.                 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE ;
    ; x5 c' d4 a! ^) B7 @- S
  14.                 // 外设地址不增            + l  w. K( z5 y5 x* X6 i4 C
  15.                 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;: }1 F1 ~- U2 ^
  16.                 // 内存地址自增
    ! ~! x6 {. p9 v( j! {
  17.                 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;( C1 X' }! w7 l* k/ ]$ E# _: g
  18.                 // 外设数据单位        
    & A% |; v; [* S( w7 u" u) k+ r
  19.                 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    # a, v  J. H& G! ?- F
  20.                 // 内存数据单位
    " h6 O- U: N: e. d
  21.                 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;         ; r- w  Y3 Z5 a8 V# v# e  r
  22.                 // DMA模式,一次或者循环模式: i8 Y8 }9 K4 \. z4 e
  23.                 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;                                                        //发送一次数据5 \% K" @- i9 W+ C( ^
  24.                 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                                                //循环发送数据
    ( ^1 k8 f# E, F2 l0 S+ [) W
  25.                 // 优先级:中        
    ( u; A8 O! ?+ n1 i& |
  26.                 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 3 F6 z- w* E9 B* E0 L$ X4 G
  27.                 // 禁止内存到内存的传输
    5 F% S+ _9 b: H( I$ G! T6 d
  28.                 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    6 L' e5 t+ K9 _& S) |$ E
  29.                 // 配置DMA通道                  
    5 q9 O$ T( h; X0 D) r# ^$ f
  30.                 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);                7 E. o1 [" h; I  f
  31.                 // 使能DMA
    4 x) a1 C3 ^. Q  f" [8 q
  32.                 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
    ' d" v* a1 r( x( u" r7 Q5 s, q
  33. }
    7 R% B' H0 F! E5 e
  34. //重新启动串口 DMA/ M% f0 n! \+ t1 U/ h
  35. void USARTx_DMA_Restart( void )* x1 b- b! L: Z- _% j& @
  36. {- r3 ?% N7 G% R6 |0 H8 _' l6 G
  37.     //重新开启DMA传输
    , p! S/ V3 ~) a2 F* h! ?
  38.     DMA_Cmd( USART_TX_DMA_CHANNEL, DISABLE );
    4 v! R1 f8 p1 ]+ }- \1 k
  39.     DMA_SetCurrDataCounter( USART_TX_DMA_CHANNEL, SENDBUFF_SIZE ); //重新设置传输的数据数量; z% S, a2 j( X: w
  40.     DMA_Cmd( USART_TX_DMA_CHANNEL, ENABLE );                       //开启DMA传输9 u4 [* `! c" h! Z2 D
  41. }
复制代码

, _+ c2 V" b! P- H) j        将DMA传输设置为单次传输模式,因为转换后的数据为16进制数据,为了方便在串口助手上观看数据,需要将16进制转换为字符串打印出来,所以每次每次在串口DMA传输数据的前,将数据转换为字符串,然后再打开 DMA传输功能,将数据通过串口发送出去。所以DMA传输时不能设置为循环发送,需要每次将数据格式转换完成后才能发送。发送完一次数据后,需要重新设置DMA传输数据的数量,然后重新启动一次 DMA传输。7 W& T1 N" w$ A' l8 R0 ]
. N' o% E) H4 o" A; I! ^
  1. int main( void )
    ! N) b. J) i3 I7 u6 K! J. |% \
  2. {; I' \9 B6 |- P4 R; U3 `4 _; u2 i4 ~
  3.     char str[5];
    % g7 Z7 L6 [, F/ g. t) I
  4.     /* LED 端口初始化 */
    ' I6 \) C8 C* L/ _6 d1 y
  5.     LED_GPIO_Config();9 d, ~4 I* @  g# U& c- E! S  q3 b
  6.     /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
    : W% w: ?) F" E
  7.     USART_Config();8 N9 S5 z; s, v$ |
  8.     ADCx_Init();9 t+ x; o5 I7 a4 P+ Z1 Y: c5 B. J
  9.     /* DMA传输配置 */% n2 Y6 O9 [5 H: ^
  10.     USARTx_DMA_Config();
    $ q7 G) E2 K1 v2 ]
  11. 1 S, h; y$ o& X/ ?9 [; Z1 M
  12.     /* USART1 向 DMA发出TX请求  单次发送模式 */. W9 ]9 K8 m  ~( j6 D8 C" [. d. k1 Y( g
  13.     USART_DMACmd( DEBUG_USARTx, USART_DMAReq_Tx, ENABLE );
    & P+ f- N9 }* h+ t; K
  14.     //串口发送缓冲区后面添加回车换行符
    - ]- q& m" X( \! N
  15.     SendBuff[4] = '\r';! H: V# D# f1 i$ Y, K8 ^
  16.     SendBuff[5] = '\n';
    7 |7 s( W5 [! w' b5 C) ~; v, i4 F
  17.     while ( 1 )
    4 j& i0 d6 q- {% U0 U
  18.     {  }( @, H  R4 U$ p
  19.         //填充数据 只打印通道1数据0 {5 {5 w9 E! |! ^, B2 i6 p
  20.         sprintf( str, "%d", ADC_ConvertedValue[0] );                //将通道1采样值转换为字符串类型3 D, ?( ^+ @, Z3 \+ h/ s# ~& a
  21.         SendBuff[0] = str[0];                                                                                                                //将字符串数据存入串口DMA发送缓存区( c( A. K$ `6 O. {' a3 g  o
  22.         SendBuff[1] = str[1];1 r1 g9 r% c, y. ~; M  f
  23.         SendBuff[2] = str[2];
    3 w* F4 {( s* g8 T8 q4 m
  24.         SendBuff[3] = str[3];
    ; k0 L) _6 }5 [- G
  25.         USARTx_DMA_Restart();
    5 j; ]* k# J- Q9 I& x7 i

  26. # V* X7 h! g% a! Q8 d- @$ k# b
  27.         SysTick_Delay_Us( 45 );/ [7 }" \( Z6 H7 k8 B- n) B1 f, e+ [
  28.     }/ Y* c- M8 x5 Y6 Y! `- o
  29. }
复制代码

) K/ B( z& v- T5 C( A& Y: P. [        STM32F103单片机的AD为12位,采样值的范围为0---4095,转换为字符串后最多为4位,再加上回车换行符,总共6个字符。这样串口DMA传输数据的长度就为6位。/ P( i' ^& _8 N# r- S" x
' S+ Q+ V' x8 _( N2 G8 ^! S& _/ Q
为了测试 DMA传输的最大速度,将串口波特率设置为921600。下面开始测试代码,用函数发生器产生一个0--2V的正弦波,频率为100HZ。然后通过串口助手观察采样的数据。
3 E+ b1 _# X+ t6 ]" c
/ p4 }" G9 i$ D$ y+ Z6 |" O# o ](PM%OQ{_EC4LAS5IY{EO.png
9 f+ N1 |, s! {6 [% o8 q$ Q! n1 j9 L; S( [3 j5 S
通过串口波形显示助手观察输出的数据波形如下6 E9 V7 f  c- ^7 @1 a" W: X2 j
* \2 a  l. Y5 y8 ^& ]1 r5 x
WQ[CFWQP453M2TZUBPKWFUI.png
# i/ F) b5 @& D1 G$ k2 w3 K! J7 d4 u' _% X
通过上面测试可以看出,在STM32F103C8T6单片机上成功实现了ADC的 DMA采集,和串口DMA发送功能。
. ]- R) v+ j7 l$ D$ E: W
  u2 S/ U6 u( {& s, ~9 ~) y) C$ c6 ^0 n) ]( b
赞 收藏 评论0 发布时间:2022-3-20 13:43

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版