STM32 串口通信RXNE+DMA中断的局限性分析

网上搜索STM32串口通信+DMA,绝大多数都是采用串口IDLE中断+DMA的处理方法,俺百思不得其解,为什么RXNE中断就不可以使用DMA的方式了呢?在实验室最近做的一个项目里,采用RXNE+IDLE中断的处理方式。由于STM32的DR寄存器是8位的,所以串口的收发都是以字节为单位进行处理的,当通过串口给单片机发送数据时(比如发送字符串"520"),RXNE收到"5"(占一个字节),触发一次RXNE中断,CPU将"5"从DR寄存器中读取,并赋值给USART_RX_BUF数组,同样"2"、"0"也是CPU重复上述顺序依次赋值给USART_RX_BUF,当处理完"520"之后,触发IDLE中断。

上述过程都是CPU全程参与工作,可以很明显的看出,CPU工作量最集中的时段是RXNE中断,因为串口总是源源不断的收到数据并要及时处理掉,如果处理不及时,会触发USART的ORE报错,之前做过一个实验,如果在RXNE中断处理函数中加入含有延迟操作的函数(例如delay、printf)后,会产生ORE报错(因为只有DR寄存器的数据被读走以后才能允许下一次的数据进入DR,如果DR的数据没有及时读走,会导致串口接收数据的累积,所以产生ORE报错,后续数据会丢失)。

之所以采用DMA,是因为想要最大限度的减轻CPU的工作负担,所以理所当然的想到用DMA来处理RXNE中断,这里还是以发送"520"为例:

一、 配置DMA(挑几个重要的参数说一说)

        1. 外设寄存器数据宽度为Byte(DR寄存器为8位),内存单元的寄存器数据宽度为Byte(与DR保持一致)

        2. DMA通道缓存大小为CNDTR,意思是一次DMA传输的总字节数。(决定了实际有效传递进入内存的数据)

        3. 外设地址(&USART -> DR)、内存地址(USART_RX_BUF(数组名是一个指针))、数据传输方向等等就不说了,按部就班配置就可以了

        4. 外设指针增量不使能(因为DR寄存器只是一个单纯的8位存储单元,不是一个数组)

        5. 内存空间指针增量使能(因为定义的USART_RX_BUF是一个数组)

二、 CNDTR = 1时RXNE中断+DMA工作过程(串口发送"1024"

        1. 串口发送"1"(占一个字节)到DR寄存器,RXNE中断拉起,使能一次DMA传输,DR的内容("5")被传递进USART_RX_BUF[0]中,DMA_ISR寄存器中相应的TC位置1(本次DMA传输完成),RXNE中断结束。

        2. 串口发送"0"、"2"、"4"重复上述过程

如果串口打印USART_RX_BUF内容,会显示"4",而不是"1024"!

三、 CNDTR = 2 时RXNE中断+DMA工作过程(串口发送"520")

        和CNDTR = 1时的区别在于每完成两个字节的传输才会将DMA_ISR中相应的TC位置1

        1. 串口发送"5"到DR寄存器,RXNE中断拉起,使能一次DMA传输,DMA将DR的内容"5"传递进入USART_RX_BUF[0],等待下一个DR寄存器的数据。

        2. 串口发送"2"到DR寄存器,DMA将DR的内容"2"传递进入USART_RX_BUF[1],TC位置1,本次DMA传输完成,RXNE中断结束。

        3. 串口发送"5"到DR寄存器,RXNE中断拉起,使能一次DMA传输,DMA将DR的内容"5"传递进入USART_RX_BUF[0],等待下一个DR寄存器的数据。

        4. 由于串口不再发送数据,程序卡死在RXNE中断中...直到再收到一个数据为止。

  如果串口打印USART_RX_BUF内容,会显示"0",而不是"520"!同样的道理,如果用这种方法串口发送"1024",打印的结果会是"24",而不是"1024"!

问题总结:

1. 为什么在RXNE中使用DMA时如果发生DMA等待(类似发生延迟的操作使程序一直卡在RXNE中断)却一直没有发生ORE报错?这里给出我个人的理(猜)解(测):在RXNE中断中使用DMA中断会“旁路”掉RXNE中断,根据中文参考手册,USART_SR寄存器中的RXNE是由硬件置1的,只要串口的数据传递进入DR寄存器中,该位就会置1(如果DMA传输没有完成的话,是不会再次唤醒RXNE中断),但是DMA会及时读取DR寄存器的内容使得RXNE位再次清零,这样就保证了来一个数据就送走一个,不会发生数据堆积导致的ORE报错。

2. RXNE中断+DMA必须要根据CNDTR严格限制输入字符的长度,否则会因为DMA的等待机制造成奇奇怪怪的结果,所以这个办法没有检错纠错能力,鲁棒性差。

3. CNDTR决定了实际有效传递进入内存的数据,正如上面的实例分析,如果CNDTR = 1,发送"1024"最终的结果只能是“4”,CNDTR = 2时,发送"520"最终的结果是0,发送"1024"最终的结果是"24",超过CNDTR规定的长度的数据会覆盖前面的数据!结果以最后一次有效长度之内的数据为准!

现在我算是死心了,还是去学习一下如何使用串口IDLE中断+DMA的方式发送可变长的数据吧...

附dma.c代码:

#include "dma.h"

u16 CNDTR = CNDTR_Count;

//创建 DMA_InitTpyeDef 结构体
DMA_InitTypeDef DMA_InitStruct;
	
//配置DMA参数并初始化
void DMA_Config(DMA_Channel_TypeDef* DMA_CHx, u32 dir, u32 cpar, u32 cmar, u16 cndtr)   //形参是在特定应用中需要格外声明的状态,该函数的作用是配置官方库函数中的 DMA_InitType 结构体
{
	
//使能DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
//记录传输数据量	
	CNDTR = cndtr;
	
//去初始化DMA
	DMA_DeInit(DMA_CHx);
	
//为 DMA_InitTpyeDef 结构体成员赋值	
	DMA_InitStruct. DMA_PeripheralBaseAddr = cpar;
	DMA_InitStruct. DMA_MemoryBaseAddr = cmar;
	DMA_InitStruct. DMA_BufferSize = cndtr;
	DMA_InitStruct. DMA_Priority = DMA_Priority_Medium;
	DMA_InitStruct. DMA_DIR = dir;
	DMA_InitStruct. DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct. DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct. DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct. DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct. DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStruct. DMA_M2M = DMA_M2M_Disable;
	
//初始化DMA
	DMA_Init(DMA_CHx, &DMA_InitStruct);
	
}


//开启一次DMA传输
void DMA_EnableCmd(DMA_Channel_TypeDef* DMA_CHx)
{
//每次开启DMA之前必须先重装在CNDTR,开启CNDTR之前必须关闭DMA
	DMA_Cmd(DMA_CHx, DISABLE);
	
//重装在CNDTR
	DMA_SetCurrDataCounter(DMA_CHx, CNDTR);

//开启本次DMA传输
	DMA_Cmd(DMA_CHx, ENABLE);
}

附usart.c代码

#include "sys.h"
#include "usart.h"	
#include "stdlib.h"
#include "dma.h"
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

/*使用microLib的方法*/
 /* 
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (uint8_t) ch);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
   
    return ch;
}
int GetKey (void)  { 

    while (!(USART1->SR & USART_FLAG_RXNE));

    return ((int)(USART1->DR & 0x1FF));
}
*/
 
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目

u16 USART_RX_STA=0;       //接收状态标记	  
int Cmd;
extern	u16 Duty_Count;
extern  u16 arr;
extern u16 Duty_Count_Init;
int MyFlag = 0;     //超载标志
int DMA_FLAG = 0;   //DMA标志
int ORE_FLAG = 0;   //ORE标志

void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

  USART_Init(USART1, &USART_InitStructure); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //开启串口DMA接收
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
uint16_t IDLE_ClearBit = 0;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif

//**************************************RXNE中断+DMA传输************************************************//
//        这种方法存在局限性:
//                          1. 必须指定数据长度,并且必须声明是否添加自动换行功能!
//                          2. 鲁棒性很差,没有检错纠错机制
		
		if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET)   //RXNE中断处理过程要快和精简,否则可能造成溢出错误!
		{
			  DMA_EnableCmd(DMA1_Channel5);    //使能一次DMA传输
        while(1)          //等待DMA传输完成
				{
					if(DMA_GetFlagStatus(DMA1_FLAG_TC5) != RESET)     //DMA传输完成
					{
						USART_RX_STA++;              
						DMA_ClearFlag(DMA1_FLAG_TC5); 
            DMA_FLAG++;						
						break;
					}
				}					
				//USART_RX_BUF[USART_RX_STA++] = USART_ReceiveData(USART1);  //每一次新的接收都会重新刷新BUF!并清除RXNE标志位!
			  if(USART_RX_STA > USART_REC_LEN)    //RXNE中断处理不能用delay不能用printf,否则会产生ORE报错!
				{
					 USART_RX_STA = 0;
					 MyFlag = 1;
					 //memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));    //发送的数据最后要带\r\n
				}
		}		
		
//**************************************RXNE中断+DMA传输************************************************//
		
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif	

附main.c代码

#include "led.h"
#include "delay.h"   //delay_ms(nms): nms < 1864 !
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
#include "string.h"
#include "servo.h"
#include "dma.h"

#define DMA_SENDBUF_BYTESIZE   2     //定义一次DMA传输的数据量(单位:字节)  不能添加自动换行!

u16 arr = 10000;
//u16 led0pwmval = 1500;
u16 Duty_Count = 1500;
u16 Duty_Count_Init = 1500;
//u8 SendBuf[USART_REC_LEN];

 int main(void)
 {		
//	int flag = 0;
	u16 psc = 72;
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	 //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	//LED_Init();			     //LED端口初始化
	 TIM4_PWM_Init(arr-1,psc-1);   //不分频。PWM频率=72000000/10000/72 = 100hz
	 TIM_SetCompare1(TIM4,Duty_Count);
   DMA_Config(DMA1_Channel5, DMA_DIR_PeripheralSRC, (u32)& USART1 -> DR, (u32)USART_RX_BUF, DMA_SENDBUF_BYTESIZE);	 //USART1接收DMA,使用通道5

   	while(1)
	{
		//printf("数据允许宽度为%d,请输入指令:(输入范围20-90)\r\n\r\n",USART_REC_LEN-2);
		printf("%s\r\n",USART_RX_BUF);
		delay_ms(1000);
		delay_ms(1000);
		delay_ms(1000);
		delay_ms(1000);
		delay_ms(1000);
	}
 }

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
对于STM32F103VET6的HAL库串口通信中断接收DMA发送,可以按照以下步骤进行配置: 1. 在CubeMX中配置串口,选择需要使用的串口,并开启DMA模式。 2. 在代码中初始化串口和DMA: ``` /* UART handler declaration */ UART_HandleTypeDef UartHandle; /* DMA handler declaration */ DMA_HandleTypeDef hdma_usart1_tx; DMA_HandleTypeDef hdma_usart1_rx; /* UART init function */ void MX_USART1_UART_Init(void) { /* Configure the UART peripheral */ UartHandle.Instance = USART1; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; if (HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); } /* Configure DMA for transmission */ hdma_usart1_tx.Instance = DMA1_Channel4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&UartHandle, hdmatx, hdma_usart1_tx); /* Configure DMA for reception */ hdma_usart1_rx.Instance = DMA1_Channel5; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH; if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(&UartHandle, hdmarx, hdma_usart1_rx); /* Enable DMA interrupts */ HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); } ``` 3. 配置串口中断DMA接收中断: ``` /* USART1 interrupt Init */ HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); /* DMA1_Channel5_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); ``` 4. 在中断处理函数中读取接收到的数据,并设置DMA发送: ``` void USART1_IRQHandler(void) { uint32_t isrflags = READ_REG(UartHandle.Instance->SR); uint32_t cr1its = READ_REG(UartHandle.Instance->CR1); uint32_t cr3its = READ_REG(UartHandle.Instance->CR3); /* Read received data */ if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { /* Clear the RXNE interrupt flag */ __HAL_UART_CLEAR_FLAG(&UartHandle, UART_FLAG_RXNE); /* Read received data */ uint8_t data = (uint8_t)(UartHandle.Instance->DR); /* Process received data */ // ... /* Restart DMA reception */ HAL_UART_Receive_DMA(&UartHandle, &rx_buffer, RX_BUFFER_SIZE); } /* Transmit data using DMA */ if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { /* Clear the TXE interrupt flag */ __HAL_UART_CLEAR_FLAG(&UartHandle, UART_FLAG_TXE); /* Transmit data using DMA */ HAL_UART_Transmit_DMA(&UartHandle, &tx_buffer, TX_BUFFER_SIZE); } } void DMA1_Channel5_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart1_rx); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { /* Process received data */ // ... } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { /* Process transmitted data */ // ... } ``` 5. 在主函数中启动串口接收: ``` int main(void) { /* Initialize peripherals */ MX_USART1_UART_Init(); /* Start DMA reception */ HAL_UART_Receive_DMA(&UartHandle, &rx_buffer, RX_BUFFER_SIZE); /* Infinite loop */ while (1) { // ... } } ``` 这样,就可以在STM32F103VE中使用HAL库实现串口通信中断接收DMA发送的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值