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

本文详细介绍了如何使用STM32F407通过RS485实现两块芯片间的通信。通过配置DMA和中断,节点1周期性发送数据帧,节点2接收到数据后通过DMA回传应用数据。在中断服务函数中,处理发送完成和接收完成的逻辑,确保数据帧的完整传输。在调试过程中解决了数据帧丢失问题。

功能介绍

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

想实现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中断函数中添加适当延时后串口监视到的数据

 

<think>我们正在使用RT-Thread RTOS在STM32F407微控制器上配置串口DMA收发功能。根据引用[1][2][3],我们可以结合DMA和空闲中断(IDLE)来实现不定长数据的接收,同时使用DMA发送数据以提高效率。以下步骤将指导完成配置: ### 1. 硬件初始化 首先,在RT-Thread Studio中创建工程并配置串口DMA: - 使能串口(例如USART2)和对应的DMA通道(接收和发送)。 - 配置串口参数(波特率、数据位、停止位、校验位等)。 - 使能串口的空闲中断(IDLE)和DMA接收。 ### 2. 配置DMA接收空闲中断 在RT-Thread中,通常使用设备驱动框架。我们需要在初始化时打开串口设备,并设置接收回调函数。同时,使能空闲中断以触发接收完成事件。 参考引用[3]中的方法,我们可以创建一个UART设备管理结构体,并初始化DMA接收: ```c #include <rtdevice.h> #define UART_RX_BUFFER_SIZE 256 static rt_uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE]; static struct rt_semaphore rx_sem; // 用于接收完成的信号量 /* 接收回调函数 */ static rt_err_t uart_dma_rx_ind(rt_device_t dev, rt_size_t size) { /* 当空闲中断发生时,会进入此回调,size为接收到的数据长度 */ rt_sem_release(&rx_sem); // 释放信号量通知接收完成 return RT_EOK; } void uart_dma_init(void) { rt_device_t serial = rt_device_find("uart2"); // 假设使用串口2 if (serial == RT_NULL) { rt_kprintf("find uart2 failed!\n"); return; } /* 初始化信号量 */ rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); /* 以DMA接收和中断方式打开串口设备 */ rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX); /* 设置接收回调函数 */ rt_device_set_rx_indicate(serial, uart_dma_rx_ind); /* 启动DMA接收,设备驱动会管理接收缓冲区 */ rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, (void*)RT_DEVICE_FLAG_DMA_RX); /* 设置串口接收缓冲区 */ struct rt_serial_rx_fifo *rx_fifo = (struct rt_serial_rx_fifo *)serial->user_data; rx_fifo->buffer = uart_rx_buffer; rx_fifo->size = UART_RX_BUFFER_SIZE; /* 使能空闲中断 */ rt_device_control(serial, RT_DEVICE_CTRL_SET_INT, (void*)RT_DEVICE_FLAG_IDLE_RX); } ``` ### 3. 接收数据处理线程 创建一个线程来等待接收完成的信号量,然后处理数据: ```c static void uart_rx_thread_entry(void *parameter) { rt_device_t serial = (rt_device_t)parameter; while (1) { /* 等待接收信号量 */ if (rt_sem_take(&rx_sem, RT_WAITING_FOREVER) == RT_EOK) { /* 获取接收数据长度 */ rt_size_t recv_len = 0; rt_device_control(serial, RT_DEVICE_CTRL_GET_RX_DATA_SIZE, &recv_len); if (recv_len > 0) { /* 读取数据 */ rt_uint8_t buffer[UART_RX_BUFFER_SIZE]; recv_len = rt_device_read(serial, 0, buffer, recv_len); /* 处理接收到的数据 */ // ... 这里添加你的处理代码 } } } } void uart_thread_init(void) { rt_device_t serial = rt_device_find("uart2"); rt_thread_t thread = rt_thread_create("uart_rx", uart_rx_thread_entry, serial, 1024, 25, 10); if (thread) { rt_thread_startup(thread); } } ``` ### 4. DMA发送数据 使用DMA发送数据可以避免阻塞线程: ```c rt_size_t uart_dma_send(rt_device_t serial, const rt_uint8_t *data, rt_size_t size) { rt_size_t sent = 0; while (sent < size) { /* 发送数据,直到全部发送完成 */ rt_size_t len = rt_device_write(serial, 0, data + sent, size - sent); if (len > 0) { sent += len; } else { rt_thread_mdelay(1); // 等待一下再试 } } return sent; } ``` ### 5. 注意事项 - 在RT-Thread中,串口设备驱动已经封装了DMA操作,我们只需使用`rt_device_write`和`rt_device_read`即可。 - 确保在RT-Thread的board.h中定义了对应的串口DMA宏,如`BSP_USING_UART2`和`BSP_UART2_RX_USING_DMA`。 - 空闲中断的触发需要在串口中断服务函数中处理,RT-Thread的设备驱动已经处理了空闲中断,我们只需设置回调即可。 ### 6. 测试 在主函数中初始化串口和线程: ```c int main(void) { uart_dma_init(); uart_thread_init(); return 0; } ``` ### 总结 通过以上步骤,我们实现了在RT-Thread中使用串口DMA收发不定长数据的功能。接收部分利用空闲中断和信号量通知线程处理,发送部分直接使用DMA传输。 [^1]: RT-Thread uart2串口dma idle接收不断帧 [^2]: 使用STM32CUBE MX创建STM32F407工程 串口DMA加空闲中断任意长度收发 [^3]: rt-thread+stm32 使用串口dma空闲中断接收不定长数据 §§相关问题§§ 1. 如何优化RT-Thread中串口DMA接收的缓冲区管理? 2. 在RT-Thread中如何处理多个串口DMA收发? 3. 如何提高串口DMA发送的效率和实时性?</think>### STM32F407 RTOS 实现串口DMA收发配置指南 #### 一、配置步骤 1. **硬件初始化** - 使用STM32CubeMX配置USART(如USART1)和DMA: - 使能串口全局中断和DMA传输 - 配置DMA通道为循环模式(Circular) - 设置空闲中断(IDLE Interrupt)使能 - 参考配置代码: ```c // CubeMX生成的DMA配置 hdma_usart1_rx.Instance = DMA2_Stream2; hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // 循环模式 ``` 2. **RTOS任务设置** - 创建串口接收任务: ```c void uart_receive_task(void *arg) { while(1) { // 等待信号量通知数据到达 rt_sem_take(&rx_sem, RT_WAITING_FOREVER); // 处理接收数据 process_rx_data(rx_buffer, data_len); } } ``` 3. **空闲中断处理** - 在串口中断服务函数中检测空闲中断: ```c void USART1_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 计算接收数据长度 uint16_t recv_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx); // 发送信号量通知任务 rt_sem_release(&rx_sem); } HAL_UART_IRQHandler(&huart1); } ``` 4. **DMA接收初始化** ```c HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); HAL_UART_EnableRxIdle(&huart1); // 启用空闲中断 ``` #### 二、关键配置说明 1. **DMA模式选择** - 接收模式:`DMA_CIRCULAR`(循环缓冲) - 发送模式:`DMA_NORMAL`(单次传输) 2. **内存管理** - 双缓冲设计防止数据覆盖: ```c uint8_t rx_buffer[2][BUFFER_SIZE]; // 双缓冲 int current_buf = 0; ``` 3. **RT-Thread特定配置** - 在`board.h`启用DMA支持: ```c #define BSP_USING_UART1 #define BSP_UART1_RX_USING_DMA ``` - 使用设备框架注册串口: ```c rt_device_t serial = rt_device_find("uart1"); rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX); ``` #### 三、发送数据实现 ```c void dma_send_data(uint8_t *data, uint16_t len) { // 等待上一次发送完成 while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX); HAL_UART_Transmit_DMA(&huart1, data, len); } ``` #### 四、注意事项 1. 启用DMA流中断并设置合适优先级 2. 在RTOS中需使用信号量/消息队列同步数据 3. 防止DMA访问冲突: ```c rt_enter_critical(); // 关中断 // 切换DMA缓冲区 rt_exit_critical(); // 开中断 ``` > 此配置结合了引用[1][2][3]的实现思路:使用空闲中断检测帧结束,通过DMA循环缓冲降低CPU负载,RTOS任务处理数据解析[^1][^2][^3]。 [^1]: RT-Thread uart2串口dma idle接收不断帧 [^2]: 使用STM32CUBE MX创建STM32F407工程 串口DMA加空闲中断任意长度收发 [^3]: rt-thread+stm32 使用串口dma空闲中断接收不定长数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值