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

STM32使用DMA加串口空闲中断接收数据

[复制链接]
jinyi7016 发布时间:2017-12-25 22:47
本帖最后由 jinyi7016 于 2017-12-25 22:51 编辑
. J6 Y( h+ l5 c1 ?2 K1 |/ w) h
' }  |; Q' h% x' J
    STM32中,需要用串口接收数据,是使用串口中断来接收数据。但是用这种方法的话,就要频繁进入串口中断,然后处理,效率就比较低。于是就想到用DMA来接收串口数据,这个STM32也是支持的。但是关键的一点,怎么知道数据接收完毕了呢?如果接收的数据长度固定,那就好办,直接设置DMA的接收数据个数就行了。但是如果长度不固定了,那应该怎么办了?
         这个时候,就要用到STM32在串口中提供的另一个好用的东西了,就是串口空闲中断。在STM32的串口控制器中,设置了有串口空闲中断,即如果串口空闲,又开启了串口空闲中断的话,就触发串口空闲中断,然后程序就会跳到串口中断去执行。有了这个,是不是可以判断什么时候串口数据接收完毕了呢?因为串口数据接收完毕后,串口总线肯定是会空闲的嘛,那这个中断肯定是会触发的了。

/ o0 p) f2 L& q6 f/ o* N: Y: ~, q' u4 W( ?# ]1 M& E
无标题.png

0 t6 s$ j; Y% l. m8 n; E/ `
还有一个问题,这串口空闲中断是只要串口空闲就会产生吗?其实不是的,串口空闲中断要触发的话,是要RXNE位被置位后,串口总线空闲才会触发的。所以我们不用担心,串口数据发送完毕后,会不会触发串口空闲中断了。

$ P+ Q& l4 |+ S. a" k8 F- J
无标题.png

6 U0 _1 O- u0 y( E9 P
1、  配置串口。包括设置串口的引脚配置,串口的配置,串口中断的配置,串口的接收DMA的配置
void USART_init(void){   GPIO_InitTypeDef   GPIO_InitStructure;    USART_InitTypeDef  USART_InitStructure;   
, B: b0 L% l. K& [7 u3 \ NVIC_InitTypeDef   NVIC_InitStructure;     //开启时钟   # d' Y. o( t0 j7 `
RCC_APB2PeriphClockCmd(USART_RCC,ENABLE);    //配置TX端口    8 p& E0 P  r, i: s8 |
GPIO_InitStructure.GPIO_Pin = GPIO_USART_TX;    4 m% d! H( b0 b% S
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   
  o; i6 y% G, _( a9 M2 [GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      j" P! S% U& n) G% g
GPIO_Init(GPIO_USART_TYPE,&GPIO_InitStructure);    //配置RX端口  ) r8 e3 J3 Q, U8 R
  GPIO_InitStructure.GPIO_Pin = GPIO_USART_RX;   
" B4 T) t1 V- X, ]2 W3 cGPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   
( ^. ~: z! \6 P1 d9 g$ wGPIO_Init(GPIO_USART_TYPE,&GPIO_InitStructure);     //配置串口模式   ( |& p3 M  E* a7 V5 `# ]& J8 C- E/ s
USART_InitStructure.USART_BaudRate = 115200;   * L0 `% \' o1 L8 s$ Z, @; J. _
USART_InitStructure.USART_WordLength = USART_WordLength_8b;   0 ^3 S  f6 x& C. q/ R
USART_InitStructure.USART_StopBits = USART_StopBits_1;   2 s  L- k' F5 f8 j5 @( {  k% r
USART_InitStructure.USART_Parity = USART_Parity_No;2 A% w/ m, W7 r$ x: w7 a: ^
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   
. U% g3 t  U/ T USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
- H5 z( S( {0 t* C% t. h   USART_Init(USART1,&USART_InitStructure);       //中断配置   : f; m! j( }% {( |" P
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;   $ c6 P5 d" U2 r/ G* f* H6 j) A
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   6 x; t, a) ]+ a  P  t6 ?  J
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;   
# N$ Z" ]& j3 X NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    - u) f6 D( S# }  J! C
NVIC_Init(&NVIC_InitStructure);     /* 若总线空闲,产生中断 */   
3 j6 h, P9 i, S+ S, q# ?% YUSART_ITConfig(USART1, USART_IT_IDLE, ENABLE);     /*开启串口DMA接收*/    4 j2 P8 @* z4 g; g
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);       USART_Cmd(USART1,ENABLE);$ ^( U  q: `) T' e$ {  D- U
}+ d  Z( {  ~- s6 C4 k8 N2 S4 g) ~0 S

8 W' e) a& {: f9 K, Q( k' u; w! p
         代码比较简单,一看就明白了,这就是使用库函数开发的好处,代码易懂。这里,关键的是要开启总线空闲中断,并且开启串口DMA接收。注意,不要开启串口接收中断,不然接收数据就会一直产生中断了。
2、  DMA配置
DMA配置,要先查看串口接收是使用的哪个DMA的哪个通道,对于USART1_RX使用的是DMA1的5通道。

5 Q! z( `/ ~& _& T6 t
无标题.png

9 T+ n9 }! J$ }6 ~2 s1 z
         然后就是代码配置DMA了。
void DMA_init(void)' a( ?! n8 j( D4 A# z  `
{  
5 |/ y$ v2 A3 W- ?5 H7 u DMA_InitTypeDef    DMA_Initstructure;
2 i- I2 e& [" i4 l//   NVIC_InitTypeDef   NVIC_Initstructure;      /*开启DMA时钟*/   
8 ]  l& T4 J5 d! f0 j7 q% URCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);  //   /* Enable the DMA1 Interrupt *///   NVIC_Initstructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;       //通道设置为串口1中断//   # k. M. o$ N  A/ B- @
NVIC_Initstructure.NVIC_IRQChannelSubPriority = 1;   
# G* }9 H: l) M% _* e2 F" A( H6 X //中断响应优先级0//   NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority=1;
% K( P$ _1 N, g# a//   NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;        
+ _- h: \9 U/ n- F, \" e+ Y//打开中断//   NVIC_Init(&NVIC_Initstructure);    /*DMA配置*/  
* `: i# l+ L7 O; G# E: [! a- M3 w DMA_Initstructure.DMA_PeripheralBaseAddr =  (u32)(&USART1->DR); 1 g: v* C, T' |8 I6 _
DMA_Initstructure.DMA_MemoryBaseAddr     = (u32)receive_data;  - C9 k' T* |3 }& E" d. f# K
DMA_Initstructure.DMA_DIR = DMA_DIR_PeripheralSRC;   $ [2 z" P" D4 d; J8 M# \2 V! n+ A& c
DMA_Initstructure.DMA_BufferSize = 128;   ) P, G& \6 w3 u0 B6 ^; @1 H
DMA_Initstructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;   % q8 p" H0 @+ \
DMA_Initstructure.DMA_MemoryInc =DMA_MemoryInc_Enable;  ( ~. s! W* j1 W2 @+ b1 b. J
DMA_Initstructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;   6 K: B7 m3 G2 X+ @' h% R
DMA_Initstructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;  ' ]0 k, R' N1 A5 T+ c: B( D5 Q
DMA_Initstructure.DMA_Mode = DMA_Mode_Normal;  , A  V6 x2 p' b3 Y$ L% X$ x
DMA_Initstructure.DMA_Priority = DMA_Priority_High;  % }! k* ?* T9 w# n3 r% m/ l
DMA_Initstructure.DMA_M2M = DMA_M2M_Disable;   0 y) r6 B" B0 r5 T3 P- V/ }. O
DMA_Init(DMA1_Channel5,&DMA_Initstructure);     //启动DMA   
. D' J/ y8 p3 o5 QDMA_Cmd(DMA1_Channel5,ENABLE);    //开启DMA发送发成中断   
- N6 H& Q# E* Y4 o. P4 g//DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); / E, m* v: Y" K% T/ Q( c6 v% ]  l
}- E1 h+ r8 A' e2 _: i* r
         因为这里,不需要用到DMA中断,所以DMA中断就不要使能了。因此DMA中断配置也就不需要了。这里,关键的是要设置DMA_DIR为DMA_DIR_PeripheralSRC,表示数据是从外设到内存。这里设定的DMA_Mode是普通模式,即数据传输就只能一次。
3、 串口中断程序编写
这个就是关键的地方了。在这里,需要做什么了。需要对DMA设置下。当进入这个中断的时候,串口接收的数据,已经在内存的数组中了。通过读取DMA的计数值,就可以知道接收到了多少个数据。然后再把DMA给diable掉,重新设置接收数据长度,在开启DMA,接收下一次串口数据。为什么要这么做了,因为在STM32手册中有如下说明:
! b! P$ ^6 X' A/ x. M. G4 A
无标题.png
/ K2 I2 O" L9 f2 c- x) j
另外还有一点,串口空闲中断触发后,硬件会自动将串口空闲中断标志位给置1,我们是需要将给标志位给置0的,不然又要进中断了,这个在手册中也有说明。

$ ]+ @( ]1 M. [/ s  O
无标题.png
* L( h% G7 Q+ @4 y, V9 `
         代码就如下了:

6 h9 ~. ^8 E' _! A3 u" ] void USART1_IRQHandler(void)
4 c: e; |! ~% m8 Q8 F# q) w+ A3 U{  
( ?( u1 J& l+ f3 U" s' T+ ^8 T  unsigned char num=0;   
' ~, S( m2 W- e  B# A if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)    8 d6 g/ d1 f2 i" c, M3 P! o
{       num = USART1->SR;      
; B# ]5 `8 @7 }2 J$ {9 V- v& m num = USART1->DR; //清USART_IT_IDLE标志      
( s" ?1 H+ ]$ U( M1 B5 P+ H4 GDMA_Cmd(DMA1_Channel5,DISABLE);    //关闭DMA       ; x7 r! D& `$ O) p" A  r
num = 128 -  DMA_GetCurrDataCounter(DMA1_Channel5);      //得到真正接收数据个数        / C0 R$ w- z$ k6 I+ T! p
receive_data[num] = '\0';       DMA1_Channel5->CNDTR=128;       //重新设置接收数据个数          - s: }1 e& [6 G4 [+ S; C
DMA_Cmd(DMA1_Channel5,ENABLE);  //开启DMA     1 k' A; M8 l8 P
  receive_flag = 1;           //接收数据标志位置1   
# b$ e  s. D% \; x/ J2 i  W }
8 Q8 v0 H" R/ W* O, ]! D$ H}
         关键的一点,就是要读取SR,DR,将USART_IT_IDLE标志给清掉,然后DMA设置要注意下。
         在主函数中,使用下面代码测试:
         int main(){    periph_init();    printf("hello world\n");    while(1)    {       while(receive_flag == 0);       receive_flag = 0;       printf("%s",receive_data);    }}
         当串口接收数据后,中断程序会使receive_flag为1,然后就跳出while循环。打印接收到的数据。
. W) e, ?: L& I8 J4 m) S
    测试结果:
; O9 a! O3 w) p0 i# T
无标题.png
3 s( g" Q% L+ W. ^; }; d8 S# r
发送什么,就接收什么。
还测试了下,在波特率460800下,都还是能正常的工作的。

3 A2 ]% j( O8 @5 P8 ?/ C
1 F) p% n/ @5 G9 t! r2 y+ p2 Y& L' S  J/ t5 z

: I+ B! \4 y4 r2 u4 {. d9 R: X
1 \+ H7 P" {* c
赞 收藏 3 评论7 发布时间:2017-12-25 22:47

举报

7个回答
秾秾电 回答时间:2018-1-24 14:18:35
请问楼主是用哪款STM32,我想移植到STM32F030上,在修改一些寄存器。
秾秾电 回答时间:2018-1-24 14:21:26
我对照了手册看了一下,应该是STM32F103吧
yyz1111 回答时间:2018-7-5 10:34:15
楼主你好!我现在正在调试,为什么我的程序没有进入串口中断呢
西点钟灵毓秀 回答时间:2018-7-5 12:44:36
标记一下,回头再看
ZDPHPN 回答时间:2018-7-10 17:11:54
我再搞一个现在使用的串口DMA收发的(收使用空闲中断的)

RS232.rar

下载

1.69 KB, 下载次数: 101

ssssss 回答时间:2018-7-11 14:57:15
如果一帧数据中几个字节间隔有点大,是不是会误判呢
tinaxiayuwei 回答时间:2018-7-21 17:00:42
yyz1111 发表于 2018-7-5 10:349 i6 V2 U8 M4 S
楼主你好!我现在正在调试,为什么我的程序没有进入串口中断呢

3 d) ^7 G( c& z* e, t% L! d/ ~. U, Z我的也是中断进不去,我用的是串口3  DMA空闲中断进不去,初始化代码一样的

所属标签

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