STM32應用DMA——串列埠收發不定長資料

鬚鬚草發表於2020-11-05

STM32應用DMA——串列埠收發不定長資料

使用STM32自帶DMA傳輸資料,可以減輕CPU負擔,只需設定一些引數即可傳送想要傳送的資料,以下是STM32F1系列晶片測試過的部分程式碼,可實現DMA串列埠收發資料。

一、初始化部分

STM32 HAL庫

void uart2_init(uint32_t m_u32bound)
{
 		//01-GPIO配置
		GPIO_InitTypeDef GPIO_InitStruct = {0};
		
		//02-開啟時鐘
		__HAL_RCC_USART2_CLK_ENABLE();			//啟動串列埠2時鐘
		__HAL_RCC_GPIOA_CLK_ENABLE(); 			//啟動GPIOA時鐘
		
		//03-配置引腳
		GPIO_InitStruct.Pin = GPIO_PIN_2;				//USART1_TX   PA.2
    	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;			//推完複用模式
    	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;   //高速配置
   	 	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);			//PA2初始化
			
		GPIO_InitStruct.Pin = GPIO_PIN_3;				//USART1_RX	  PA.3
    	GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 		//輸入模式:浮空輸入
		GPIO_InitStruct.Pull =	GPIO_NOPULL;			//沒有上下拉
		HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);  		//PA3初始化

		//04-設定串列埠中斷
		HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);  
		HAL_NVIC_EnableIRQ(USART2_IRQn);
		
		//05-USART 配置
		I_huart2.Instance = USART2;
		I_huart2.Init.BaudRate = m_u32bound;			//波特率
		I_huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;	//沒有硬體控制流
		I_huart2.Init.Mode = UART_MODE_TX_RX;			//收發模式
		I_huart2.Init.StopBits = UART_STOPBITS_1; 		//停止位1位
		I_huart2.Init.Parity = UART_PARITY_NONE; 		//無奇偶校驗位
		I_huart2.Init.OverSampling = UART_OVERSAMPLING_16; //
		I_huart2.Init.WordLength = UART_WORDLENGTH_8B;  //資料位:8個
		if(HAL_UART_Init(&I_huart2)!=HAL_OK)		 //初始化串列埠
			while(1);
		
		//04-設定中斷優先順序
		HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); 
		HAL_NVIC_EnableIRQ(USART2_IRQn);
		
		//05-啟動DMA1時鐘
		__HAL_RCC_DMA1_CLK_ENABLE();

		//07-DMA1  USART2_RX
	    hdma_usart2_tx.Instance = DMA1_Channel7;						//通道7
	    hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;			//外設到記憶體
	    hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;				//外設地址不增長
	    hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;					//記憶體地址增長模式
	    hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;  //位元組對齊
	    hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;		//位元組對齊
	    hdma_usart2_tx.Init.Mode = DMA_NORMAL;							//正常模式
	    hdma_usart2_tx.Init.Priority = DMA_PRIORITY_HIGH;				//傳輸等級:高
	    if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
			while(1);
		
		//06-DMA1  USART2_RX
		hdma_usart2_rx.Instance = DMA1_Channel6;  				//通道6
		hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; 	//記憶體到外設
		hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;  		//外設地址不增長
		hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;  			//記憶體地址增長
		hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;//位元組對齊
		hdma_usart2_rx.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;	 //位元組對齊
		hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;  				//迴圈模式
		hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;   		//傳輸等級:低
		if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
			while(1);
		
		//07-開啟傳送完成中斷
		__HAL_UART_ENABLE_IT(&I_huart2, UART_IT_TC);	//傳送完成中斷
		
		//08-開啟DMA接收
		HAL_DMA_Start(&hdma_usart2_rx,(uint32_t)&USART2->DR,(uint32_t)I_USART2.u8rxbuff,BUFFMAX);
		SET_BIT(I_huart2.Instance->CR3, USART_CR3_DMAR);//USART2請求  DMA啟動 
				
		//09-開啟DMA傳送
		HAL_DMA_Start(&hdma_usart2_tx, (uint32_t)I_USART2.u8txbuff,(uint32_t)&USART2->DR, 0);
		SET_BIT(I_huart2.Instance->CR3, USART_CR3_DMAT);//USART2請求  DMA啟動

}

二、串列埠中斷部分(傳送資料部分)

這個需要開啟串列埠傳送完成中斷,當DMA往串列埠送資料送完後,串列埠會產生髮送完成中斷。

//串列埠2傳送中斷
void USART2_IRQHandler(void)
{
  if((USART2->SR & UART_FLAG_TC)!=RESET)    
	{    
		//當DMA傳輸資料完成以後,就會進入一次這個中斷
		USART2->SR = ~(UART_FLAG_TC);
		
		u8txmode = SEND_FINISH//此處設定:傳送完成標誌
	}
}

三、傳送資料函式

每當呼叫這個函式就會,要求DMA傳送一段資料給串列埠,接下來等待進入串列埠傳送中斷後,就已經完成傳送

//入口引數
//txlen : 傳送資料長度
void USART2_TxdSend(uint16_t u16txlen)
{
	__HAL_DMA_DISABLE(&hdma_usart2_tx); 						//暫停DMA
	__HAL_DMA_GET_COUNTER(&hdma_usart2_tx) = u16txlen; 			//設定傳送資料長度
 	__HAL_DMA_ENABLE(&hdma_usart2_tx); 							//啟動DMA
	__HAL_UART_CLEAR_FLAG(&I_huart2, UART_FLAG_TC); 			//清除傳送中斷
		
}

三、開啟定時器

開啟定時器的目的是希望,資料能夠完成傳送過來,當超過一定時間後,接收完成

void Timer4_Init(void)
{
  TIM_MasterConfigTypeDef sMasterConfig = {0};
	
	//01-定時器4時鐘
  __HAL_RCC_TIM4_CLK_ENABLE();
	
	//02-定時器4
  I_htim4.Instance = TIM4;			//TIM4¶¨Ê±Æ÷
  I_htim4.Init.Prescaler = 35;		//Ô¤·ÖƵϵÊý
  I_htim4.Init.CounterMode = TIM_COUNTERMODE_UP;  //ÏòÉϼÆÊý
  I_htim4.Init.Period = 10000;				//ÖØ×°ÔØÖµ   0 ->> 10000
  I_htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;		//²»·ÖƵ
  I_htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;	//ʹÄÜÖØÐÂ×°ÔØ
  if (HAL_TIM_Base_Init(&I_htim4) != HAL_OK)
		while(1);

  //03-設定中斷
  HAL_NVIC_SetPriority(TIM4_IRQn, 3, 0);  
  HAL_NVIC_EnableIRQ(TIM4_IRQn);
	
  //05-¶¨Ê±Æ÷´¥·¢Ô´ÉèÖÃ
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;	//更新中斷
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&I_htim4, &sMasterConfig) != HAL_OK)
		while(1);
	
	//06-清除中斷標誌位
	__HAL_TIM_CLEAR_FLAG(&I_htim4,TIM_FLAG_UPDATE);
	
	//07-開啟定時器中斷
	HAL_TIM_Base_Start_IT(&I_htim4);
	
	
}

四、定時器中斷(接收資料部分)

#define		RECE_FINISH		0   //接收資料完成標誌
#define		RECE_ING		1	//正在就收資料
#define		RECE_ILDE		2	//空閒狀態

uint16_t  u16rxp;   //現在接收資料長度
uint16_t  u16timer; //接收計時器
uint8_t	  u8rxmode; //接收狀態 

//定時器4(每1毫秒進入一次中斷)
void TIM4_IRQHandler(void)
{
	 //獲取定時器更新
	if(__HAL_TIM_GET_FLAG(&I_htim4,TIM_FLAG_UPDATE) != RESET)
	{
		//清除定時器中斷
		__HAL_TIM_CLEAR_FLAG(&I_htim4,TIM_FLAG_UPDATE);
		
		if(u8rxmode != RECE_FINISH)
		{
			if(u16rxp != (BUFFMAX - DMA1_Channel6->CNDTR))
			{//查詢接收資料有變化
				u16timer =0;
				u16rxp = (BUFFMAX - DMA1_Channel6->CNDTR);  //當前已經獲取的資料長度
			}
				
			if(u16timer < 20 )
				u16timer ++; //接收資料計數
				
			if(u16timer ==20)
			{//超過一定時間,DMA沒傳過任何資料
				u16timer =0;
				u8rxmode = RECE_FINISH;
				__HAL_DMA_DISABLE(&hdma_usart2_rx); //暫停DMA2		
				DMA1_Channel6->CNDTR = BUFFMAX;   	//寫入緩衝區長度
				__HAL_DMA_ENABLE(&hdma_usart2_rx);  //啟動DMA2
			}
		}

	}
}

相關文章