STM32——采用DMA的方式实现串口收发数据

本文详细介绍了如何在STM32上利用DMA进行串口数据的发送与接收,通过具体的代码实现,展示了DMA在USART上的应用,有效减轻了MCU的处理负担。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32——采用DMA的方式实现串口收发数据


概述

想必看到这篇博客的你已经知道了DMA的好处了吧,所以这儿就不过多地讲述DMA对于缓解MCU压力有多么重要的用途,DMA在很多方面都可以使用,如IIC,SPI,USART等,这儿主要给出DMA在USART上面的一个实例。


代码实现

主要代码直接在一个程序中实现


全局变量以及宏定义

#define DEFAULT_BAUD 115200
#define UART_RX_LEN		128

/*串口接收DMA缓存*/
uint8_t Uart_Rx[UART_RX_LEN] = {0};

/*串口发送DMA缓存*/
uint8_t Uart_Send_Buffer[100]={0};

uint8_t Data_Receive_Usart=0;

DMA和USART的初始化的函数

void usart_dma_init(void)
{
    GPIO_InitTypeDef	GPIO_InitStructure;
    USART_InitTypeDef	USART_InitStructure;
    DMA_InitTypeDef		DMA_InitStructure;
    NVIC_InitTypeDef	NVIC_InitStructure;


	
    /*  配置GPIO的模式和IO口 */	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);  
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;					//TX
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;			//复用推挽输出
    GPIO_Init(GPIOA,&GPIO_InitStructure);					//初始化串口输入IO
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;				//RX
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;		//模拟输入
    GPIO_Init(GPIOA,&GPIO_InitStructure); 	


	
    /*初始化串口接收和发送函数*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE);
    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_Rx | USART_Mode_Tx;    
    USART_InitStructure.USART_BaudRate = DEFAULT_BAUD; 
	
    /*初始化串口*/
    USART_Init(USART1,&USART_InitStructure);
	
    /*中断配置*/
    USART_ITConfig(USART1,USART_IT_TC,DISABLE);
    USART_ITConfig(USART1,USART_IT_RXNE,DISABLE);
    USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); 
	
    //配置UART1中断  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;              //通道设置为串口1中断  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;      //中断占先等级0  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;             //中断响应优先级0  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //打开中断  
    NVIC_Init(&NVIC_InitStructure);



    /*DMA发送中断设置*/
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
	
	
    /*DMA1通道4配置发送*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel4);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart_Send_Buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = 100;
    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_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4,&DMA_InitStructure);
    DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);
    //DMA_Cmd(DMA1_Channel4, ENABLE);//使能通道4,一般发送的时候再使能
 
 

    /*DMA1通道5配置接收*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel5);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)Uart_Rx;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = UART_RX_LEN;
    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_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel5,&DMA_InitStructure);
 
    /*使能通道5*/
    DMA_Cmd(DMA1_Channel5,ENABLE);

 
        
    //采用DMA方式发送
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
	
    //采用DMA方式接收
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
	
    //启动串口  
    USART_Cmd(USART1, ENABLE);
}

DMA发送使能函数

/**@ brief 使能发送数据
 *
 * 启动DMA数据发送功能
 * 
 * @param size表示需要发送的DMA中数据的个数
 */
void uart_dma_send_enable(uint16_t size)
{
    DMA1_Channel4->CNDTR = (uint16_t)size; 
    DMA_Cmd(DMA1_Channel4, ENABLE);       
}	

串口接收的中断函数

/**@ brief串口1接收中断
 *
 * 收到一帧数据进入一次,进行DMA的读取
 * 
 */
void USART1_IRQHandler(void)                               
{   
    uint32_t temp = 0;
    uint16_t i = 0;
	
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
    {
        //USART_ClearFlag(USART1,USART_IT_IDLE);
        temp = USART1->SR;
        temp = USART1->DR; //清USART_IT_IDLE标志
		
        DMA_Cmd(DMA1_Channel5,DISABLE);
 
        temp = UART_RX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);
        for (i = 0;i < temp;i++)
        {
            Data_Receive_Usart = Uart_Rx[i];
			
            //+++对收到的数据加一后回发出去		
            Uart_Send_Buffer[i]=Data_Receive_Usart+1;						
            uart_dma_send_enable(temp);
            //+++			
        }
		
        //设置传输数据长度
        DMA_SetCurrDataCounter(DMA1_Channel5,UART_RX_LEN);
		
        //打开DMA
        DMA_Cmd(DMA1_Channel5,ENABLE);
    } 
} 

DMA发送中断

/**@ brief DMA发送中断
 *
 * 发送数据,将DMA中的数据发送出去
 * 
 */
void DMA1_Channel4_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_FLAG_TC4)==SET)
    {
        DMA_ClearFlag(DMA1_FLAG_GL4);        
        DMA_Cmd(DMA1_Channel4, DISABLE);  
    }
}

到此为止主要代码就已经结束了,基本上能够满足测试要求


项目工程下载

串口1的DMA实现 点击下载

串口2的DMA实现 点击下载

串口3的DMA实现 点击下载

### STM32 使用 DMA 进行 UART 数据收发 #### 初始化设置 为了实现基于DMA的UART通信,在初始化阶段需完成几个重要步骤。首先,要确保正确配置了串口参数以及DMA控制器的相关属性。这通常涉及到指定波特率、数据位数、停止位等基本通讯参数[^1]。 对于DMA的具体启用操作,则是在相应的寄存器中设定特定标志来激活该功能。例如,通过设置`USART1_CR3`中的`DMAT`位可以开启发送方向上的DMA支持;而对于接收端来说,同样需要在相同位置找到对应的控制选项并加以设置[^2]。 ```c // 配置USART1_CR3使能DMA传输模式 USART1->CR3 |= USART_CR3_DMAT; ``` #### 开启DMA服务函数 一旦硬件层面准备完毕之后,就需要调用HAL库提供的API接口来进行实际的数据交换过程管理。比如,利用`HAL_UART_Receive_DMA()`方法启动一次性的DMA读取动作,它接受三个参数——指向UART句柄结构体指针、目标缓冲区地址以及预期接收到的最大字节数量: ```c HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); ``` 此命令执行后,DMA引擎将会自动处理后续所有的数据运工作而无需CPU频繁介入,直到预设数量的信息被成功获取为止。 另外,如果希望采用DMA方式发送数据出去的话,也可以借助类似的API如`HAL_UART_Transmit_DMA()`, 它们的工作原理相似,只是作用对象变成了输出流而非输入流。 #### 中断与回调机制 尽管DMA本身能够减少处理器负担,但在某些情况下仍然可能需要用到中断机制来响应特殊事件的发生,比如当整个传输完成后触发通知以便应用程序知晓当前状态变化。因此,在项目开发过程中往往还需要适当调整中断优先级,并编写合适的ISR(Interrupt Service Routine)或者注册自定义的回调函数用于捕捉这些信号[^3]。 #### 实际应用案例分析 考虑到不同应用场景下需求各异,这里给出一段简单的代码片段作为参考实例,展示了如何在一个典型的嵌入式系统环境中运用上述理论知识构建起完整的DMA-UART交互流程: ```c #include "main.h" UART_HandleTypeDef huart1; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); int main(void){ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); // 设置接收缓存数组大小为12个字符长度 uint8_t buff_receive[12]; // 打开DMA接收数据的功能 HAL_UART_Receive_DMA(&huart1,buff_receive,sizeof(buff_receive)); while (1){} } /** * @brief This function handles DMA stream transfer complete interrupt. */ void DMA2_Stream7_IRQHandler(void){ HAL_DMA_IRQHandler(huart1.hdmarx); } ``` 这段程序实现STM32单片机上通过DMA技术高效地从外部设备那里抓取消息到内部存储空间内的目的,同时也体现了良好的编程实践风格,包括但不限于合理的模块划分、清晰的任务分工等方面[^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iot 小胡

从未指望过会有人打赏...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值