STM32F1应用DMA——串口收发不定长数据

STM32F1应用DMA——串口收发不定长数据

使用STM32自带DMA传输数据,可以减轻CPU负担,只需设置一些参数即可发送想要发送的数据,以下是STM32F1系列芯片测试过的部分代码,可实现DMA串口收发数据。下图来自STM32官网的手册,RM0008.pdf
发送数据逻辑图:
在这里插入图片描述
接收数据逻辑图
在这里插入图片描述
下面是使用STM32 HAL库进行配置,大致实现思路都是一样的,先开启串口初始化(开启DMA传输),相应的DMA初始化,然后设置好传输地址,传输字节个数,然后启动使能

一、初始化部分

uint8_t u8txbuff[1024]; //发送缓冲区
uint8_t u8rxbuff[1024];	//接收缓冲区

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)u8rxbuff,BUFFMAX);
		SET_BIT(I_huart2.Instance->CR3, USART_CR3_DMAR);//USART2请求  DMA启动 
				
		//09-开启DMA发送
		HAL_DMA_Start(&hdma_usart2_tx, (uint32_t)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发送一段数据给USART的数据寄存器当中,接下来等待进入串口发送中断后,就可以判定DMA传送数据到串口已经完成发送。

//入口参数
//txlen : 发送数据长度
void USART2_TxdSend(uint16_t u16txlen)
{
	__HAL_DMA_DISABLE(&hdma_usart2_tx); 						//暂停DMA
	__HAL_UART_CLEAR_FLAG(&I_huart2, UART_FLAG_TC); 			//清除发送中断
	__HAL_DMA_GET_COUNTER(&hdma_usart2_tx) = u16txlen; 			//设置发送数据长度
 	__HAL_DMA_ENABLE(&hdma_usart2_tx); 							//启动DMA
	
		
}

三、开启定时器

开启定时器的目的是希望,周期性的去查询DMA传送了多少的数据到内存,也就是说接收了多少个字节。当个DMA传送字节个数没有变化之后(DMA_CNDTR寄存器没有变化),又或者时超过一定时间后,就可以判定一帧数据已经接收完成了。

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);
	
	
}

四、定时器中断(接收数据部分)

DMA的DMA_CNDTR寄存器每当传输一个数据时,这个寄存器会自动减1,即接收一个数据。当串口处于接收状态时,使用定时器中断1ms周期查询DMA的DMA_CNDTR寄存器变化。

假定BUFFMAX为缓冲区长度:
①当BUFFMAX等于DMA_CNDTR寄存器值,没有输入接收(空闲状态)
②当BUFFMAX不等于DMA_CNDTR寄存器值,有新的数据接收(接收状态)
③当DMA_CNDTR寄存器超过20ms没有变化,判定接收数据完成。

#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
			}
		}

	}
}

五、测试

#include <string.h>

extern uint16_t u16rxp;
extern uint8_t u8txbuff;
extern uint8_t u8rxbuff;
extern uint8_t u8rxmode;

int main(void)
{
	uart2_init(115200); //波特率设置 115200
	Timer4_Init();	//开启定时器
	while(1)
	{
		if(u8rxmode==RECE_FINISH) //判定接收完成
		{
			memcpy(u8txbuff,u8rxbuff,u16rxp); //将接收的数据放入发送缓冲区
			USART2_TxdSend(u16rxp); //发送数据
			u8rxmode=RECE_ILDE;//进入空闲状态
		}
			
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值