1 前言" U `" l6 Z, H
% d) C7 m. i# }. C
直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。因此,大量数据拷贝时,使用DMA可以释放CPU资源。DMA数据拷贝过程,典型的有: - 内存—>内存,内存间拷贝
- 外设—>内存,如uart、spi、i2c等总线接收数据过程
- 内存—>外设,如uart、spi、i2c等总线发送数据过程* @- ]& X, b' C- P6 V* P5 S7 z
" Z; w) P3 v0 a* i 5 }, @9 o# v' d
2 串口有必要使用DMA吗: ~: k3 a7 G( w6 ]( {0 n
8 q4 p! p6 B4 i
0 L5 r @2 m- H6 x- Q O5 L R' @ 串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。对于小于或者等于115200bps波特率的,而且数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。
; q$ H# l2 n9 w0 D9 u/ |' l: r 对于数量大,或者波特率提高时,必须使用DMA以释放CPU资源,因为高波特率可能带来这样的问题: - 对于发送,使用循环发送,可能阻塞线程,需要消耗大量CPU资源“搬运”数据,浪费CPU
- 对于发送,使用中断发送,不会阻塞线程,但需浪费大量中断资源,CPU频繁响应中断;以115200bps波特率,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源
- 对于接收,如仍采用传统的中断模式接收,同样会因为频繁中断导致消耗大量CPU资源
3 e( _0 K( I1 @( Q6 _8 K! @+ r. U/ Z8 ? A8 g- g9 E! n; B! \8 O1 n
( A0 g4 f0 |1 t- z0 x 因此,高波特率场景下,串口非常有必要使用DMA。 3 q: b1 i: w: X
3 实现方式% O& M5 W, l$ K
8 M$ Q0 v$ W7 O9 i5 G% E7 @$ u
4 H$ s. j: }1 a2 i" p3 H0 }" v% @1 G! b! f& b! U6 {7 p3 [0 s& v# m5 W/ }
4 STM32串口使用DMA. ^3 _- R' L3 p' _ \8 O
8 T1 \% O) Z8 z5 ^* u( `+ e9 Y( x8 `+ N5 [: S* q g4 p
关于STM32串口使用DMA,不乏一些开发板例程及网络上一些博主的使用教程。使用步骤、流程、配置基本大同小异,正确性也没什么毛病,但都是一些基本的Demo例子,作为学习过程没问题;实际项目使用缺乏严谨性,数据量大时可能导致数据异常。
- N \, M+ N9 z1 o测试平台: - STM32F030C8T6
- UART1/UART2
- DMA1 Channel2—Channel5
- ST标准库
- 主频48MHz(外部12MHz晶振)3 J8 U$ Y% T) U9 \* s
; W- c8 g2 A( M$ ?- B/ ], g) l6 s% X
9 e$ v0 W6 M8 G3 E F y1 ]2 Z. j2 m/ E' d. b' S" |0 f
* ^, a2 [4 k, [; y2 { 5 串口DMA接收# F9 e* J3 |9 j+ ~
3 s& u1 A( v' p9 M2 H
5.1 基本流程( J4 Q/ L# a* A: ~' V
5 ?# \: ~- v6 |% h. S; j4 @3 ]( Y串口接收流程图
$ O3 k# {) K- [& C; c2 Z+ g
5 d3 |0 y9 e8 l2 V$ j) I5.2 相关配置# G9 b* A+ i: G( q6 a0 \
/ \4 G6 ~. G: P: ?关键步骤 【1】初始化串口 【2】使能串口DMA接收模式,使能串口空闲中断 【3】配置DMA参数,使能DMA通道buf半满(传输一半数据)中断、buf溢满(传输数据完成)中断
) h. C: Z( _# t, v( I为什么需要使用DMA 通道buf半满中断? # T$ L s) z; u5 |
很多串口DMA模式接收的教程、例子,基本是使用了“空间中断”+“DMA传输完成中断”来接收数据。实质上这是存在风险的,当DMA传输数据完成,CPU介入开始拷贝DMA通道buf数据,如果此时串口继续有数据进来,DMA继续搬运数据到buf,就有可能将数据覆盖,因为DMA数据搬运是不受CPU控制的,即使你关闭了CPU中断。 & I: G y! B: t- t' q! M
严谨的做法需要做双buf,CPU和DMA各自一块内存交替访问,即是"乒乓缓存” ,处理流程步骤应该是这样: + f& \" e2 s, F5 F: W: b5 s4 i
【1】第一步,DMA先将数据搬运到buf1,搬运完成通知CPU来拷贝buf1数据
. D+ t* ?/ y+ J5 [# W【2】第二步,DMA将数据搬运到buf2,与CPU拷贝buf1数据不会冲突
* i" t& Y3 P/ L2 u; {4 Y【3】第三步,buf2数据搬运完成,通知CPU来拷贝buf2数据
k7 v- C' ^5 d$ b# |" \3 b【4】执行完第三步,DMA返回执行第一步,一直循环
0 m! y$ t% j) n2 r6 ^6 Z
; k G+ O$ e0 N, c. h8 _
/ w' ~; K: g0 d
# E" Y9 X' ^3 q% Q; j' c5 y双缓存DMA数据搬运过程
6 w5 ~3 @( J5 n# ^6 y# A& O) Y, g/ J
# h. d& [% Z9 l. k0 K8 \* } STM32F0系列DMA不支持双缓存(以具体型号为准)机制,但提供了一个buf"半满中断",即是数据搬运到buf大小的一半时,可以产生一个中断信号。基于这个机制,我们可以实现双缓存功能,只需将buf空间开辟大一点即可。 9 I3 Y: C% q( i8 J9 R6 l) W
【1】第一步,DMA将数据搬运完成buf的前一半时,产生“半满中断”,CPU来拷贝buf前半部分数据
! }1 i4 c! W: s) K! f0 L4 k5 n【2】第二步,DMA继续将数据搬运到buf的后半部分,与CPU拷贝buf前半部数据不会冲突
, \ X9 O, l5 n; b7 k7 }& @9 s; Y# a【3】第三步,buf后半部分数据搬运完成,触发“溢满中断”,CPU来拷贝buf后半部分数据
1 e* G2 w4 G" `; i$ s【4】执行完第三步,DMA返回执行第一步,一直循环
" n4 O/ E9 y: T6 x# T6 I) q6 L7 ]. ~0 C- D B+ v5 P& B) [
9 T8 Y& c% @. ^, I! a/ v7 b" w
* {" Z/ F5 o$ E0 ]5 q1 I
* A0 t- M& \9 f% `6 G! \使用半满中断DMA数据搬运过程 # _8 W4 q; q4 r, \1 Z
; _: B, ^' K8 Z
UART2 DMA模式接收配置代码如下,与其他外设使用DMA的配置基本一致,留意关键配置: - 串口接收,DMA通道工作模式设为连续模式
- 使能DMA通道接收buf半满中断、溢满(传输完成)中断
- 启动DMA通道前清空相关状态标识,防止首次传输错乱数据
1 P0 m3 D4 n1 N6 {* _
; H9 z8 h! \2 l& |' z: B
9 ?/ V# e, t& p0 Q2 `
5 G3 ?' o& X9 M; Y6 m! ~9 K* T7 [7 M6 t8 Y: t- o( P
- void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
2 U0 Z+ O4 f: u( n; }9 U9 L2 T - { h) x$ Q8 P3 x' T0 i, k
- DMA_InitTypeDef DMA_InitStructure;5 g9 q( n+ [' f5 @2 p* [5 J, [
-
& Z! R/ |0 q3 `- {2 F6 _0 Q - DMA_DeInit(DMA1_Channel5); ) q' C/ W8 K; W% |! f; s3 K" l
- DMA_Cmd(DMA1_Channel5, DISABLE);
( q5 b" o$ v5 R- j2 o: } - DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->RDR);/* UART2接收数据地址 */% f% ]& F5 P$ G" O
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr; /* 接收buf */
4 P2 y+ _* T2 T) J6 A - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设->内存 */ h0 X& p" T1 R) C# i, j
- DMA_InitStructure.DMA_BufferSize = mem_size; /* 接收buf大小 */2 [7 J* A+ Z% w1 L' e
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
: a5 S. |8 w% O5 c5 c - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; % x& L4 l/ ^4 |8 C+ S
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; 4 T) h7 w/ a L, i
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;# r% m* ?# V4 i, ?. M0 y. ^
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* 连续模式 */
2 X0 T* F1 B9 m% X: ~- P4 I) W; j - DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; ( D" ^ o5 A1 V4 k- U" b& `
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; / a' J; B+ p. A/ F- G0 I' ^6 `( j
- DMA_Init(DMA1_Channel5, &DMA_InitStructure); " m4 u4 Z* \# k8 |
- DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、溢满、错误中断 */
( ?' q h5 ?% B% w" P' m7 h - DMA_ClearFlag(DMA1_IT_TC5); /* 清除相关状态标识 */# a! [' s/ }" A9 C5 l
- DMA_ClearFlag(DMA1_IT_HT5);
* `+ s/ @3 F+ ] }- P2 x# u; B - DMA_Cmd(DMA1_Channel5, ENABLE);
, z6 [2 T. Q; ~' @ ~ - }
复制代码 # H! K+ T2 h/ Q# R0 v* `
+ |7 @/ h, V; j, e
DMA 错误中断“DMA_IT_TE”,一般用于前期调试使用,用于检查DMA出现错误的次数,发布软件可以不使能该中断。
& Z, P4 I# C0 L5.3 接收处理
2 Q0 |! J8 T4 D" s8 b 基于上述描述机制,DMA方式接收串口数据,有三种中断场景需要CPU去将buf数据拷贝到fifo中,分别是: - DMA通道buf溢满(传输完成)场景
- DMA通道buf半满场景
- 串口空闲中断场景
4 ]5 e. E4 i' R* R; U- U( ]* H- g, D* v1 n
前两者场景,前面文章已经描述。串口空闲中断指的是,数据传输完成后,串口监测到一段时间内没有数据进来,则触发产生的中断信号。
1 i- G _0 C1 c$ |2 U4 R 5.3 .1 接收数据大小
( M. |4 V5 B: I 数据传输过程是随机的,数据大小也是不定的,存在几类情况: - 数据刚好是DMA接收buf的整数倍,这是理想的状态
- 数据量小于DMA接收buf或者小于接收buf的一半,此时会触发串口空闲中断
0 S: m0 s; V( `1 l: m" a6 j$ A- {, t8 U6 S& u( I, H- Y D/ z
因此,我们需根据“DMA通道buf大小”、“DMA通道buf剩余空间大小”、“上一次接收的总数据大小”来计算当前接收的数据大小。 - /* 获取DMA通道接收buf剩余空间大小 */
* O! V+ @6 B$ t: } - uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
复制代码
1 {0 o* H) g9 t3 m7 YDMA通道buf溢满场景计算 ! b: \' }" s9 y5 u
- 接收数据大小 = DMA通道buf大小 - 上一次接收的总数据大小
复制代码
, ~% J/ x3 c3 h( N/ o) j/ r' v9 m" F, g5 l$ R( Q
DMA通道buf溢满中断处理函数: - void uart_dmarx_done_isr(uint8_t uart_id)3 \5 U( Q" ~0 m: b: x D9 Y
- {
/ q0 J& I5 `% I9 l - uint16_t recv_size;
4 _9 i9 ~# n6 U `/ Z: v, v6 c, T - & n1 j9 t$ r! F8 {5 M% f4 B
- recv_size = s_uart_dev[uart_id].dmarx_buf_size - s_uart_dev[uart_id].last_dmarx_size;
4 N# ` }' x4 D* w$ O. f( Q, a - + V& {4 I: w; W, d
- fifo_write(&s_uart_dev[uart_id].rx_fifo,
$ R$ Q/ K6 t j& Z - (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);8 j! o, ]+ n, @( a7 v
- W( j' R5 ^# I$ E G$ R# ~- s_uart_dev[uart_id].last_dmarx_size = 0;
- f! F0 L0 Q* z, e9 R6 r \7 y! }7 m - }
复制代码
2 u' D$ u1 O" O( O7 q7 s) G# yDMA通道buf半满场景计算 9 G7 v* [% L) R! _0 S: g: D
- 接收数据大小 = DMA通道接收总数据大小 - 上一次接收的总数据大小- m5 Q5 |6 q4 S. m+ i
- DMA通道接收总数据大小 = DMA通道buf大小 - DMA通道buf剩余空间大小
复制代码
: {0 d$ v3 P- p. B% q* ?7 N6 e) @DMA通道buf半满中断处理函数:
( h5 ~8 v% |" X: m9 N/ u2 e
- void uart_dmarx_half_done_isr(uint8_t uart_id)
2 V( H" _1 K. M: i - {; O5 ]1 m. y g0 x; B( a
- uint16_t recv_total_size; L U$ W |; I5 U
- uint16_t recv_size;
D7 {' E5 a1 ` - 4 d! P4 Z! {1 \; x& N. Q' V
- if(uart_id == 0)' \0 }0 q8 S6 V
- {9 t3 I1 M7 c6 ?
- recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart1_get_dmarx_buf_remain_size();4 a: i0 \5 v$ X- u
- }
- g D! e( U/ ` - else if (uart_id == 1)" L$ M9 g. U3 R' i8 M3 ?
- {
6 ^( I# s* w2 } - recv_total_size = s_uart_dev[uart_id].dmarx_buf_size - bsp_uart2_get_dmarx_buf_remain_size();$ p3 V, o, o! L% Z: y2 e
- }7 z& B# R7 @: Z7 ?- I Z
- recv_size = recv_total_size - s_uart_dev[uart_id].last_dmarx_size;
, U' ^$ K4 k7 ]- ]/ Y4 Z -
* Q; G) p/ g# l - fifo_write(&s_uart_dev[uart_id].rx_fifo, + `1 o m$ I! V3 y$ n, E
- (const uint8_t *)&(s_uart_dev[uart_id].dmarx_buf[s_uart_dev[uart_id].last_dmarx_size]), recv_size);
, X9 [0 f& b1 B" } - s_uart_dev[uart_id].last_dmarx_size = recv_total_size;/* 记录接收总数据大小 */
2 W( N# C! k& h7 S' h% I8 X - }
复制代码 & |% T' A" ~; f! h& q
4 I _/ N6 b' B, R: t7 f
串口空闲中断场景计算
1 t; {7 E+ r1 L7 W
串口空闲中断场景的接收数据计算与“DMA通道buf半满场景”计算方式是一样的。 串口空闲中断处理函数: 注:
4 d" |" Z7 S( E& O2 T" b/ ^串口空闲中断处理函数,除了将数据拷贝到串口接收fifo中,还可以增加特殊处理,如作为串口数据传输完成标识、不定长度数据处理等等。 9 \8 [; s1 c4 G
5.3.2 接收数据偏移地址
4 W0 }3 k3 E2 \! @: O" @# q. E v 将有效数据拷贝到fifo中,除了需知道有效数据大小外,还需知道数据存储于DMA 接收buf的偏移地址。有效数据偏移地址只需记录上一次接收的总大小即,可,在DMA通道buf全满中断处理函数将该值清零,因为下一次数据将从buf的开头存储。 ' \% [( l4 z! b! y; E
在DMA通道buf溢满中断处理函数中将数据偏移地址清零: . N, ?5 `& T3 g! z& g8 W
- void uart_dmarx_done_isr(uint8_t uart_id)6 u2 A# s$ N/ o1 s E
- {
( u4 j" z) S' g: G) m - /* todo */
1 Z' x0 V) V$ x3 `5 g - s_uart_dev[uart_id].last_dmarx_size = 0;
2 o8 f6 [4 @9 c% H. n7 b) n& j* v - }
复制代码
: G! U4 V* @ S; [8 d* y% Q- K4 T
: _/ o* f# z4 L2 N( `5.4 应用读取串口数据方法) c" z( R6 ~# _8 T
经过前面的处理步骤,已将串口数据拷贝至接收fifo,应用程序任务只需从fifo获取数据进行处理。前提是,处理效率必须大于DAM接收搬运数据的效率,否则导致数据丢失或者被覆盖处理。
* h; J0 r9 S, R* w6 串口DMA发送
3 c% Z, M0 Q& |* o& \
3 _ ~9 s; [; j/ Q0 {* L, T. C: g0 E5.1 基本流程
. v( L1 A# X( C! p, B+ n2 W
8 j. U$ F3 R& g2 q% P) G/ M0 h% y4 j2 r2 I" y7 `/ P; D
串口发送流程图 ' e2 Z ?, E9 x1 g( L! _6 }) l
6 M$ S5 Z' x' k0 k( N: j' C' ~9 G4 k3 Z2 c/ s
5.2 相关配置
$ @1 R% i# p/ D4 K- E2 {- {$ I: R5 K关键步骤
1 j! ^* G. Y4 q$ c
【1】初始化串口 % e; d, i. m8 z% N
【2】使能串口DMA发送模式 - m- E) |: D0 V* |6 j
【3】配置DMA发送通道,这一步无需在初始化设置,有数据需要发送时才配置使能DMA发送通道
% f' s5 X( h3 d" P. { UART2 DMA模式发送配置代码如下,与其他外设使用DMA的配置基本一致,留意关键配置:
* |$ U/ X7 p; t' K# p
- 串口发送是,DMA通道工作模式设为单次模式(正常模式),每次需要发送数据时重新配置DMA
- 使能DMA通道传输完成中断,利用该中断信息处理一些必要的任务,如清空发送状态、启动下一次传输
- 启动DMA通道前清空相关状态标识,防止首次传输错乱数据* f" l" i- J0 T/ T+ `
8 b, ~1 c* w0 E! W; B) V4 _- {) S' a
- . s! m( j: K+ ^9 n, ]
4 N1 {7 R/ R$ u5 D1 F
: r5 P9 y T4 L2 F5.3 发送处理 串口待发送数据存于发送fifo中,发送处理函数需要做的的任务就是循环查询发送fifo是否存在数据,如存在则将该数据拷贝到DMA发送buf中,然后启动DMA传输。前提是需要等待上一次DMA传输完毕,即是DMA接收到DMA传输完成中断信号"DMA_IT_TC"。 串口发送处理函数: 9 b! V) D: N6 j- y1 r# h) S
- void uart_poll_dma_tx(uint8_t uart_id)
! u9 w# _* G0 V! m - {
7 o0 ~! ?: K6 a9 r$ c. h - uint16_t size = 0;5 W) w5 m( s7 D7 [9 F0 e U; T( P! s
-
7 X- V& ^8 {3 T' [( e( { - if (0x01 == s_uart_dev[uart_id].status)2 y$ `/ P6 P* r+ }2 ?% O
- {
, r% l' h# t1 t9 Z' `3 \9 i - return;
$ F9 o4 u9 n2 c; Y/ h _; m* Y- v - }9 |" e9 H% L% \
- size = fifo_read(&s_uart_dev[uart_id].tx_fifo, s_uart_dev[uart_id].dmatx_buf,
2 ]0 e% Y% f: E) {. Y* A: h C8 d - s_uart_dev[uart_id].dmatx_buf_size);; ^# Q* H. w; Z" Q
- if (size != 0)
( t! Y& G3 o- A2 ^6 D) m - {2 M7 H% d {& U* P* w! n
- s_UartTxRxCount[uart_id*2+0] += size;9 i/ |1 u& ]# [- }* j
- if (uart_id == 0)8 H" _1 u2 l& P# ?; g& p
- {5 J) A) x- U8 C+ D1 n8 D: w5 o
- s_uart_dev[uart_id].status = 0x01; /* DMA发送状态 */
. R, W' u, A$ n4 u - bsp_uart1_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);% V; g" H6 [+ H ?- X6 h! z
- }* i/ l4 U4 P( ?1 ]8 U
- else if (uart_id == 1)( F) J, g8 q$ q* z! W5 y
- {
" O/ a& s2 R6 k/ c - s_uart_dev[uart_id].status = 0x01; /* DMA发送状态,必须在使能DMA传输前置位,否则有可能DMA已经传输并进入中断 */
8 x8 U. F3 U5 ]; h0 Y5 W - bsp_uart2_dmatx_config(s_uart_dev[uart_id].dmatx_buf, size);
# q8 S. d( [+ Z- _ - }
1 r6 H1 M% I. f5 d- X, G - }/ F1 B2 d4 b3 o. v% k& y* p; M
- }
复制代码
9 N( W8 b/ m! ~. Z [注意发送状态标识,必须先置为“发送状态”,然后启动DMA 传输。如果步骤反过来,在传输数据量少时,DMA传输时间短,“DMA_IT_TC”中断可能比“发送状态标识置位”先执行,导致程序误判DMA一直处理发送状态(发送标识无法被清除)
: L* N3 K8 E7 C! ~3 o8 \
6 v5 X1 s9 v. j5 R9 F; {3 l
注:- d% R* W' N+ I3 a) c
关于DMA发送数据启动函数,有些博客文章描述只需改变DMA发送buf的大小即可;经过测试发现,该方法在发送数据量较小时可行,数据量大后,导致发送失败,而且不会触发DMA发送完成中断。因此,可靠办法是:每次启动DMA发送,重新配置DMA通道所有参数。该步骤只是配置寄存器过程,实质上不会占用很多CPU执行时间。 & V2 `; m+ h S6 G# w8 C2 Z$ P
DMA传输完成中断处理函数: - void uart_dmatx_done_isr(uint8_t uart_id)* c( l! N' k3 U/ |
- {6 L) {$ ~. V$ @: t& ^; t
- s_uart_dev[uart_id].status = 0; /* 清空DMA发送状态标识 */
1 u" w7 y! ]2 H f: z) `: j; D% ~ - }
复制代码 . ?; k7 P/ \' F( ?
5 m1 Z1 q7 _9 m( f" {5 ?5 O/ c
4 M' s9 R; |( ]$ {# Y 上述串口发送处理函数可以在几种情况调用:
7 W: J9 G! W+ D$ A# R" N
4 Q f" w! ]' h
- 主线程任务调用,前提是线程不能被其他任务阻塞,否则导致fifo溢出2 Z) l7 A$ v0 b$ L. b( I
8 V/ D" d3 `. w0 n/ E
- void thread(void)
# j1 }* ^0 @% |! _6 f - {/ O. G5 \# G+ P. A b# |9 W' U* Q
- uart_dmatx_done_isr(DEV_UART1);! B# L+ P' e0 F
- uart_dmatx_done_isr(DEV_UART2);$ c; q. _* K: `2 }% F7 S; e2 A
- }
复制代码
c6 K( ?, x/ V" H) y
/ C- x) U! ]% \/ G' \" `; d- 定时器中断中调用
& e* |% B7 q) G( W2 E# r
- void TIMx_IRQHandler(void)/ D( f( p8 \3 c. R/ c' r( m; N
- {
# A) p+ L, g2 b9 S - uart_dmatx_done_isr(DEV_UART1);& g& S- i2 n) S( N
- uart_dmatx_done_isr(DEV_UART2);* w! o5 u8 ~; t# p# _
- }
复制代码 # l1 @! {- P4 j( X U7 q
6 c6 m+ f3 H: s' `2 Y! t; J+ k% a- DMA通道传输完成中断中调用/ D+ Z' w& q& O) h% O
6 W; n7 b* e; l6 r6 S2 t5 n
- void DMA1_Channel4_5_IRQHandler(void) I6 J( g! i3 Q. Z; Y/ P; f* j# T$ F
- {* m# v4 [3 S% f. a
- if(DMA_GetITStatus(DMA1_IT_TC4))! E: ]- }9 K" w4 Y
- {5 K7 j4 b; S7 ?, l$ b. {
- UartDmaSendDoneIsr(UART_2);: b1 \" [, F% f d+ t7 I+ n
- DMA_ClearFlag(DMA1_FLAG_TC4);
) K+ |" p0 n+ M: m7 R3 c! L4 F! W - uart_dmatx_done_isr(DEV_UART2);, }$ m$ F! r( R2 X0 d
- }
# K6 h, B7 x L& Q6 X6 t+ c) w- j - }
复制代码每次拷贝多少数据量到DMA发送buf: 关于这个问题,与具体应用场景有关,遵循的原则就是:只要发送fifo的数据量大于等于DMA发送buf的大小,就应该填满DMA发送buf,然后启动DMA传输,这样才能充分发挥会DMA性能。因此,需兼顾每次DMA传输的效率和串口数据流实时性,参考着几类实现: - 周期查询发送fifo数据,启动DMA传输,充分利用DMA发送效率,但可能降低串口数据流实时性
- 实时查询发送fifo数据,加上超时处理,理想的方法
- 在DMA传输完成中断中处理,保证实时连续数据流
/ F/ b% s4 C5 |" y" c4 D) @8 a: K3 S* G8 o6 i& W' \& v
2 ?) A. c& @8 M
6 串口设备
4 k) u' {* G$ B, R6 T
9 N0 S8 t- |/ F1 U, j7 r' {+ L1 A6.1 数据结构
# {- m3 m" m* Z' ?5 K- /* 串口设备数据结构 */( x% h7 C5 i; L& _" F4 U5 o$ \2 Q
- typedef struct" ~1 q- b2 b T: I+ |& q6 D+ M
- {& H4 H$ O% G# s3 S- J
- uint8_t status; /* 发送状态 */1 o4 \8 l7 m# O
- _fifo_t tx_fifo; /* 发送fifo */+ o" ]. J# T8 e( D7 V8 q& q
- _fifo_t rx_fifo; /* 接收fifo */7 M$ r, I. L- j7 X
- uint8_t *dmarx_buf; /* dma接收缓存 */
% B5 T) ~% I( f: Y - uint16_t dmarx_buf_size;/* dma接收缓存大小*/
% o" P: s% G5 Q E - uint8_t *dmatx_buf; /* dma发送缓存 */
, W4 G5 _0 h! `9 Z - uint16_t dmatx_buf_size;/* dma发送缓存大小 */! t! g9 v% ]$ i/ {3 z
- uint16_t last_dmarx_size;/* dma上一次接收数据大小 */
0 K9 c. c8 {/ k7 M - }uart_device_t;
复制代码
( _8 `% k( R5 y7 c! {" o0 ~% H7 e. h8 E& s
6.2 对外接口2 r. c3 p6 q' o1 W3 N
7 Z& I, b0 A9 H
- /* 串口注册初始化函数 */' n. N- j( d7 w6 @; D
- void uart_device_init(uint8_t uart_id)
6 ^8 k5 u4 a, {' P6 ~0 _ - {, b; i4 \7 {# N9 O* k; }
- if (uart_id == 1)0 E$ H Z2 X4 W0 `& [) b7 ~
- { @0 y" q" g& ^8 j
- /* 配置串口2收发fifo */
' ^: g$ ~! t+ ` F1 t% c D, Q1 K - fifo_register(&s_uart_dev[uart_id].tx_fifo, &s_uart2_tx_buf[0], ) u3 p& C* c$ M Y! A
- sizeof(s_uart2_tx_buf), fifo_lock, fifo_unlock);) o) q9 n2 o) \! U/ A6 v
- fifo_register(&s_uart_dev[uart_id].rx_fifo, &s_uart2_rx_buf[0],
! ], ^: @- e2 H% U/ @8 M - sizeof(s_uart2_rx_buf), fifo_lock, fifo_unlock);6 u' f# _# K$ g
- " m$ z# y/ e2 n: @7 t# P( e! p9 Q
- /* 配置串口2 DMA收发buf */
: v6 v) p+ V% m4 c& N# X - s_uart_dev[uart_id].dmarx_buf = &s_uart2_dmarx_buf[0];
1 k1 Z( ~. ?2 O% E4 l - s_uart_dev[uart_id].dmarx_buf_size = sizeof(s_uart2_dmarx_buf);. O4 j$ q" ^+ m: {8 ?; N' U5 v
- s_uart_dev[uart_id].dmatx_buf = &s_uart2_dmatx_buf[0];
) _/ f5 r. D; q! F - s_uart_dev[uart_id].dmatx_buf_size = sizeof(s_uart2_dmatx_buf);2 [: k- q7 n" m0 c2 A4 [( V
- bsp_uart2_dmarx_config(s_uart_dev[uart_id].dmarx_buf,
3 I- i' @8 I2 o5 V6 e3 _% j - sizeof(s_uart2_dmarx_buf));3 R' F$ _3 f) S( F# S& A
- s_uart_dev[uart_id].status = 0;% I5 ~4 m, n. j7 y7 c' t4 B
- }
; D& d. Y. j- V( @* ~ - }# g C2 j% E7 v# k( w) U6 O
- 0 |, |+ V0 V$ L# Y2 D5 D
- /* 串口发送函数 */
- h6 O' R- `$ d - uint16_t uart_write(uint8_t uart_id, const uint8_t *buf, uint16_t size)7 x; q' O/ w7 M
- {- g8 r2 }, N) H1 | ?
- return fifo_write(&s_uart_dev[uart_id].tx_fifo, buf, size);/ b4 r' @( J3 s: |
- }" h( o# O' a' _$ w7 Y8 l
7 c* O7 S* h' l- /* 串口读取函数 */; D9 T/ x5 u5 B7 N$ f/ w
- uint16_t uart_read(uint8_t uart_id, uint8_t *buf, uint16_t size)
0 e7 l$ @9 B8 |. B j2 ~ - {
% R' _- [2 z+ _ - return fifo_read(&s_uart_dev[uart_id].rx_fifo, buf, size);5 _7 B; K' G$ s
- }
复制代码
/ N8 u( Y5 h1 [( }$ h8 q8 X5 w3 Q8 完整源码
' p3 C7 w) b ?% }% g- _8 |: Q6 w* S2 a& v9 N5 d( C
% L9 G9 J* S0 D
串口&DMA底层配置: , h( y6 X0 J' z5 _
- #include <stddef.h>( b# f1 B' m( g2 C
- #include <stdint.h>
1 H0 u1 M" \, S6 t+ K% V) u! w - #include <stdbool.h>
4 W( P" b. N* B - #include "stm32f0xx.h"
& V% i$ n+ C/ l - #include "bsp_uart.h"; ^! |" @4 K& {- H
- ( J; O1 A( f2 J4 j, {7 N4 S
- /**
6 i: ]* L2 M; B' ~3 M - * @brief ( n' E/ S v1 w1 a" b- N# T" ~ D
- * @param 2 ]( d# p" c/ @ a
- * @retval , `6 |4 e% ]5 O* h# Q
- */
+ P3 ~) F' Y9 v - static void bsp_uart1_gpio_init(void)' C( }. |1 s5 r! p0 `
- {
) Y D! \8 [" U0 J6 {( E# X - GPIO_InitTypeDef GPIO_InitStructure;
9 D' O- Q! n5 u& q6 x$ ? - #if 0
9 {% \- D1 Z, g8 ` T* M/ r - RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
6 d( J8 s% S8 t, W -
7 `; P( I2 b$ d+ }: j$ c - GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);
8 [2 c2 W1 J& B+ Z - GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0);
! g1 Y5 _( k% j- L( n - 1 e) }9 n Q) S* _8 u, V
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;- l3 a: M: \( x, Z% M. m8 u8 X- S
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
2 Z! s. m/ J. a - GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;. G# r, o, A& y. M6 D
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;8 u1 N) _* ^: @7 ~, M
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
$ L- X% x8 x+ `5 X - GPIO_Init(GPIOB, &GPIO_InitStructure);
! w/ ]- c$ p% { - #else5 R# l3 S7 o& F9 ^: N
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
_' q- P, p2 g( S% l6 S) \ - / h: r5 s) X, l8 l
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_1);
+ o4 r; b: |) u% P# d+ N, h+ E - GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_1); / d9 e; J0 F: P& B$ M; ]3 z
-
0 z: b+ s5 K4 Z/ `' j/ M& t - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;, D2 Y. L, D2 R% Q8 v! H
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;* W. o( ]' q. \5 M( Q3 ]
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
( O) G' B1 G" {) `7 \ - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;/ r) o; ?% S. G! |2 x: M
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;% _& y/ [/ k" [2 p' s4 v( P+ T
- GPIO_Init(GPIOA, &GPIO_InitStructure);
# v) e; O, k; o% p - #endif
& O5 s* \% J( q0 ]. K5 ?; Y I - }- |& i4 e) J& [6 ]* ~9 S* Z7 W U7 x
6 G" V6 q# _7 W* H, l7 I; P- /**
6 v" _9 ~2 G: g7 O - * @brief
& G, l3 N& B/ M1 [ - * @param 1 f7 f2 Z2 ~* \' A7 x: B
- * @retval 8 k7 _! S' x: h3 M. _
- */
, A5 Q" `2 k! T - static void bsp_uart2_gpio_init(void). P2 F! p! j6 n; D' v! {
- {
5 O- D; q$ R" N" F% V - GPIO_InitTypeDef GPIO_InitStructure;
+ W0 E, G* `" W) T: G3 N: f8 w - ( F9 M) T. C. I. p
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);/ K' ?$ S# o# p) ]. u/ s
- 8 A+ n0 d& e* e+ @6 I
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1);( L) W' t& H; C0 [, G
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_1);
+ V7 K& ]. \7 Z; {% f0 m - , s1 W( y- p0 v8 g
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
& H% W# k$ A1 Q" s5 `) b - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;3 R9 L( _/ ] U5 H3 f3 y- u: \: t( _
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;) J. a- P: ^6 r4 ^
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
0 @4 H$ |, S# B& Q u) D - GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
, l5 D8 e# L `2 H9 q - GPIO_Init(GPIOA, &GPIO_InitStructure);( w0 R$ [! Z# J9 }
- }0 y9 I) z+ }3 O
# y5 H- d' O6 w6 N' f- /**
- \- C' t' l. r6 ~* @ - * @brief ?4 U4 s. G" ~# x0 R7 _: X
- * @param 5 z T1 u( L$ j9 j
- * @retval
" \ Y+ B1 a3 b0 C. }2 n0 B - */
# n4 [- U7 g- V/ w; l7 M2 a! M0 h1 B - void bsp_uart1_init(void)4 v5 ?/ \. \( C+ V
- {
' |2 H, z5 T! g8 D% F g - USART_InitTypeDef USART_InitStructure;' K5 g# i- R1 R3 S" N
- NVIC_InitTypeDef NVIC_InitStructure;
$ @8 [7 T# j* `( D% ? - # S* d+ S, x: c
- bsp_uart1_gpio_init();6 y& v* ]7 D7 y& [4 C
- # C1 L& X/ N2 m ]4 n+ x. n8 h* g% g
- /* 使能串口和DMA时钟 */( U( O( g$ d: r
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);) u3 y" [8 p$ d3 _
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);' c2 T9 N/ I& k0 } C
- + j) q) P9 A9 J, S5 i2 g
- USART_InitStructure.USART_BaudRate = 57600;
/ T+ `+ ^& h [" L9 P- Q: s6 a% K - USART_InitStructure.USART_WordLength = USART_WordLength_8b;: K7 F$ c+ \# U! L7 D" [
- USART_InitStructure.USART_StopBits = USART_StopBits_1;. Z) Q. O- w; {6 `) c: {; ]# Z# u+ O
- USART_InitStructure.USART_Parity = USART_Parity_No;
o/ ]' Q3 ~- e9 B1 E: ~0 d - USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;; R$ p: m5 m. L4 c/ C$ {/ M
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;/ g& W) R- j5 A; Y& J5 {. r( k2 F( a& }
- USART_Init(USART1, &USART_InitStructure);. `; B. z" W7 s- Z% m
-
$ |1 O6 w7 h6 s# n4 u - USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); /* 使能空闲中断 */
$ T- ^+ P5 w" v - USART_OverrunDetectionConfig(USART1, USART_OVRDetection_Disable);" r; A+ O# p# H% [ i& F
- % z0 {, Z a- M
- USART_Cmd(USART1, ENABLE);
6 y" ]& P0 y' _; s- r$ O7 w4 q - USART_DMACmd(USART1, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); /* 使能DMA收发 */
1 c$ K! ~8 G, b; J w
! X' e6 U7 E. O1 z9 U" `- /* 串口中断 */0 T9 t, ~1 o* b
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
( V& C/ n; E: y+ ] A# Z8 v - NVIC_InitStructure.NVIC_IRQChannelPriority = 2;
- T: u4 N& Y4 J! Z7 r6 z5 K - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;& A0 \7 g; N' y7 Q/ l$ O# V
- NVIC_Init(&NVIC_InitStructure);& y+ l2 W h' @2 J# @8 ^
- " B& ^+ K! s, M& b
- /* DMA中断 */
' `* U) v) D! `2 }* `' U! H; Y - NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_3_IRQn; * j2 w/ V2 W2 L$ y8 {7 T
- NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
' _8 r9 p! l6 h! q6 j& [" C& B* v - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
, R7 V4 `/ x$ o0 x - NVIC_Init(&NVIC_InitStructure);& S* P; s/ ]1 P; a
- }3 {8 P7 r T$ y, p
% e1 x3 i9 `( u9 p& P# G- /**
: ^4 V$ v1 Q" i1 G" L - * @brief
7 m0 p3 E% K0 r3 x! b - * @param
0 N: T5 y5 }. P$ M4 o! i - * @retval 4 S' L% y6 q5 L7 Y7 i
- */4 z; p( e5 H) N2 l Y- u/ H
- void bsp_uart2_init(void)
, d" Q- ?* s4 s* e* C! H - {! H3 P: b4 ]; U: u7 ]
- USART_InitTypeDef USART_InitStructure;" k ~: U$ D: T
- NVIC_InitTypeDef NVIC_InitStructure;
R) k, k" J. B- [0 i- _2 h -
8 j4 n: C* P) h: `+ w1 M+ T - bsp_uart2_gpio_init();
4 J( o# b) b9 d' R -
; k6 M; B! _ A" e, v - /* 使能串口和DMA时钟 */0 g3 }* v/ F& s
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
" X9 A8 n" r/ X' @# \ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);' ^( `4 I! }9 K: E1 `5 a
: _' D0 g% d& t: ]1 z- USART_InitStructure.USART_BaudRate = 57600;& D. f5 X) i+ x, N% u
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;
8 ]: K5 [# o7 x8 ~* s1 ~- n - USART_InitStructure.USART_StopBits = USART_StopBits_1;8 B2 Z0 ^8 J# ^
- USART_InitStructure.USART_Parity = USART_Parity_No;8 q( X9 \8 E! H9 t7 }
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;) m7 E& f: R1 M+ T9 V- g+ O
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
% _& S4 A# O6 x9 j1 I - USART_Init(USART2, &USART_InitStructure);
. |: H3 k4 F% |+ x& O - + U: h V8 n' B
- USART_ITConfig(USART2, USART_IT_IDLE, ENABLE); /* 使能空闲中断 */
) p% g1 V2 o P9 K - USART_OverrunDetectionConfig(USART2, USART_OVRDetection_Disable);( |" o, S3 x u4 M- p6 }+ v% I
- . Q! W& r% M: |5 n* A5 Y
- USART_Cmd(USART2, ENABLE);2 l1 f3 ]! q, y' z* Y
- USART_DMACmd(USART2, USART_DMAReq_Rx|USART_DMAReq_Tx, ENABLE); /* 使能DMA收发 */
# V: l% t9 V" h6 ]7 y - 6 U. R, R$ t2 \; x: c( i8 `9 x, I
- /* 串口中断 */
5 F1 S* o1 f4 m1 X - NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;0 e0 r# I- r. }6 a: f
- NVIC_InitStructure.NVIC_IRQChannelPriority = 2;/ m0 t8 Y0 _' s _7 U9 m+ j) n
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;6 l, h4 l: ^8 E L5 y4 r
- NVIC_Init(&NVIC_InitStructure); A7 [( [$ U. M, M4 I
# n+ ]) k; L( ?* a6 B$ G5 e- /* DMA中断 */
2 Z% |0 d, O2 ^! s( n; k - NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_5_IRQn; [( |& e, L9 l$ ?& p/ T
- NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
, {' H9 f/ c [9 T0 l2 g" e - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
* x0 R' q i; L- ~+ q4 d: b- q - NVIC_Init(&NVIC_InitStructure);
5 k; B% x& o( @" l - }9 e. m2 v0 y7 L1 b
: d) P6 t% _7 H' L; a! k3 v% ^- void bsp_uart1_dmatx_config(uint8_t *mem_addr, uint32_t mem_size), Y5 v: W1 i0 W2 G0 b
- {% d8 \5 F+ p+ j, s5 w& G
- DMA_InitTypeDef DMA_InitStructure;# y- Y1 o2 O/ ]3 D
-
: l+ U( K% m" v" Q! W- c5 @. Z - DMA_DeInit(DMA1_Channel2);+ ^" x0 S- @4 s, Y" S
- DMA_Cmd(DMA1_Channel2, DISABLE);
8 |! \' X1 J0 m - DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->TDR);( [) r' `! B* E: m A( [% t; h
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr; : i% G$ n' B# N
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* 传输方向:内存->外设 */% q2 M# o1 M, N3 ?2 c
- DMA_InitStructure.DMA_BufferSize = mem_size; ' A. I9 H! l( r7 V* I5 D8 x- M& m" _& r
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
! k$ n; _/ ^3 k' b! d - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
/ {. i( h8 ?& S+ ^: A! ]$ Q - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
9 ~! E, t0 l+ A* b - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;0 ^: p( ~ P) }5 @, ~
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
+ {0 M" V, G, M. L. R3 D - DMA_InitStructure.DMA_Priority = DMA_Priority_High;
% m) B0 M& p) O/ }: @4 p4 Q/ h+ l - DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 1 \( f" ]: M; Q$ G; T5 r# f3 k
- DMA_Init(DMA1_Channel2, &DMA_InitStructure); + t, Z! H1 L' l! t- T) ~: ? o
- DMA_ITConfig(DMA1_Channel2, DMA_IT_TC|DMA_IT_TE, ENABLE); 4 l M# W* J; @
- DMA_ClearFlag(DMA1_IT_TC2); /* 清除发送完成标识 */
2 Q, t* B8 R8 s9 e4 T - DMA_Cmd(DMA1_Channel2, ENABLE); * ~7 \; {! I2 i: A* t
- }9 ?9 o1 E7 b" _0 n7 _
, z2 ?$ q I( [$ h, _5 a- void bsp_uart1_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)
! J8 e0 o$ { `' D" Y) d% T - {
3 W$ k; Q- F4 W* M - DMA_InitTypeDef DMA_InitStructure;5 H/ ~/ y, z: ` D: L6 `: k. V
- + I& a/ E, m: Y) q' W' X
- DMA_DeInit(DMA1_Channel3);
0 ~8 ?; R$ Y9 f6 ^5 z: ^0 ` - DMA_Cmd(DMA1_Channel3, DISABLE);' j/ j M# j. w6 u2 R$ Y
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->RDR);
4 C$ ~6 ]1 ?# L5 K6 c- F - DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
5 Y; ~& P: T Y, A: m J7 N- P - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设->内存 */
4 |% W( F+ p8 v1 Y; H - DMA_InitStructure.DMA_BufferSize = mem_size; , U+ E3 o- D, O5 c% v. k7 Y' H
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 6 t0 Z6 w# R5 k0 ^2 r7 L$ }; T, S
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
2 i. l7 ^; G( k3 p/ H% T - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; $ C) ?7 @0 Z7 z0 W" [6 ]
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;0 h* T" G6 q6 h6 l% F' P9 |' `6 V
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 1 @4 q( H2 Y, [1 H. u6 i. g! j# H( d- V) k
- DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; - m2 ~+ ~8 a$ z
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
7 {5 ^- e7 [& E1 t - DMA_Init(DMA1_Channel3, &DMA_InitStructure);
/ Q3 W3 e/ D2 d5 l) }! l" V& T0 \ - DMA_ITConfig(DMA1_Channel3, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、全满、错误中断 */! Q) Q( o# V d; p
- DMA_ClearFlag(DMA1_IT_TC3);
# S% J+ K X/ d# i# [ - DMA_ClearFlag(DMA1_IT_HT3);
2 M) j; A" | _/ X- z4 T4 j" a - DMA_Cmd(DMA1_Channel3, ENABLE);
: }, ~4 l" [" N% o- K! {2 W2 e - }2 W% E6 m5 ?0 j9 h
- ( _+ q/ l8 {2 _' n9 r: Z* @; ^ H
- uint16_t bsp_uart1_get_dmarx_buf_remain_size(void)% \ E0 Z" Y; y7 F
- {3 y$ ]! j" p+ g! _# L( p, N% ~
- return DMA_GetCurrDataCounter(DMA1_Channel3); /* 获取DMA接收buf剩余空间 */
0 x7 D, w/ [2 X- G - }
9 v2 p v' N$ Z8 Y( I: \8 r
: o; C# _% @* R6 J( t- void bsp_uart2_dmatx_config(uint8_t *mem_addr, uint32_t mem_size)$ O$ @& T7 I T/ k
- {% J6 |( ^& a! o6 C2 v' O9 ?
- DMA_InitTypeDef DMA_InitStructure;$ r- b; Q, j$ h5 p" L
- " n: C* K" f: ?
- DMA_DeInit(DMA1_Channel4);
! F$ q* P, x) f6 Q; n- V4 J ?% B - DMA_Cmd(DMA1_Channel4, DISABLE);: `* ^) _4 d5 b9 e
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->TDR);
8 O, H1 j# b' M1 n/ P# Q - DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr; F1 J8 I& w4 ~; Z' H* B1 R* x! B
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /* 传输方向:内存->外设 */5 ?. k9 w8 Z0 d U
- DMA_InitStructure.DMA_BufferSize = mem_size;
5 }6 B9 S7 N* M; n - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; ( c" s. x" ~& z/ u d& p0 E. R
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
' K5 y" |" L0 m! J! ]* n* t - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
0 m+ S7 O; Y& N" @ D" a - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;0 I; |- V& {- [
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; 0 u' A, ]2 {+ @' u+ H
- DMA_InitStructure.DMA_Priority = DMA_Priority_High;
i( y% n, |! _( V - DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
. U$ \9 t5 ?3 {* V - DMA_Init(DMA1_Channel4, &DMA_InitStructure); / P- }; f& J$ v' C- k. M: s
- DMA_ITConfig(DMA1_Channel4, DMA_IT_TC|DMA_IT_TE, ENABLE); 5 W, u6 q0 |3 r9 y7 M# U' Y$ a
- DMA_ClearFlag(DMA1_IT_TC4); /* 清除发送完成标识 */
9 { y, p8 O. U$ `/ ^0 Z$ g - DMA_Cmd(DMA1_Channel4, ENABLE);
F \( t* t' R6 T+ O" l( v - } |* \$ @8 R' z( C7 z: D# H
/ Z- T8 ^4 O) Z( j0 f- void bsp_uart2_dmarx_config(uint8_t *mem_addr, uint32_t mem_size)) `$ n2 f3 l6 e& h
- {
8 j; n; M3 w2 Z: d - DMA_InitTypeDef DMA_InitStructure;
[' N( F% M" v - ' a- l: I% T6 u5 }0 n% L7 @, T
- DMA_DeInit(DMA1_Channel5);
4 d- c4 H+ O5 D- G- o - DMA_Cmd(DMA1_Channel5, DISABLE);5 ` {, Y4 J4 V% j \" S
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->RDR);! g. o4 `/ L! W6 P8 Y% R5 z* x
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)mem_addr;
8 Y1 h. {7 K5 o+ w5 l1 j - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 传输方向:外设->内存 */
. ~) q. v, C) ], [; L% Q; ` O - DMA_InitStructure.DMA_BufferSize = mem_size; * \* v2 J& m" b) A+ X, `6 u" h
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
3 H% @4 h( {8 i - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 8 Q3 _7 E1 c3 W2 ^. u$ A
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
' R8 d: x2 F9 {5 {5 n6 y5 x) V2 `0 d# Z - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;! }( T+ v0 a0 z0 G6 B
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
% Z' [ s3 C9 u0 [8 l3 X - DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; - |. Q2 L7 e% \+ \. u5 D5 b2 s
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 3 A1 W$ d V( g& _, q
- DMA_Init(DMA1_Channel5, &DMA_InitStructure);
- r3 i+ r/ ]5 n' ] - DMA_ITConfig(DMA1_Channel5, DMA_IT_TC|DMA_IT_HT|DMA_IT_TE, ENABLE);/* 使能DMA半满、全满、错误中断 */
7 w, |0 C: Y6 u7 t - DMA_ClearFlag(DMA1_IT_TC5);5 v% Y% w# q3 Y2 w
- DMA_ClearFlag(DMA1_IT_HT5);3 J% q- E+ G' ^* y
- DMA_Cmd(DMA1_Channel5, ENABLE); 9 s7 L: D) S5 D, r
- }
, T( h3 n1 m/ Q. c
& }% S* g ?) A2 B6 K5 _- uint16_t bsp_uart2_get_dmarx_buf_remain_size(void)) s/ V9 W% Y& O1 d; u" R5 Q8 m
- {
7 P1 R& _' y* V5 h; B - return DMA_GetCurrDataCounter(DMA1_Channel5); /* 获取DMA接收buf剩余空间 */
6 R7 i: Z0 a7 l( |$ L( s- d- u( [/ ~! e - }
复制代码 ! M9 {/ r+ ~9 C" f
& N. z( |+ u! Z$ `/ F
压力测试: - 1.5Mbps波特率,串口助手每毫秒发送1k字节数据,stm32f0 DMA接收数据,再通过DMA发送回串口助手,毫无压力。
- 1.5Mbps波特率,可传输大文件测试,将接受数据保存为文件,与源文件比较。
- 串口高波特率测试需要USB转TLL工具及串口助手都支持才可行,推荐CP2102、FT232芯片的USB转TTL工具。
) p% M3 p) h9 F8 g2 p
# Y& R$ R$ N2 `3 d' d! k9 f . o& a/ K3 l; I( y7 g* a% q" q
- N C% h l) t7 b0 G1.5Mbps串口回环压力测试 0 y1 `9 Y4 p; \2 ^- Q0 r& F
; s: I* b; k5 A* W, L2 G- w8 m4 Y
0 Y" t, v4 w8 }# y( n9 S |