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