STM32F407串口之间通过DMA的方式进行_Send_Recv

功能介绍

本文主要是发送端的内容。

想实现2块STM32F407的芯片之间通过RS485来实现高效的通信方式。

两块板子,一个为节点1,另一个为节点2;

节点1,先发起双方之间的通信,在任务函数中,每间隔一段时间比如100毫秒,发送一帧固定长度的数据给节点2;节点2在等待节点1的数据帧的到来,节点2在接收完一帧数据后,将自己的一些应用数据封装成一帧数据,再通过DMA的方式经过串口将一帧数据发送给节点1。

节点2接收一帧数据后,在任务函数中进行对数据的解析及处理。

节点1,使用的串口为USART3,下面是串口和DMA的初始化代码:

	// 使能USART3和DMA1时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);		
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);	

	//配置USART3的GPIO
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOB,&GPIO_InitStructure);

	// RS485 收发模式控制
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOE,&GPIO_InitStructure);

	//将USART3的GPIO引脚与AF模式映射起来
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);

	//配置USART3的参数
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    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_Tx|USART_Mode_Rx;
	USART_Init(USART3, &USART_InitStructure);

	// 配置USART2的中断
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	// 使能USART2的中断
	USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
	USART_Cmd(USART3, ENABLE);

	// 配置DMA1的参数 发送数据流
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_Channel = DMA_Channel_4;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART3->DR);
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)tx_buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = TX_BUFFER_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_Mode_Circular DMA_Mode_Normal
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream3, &DMA_InitStructure);
	// 设置DMA的发送完成中断
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
	NVIC_Init(&NVIC_InitStructure);
	DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);
	NVIC_EnableIRQ(DMA1_Stream3_IRQn);
	
	// 配置DMA1的参数 接收数据流
	DMA_InitStructure.DMA_Channel = DMA_Channel_4;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART3->DR);
	DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t) rx_buffer;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
	DMA_InitStructure.DMA_BufferSize = RX_BUFFER_SIZE;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_Mode_Normal  DMA_Mode_Circular
	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
	DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
	DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
	DMA_Init(DMA1_Stream1, &DMA_InitStructure);
	
	// 设置DMA的接收完成中断 可以判断一帧数据的接收完成
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn;  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
	NVIC_Init(&NVIC_InitStructure);
	DMA_ITConfig(DMA1_Stream1, DMA_IT_TC, ENABLE);
	NVIC_EnableIRQ(DMA1_Stream1_IRQn);
	
	//启动DMA1 先关闭哦
    DMA_Cmd(DMA1_Stream3, DISABLE);	 // 发送
	DMA_Cmd(DMA1_Stream1, DISABLE);  // 接收
	//启动USART3
    USART_Cmd(USART3, ENABLE);
	USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送   
	USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收  
    // 默认是发送模式
	PEout(15) = 1; //开启发送模式	
}

初始化好需要用到的硬件GPIO USART3 DMA后,在操作系统的任务函数中,我们就可以进行数据的发送了,下面是任务函数中的发送数据的相关参考代码:

void send_task(void)
{
    u16 vCrc = 0;

	// 封装需要发送的数据帧,
    // 这里发送了11个字节的数据
	tx_buffer[0] =0x11;
	tx_buffer[1] =0x22;
	tx_buffer[2] =0x33;
	tx_buffer[3] =0x44;
	tx_buffer[4] =0x55;
	tx_buffer[5] =0x66;
	tx_buffer[6] =0x77;
	tx_buffer[7] =0x88;
	tx_buffer[8] =0x99;

    #if 0
	tx_buffer[9] =0xAA;
	tx_buffer[10] =0xBB;
    #endif
    
    vCrc = usMBCRC16( (u8 *)tx_buffer, 9 );
	tx_buffer[9] = vCrc >> 8;
	tx_buffer[10] = vCrc;

    // 切换RS485为发送模式
	PEout(15)= 1; //开启发送模式
	
	DMA_Cmd(DMA1_Stream3, DISABLE);
   	while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE);
    DMA_SetCurrDataCounter(DMA1_Stream3, TX_BUFFER_SIZE);
	DMA_Cmd(DMA1_Stream3, ENABLE);

	// 等待接收数据帧的到来
	if(1==Frame_Recv_Flag)
	{
		// 解析接收数据帧
	}
}

节点1,发送一帧数据后,我们需要知道什么时候发送完成 数据的发送,然后切换RS485的收发模式,为接收数据帧做准备。在这里我使能了DMA的传输完成的中断。在中断服务函数中,除了清除标志位就是切换RS485的收发模式,最好在这里做个LED翻转,可以提示我们一帧数据发送出去了。下面是部分参考代码:

void DMA1_Stream3_IRQHandler( void )
{
	OSIntEnter();
	if (DMA_GetITStatus(DMA1_Stream3, DMA_IT_TCIF3) != RESET)
    {
        DMA_Cmd(DMA1_Stream3, DISABLE);
        DMA_ClearITPendingBit(DMA1_Stream3, DMA_IT_TCIF3);
    }
    // 在这里最好加个串口的传输完成的判断,否则有可能发送少几个字节的数据
	while(!USART_GetFlagStatus(USART3, USART_FLAG_TC));
    // 2023-07-18 当时每次断电重启后,第一帧数据还是少2个字节的数据,后面再发不会少2个字节的数据
    // 2023-07-18 在这里适当的延时1到2个ms的时间后去切换485的接收使能好像可以了
    delay_ms(1);// 2023-07-18
	PEout(15)= 0; //开启接收模式
	Frame_Recv_Flag = 0;// 为了接收下一帧数据,先清零
	LED_01_TOG;// 每发送一帧数据完成就切换一下
	OSIntExit();
}

假设这时候节点2已经接收完节点1发送的数据帧后,做了数据帧的解析后,也发送了一帧数据过来,这时因为节点1使能了串口USART3的接收非零中断,进入中断函数中,参考代码如下:

void USART3_IRQHandler(void)
{// 每进入一次该中断,就打开一下DMA的接收数据流
	OSIntEnter();
    if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(USART3, USART_IT_RXNE);
        DMA_Cmd(DMA1_Stream1, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Stream1, RX_BUFFER_SIZE);
        DMA_Cmd(DMA1_Stream1, ENABLE);
    }
	//LED_01_TOG;// 每接收一个字节的数据就反转一下
	OSIntExit();
}

这个中断函数中就是进行了中断标志位清零,然后打开DMA,瞬间就进行了数据的拷贝

当DMA的接收数据流的传输完成中断响应,就标识着一帧数据完成,该中断函数的参考代码如下:

void DMA1_Stream1_IRQHandler( void )
{
	OSIntEnter();
    if (DMA_GetITStatus(DMA1_Stream1, DMA_IT_TCIF1) != RESET)
    {
        DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1);
		PEout(15) = 1;         // 开启RS485发送模式
		Frame_Recv_Flag = 1;   // 数据帧接收完成 标志位 
    }
	// 接收完数据
	LED_02_TOG;// LED_02 串口助手每发一帧数据,灯就会一闪一下
	OSIntExit();
}

在中断函数中,进行了中断标志位清零,RS485的模式切换,数据帧接收完成的标志位置1,

在任务函数中,数据帧接收完成标志位置1后,就可以解析数据帧了。

在测试的过程中可以通过观察,LED1和LED2的状态来指示我们一帧数据的发送和接收。

LED1闪烁一下,说明有一帧数据通过串口发送完成。

LED2闪烁一下,说明有一帧数据被串口接收完成。

这里先分析到节点1的发送数据帧和接收数据帧的实现过程,节点2的后续有时间再写了啊!

记录一下调试过程中出现的问题的图片记录:

1.出现第一帧少2个字节的情况:

 2.修改DMA接收完成中断的内容:

 3.DMA中断函数中添加适当延时后串口监视到的数据

 

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值