STM32F407串口之间通过DMA的方式进行数据_Recv_Send

本文介绍了如何使用STM32的USART2通过DMA进行数据的接收和发送操作。首先,初始化USART2和DMA,设置接收和发送的DMA通道。在USART2的中断处理函数中启动接收DMA,当接收完成后,进入发送模式。在DMA传输完成中断处理函数中,处理接收到的数据并切换回接收模式。整个过程涉及到串口、DMA、中断和数据帧的处理。

上次简介了串口通过DMA的方式进行数据的先发送后接收的模式,本文是对发送的数据进行先接收然后再发送的详细过程的介绍。

1.USART2的初始化

static void USART2_Init( void )
{
	GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	// 使能USART2和DMA1时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

	// 配置USART2引脚
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStruct);

	// 模式控制: 因为使用的是RS485
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

	// 2023-05-18 缺少端口复用
	//串口2对应引脚复用映射
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); 
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); 

	// 配置USART2
    USART_InitStruct.USART_BaudRate = 115200;// 波特率可以适当大些
    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_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStruct);

	// 配置USART2的中断
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	// 使能USART2的中断
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
	USART_Cmd(USART2, ENABLE);

	// 初始化DMA
	#if 1
	DMA_InitTypeDef DMA_InitStructure;
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
	// 配置DMA1通道4,用于USART2接收
	DMA_InitStructure.DMA_Channel = DMA_Channel_4;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART2->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_Stream5, &DMA_InitStructure);

	DMA_ITConfig(DMA1_Stream5, DMA_IT_TC, ENABLE);
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	#endif

	#if 1
	DMA_InitTypeDef DMA_InitStruct;
	// 配置DMA1通道7,用于USART2发送
    DMA_InitStruct.DMA_Channel = DMA_Channel_4;
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t) & (USART2->DR);
    DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t) tx_buffer;
    DMA_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStruct.DMA_BufferSize = TX_BUFFER_SIZE;
    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_VeryHigh;
    DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream6, &DMA_InitStruct);

	// 设置中断
	DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);
	NVIC_EnableIRQ(DMA1_Stream6_IRQn);
	#endif
	
	// 使能USART2接收DMA请求
    USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
	// 使能USART2
    USART_Cmd(USART2, ENABLE);
	// 使能DMA1通道4
	DMA_Cmd(DMA1_Stream5, ENABLE);

	#if 1
	// 使能USART2发送DMA请求
	USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
	// 使能USART2
    USART_Cmd(USART2, ENABLE);
	// 使能DMA1通道7 先关闭
    DMA_Cmd(DMA1_Stream6, DISABLE);
	#endif

	TX_ENABLE = 0; //开启接收模式	
}

2.初始化完USART2后开始等待数据的到来,通过USART2的中断的方式来等到数据的到来

// 然后在USART2中断处理函数中,启动DMA传输:
void USART2_IRQHandler(void)
{
	OSIntEnter();
    if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);
        DMA_Cmd(DMA1_Stream5, DISABLE);
        DMA_SetCurrDataCounter(DMA1_Stream5, BUFFER_SIZE);
        DMA_Cmd(DMA1_Stream5, ENABLE);
    }
	LED1_TOG;// 每接收一个字节的数据就反转一下
	OSIntExit();
}

 3.USART2接收完一个完整数据帧的标志是DMA的数据流中断

// 最后在DMA传输完成中断处理函数中,处理接收到的数据:
// 接收完成设定的字节数,才发生中断,这里是10个字节
void DMA1_Stream5_IRQHandler(void)
{
	OSIntEnter();
    if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5) != RESET)
    {
        DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);
		#if 0    // 测试用
        for (int i = 0; i < BUFFER_SIZE; i++)
        {
            printf("%c", buffer[i]);
        }
		#endif
		TX_ENABLE = 1; //开启发送模式
		Frame_Recv_Flag = 1;// 接收一帧数据的标志位

		#if 0    // 测试用
		// 接收完一帧数据后,开启发送模式,并把一帧数据发送出去
		TX_ENABLE = 1; //开启发送模式
		  // 发送数据
	    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] = 0x99;

		// 为啥最后2个字节的数据发送不出去呢
        // 最终不在这里发送数据,
		tx_buffer[8] = 0x99;
		tx_buffer[9] = 0x99;

		DMA_Cmd(DMA1_Stream6, DISABLE);
   		while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE);
    	DMA_SetCurrDataCounter(DMA1_Stream6, 10);
		DMA_Cmd(DMA1_Stream6, ENABLE);

		//DMA_ClearFlag(DMA1_Stream6, DMA_FLAG_TCIF6);
		//DMA_Cmd(DMA1_Stream6, ENABLE);
		#endif
		// 接收完数据
		LED2_TOG;// 串口助手每发一帧数据,灯就会一闪一下,即接收完一帧数据
    }
	OSIntExit();
}

4.在任务函数中进行接收的数据帧进行解析,然后发送一帧数据出去

void CommPoll_bk_01( void )
{
	if(1 == Frame_Recv_Flag) // 如果有一帧数据过来的话,
	{
		// 如果有一帧数据过来,先解析这一帧数据
		// 然后再发送一帧数据回去
		// 先封装一帧数据
		// 然后启动发送
		#if 1
		// 接收完一帧数据后,开启发送模式,并把一帧数据发送出去
		TX_ENABLE = 1; //开启发送模式

		// 发送数据帧的组装
	    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] = 0x99;
		tx_buffer[8] = 0x99; // 为啥最后2个字节的数据发送不出去呢
		tx_buffer[9] = 0x99;

		tx_buffer[10] = 0x11;
		tx_buffer[11] = 0x22;
		tx_buffer[12] = 0x33;
		tx_buffer[13] = 0x44;
		tx_buffer[14] = 0x55;
		tx_buffer[15] = 0x66;
		tx_buffer[16] = 0x77;
		tx_buffer[17] = 0x99;
		tx_buffer[18] = 0x99; // 为啥最后2个字节的数据发送不出去呢
		tx_buffer[19] = 0x99;

		tx_buffer[20] = 0x11;
		tx_buffer[21] = 0x22;
		tx_buffer[22] = 0x33;
		tx_buffer[23] = 0x44;
		tx_buffer[24] = 0x55;
		tx_buffer[25] = 0x66;
		tx_buffer[26] = 0x77;
		tx_buffer[27] = 0x99;
		tx_buffer[28] = 0x99; // 为啥最后2个字节的数据发送不出去呢
		tx_buffer[29] = 0x99;

		tx_buffer[30] = 0xAA;
		tx_buffer[31] = 0xBB;
		tx_buffer[32] = 0xCC;
		tx_buffer[33] = 0xDD;
		tx_buffer[34] = 0xEE;
		tx_buffer[35] = 0xFF;
		tx_buffer[36] = 0x77;
		tx_buffer[37] = 0x99;
		tx_buffer[38] = 0x99; // 为啥最后2个字节的数据发送不出去呢
        // 2023-07-15 发不出去的原因是:需要在DMA中断函数中,等待USART的传输完成标志的到来才
        // 进行RS485的切换,因为DMA传输比USART快的缘故
		DMA_Cmd(DMA1_Stream6, DISABLE);
   		while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE);
    	DMA_SetCurrDataCounter(DMA1_Stream6, TX_BUFFER_SIZE);
		DMA_Cmd(DMA1_Stream6, ENABLE);

		#endif
	}
}

5.DMA发送完成一帧数据的中断函数

void DMA1_Stream6_IRQHandler(void)
{
	OSIntEnter();
    if (DMA_GetITStatus(DMA1_Stream6, DMA_IT_TCIF6) != RESET)
    {
        DMA_Cmd(DMA1_Stream6, DISABLE);
        DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);
    }
	// 这里需不需要等待串口发送完成,才切换到接收模式,因为DMA发送很快,不一定串口发送完
	// 验证了问题就出现再这里,DMA发送先完成,要等到串口发送完成才切换为接收模式
	while(!USART_GetFlagStatus(USART2, USART_FLAG_TC));
	
	TX_ENABLE = 0; //开启接收模式
	Frame_Recv_Flag = 0;// 为了接收下一帧数据,先清零
	LED3_TOG;// 每发送一帧数据完成就切换一下
	OSIntExit();
}

<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、付费专栏及课程。

余额充值