STM32单片机串口空闲中断+DMA接收不定长数据
在上一篇文章STM32单片机串口空闲中断接收不定长数据中介绍了利用串口空闲中断接收不定长数据,这种方式有一个问题就是串口每接收到一个字节就会进入一次中断,如果发送的数据比较频繁,那么串口中断就会不停打断主程序运行,影响系统运行。那么能不能在串口接收数据过程中不要每接收一个数据中断一次,只有在一帧数据接收结束完成后只中断一次?
用串口的空闲中断加上DMA功能,就可以实现每帧数据接收完成后只中断一次,而在数据接收过程中,由DMA存储串口接收到的每个字节。
关于串口的空闲检测和DMA在STM32参考手册中有详细介绍。
下面看如何初始化串口空闲中断和 DMA。
void uart2_init( u16 baud )
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOA, &GPIO_InitStructure );NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init( &NVIC_InitStructure );USART_InitStructure.USART_BaudRate = baud;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_Init( USART2, &USART_InitStructure );
#if (UART2_DMA == 1)USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收uartDMA_Init(); //初始化 DMA
#elseUSART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断
#endifUSART_Cmd( USART2, ENABLE ); //使能串口2//RXNE中断和IDLE中断的区别?
//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。
}
为了方便对比空闲中断和DMA与常规串口设置的差别,这里用了一个宏定义UART2_DMA来设置是否需要开启空闲中断和DMA,如果宏定义UART2_DMA值为1,那么就初始化空闲中断和DMA。这里要将UART2_DMA设置为1,需要开启串口2的IDLE中断,使能串口2的DMA功能,然后初始化 DMA。
void uartDMA_Init( void )
{DMA_InitTypeDef DMA_IniStructure;RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟DMA_DeInit( DMA1_Channel6 ); //DMA1通道6对应 USART2_RXDMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小 也就是 DMA一次传输的字节数DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式DMA_IniStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级DMA_IniStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输DMA_Init( DMA1_Channel6, &DMA_IniStructure );DMA_Cmd( DMA1_Channel6, ENABLE );
}
将 DMA的外设地址设置为串口2的数据寄存器 USART2->DR,DMA的内存地址设置为 DMA数据缓存数数组dma_rec_buff。这样当串口2的数据寄存器中有数据后,DMA就会自动将这个数据存储到dma_rec_buff数组中。当串口2的空闲中断出现后,直接去dma_rec_buff数组中读取接收到的数据就行。
下面看串口2的中断执行过程
void USART2_IRQHandler( void )
{u8 tem = 0;
#if (UART2_DMA == 1) //如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET )//空闲中断 一帧数据发送完成{USART_ReceiveData( USART2 ); //读取数据注意:这句必须要,否则不能够清除空闲中断标志位。DMA_Cmd( DMA1_Channel6, DISABLE ); //关闭 DMA 防止后续数据干扰uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 ); //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量 copy_data( dma_rec_buff, uart2_rec_cnt ); //备份数据receiveOK_flag = 1; //置位数据接收完成标志位USART_ClearITPendingBit( USART2, USART_IT_IDLE ); //清除空闲中断标志位myDMA_Enable( DMA1_Channel6 ); //重新恢复 DMA等待下一次接收}
#else //如果未使能 UART2_DMA 则通过常规方式接收数据 每接收到一个字节就会进入一次中断if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断{tem = USART_ReceiveData( USART2 );USART_SendData( USART2, tem );}
#endif
}
在串口中断中如果检测到了空闲中断标志,说明串口接收一帧数据结束,这时候就关闭 DMA,然后计算DMA缓冲区中接收到的字节个数,将接收到的数据拷贝到备份数组中,然后置位数据接收成功标志最后清除中断标志位,重新启动 DMA,开始进行下一次数据接收。
如果串口数据的接收频率不是很高,在这里也可以不用备份DMA缓存区接收到的数据,备份数据主要是防止,数据接收频率很高,当上一笔数据还没处理完成后,又接收到了新的数据,那么数组中的数据就会被覆盖,可能导致程序异常。所以将处理数据的数组和接收数据的数组分开,可以防止在处理数据过程中数据被改变的情况发生。
下面看一下完整代码
#ifndef __UART2_H
#define __UART2_H
#include "sys.h"#define DMA_REC_LEN 50 //DMA数据接收缓冲区void uart2_init(u16 baud);
void uartDMA_Init( void );
void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx );
void uart2_Send( u8 *buf, u16 len );
void copy_data( u8 *buf, u16 len );
#endif
//串口2空闲中断 + DMA数据传输
#include "uart2.h"
#define UART2_DMA 1 //使用串口2 DMA传输u8 dma_rec_buff[DMA_REC_LEN] = {0};
u16 uart2_rec_cnt = 0; //串口接收数据长度u8 data_backup[DMA_REC_LEN] = {0}; //数据备份
u16 dataLen_backup = 0; //长度备份
_Bool receiveOK_flag = 0; //接收完成标志位/*
空闲中断是什么意思呢?
指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0;
注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。
所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。
*/void uart2_init( u16 baud )
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOA, &GPIO_InitStructure );GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOA, &GPIO_InitStructure );NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init( &NVIC_InitStructure );USART_InitStructure.USART_BaudRate = baud;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_Init( USART2, &USART_InitStructure );
#if (UART2_DMA == 1)USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断USART_DMACmd( USART2, USART_DMAReq_Rx, ENABLE ); //使能串口2 DMA接收uartDMA_Init(); //初始化 DMA
#elseUSART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断
#endifUSART_Cmd( USART2, ENABLE ); //使能串口2//RXNE中断和IDLE中断的区别?
//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。
}void uartDMA_Init( void )
{DMA_InitTypeDef DMA_IniStructure;RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE ); //使能DMA时钟DMA_DeInit( DMA1_Channel6 ); //DMA1通道6对应 USART2_RXDMA_IniStructure.DMA_PeripheralBaseAddr = ( u32 )&USART2->DR; //DMA外设usart基地址DMA_IniStructure.DMA_MemoryBaseAddr = ( u32 )dma_rec_buff; //DMA内存基地址DMA_IniStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存DMA_IniStructure.DMA_BufferSize = DMA_REC_LEN; //DMA通道的DMA缓存的大小 也就是 DMA一次传输的字节数DMA_IniStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变DMA_IniStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //数据缓冲区地址递增DMA_IniStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设以字节为单位搬运DMA_IniStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据缓冲区以字节为单位搬入DMA_IniStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式DMA_IniStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级DMA_IniStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输DMA_Init( DMA1_Channel6, &DMA_IniStructure );DMA_Cmd( DMA1_Channel6, ENABLE );
}//重新恢复DMA指针
void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx )
{DMA_Cmd( DMA_CHx, DISABLE ); //关闭DMA1所指示的通道DMA_SetCurrDataCounter( DMA_CHx, DMA_REC_LEN ); //DMA通道的DMA缓存的大小DMA_Cmd( DMA_CHx, ENABLE ); //DMA1所指示的通道
}
//发送len个字节
//buf:发送区首地址
//len:发送的字节数
void uart2_Send( u8 *buf, u16 len )
{u16 t;for( t = 0; t < len; t++ ) //循环发送数据{while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );USART_SendData( USART2, buf[t] );}while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
}//备份接收到的数据
void copy_data( u8 *buf, u16 len )
{u16 t;dataLen_backup = len; //保存数据长度for( t = 0; t < len; t++ ){data_backup[t] = buf[t]; //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。}
}//利用空闲中断接收串口不定长数据
//串口接收一组数据结束后才会进入中断,在数据发送过程中,已经接收到的数据将会被存入DMA的缓冲区中
void USART2_IRQHandler( void )
{u8 tem = 0;
#if (UART2_DMA == 1) //如果使能了 UART2_DMA 则使用串口空闲中断和 DMA功能接收数据if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET ) //空闲中断 一帧数据发送完成{USART_ReceiveData( USART2 ); //读取数据注意:这句必须要,否则不能够清除空闲中断标志位。DMA_Cmd( DMA1_Channel6, DISABLE ); //关闭 DMA 防止后续数据干扰uart2_rec_cnt = DMA_REC_LEN - DMA_GetCurrDataCounter( DMA1_Channel6 ); //DMA接收缓冲区数据长度减去当前 DMA传输通道中剩余单元数量就是已经接收到的数据数量//uart2_Send( dma_rec_buff, uart2_rec_cnt ); //发送接收到的数据copy_data( dma_rec_buff, uart2_rec_cnt ); //备份数据receiveOK_flag = 1; //置位数据接收完成标志位USART_ClearITPendingBit( USART2, USART_IT_IDLE ); //清除空闲中断标志位myDMA_Enable( DMA1_Channel6 ); //重新恢复 DMA等待下一次接收}
#else //如果未使能 UART2_DMA 则通过常规方式接收数据 每接收到一个字节就会进入一次中断if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断{tem = USART_ReceiveData( USART2 );USART_SendData( USART2, tem );}
#endif
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "uart2.h"
#include "string.h"extern u8 data_backup[DMA_REC_LEN]; //数据备份
extern u16 dataLen_backup; //长度备份
extern _Bool receiveOK_flag; //接收完成标志位int main(void)
{u8 j = 0;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);delay_init(); //延时函数初始化LED_Init(); //初始化与LED连接的硬件接口uart2_init(9600);while(1){if( receiveOK_flag ) //一帧数据接收完成后开始处理数据{receiveOK_flag = 0;uart2_Send( data_backup, dataLen_backup ); //发送数据memset( data_backup, 0, sizeof( data_backup ) ); //清空备份数组}j++;if(j > 50){j = 0;LED = !LED;}delay_ms(10);}
}
下面看一下测试效果
工程下载地址 https://download.csdn.net/download/qq_20222919/12926810
STM32单片机串口空闲中断+DMA接收不定长数据相关推荐
- STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷
STM32使用串口1配合DMA接收不定长数据,减轻CPU载荷 http://www.openedv.com/thread-63849-1-1.html 实现思路:采 用STM32F103的串口1,并配 ...
- STM32使用串口1配合DMA接收不定长数据,大大减轻CPU载荷
摘自:http://www.openedv.com/thread-63849-1-1.html 参考:https://blog.csdn.net/heda3/article/details/80602 ...
- MM32F3277空闲中断+DMA接收不定长数据
摘要:在实际项目中经常用到串口接收一些不定长的数据,怎么判断这一帧数据接收完成了呢?通常使用UART非空中断配合简单的数据协议,在数据中加入帧头.帧尾,在程序中判断是否接收到帧尾来确定数据接收完毕,对 ...
- STM32使用串口1配合DMA接收不定长数据,大大减轻CPU载荷。
最近经常看见坛友在论坛上问串口接收的问题,我之前刚好由于项目需要用到PLC的PPI协议,需要不停地利用串口接收数据,一开始的时候采用单字节中断的方式接收判断.但是用来做通信的时候需要不停的产生串口接收 ...
- STM32单片机串口空闲中断接收不定长数据
在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成.但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志 ...
- STM32 HAL库 串口DMA接收不定长数据
STM32 HAL库 串口DMA接收不定长数据 整体思路:我是用的CUBEMX软件生成的工程,使能了两个串口,串口2用来接收不定长的数据,串口1用来发送串口2接收到的数据:串口2我找了一个UBLOX卫 ...
- stm32LL库串口空闲中断+DMA接收
stm32LL库串口空闲中断+DMA接收 俺用的STM32F407VGT6,cubemx生成的代码. cubemx的串口配置 这里就按默认参数配就行,主要是要打开后面的DMA 在这里点添加就完事了,如 ...
- android 串口一直打开_STM32之串口DMA接收不定长数据
STM32之串口DMA接收不定长数据 引言 在使用stm32或者其他单片机的时候,会经常使用到串口通讯,那么如何有效地接收数据呢?假如这段数据是不定长的有如何高效接收呢? 同学A:数据来了就会进入串口 ...
- 【无标题】STM32F767串口空闲中断+DMA实现不定帧长度的接收
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.配置串口与DMA 二.空闲中断服务函数 二.串口+DMA发送 三.主函数 总结 前言 提示:这里可以添加本文要记录 ...
最新文章
- 电子信息工程考研专业c语言,电子信息工程考研方向
- 判断图有无环_萧阳环保教你判断布袋除尘器是否合格记住这3点
- MYSQL-RJWEB 博客学习
- java 深克隆(深拷贝)与浅克隆(拷贝)详解
- 【SpringBoot2 从0开始】开发小技巧 - lombok、devtools、Spring Initailizr
- BZOJ4066:简单题(K-D Tree)
- php的修改数据库语句怎么写,php的数据库修改语句是什么
- 树莓派入门(树莓派登录的几种方式)
- OSGi:进入微服务架构的门户
- 计算机二级web题目(7.3)--简单应用题1
- [WPF]ListView点击列头排序功能实现
- 【ZOJ - 3715】Kindergarten Election(枚举得票数,贪心)
- 使用OpenCV可视化Intel Realsensen D435 深度图为彩色图
- iOS:苹果内购实践
- 第七讲 数组动手动脑和课后作业
- putty+Xming使用方法
- C语言变量相关试题,C语言模拟试题
- source ~/.bash_profile是什么意思
- MySQL数据库中存储引擎和数据类型
- Java文件导入 project .mpp文件解析导入 并通过父子级关系或WBS码构造层级关系