STM32 LL库 串口DMA发送接收配置教程

本文介绍了如何使用STM32CubeMX配置STM32F407VGT6的串口和DMA,实现115200波特率的串口通信。通过配置DMA2的STREAM2和STREAM7进行串口接收和发送,同时启用中断处理发送完成和空闲中断。发送功能通过设置内存地址和数据长度,然后使能DMAStream发送数据。接收功能在空闲中断中获取接收数据的长度。

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

STM32 LL库 串口DMA发送 接收配置教程

STM32CubeMX配置

  1. 配置时钟树在这里插入图片描述
    2.我这个是STM32F407VGT6 所以使用168MHZ主频在这里插入图片描述
  2. 串口配置

波特率采用默认的115200,都不做修改在这里插入图片描述

  1. DMA配置
    配置DMA发送和接收,接收可根据自己需要设置成正常模式和循环模式

    别忘了开中断
    在这里插入图片描述
    至此STM32CubeMX配置完成

代码初始化

STM32CubeMX初始化完成后,配置的代码我们不用管,接下来写发送接收配置

  1. 首先自己封装个函数用于初始化串口
void Debug_Init(void)//我这里就叫 Debug_Init 可以自己设置
{
}
  1. 然后调用STM32CubeMX配置的函数
void Debug_Init(void)//我这里就叫 Debug_Init 可以自己设置
{
   MX_USART1_UART_Init();//硬件初始化
}
  1. 然后进行以下配置
void Debug_Init(void)
{
    MX_USART1_UART_Init();//硬件初始化
    
    //配置串口方向,我这里是发送和接收都要
    LL_USART_SetTransferDirection(USART1,LL_USART_DIRECTION_TX_RX);

    /*接收配置*/
    //使能空闲中断
    LL_USART_EnableIT_IDLE(USART1);
    //设置内存地址,也就是设置接收的数据要放哪
    LL_DMA_SetMemoryAddress(DMA2,LL_DMA_STREAM_2,(uint32_t)buf);
    //设置接收最大数据长度
    LL_DMA_SetDataLength(DMA2,LL_DMA_STREAM_2,MAX_SIZE);
    //设置外设地址,也就是设置串口的数据寄存器
    LL_DMA_SetPeriphAddress(DMA2,LL_DMA_STREAM_2,(uint32_t)&USART1->DR);
	//使能DMA2发送完成中断
    LL_DMA_EnableIT_TC(DMA2,LL_DMA_STREAM_2);
    //使能接收的DMA2 STREAM_2
    LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_2);
    //使能DMA串口接收
    LL_USART_EnableDMAReq_RX(USART1);
    
    /*发送配置*/
    //设置外设地址,因为这个是DMA串口发送,
    LL_DMA_SetPeriphAddress(DMA2,LL_DMA_STREAM_7,(uint32_t)&USART1->DR);
    //使能DMA串口发送
    LL_USART_EnableDMAReq_TX(USART1);
    //使能DMA2发送完成中断
    LL_DMA_EnableIT_TC(DMA2,LL_DMA_STREAM_7);
}

!!!注意!!!
以上代码配置中的 DMA2,LL_DMA_STREAM_2,LL_DMA_STREAM_7,是根据STM32CubeMX设置的,移植的时候注意一下自己的配置

以上配置中我特意没写使能发送的 DMA_Stream,我们按下不表

我这里
USART1_RX 对应DMA2,DMA_STREAM_2
USART1_TX 对应DMA2,DMA_STREAM_7
在这里插入图片描述
以上配置完成,接下来编写DMA串口发送接收功能

发送功能

发送功能很简单,因为配置中设置了发送的外设固定为USART1->DR

  //设置外设地址,因为这个是DMA串口发送,
    LL_DMA_SetPeriphAddress(DMA2,LL_DMA_STREAM_7,(uint32_t)&USART1->DR);

所以我们只需要配置内存地址,数据长度再使能DMA_Stream 即可发送
如下

void UART_DMA_Send(USART_TypeDef *USARTx,uint8_t *str,uint16_t size)
{
	//配置内存地址
    LL_DMA_SetMemoryAddress(DMA2,LL_DMA_STREAM_7,(uint32_t)str);
    //配置数据长度
    LL_DMA_SetDataLength(DMA2,LL_DMA_STREAM_7,size);
    //使能DMA STREAM  也就是发送数据
    LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7);
}

为什么我要在发送中才设置 LL_DMA_EnableStream?

首先我们先看一下这个函数

__STATIC_INLINE void LL_DMA_EnableStream(DMA_TypeDef *DMAx, uint32_t Stream)
{
  SET_BIT(((DMA_Stream_TypeDef *)((uint32_t)((uint32_t)DMAx + STREAM_OFFSET_TAB[Stream])))->CR, DMA_SxCR_EN);
}

很明显,我们可以看到这个函数的作用是配置DMA_SxCR_EN,我们查看手册
在这里插入图片描述
这是一个使能DMA通道的,那这个和发送有啥关系呢,接收都可以在配置的时候使能,我们继续查看手册在这里插入图片描述
在这里插入图片描述

我们可以看到当DMA_SxCR_EN=1时数据长度寄存器,外设地址寄存器,内存地址寄存器均为只读状态,所以LL_DMA_EnableStream()这个函数我们只有在全部设置好的时候使用即可发送数据,那新的问题又出现了,我们在这里设置了DMA_SxCR_EN=1,这个又不会自动置0那应该怎么办呢?
其实我们在配置发送的时候设置了DMA发送中断

    //使能DMA2发送完成中断
    LL_DMA_EnableIT_TC(DMA2,LL_DMA_STREAM_7);

那我们可以在中断里面写一个函数用于置0,如下

void DMA2_Stream7_IRQHandler(void)
{
    if(LL_DMA_IsActiveFlag_TC7(DMA2))
    {
    	//清除Stream7 TC中断
        LL_DMA_ClearFlag_TC7(DMA2);
        //关闭DMA2 Stream7
        LL_DMA_DisableStream(DMA2,LL_DMA_STREAM_7);
    }
}

其中LL_DMA_IsActiveFlag_TC7 中的TC7的7指的是Stream7,所以大家要根据自己的实际情况修改

最后我对这个发送函数做一个封装

int uart_printf(USART_TypeDef *USARTx,const char *fmt, ...)
{
    va_list arg;
    int cnt;
    char buffer[FIFO_MAX_SIZE];

    va_start(arg,fmt);
    cnt=vsprintf(buffer,fmt,arg);
    va_end(arg);
	
	//这里使用了strlen 记得声明string.h
    UART_DMA_Send(USARTx,(uint8_t*)buffer,strlen(buffer));

    return cnt;
}

接收功能

接收功能如下所示

void USART1_IRQHandler(void)
{
    if(LL_USART_IsActiveFlag_IDLE(USART1))
    {
    	//清除空闲中断
        LL_USART_ClearFlag_IDLE(USART1);
        //得到接收数据的长度LL_DMA_GetDataLength(DMA2, LL_DMA_STREAM_2);得到的是剩余的内存大小
        rx_len =MAX_SZIE - LL_DMA_GetDataLength(DMA2, LL_DMA_STREAM_2);
    }
}

至此,STM32 LL库 DMA串口收发到此完成

### STM32 LL实现 `printf` 函数 在STM32微控制器中,通过LL可以轻松实现标准输入/输出功能,例如使用`printf`函数。以下是具体方法以及相关说明。 #### 1. USART初始化 为了使`printf`能够正常工作,需要先配置USART外设并将其与DMA关联起来。这可以通过调用LL中的API来完成。以下是一个简单的USART初始化示例: ```c #include "stm32f4xx_ll_usart.h" #include "stm32f4xx_ll_rcc.h" #include "stm32f4xx_ll_bus.h" void UART_Init(void) { /* 启用GPIOA和USART2时钟 */ LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2); LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); /* 配置PA2作为TX引脚 */ LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_2, LL_GPIO_MODE_ALTERNATE); LL_GPIO_SetAFPin_0_7(GPIOA, LL_GPIO_PIN_2, LL_GPIO_AF_7); /* 初始化USART参数 */ LL_USART_ConfigBaudRate(USART2, SystemCoreClock, LL_USART_BAUDRATEPRESCALER_DIV1, 9600); // 设置波特率为9600 LL_USART_SetDataWidth(USART2, LL_USART_DATAWIDTH_8B); LL_USART_SetStopBitsLength(USART2, LL_USART_STOPBITS_1); LL_USART_SetParity(USART2, LL_USART_PARITY_NONE); LL_USART_SetTransferDirection(USART2, LL_USART_DIRECTION_TX); /* 启动USART */ LL_USART_Enable(USART2); } ``` 上述代码实现了基本的USART初始化过程[^1]。 --- #### 2. 重定向`printf`到USART 为了让`printf`能够在串口上打印数据,需重新定义`_write`函数(这是ISO C标准的一部分),该函数负责处理字符流的实际写入操作。下面展示了一个典型的实现方式: ```c #include <sys/stat.h> #include <sys/types.h> int _write(int file, char *ptr, int len) { if (file != STDOUT_FILENO && file != STDERR_FILENO) { errno = EBADF; return -1; } for (int i = 0; i < len; ++i) { while (!LL_USART_IsActiveFlag_TXE(USART2)); // 等待发送缓冲区为空 LL_USART_TransmitData8(USART2, ptr[i]); // 发送单字节数据 } return len; } ``` 此部分代码将标准输出重定向至USART接口[^2]。 --- #### 3. 支持浮点数格式化输出 默认情况下,在STM32CubeIDE环境中编译器不会启用完整的浮点支持,因此如果尝试直接使用带浮点参数的`printf`可能会失败。解决办法是在链接选项中加入如下标志: - **Linker Flags**: `-u _printf_float` 这样即可激活新libgcc中针对浮点运算的支持。 --- #### 完整测试程序 最后提供一个完整的测试案例供参考: ```c #include <stdio.h> #include "uart_init.h" // 假定已创建UART初始化头文件 int main(void) { HAL_Init(); // 如果适用HAL框架则保留这一句;否则忽略它 UART_Init(); printf("Hello World! This is a test.\n"); float pi = 3.1415926; printf("The value of Pi is approximately %.6f\n", pi); while (1) {} } // 别忘了添加_write函数定义... ``` 以上即为基于LL构建`printf`功能的整体流程[^2]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值