本帖最后由 radio2radio 于 2019-3-11 14:32 编辑 ; k$ A9 W, S5 F- i
# B- v3 M1 ?" L周末有时间,测试了一下STM32F103的DMA串口收发程序,基于CubeMX的,结果却是令人大失所望。: `, c: y2 Y" L+ k
, d/ C! J1 j. N6 H
我在去年,测试了一下【中断模式】的,结果是速度超快。
' z5 [ i& d4 w1 [3 F, \速度115200bps和1Mbps,双向同时收发100万字符无差错。 2Mbps,单方向100万字符无差错。
- J% T' M+ p6 j1 E详情请见: STM32基于CubeMX的高速串口收发程序(中断模式)+ \8 l! J9 \# v& b! C. m! N
7 p! S) @% n6 i6 B那时就有网友,问我为什么不用DMA模式,我也认为DMA的好处多多,只是没有时间验证一下。
7 {' {( A- \; T+ L4 W现在,我得到的结果是,DMA模式用在UART这种低速外设上面,可能性能并不好,不如中断模式的。
& G+ w& f9 m9 x, ?, |6 i% f请网友们给看一看,希望我的代码有问题。% Z1 S9 U8 U& h9 x) a6 T0 \
先说我的测试结果吧:7 e& I1 e( _( {+ _. c8 M2 W
STM32F103C8T6 Bluepill板,MCU时钟72MHz,用CubeMX配置出DMA模式的两个串口收发。( W# f) z0 H1 N$ ] b$ p6 c" S
添加少量代码,就做成了两个串口互相收发。 与上面说的中断模式的用法一样。( q8 i- Y V2 R0 l. n
结果是,115200波特率,以10ms间隔发送接收40个字符,单方向正常,双方向同时收发就丢失数据。
0 o- Q9 \3 m) h& R* e! J9 z如果时间间隔放到200ms,双方向同时收发,也能正常了。1 f5 N6 W J( M8 N- C
1 u1 E" \! V7 {! m4 B下面,看看我用的代码:
$ m! i' G% K* h* V% I. h/ u2 {CubeMX的配置过程,就不累叙了,附件里面有配置文件。1 q4 f% L& x% L0 D( W2 E0 s- C6 d
' i0 W: z- H$ V1 w0 S
- //main.c 添加的代码:- P5 S7 |9 S2 b) a; v# l ~
- . b) ~( u; F, {: m
- //变量:
8 h& U1 b# x; g4 a - #define DMA_RX_BUFFER_SIZE 128
! h2 R; K4 d7 r# C+ w6 y6 h$ y - uint8_t UART1_Rx_Buffer[DMA_RX_BUFFER_SIZE] = {0};0 } @2 W- a& T
- uint8_t UART2_Rx_Buffer[DMA_RX_BUFFER_SIZE] = {0};
- m: R, B5 g) W0 \8 F3 L - ' n r, ?+ B1 n# B! M7 d w
- uint8_t recv_end_flag1 = 0;
& \$ I. v" c. K, X" H i& ^' v - uint8_t rx_len1 = 0;
. A( J. Q, A8 J - uint8_t recv_end_flag2 = 0;. z$ K% o3 i. W/ R
- uint8_t rx_len2 = 0;. Z9 h. e% }9 d$ }" z
" z& C4 j# ?6 Z3 D8 D: a p; \6 t/ _- 。。。
4 H. r; X8 O2 G" A5 w - 5 a/ z( n- e' V9 B
- //初始化% |- X% b8 U) s1 S; R* b
- __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); v1 @% c; v8 i# c$ m
- __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
; ~/ z* I! b V- _# i- ~/ \/ T -
' r8 t) d: B6 _3 h' H* _ - HAL_UART_Receive_DMA(&huart1, UART1_Rx_Buffer, DMA_RX_BUFFER_SIZE);
7 O4 D# d) T% k; T, u- N: m - HAL_UART_Receive_DMA(&huart2, UART2_Rx_Buffer, DMA_RX_BUFFER_SIZE);
/ a6 ]& b+ i1 h- J4 u# x! B, P1 |1 | - 6 R9 y( n% X- k. H6 i
- 。。。( t! [; J0 K2 N* o
-
8 |4 U' h2 G- _9 N( p! D - //main()
: z7 ]7 J2 H$ j9 V- z. n: h - if(recv_end_flag1 ==1){ //UART1 Rx, UART2 Tx
' V% V9 S$ F! O' o9 S; n4 J2 w) N
, A6 i" ^3 U5 D- HAL_UART_Transmit_DMA(&huart2,UART1_Rx_Buffer,rx_len1);3 l) c' Q, O j( F) f+ x
: B* O% z8 u) N8 p8 Z9 L; y- rx_len1=0;: `5 e7 @% }1 N1 u
- 2 u6 L1 M2 j2 D4 Q- N: Q( v; [# Y
- recv_end_flag1=0;5 |: G8 R5 ~/ m5 C
- ! w: O# S! _1 R- K
- HAL_UART_Receive_DMA(&huart1,UART1_Rx_Buffer,DMA_RX_BUFFER_SIZE);9 V# q9 ~; t* z; H
- }
* {6 d$ t- l: U' p -
1 T$ g6 r9 v: x6 I, X7 Z - if(recv_end_flag2 ==1){ //UART2 Rx, UART1 Tx; n! {8 |& R" _3 a; y3 d$ P. r9 J
4 f* ~1 I8 H# H4 \; [/ [! d7 e- HAL_UART_Transmit_DMA(&huart1,UART2_Rx_Buffer,rx_len2);
4 A4 n( r# y# G- { - ) T: G/ G& R0 l ?5 \, z; q
- rx_len2=0;
6 P$ q( C0 T3 ^4 U# Z% N - & L4 z5 }0 T- d7 h
- recv_end_flag2=0;5 H7 ~- o( m4 N8 Y% d+ B
- # a" ?: w f5 i
- HAL_UART_Receive_DMA(&huart2,UART2_Rx_Buffer,DMA_RX_BUFFER_SIZE);+ N0 ], h# ~/ w8 i/ V. f- \
- }
" j0 E' v1 B# J - + J. r% F. \# ^6 J+ I0 b
- ( z& q( z$ P0 J
-
5 L# ~, L( m0 ]% H+ G -
, U9 p8 ~0 q4 J6 K! S5 n' |' y* P) M -
" c! Z! u, P! d V5 ~( M; K" S - //stm32f1xx_it.c 中断服务程序里面修改的代码:
2 g9 F1 [7 l0 ^, M - + c, ~5 g4 F; }: o" \& U w
- extern uint8_t recv_end_flag1;+ i3 J, p' D8 b0 B$ b
- extern uint8_t rx_len1;
. @9 N/ c% p; i, x0 q - extern uint8_t recv_end_flag2;
, A3 V1 j. w! c - extern uint8_t rx_len2;, g, T7 I& x" p; S" W
" v1 Z7 b: c/ B- #define BUFFER_SIZE 128
) ]9 E& Y& q+ r3 S( U! b7 C
5 {2 ] h6 l+ i1 N8 p! W7 ?- void USART1_IRQHandler(void)& J; a; b3 W& \7 J. W- U
- {+ Z8 r5 b; r, A6 h
- /* USER CODE BEGIN USART1_IRQn 0 */
+ `) M4 X Y+ p2 M - uint32_t tmp_flag = 0;) |) G; J. E" Y( a' L" v
- uint32_t temp;+ a+ R# x# }2 q z+ d& W9 |2 t
- 1 k( ^( v% \. v- Q
- /* USER CODE END USART1_IRQn 0 */ 8 I7 J U C9 x9 F$ M! c/ ~
- HAL_UART_IRQHandler(&huart1);
9 i5 X2 o, P) F: a* C. W1 G7 r! ? - /* USER CODE BEGIN USART1_IRQn 1 */
7 }! V& f4 s& f - $ j3 O7 _* M) m& \" I* I, c
- tmp_flag = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
, C0 l6 S5 j) B( @4 K8 r6 A0 s
* A* n) w K- f- if((tmp_flag != RESET)){
$ M$ E/ ?! a- {8 L( M) |& M! U! A; S - 6 c+ w2 [5 {# k) j3 b6 U
- __HAL_UART_CLEAR_IDLEFLAG(&huart1);& M( I4 C2 ]$ J" e( r; ~* U$ B
- % J7 _& M8 r, [2 T9 U. M
- HAL_UART_DMAStop(&huart1);
2 B, n# P, d3 N# ~+ Q" {/ }
0 S- ~; i6 h$ ?8 }/ r* N6 T- temp = hdma_usart1_rx.Instance->CNDTR;: B: X$ j# _6 [1 O$ C& F
- 3 x5 d9 l8 }5 m
- rx_len1 = BUFFER_SIZE - temp;
! \4 k- K, X2 F5 T0 u" Y2 _5 q - 1 @1 g/ f$ J8 P$ y6 i5 z
- recv_end_flag1 = 1;
5 V- t1 s y$ _, c - }
# I2 [3 A4 x3 \" B/ q - /* USER CODE END USART1_IRQn 1 */: e9 }9 {9 Z9 i0 l5 C- z3 q- T
- }6 _' W. w" u* s, A! Q* r, h
/ z/ S- C: a# v: E2 M! ^
" I$ A7 t0 w- O n3 e! N( p. { ~7 V- /**" H+ ^0 J% ?3 x- `
- * @brief This function handles USART2 global interrupt.# L8 H; P* e% s1 ~ C; n
- */1 q) |1 C' b% z) i) g# y
- void USART2_IRQHandler(void)
( t. x9 X# [$ N* Q9 g, M - {/ S4 L9 Z# W$ I T8 T$ x7 Y0 f/ @
- /* USER CODE BEGIN USART1_IRQn 0 */, X" a3 \$ C8 I
- uint32_t tmp_flag = 0;
& \% {) v2 K5 H' |/ K - uint32_t temp;
5 k) S7 f) ]! Q" B# c& @+ N+ p, e' B
7 h& v C M+ [4 W! [( H c; f6 L- /* USER CODE END USART1_IRQn 0 */
3 g0 }; R. S/ r6 b2 n - HAL_UART_IRQHandler(&huart2);
5 U. a* ^1 C3 m4 o- d/ h7 |# n: e - /* USER CODE BEGIN USART1_IRQn 1 */- j) X/ v0 N+ c! W
- ( n9 K Q1 d/ i) e$ e
- tmp_flag = __HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE);. k% |# J+ Z8 i* Z: N) {
- * f. z; n2 g* e0 V
- if((tmp_flag != RESET)){5 m E& c3 h$ _7 e* {
- ) A5 E# P- I9 ~9 W
- __HAL_UART_CLEAR_IDLEFLAG(&huart2);
( N2 G, L+ O( f, H
% p: j& b* _( w X/ ?% L% g' j& _- HAL_UART_DMAStop(&huart2);
: j) v* s: V( e" U - ( u/ A; A/ p/ S
- temp = hdma_usart2_rx.Instance->CNDTR;
4 @" J7 n0 X5 f - $ }8 T `* R+ O& l m" D4 A
- rx_len2 = BUFFER_SIZE - temp;
; D1 a; d. f8 g. M
4 s1 h5 A; R% G" g! I- recv_end_flag2 = 1;+ V/ y6 _- k4 H: M9 p- R% U
- }# _" N O3 f* a( a' Y
- /* USER CODE END USART1_IRQn 1 */5 _$ F5 P5 _( K
- }& j' [' U5 Z( u: t, d9 [3 M
复制代码 ) K; f: e0 J M5 H9 L. i
) m9 ?' Z3 @; q2 I
' H& d& U6 x7 Z* Y: s
上面的代码,也是参考了网上网友的帖子。 希望网友指出问题,和给出更好的代码方案。
3 \4 U% j8 R9 a也还听说串口DMA有三种方法,我这里用的只是其中之一的“空闲中断”法。
1 ]4 i% y4 B7 U; j9 _1 e5 t+ E2 h8 a; g/ [! ]$ H
附完整代码:
4 O5 p7 B- \+ l/ E: N& U4 v& K. q% L0 J
|
1. 数据包长度不超过DMA缓存的长度。2. 发送的间隔不少于200ms。) s& y: ^/ s9 _; k
就可以115200双向同时收发无差错。
1 `( t5 U b$ I% X) u/ \- E
至于单方向收发,1Mbps,2Mbps,都没有问题的,放心使用。
所以个人认为LZ的测试方式其实问题在于,用中断CPU可以收一个马上发一个,虽然两边都CPU占满了但响应肯定快;但DMA是收完全部时再全部返回,那CPU虽然很闲,但响应就肯定慢。这样测试时DMA对CPU占用少的优势就测不出来。
同意,谢谢。6 v& l. {" |2 z4 ]' r" f- Q4 W
总之,串口通讯,本身是没有纠错重发功能,速度又很慢的外设。
影响丢数据的因素,也就是那么几种,照顾到了就没有问题。