STM32串口DMA接收不定长数据


A:全称Direct Memory Access,即直接存储器访问
DMA 传输将数据从一个地址空间复制到另外一个地址空间。CPU只需初始化DMA即可,传输动作本身是由 DMA 控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器参与处理,CPU可以干其他事情,当DMA传输完成的时候产生一个中断,告诉CPU我已经完成了,然后CPU知道了就可以去处理数据了,这样子提高了CPU的利用率,因为CPU是大脑,主要做数据运算的工作,而不是去搬运数据。DMA 传输对于高效能嵌入式系统算法和网络是很重要的

在STM32的DMA资源

STM32F1系列的MCU有两个DMA控制器(DMA2只存在于大容量产品中),DMA1有7个通道,DMA2有5个通道,每个通道专门用来管理来自于一个或者多个外设对存储器的访问请求。还有一个仲裁器来协调各个DMA请求的优先权。
image
image
而STM32F4/F7/H7系列的MCU有两个DMA控制器总共有16个数据流(每个DMA控制器8个),每一个DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
image
image

串口DMA接收不定长数据

DMA在接收数据的时候,串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。等到接收到数据的时候,告诉CPU去处理即可。

判断数据接收完成

那么问题来了,怎么知道数据是否接收完成呢?
其实,有很多方法:

  • 对于定长的数据,只需要判断一下数据的接收个数,就知道是否接收完成,这个很简单,暂不讨论。
  • 对于不定长的数据,其实也有好几种方法,麻烦的我肯定不会介绍,有兴趣做复杂工作的同学可以在网上看看别人怎么做,下面这种方法是最简单的,充分利用了stm32的串口资源,效率也是非常之高。

stm32串口的状态寄存器

image
当我们检测到触发了串口总线空闲中断的时候,我们就知道这一波数据传输完成了,然后我们就能得到这些数据,去进行处理即可。这种方法是最简单的,根本不需要我们做多的处理,只需要配置好,串口就等着数据的到来,dma也是处于工作状态的,来一个数据就自动搬运一个数据。

接收完数据时处理

串口接收完数据是要处理的,那么处理的步骤是怎么样呢?

  • 暂时关闭串口接收DMA通道,有两个原因:1.防止后面又有数据接收到,产生干扰,因为此时的数据还未处理。2.DMA需要重新配置。
    清DMA标志位。
  • 从DMA寄存器中获取接收到的数据字节数(可有可无)。
  • 重新设置DMA下次要接收的数据字节数,注意,数据传输数量范围为0至65535。这个寄存器只能在通道不工作(DMA_CCRx的EN=0)时写入。通道开启后该寄存器变为只读,指示剩余的待传输字节数目。寄存器内容在每次DMA传输后递减。数据传输结束后,寄存器的内容或者变为0;或者当该通道配置为自动重加载模式时,寄存器的内容将被自动重新加载为之前配置时的数值。当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。
  • 给出信号量,发送接收到新数据标志,供前台程序查询。
  • 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如重置DMA接收数据长度,必须要在关闭DMA的条件进行,否则操作无效。

注意事项

STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。如果中断发送数据帧的速率很快,MCU来不及处理此次接收到的数据,中断又发来数据的话,这里不能开启,否则数据会被覆盖。有两种方式解决:

  • 在重新开启接收DMA通道之前,将Rx_Buf缓冲区里面的数据复制到另外一个数组中,然后再开启DMA,然后马上处理复制出来的数据。
  • 建立双缓冲,重新配置DMA_MemoryBaseAddr的缓冲区地址,那么下次接收到的数据就会保存到新的缓冲区中,不至于被覆盖。

程序实现

这里我写的是面板的代码,面板通过USART2与主板进行串口通信,当面板往串口发送数据时,主板收到数据,判断是否有效,接着从串口回复一个或多个数据。
适用于一发多收的情况,大家也可以看情况,适当修改

代码实现

usart.c

uint8_t uart2_recv_data[REVBUFSIZE] = {0};  // 接收数据缓冲区
//uint8_t uart2_recv_data[20] = {0};  // 接收数据缓冲区
uint8_t uart2_recv_flag = 0;        // 接收完成标志位
uint8_t uart2_recv_len = 0;         // 接收的数据长度
uint8_t uart2_send_flag = 0;        // 发送完成标志位


uint8_t uart2_sent_data[12] = {0};  // 发送数据缓冲区
uint8_t uart2_recv_data_temp[REVBUFSIZE] = {0};  // 接收数据缓冲区

/**
 *  @brief  串口1中断控制器配置
 *  @param  无
 *  @retval 无
 *  @note   中断优先级分组全工程只配置一次,在 main 函数最开始进行配置  
 *  @note   中断处理函数在 CMSIS/stm32f10x_it.c 中进行处理   
 */
void uart2_nvic_config(void)
{
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 配置串口1的中断控制器
    NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;          // 在 stm32f10x.h 中找 IRQn_Type 枚举
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;         // 子优先级
    NVIC_Init(&NVIC_InitStruct);
}

/**
 *  @brief  MA1 通道7, UART2_TX 中断控制器配置
 *  @param  无
 *  @retval 无
 *  @note   中断优先级分组全工程只配置一次,在 main 函数最开始进行配置  
 *  @note   中断处理函数在 CMSIS/stm32f10x_it.c 中进行处理   
 */
void DMA1_Channel7_nvic_config(void)
{
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 配置串口1的中断控制器
    NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel7_IRQn;   // 在 stm32f10x.h 中找 IRQn_Type 枚举
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;  // 抢占优先级
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;         // 子优先级
    NVIC_Init(&NVIC_InitStruct);
}

/**
 *  @brief  串口1 DMA初始化配置
 *  @param  无
 *  @retval 无 
 *  @note    UART2_TX -> DMA1 Channel7; UART2_RX -> DMA1 Channel6
 */
void uart2_dma_config(void)
{
    DMA_InitTypeDef DMA_InitStruct;
    
    DMA_DeInit(DMA1_Channel7);  // DMA1 通道7, UART2_TX
    DMA_DeInit(DMA1_Channel6);  // DMA1 通道6, UART2_RX
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  // 使能DMA1的时钟
    
    // 配置 DMA1 通道7, UART2_TX  PA2
    DMA_InitStruct.DMA_PeripheralBaseAddr = (USART2_BASE + 0x04);   // 数据寄存器(USART_DR) 地址偏移:0x04
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)uart2_sent_data;  // 内存地址
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;		
    DMA_InitStruct.DMA_BufferSize = sizeof(uart2_sent_data)/sizeof(uart2_sent_data[0]);;      // 寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输
    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_Mode = DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel7, &DMA_InitStruct);
    
    // 配置 DMA1 通道6, UART2_RX	PA3
    DMA_InitStruct.DMA_PeripheralBaseAddr = (USART2_BASE + 0x04);   // 数据寄存器(USART_DR) 地址偏移:0x04
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)uart2_recv_data;  // 内存地址
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                 // 外设到内存
    DMA_InitStruct.DMA_BufferSize = sizeof(uart2_recv_data)/sizeof(uart2_recv_data[0]);
    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_Mode = DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel6, &DMA_InitStruct);
      
    USART_DMACmd(USART2, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求
    
    DMA1_Channel7_nvic_config();
    
    // 配置 DMA2 通道7, UART2_TX 传输完成中断
    DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE);
    
    DMA_Cmd(DMA1_Channel6, ENABLE);     // 开启接收	rx
    DMA_Cmd(DMA1_Channel7, DISABLE);    // 禁止发送 tx
}


/**
 *  @brief  串口2初始化
 *  @param  波特率
 *  @retval 无
 *  @note   UART2_TX -> PA2; UART1_RX -> PA3     
 */
void uart2_init(uint32_t baud)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    
    GPIO_DeInit(GPIOA);     // 恢复 GPIOA 的寄存器到默认值
    USART_DeInit(USART2);   // 恢复 UART2 的寄存器到默认值
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,  ENABLE);   // 使能 GPIOA 的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);   // 使能 UART2 的时钟
    
    // 配置发送引脚 PA2
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置接收引脚 PA3
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置串口参数 收发一体、8n1、无流控
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; 
    USART_InitStruct.USART_BaudRate = baud;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_Init(USART2, &USART_InitStruct);
    
    uart2_nvic_config();    // 中断控制器配置
    uart2_dma_config();     // DMA配置
    
    USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);  // 使能空闲中断
    
    USART_Cmd(USART2, ENABLE);  // 使能串口1
}

中断处理函数

// 串口2的中断处理函数
void USART2_IRQHandler(void)
{   
    uint8_t clear;
		u8 i =0;
	//	u32 buff_length;
    
		//接收到数据
    if( USART_GetITStatus(USART2, USART_IT_IDLE) != RESET )
    {    

			
			DMA_Cmd(DMA1_Channel6, DISABLE);		//禁止接收数据
			
			DMA_ClearFlag( DMA1_FLAG_TC6);		//清DMA标志位
			
			//buff_length = sizeof(uart2_recv_data)/sizeof(uart2_recv_data[0]) - DMA_GetCurrDataCounter(DMA1_Channel6);
			
			DMA1_Channel6->CNDTR = sizeof(uart2_recv_data)/sizeof(uart2_recv_data[0]);
			
			DMA_Cmd(DMA1_Channel6, ENABLE);		//使能接收数据
			
			uart2_recv_flag = 1;                // 接收标志置1
			
			for(u8 i = 0; i< REVBUFSIZE;i++)
			{
				uart2_recv_data_temp[i] = uart2_recv_data[i];
			}
				
			USART_ReceiveData(USART2);
    }
}


// 发送完成
void DMA1_Channel7_IRQHandler(void)
{
    if( DMA_GetITStatus(DMA1_IT_TC7) != RESET ) // DMA1 通道7, UART1_TX 传输完成
    {
        
			
			DMA_ClearITPendingBit(DMA1_IT_TC7);     // 清除中断
       
			
			DMA_Cmd(DMA1_Channel7, DISABLE);        //  禁止发送
			
			DMA_Cmd(DMA1_Channel6, DISABLE);    // 禁止接收
			memset(uart2_recv_data, '\0', sizeof(uart2_recv_data)/sizeof(uart2_recv_data[0]));  // 清空接收缓冲区
			//DMA_SetCurrDataCounter(DMA1_Channel6, DMA_GetCurrDataCounter(DMA1_Channel6)); // 设置DMA接收的数据量
			DMA_SetCurrDataCounter(DMA1_Channel6, REVBUFSIZE); // 设置DMA接收的数据量
			DMA_Cmd(DMA1_Channel6, ENABLE);     // 使能接收
			
    }
}

usart.h


extern uint8_t uart2_recv_data[REVBUFSIZE]; // 接收数据缓冲区
extern uint8_t uart2_recv_flag;     // 接收完成标志位
extern uint8_t uart2_recv_len;      // 接收的数据长度
extern uint8_t uart2_send_flag;     // 发送完成标志位

extern uint8_t uart2_recv_data_temp[REVBUFSIZE];
extern uint8_t uart2_sent_data[12] ;  // 发送数据缓冲区


void uart2_init(uint32_t baud);



主函数

	while(1)
 {				


					// 接收完成
				if(uart2_recv_flag == 1)
        {
            uart2_recv_flag = 0;
         
					printf("\r\n\r\n");
            
					for(u16 i = 0 ;i<REVBUFSIZE; i++)
						printf("buf[%d] = %X\r\n", i, uart2_recv_data_temp[i]);
					
					
					
					
					
					 
					
        }
				

			
        
		
	}
posted @ 2024-12-05 11:29  Dazz_24  阅读(457)  评论(1)    收藏  举报