STM32串口空闲中断接收数据的流程:: M' g2 k9 O6 o! A7 r8 R
串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。
- \, b* }4 H+ c判断数据数据接收完成:
p9 I5 Q6 [- B' w( t3 U; A2 H2 Z
这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事: - 关闭串口接收DMA通道,2点原因:a.防止后面又有数据接收到,产生干扰。b.便于DMA的重新配置赋值,下面第4点。
- 置位接收完成标志位
- 处理接收buffer中数据
- 重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。
- 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。! G7 X1 v" q: S* t
说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。IDLE位不会再次被置高直到RXNE位被置起(即又检测到一次空闲总线)。RXNE接收中断可以不用开启,减少进中断的次数。) P; V. g8 C1 G# ~! @$ q
, J# w3 a( v6 y! h" ?4 E) z! J- I- J. v9 |! k; I& K$ B, i
& @0 M1 y/ P$ q. ~; }- j3 @
- void My_UART_Init(void)
: ?6 l$ U+ D4 E1 Q1 L/ W! M9 G4 b - {
6 t% n; Z& A5 w) H3 q( q - GPIO_InitTypeDef GPIO_InitStruct;- w5 Q) `; |4 [ u: C3 [& s4 G
- USART_InitTypeDef USART_InitStruct;
! _( }/ N0 z$ G% x - NVIC_InitTypeDef NVIC_InitStruct;+ f' B; j1 I/ f. E# V
- DMA_InitTypeDef DMA_InitStruct;2 O. K4 z' x2 ?+ e$ {
- 6 G4 I# I! I% a1 ]0 S, e9 n
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 使能DMA
; @3 [; a" T8 f; J - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA
* N( K9 K2 k3 ^) ^' t" N. [7 w0 H - RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); // 使能时钟 复用USART4 V* W* w- Y- }( ^
- # \) q8 ~, Y/ J# y% B
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
0 l1 h" s$ ]! e: Z - GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;% B; E( V8 N. V5 r
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;1 W. d: d; D5 a3 c% l1 M
- GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化 USART_TX 即 GPIOA.9
# a S9 `5 R8 ^+ n5 u
! G' J$ j2 {% Y8 J# \* i- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;9 W5 X$ a7 M4 Z4 m
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
1 b ~2 I- A( _" s+ u+ H& P - GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
2 [8 f; D9 u* L4 |& ^; _' \% ` - GPIO_Init(GPIOA,&GPIO_InitStruct); //初始化 USART_RX 即 GPIOA.10) D* O$ \1 ~! O, K; ]5 i
- " m8 Z- o5 z+ O+ x* C
- USART_InitStruct.USART_BaudRate = 115200;
; `- V5 U4 R+ K% O - USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;. O' j/ W7 v- u9 r" g( O
- USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;( R) O. ] f. k
- USART_InitStruct.USART_Parity = USART_Parity_No;
8 J4 }; w- y- J7 S! S* M - USART_InitStruct.USART_StopBits = USART_StopBits_1;0 _. F) l4 C; {! c% w
- USART_InitStruct.USART_WordLength = USART_WordLength_8b;
% s' P M, X j& o - USART_Init(USART1,&USART_InitStruct); //初始化 USART- @6 C) ?4 n9 {% u/ |% b4 a [
9 \ i w. m8 W6 m) ^- // USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启 USART 接收缓冲区非空中断
. {8 s7 X. I6 V: C - // USART_ITConfig(USART1, USART_IT_TXE, ENABLE); // 开启 USART 发送缓冲区空中断
7 X, E& ] E0 l - m5 N) H) w. ^
- USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启 USART1 总线空闲中断3 ], Q0 ^9 ]6 l# ?
- USART_Cmd(USART1, ENABLE);//使能USART中断( ~9 |$ P5 c: S3 ^% g* S( q
% _* K; p* G; E- DMA_DeInit(DMA1_Channel5);
$ T$ T# ~: t7 k) V6 b - DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR); //外设--->内存3 E9 F/ a5 w# v" _0 V
- DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)RxBuffer;
/ w3 j, e, ]" G - DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
! M3 o- F) m" c% Z - DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
" ~& i# k9 P# |+ H) x, Q - DMA_InitStruct.DMA_BufferSize = BufferSize;- C$ y$ A- x R. i9 Q
- DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
, i; g# O/ R' M9 o4 \ - DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;' ?, \9 o* R% F# L
- DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;( C: h2 o4 e+ N/ ~
- DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;, s: j- j; @ s. u7 r
- DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
' L. z4 T0 Q8 }) N3 [! H# o( U - DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
) j# @. x' c8 t' ^5 b: t+ | - DMA_Init(DMA1_Channel5, &DMA_InitStruct);, x7 B1 U4 C4 z' t
I$ `) D6 l) N i3 r: s) x, f- DMA_Cmd(DMA1_Channel5, ENABLE);
+ k% P. h+ W2 j( h* ?$ v/ C - USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); // 使能 USART1接收DMA
6 o/ i5 L# U7 w7 L - $ r: n W+ t) e; u2 r* e5 h7 W
- NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;& `$ G, y; s. M9 Z5 z; p
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
1 f) s* J3 B$ g( X% w) _; k - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x01; //抢占优先级 2位 00 01 10 11
/ T- G! a& q0 L2 f! }4 {% S) Y) V - NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0x01; //响应优先级 2位 00 01 10 11
- G9 i$ l2 w' B( J - NVIC_Init(&NVIC_InitStruct); //初始化中断
/ m/ M. Q) m$ p# N' d - }
复制代码
& o, g5 s5 z2 {% z0 y$ F- A t
; y* U+ B/ G# \) m. s/ ]4 b( x0 C1 l9 ~' G; f" g W0 C- ^
/ y/ I8 V ^) o7 c2 z5 T$ z( r5 o% `+ h: S$ H
0 s# y+ L6 g; @
* R" f2 F$ P3 c& u+ {% y当MCU通过USART接收外部发来的数据时,在进行第①②③步的时候,主程序可以不用管,DMA直接将接收到的数据写入缓存RxBuffer,程序此时也不会进入接收中断,当数据接收完成之后产生接收空闲中断,在中断服务函数中将接收完成标志位置1,计算出接收缓存中的数据长度,清除中断位,失能DMA防止在处理数据时候接收数据。主程序中检测到接收完成标志被置1,进入数据处理程序,现将接收完成标志位置0,重新设置DMA下次要接收的数据字节数,使能DMA进入接收数据状态。 R- @9 a6 @8 N% S8 P- c& Z
8 \ e+ `- y+ V
- void USART1_IRQHandler(void)
/ h0 j6 m! E4 _ - {
/ }3 N3 _" ~$ q) B - uint8_t clear = clear; // 用来消除编译器的“没有用到”的提醒4 g4 y1 z- e, s y
- uint8_t data = 0;' s) T: k' o* m9 X) C# a
- - W5 i3 T) [" x( a2 e" c. c
- if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET), O4 Q2 z4 a- _0 U Y
- {1 y5 |; y1 x; q" C! `) ]1 v. n
- clear = USART1->SR;
+ m4 a; g0 i9 D6 b& W. u5 C. } - clear = USART1->DR;
# H+ Z5 W; D; t+ H1 U
1 Z+ ?3 R( \' W1 G# d- // RxCounter = BufferSize - DMA1_Channel5->CNDTR;//缓存中的字节数 W7 U( D: {. F' o* Z
- RxCounter = BufferSize - DMA_GetCurrDataCounter(DMA1_Channel5);//缓存中的字节数
: A9 F/ Q6 u: {9 Q9 N" @0 H; v" J" k
: Y6 G2 S2 ~$ d- // USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);0 |, d/ p- x# p( @) Q! C0 a
- 3 V% F7 U$ \' g7 n: I A1 b$ v+ w" r
- RxStatus = 1; //标记接收到一帧
v( X+ N3 |/ D2 h% M - USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断
4 N0 o; a% o5 y6 w9 o2 v - * R9 u; F: V7 X# H2 s3 z: g0 _
- DMA_Cmd(DMA1_Channel5, DISABLE); // 停止DMA,清除DMA缓存
3 z; m3 } X: g; L) Y5 `) C - }- G8 r) i$ X- h! A
- }2 {1 S. n' B+ ]9 z+ p4 b& G
- 6 ]; I; }! p$ j$ n
- int main(void)
' M# `8 y' q2 z2 B$ [3 a( l: ^1 H - {
- z6 e: ~0 e F8 ^. g - uint8_t i = 0;) l: o R( `3 V$ S
- % y) H Q @" q5 C3 l
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 初始化中断优先级分组
* m, v9 b( \2 P7 n, c
# K+ d# T- W1 n- G- My_UART_Init();
" L) K1 W& X( o% U8 o+ H. K
7 g$ a& j7 T5 J- while(1)
+ O$ _ W& O: k* ]8 ~- z# \/ M) y - {0 ^5 d+ u8 ^6 d8 a- Y/ J
- if(RxStatus == 1)
6 u5 g1 B- D! l3 h0 f: U7 B" ^' S - {
( J( h% r: o" s S - RxStatus = 0;' j$ E! Q- N3 l5 A' B
- i = 0;1 K* ^% ]/ I+ ~9 `
[; E$ V% S% g& C* i3 x" U- while(RxCounter--)
) ^ _4 J8 x6 B - {
3 _% a, W$ L* ~$ P. ^ - USART_SendData(USART1, RxBuffer[i++]);6 s& N4 c3 M4 I3 I$ m' ~
- while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET);
- L1 ?7 q+ c( M6 V% y# b L - }
! s3 g) i" ?* v* W1 g9 A. v
) m/ K6 b0 r8 u: h: z6 Y- memset(RxBuffer, 0, i); // 清除缓存' Y$ {7 h5 _, i I1 u+ b
- RxCounter = 0;: J6 @* M$ i( |0 A& _0 E, ^" |
8 _7 l9 W% }. x- i- P* J0 v- // DMA1_Channel5->CNDTR = BufferSize;0 I) s+ ^0 _" v7 n9 }
- DMA_SetCurrDataCounter(DMA1_Channel5, BufferSize);
8 T' }, ^6 U5 L) W" R, O+ X - DMA_Cmd(DMA1_Channel5, ENABLE);
8 ]) p% m% d6 `' F% |! \% b - }1 p, F; _# I+ d- i; W/ u! A: o
- }
5 ? L) z: `+ g( E" ? - }
复制代码
/ n) y- I8 h1 n1 S3 D5 a0 T
9 E! p z9 j! Y; K9 K DMA介绍DMA的定义
6 c: |# \/ x( W5 w* ?- U% S" g+ x3 s1 T- G1 N
直接存储器存取(Direct Memory Access,DMA)是计算机科学中的一种内存访问技术。它允许某些电脑内部的硬体子系统(电脑外设),可以独立地直接读写系统存储器,而不需绕道 CPU。在同等程度的CPU负担下,DMA是一种快速的数据传送方式。它允许不同速度的硬件装置来沟通,而不需要依于 CPU的大量中断请求。
5 ^9 o- B! u0 f% k; s DMA有什么用?
" Z( @2 s/ d2 P3 P0 s, N
% o. r. A: b* W9 m7 p: t1 a直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。 3 {7 \' _- C+ B' A! p
有多少个DMA资源?8 g/ y# ~+ u; _! b" T
y% r# ~- @" Q有两个DMA控制器,DMA1有7个通道,DMA2有5个通道。
9 a! [$ ?- U) g% ?7 A% c 数据从什么地方送到什么地方?) K& z. @5 a f
" Y9 S1 u2 G& t" p, L% H& [0 U' E
- 外设到SRAM(I2C/UART等获取数据并送入SRAM);
1 N4 Q6 L" s4 `5 P" a9 K! `* G6 pSRAM的两个区域之间; - 外设到外设(ADC读取数据后送到TIM1控制其产生不同的PWM占空比);
- SRAM到外设(SRAM中预先保存的数据送入DAC产生各种波形);
t% d1 z1 B- b, Q. W& w# o8 p) l
3 R) m J( N7 ]% p- [
& f* E, D, B, p( G: e# x% [; t" `% M* P
DMA可以传递多少数据?
/ c! B9 D' _' @1 N- A' W; K% j1 x
传统的DMA的概念是用于大批量数据的传输,但是我理解,在STM32中,它的概念被扩展了,也许更多的时候快速是其应用的重点。数据可以从1~65535个。
. V2 ^4 N$ \4 J/ e( z5 P DMA控制器与仲裁器' Y& j+ Z, o* U& A" i- N+ I2 B
1 w: h" X+ O4 p- t3 k4 \3 N
现在越来越多的单片机采用DMA技术,提供外设和存储器之间或者存储器之间的高速数据传输。当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。STM32就有一个DMA控制器,它有7个通道,每个通道专门用来管理一个或多个外设对存储器访问的请求,还有一个仲裁器来协调各个DMA请求的优先权。 ! _' h0 @, O. Y/ y* b! p' a5 `, \
DMA 控制器和Cortex-M3核共享系统数据总线执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求可能会停止 CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。 ' m6 a0 D0 d$ P2 Q
在发生一个事件后,外设发送一个请求信号到DMA控制器。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问外设的时候,DMA控制器立即发送给外设一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果发生更多的请求时,外设可以启动下次处理。
: J) X9 ~+ m& \1 h% \. G 代码展示基于HAL库的串口空闲+DMA主要接收代码) @! s" B9 K: \1 f* i4 X) ?
5 u- Q/ i+ e5 p6 E1 A下面是头文件
, X* ]# c8 b" m
- #ifndef DMAT_H! t& H4 ^* N3 T+ W6 A
- #define DMAT_H
4 `+ c: t/ d1 }: y" w h - #include "stm32f1xx_hal.h"2 z! W1 o$ K0 G- g' x
- #include "usart.h"# i* _, `+ C' H, {! @; ~" R4 z* J1 `+ u
- _2 i% S$ z+ l% G4 n7 X
- extern uint8_t usart2_rx_buffer[33];
/ x. k3 O2 O! I( ~5 S# ?0 T - extern uint8_t usart3_rx_buffer[25];
8 W5 [$ w( L: X, g) T* E - void MyU2Init(void);
. S$ Q0 W6 P+ H) N - void MyU3Init(void);
+ C' ]2 W+ k/ S - void USART2Handle(void);
2 a/ i, J# T/ c* w$ { - void USART3Handle(void);6 m. A- K% h! L2 Q
- void MyUsartHandle(UART_HandleTypeDef *HUART);2 A" N3 w y: h7 A" M; `, X
- #endif
复制代码
7 }" g; s8 f6 d5 i8 ]3 {# i* [8 f: q7 p1 [! [
下面是源文件 * l! E0 E& c' g. g
/ T8 o) j; s1 g9 `6 `) I
- #include "dmat.h"$ Q# W$ s8 i; u& E P
& ^; ?. q/ n; ? H. P- uint8_t usart2_rx_buffer[33]={0};. C* {" R9 }$ h2 {
- uint8_t usart3_rx_buffer[25]={0};1 \8 O. L% v" k' [8 u
- _0 u( P2 h; N" h
- float ROLL_ANGLE=0;4 O( ?. E5 H; ~8 E% s/ c; Q X
- float PITCH_ANGLE=0;
; ~4 }5 p( M; S2 R, T - float YAW_ANGLE=0;
% _6 H, j0 r* ]( w ^5 c - void MyU2Init(void); n6 i& }+ t/ @) s$ i9 U
- {! u9 l: h/ p6 u" @
- __HAL_UART_CLEAR_IDLEFLAG(&huart2);
, e1 i( Z, B/ P3 G8 [/ s - __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
5 D7 ^3 F, k8 L1 z- l1 ` - HAL_UART_Receive_DMA(&huart2,usart2_rx_buffer,33);& o1 ]5 c% z/ ~
- }9 Y1 D" i, g( r
4 ?7 s& L: _* S- J9 a- void MyU3Init(void)# D1 J' G) y }
- {
9 O9 [6 O& R; J" K - __HAL_UART_CLEAR_IDLEFLAG(&huart3);4 a6 t2 L& @/ b* N0 A
- __HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);) T5 t) x; d0 \% _, E5 D S/ K
- HAL_UART_Receive_DMA(&huart3,usart3_rx_buffer,25);
: K; _# L1 A# ]4 \ - }
4 _6 }6 w! v9 h+ H7 p c8 O$ R9 s - //可以在里面加上想要的功能+ u5 i' s7 o# `6 g! W5 e% F j6 S9 h
- //以及数据处理的代码
m/ H& k8 C, } - //这是采用串口+DMA+空闲中断来处理串口接收的数据
, C6 [ v4 e, O7 x' b - void USART2Handle(void)8 t1 x$ j0 N" A9 N2 b% G/ P
- {
% p ^6 ~( ]- a- H% K6 J, ~! a - if(usart2_rx_buffer[0]==0x55&&usart2_rx_buffer[11]==0x55&&usart2_rx_buffer[22]==0x55)0 e3 m( h8 H @# m* F2 @
- {
+ y& T5 @7 ]/ P4 c1 m" u. \ - //总共33个字节的数据,每11个字节的数据算一个数据包,三个数据包分别是加速度包,角速度包和角度包/ W6 e c$ ^7 y# ~ d3 X
- if(usart2_rx_buffer[23]==0x53)
; |6 n4 m1 ]3 D( F - {
( x6 M) m0 I8 g7 b, x4 e Q - //下面的数据是针对角度包来解析的,得到三个角度
5 x) U- m6 u& q4 r - ROLL_ANGLE=((int16_t)(usart2_rx_buffer[25]<<8|usart2_rx_buffer[24]))/32768.0*180;
4 \4 t9 K) }7 p7 g' y# L; n - PITCH_ANGLE=((int16_t)(usart2_rx_buffer[27]<<8|usart2_rx_buffer[26]))/32768.0*180;
1 Z6 Z! M" Z9 ]9 W - YAW_ANGLE=((int16_t)(usart2_rx_buffer[29]<<8|usart2_rx_buffer[28]))/32768.0*180;
; N. b! P& [5 l# f( w+ o! s8 ` - }
w+ D, c# v7 H! K8 Q$ f3 B - }, S; H4 n5 g; @4 h2 F- r
- }5 J0 U! p& @0 `
- void USART3Handle(void)
1 x! l; ~0 C2 h- [. N; { - {
& z- W6 a. R# D% B8 @& i - }$ D+ O2 P5 y- e* D5 m O
- void MyUsartHandle(UART_HandleTypeDef *HUART)* h; X/ X# E) D- k, x2 `
- {
1 r! ?" I0 `9 Z& z' [8 E3 ] - if(HUART==&huart2)
) k% R+ Y( d V. C; A - {
/ O7 w8 {) r& @, L ~2 k -
( H' j l! y' m. n& V( H$ ] - if(__HAL_UART_GET_FLAG(HUART,UART_FLAG_IDLE))2 ^% ^+ |, i# ]/ p+ ~. F2 B5 f
- {
5 J( S9 c* Q% t4 f. H0 T0 U -
3 c+ e: ^; T" ]! ~% A - __HAL_UART_CLEAR_IDLEFLAG(HUART);' o8 P& ]: L4 Z) D6 N
- HAL_UART_DMAStop(HUART);& m- V t" u" o2 z, T
- USART2Handle();
; u I3 N9 s/ V& L4 O8 C# i- K/ y - HAL_UART_Receive_DMA(HUART,(uint8_t*) usart2_rx_buffer,33);+ {5 H8 Q5 @. |( T% g, C
-
4 f }! _* n N+ ` - }
- \( [" J$ _# y% i0 O; q, q) N - }
2 r+ C0 P' V+ j) ~ - else if(HUART==&huart3)
% `' m7 z5 {3 I1 f2 g! V - {; R9 h/ k4 V( ^& W3 u
- if(__HAL_UART_GET_FLAG(HUART,UART_FLAG_IDLE))
5 I! \+ a3 L% M e6 Q1 ? - {
/ S8 Y# }* E' k: y" ?# k% p - __HAL_UART_CLEAR_IDLEFLAG(HUART);
D- e7 a9 f6 |; z" K- c7 ? Y6 y. K - HAL_UART_DMAStop(HUART);. m5 D3 I3 n& @% U8 F
- USART3Handle();
z. _6 ?: a# _" t9 y9 M$ M* L; j - HAL_UART_Receive_DMA(HUART,(uint8_t*) usart2_rx_buffer,25);0 X% i% [0 ^- p( H
- }3 @8 ]& e5 x7 {) C& C2 y
- }
$ e. [2 C( x' E [ - }
7 p1 S6 c+ X P! M
复制代码
# Z* W/ I; A* T3 ?3 B6 e, b7 ~6 U$ y
: a6 @/ q. e# E+ s4 I6 } |