stm32使用DMA实现串口发送printf函数

        相信学过C语言的小伙伴对printf函数都不陌生,这个函数确实非常好用,在咱们stm32里面也可以通过重定向使用printf函数。笔者在用stm32做数据采集的时候遇到一个问题,每隔10ms使用printf向上位机发送数据,由于高频率发送数据,导致stm32软定时出现不准确的问题。

        基于上述问题,笔者向通过DMA的方式实现数据串口发送,从而解放CPU去处理其他任务;并且想通过DMA实现像printf一样方便的函数接口,为了区分stdio.h的printf函数,笔者实现的函数命名为print。下面是代码实现过程:

bsp_usart.h如下:

#ifndef __USART_H
#define	__USART_H


#include <stdio.h>
#include <stdarg.h>
#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_dma.h"


#define buffer_size  512


extern char DMA_Send_Buffer[];
	
// USART1
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO ?????
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler

void USART_Configration(void);
void Uart_Send_DMA_Config(void);
void print(char *fmt, ...);


#endif /* __USART_H */

bsp_usart.c如下:

#include "bsp_usart.h"


char DMA_Send_Buffer[buffer_size] = {0};

void USART_Configration( void )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
 
    DEBUG_USART_GPIO_APBxClkCmd( DEBUG_USART_GPIO_CLK, ENABLE );
 
    DEBUG_USART_APBxClkCmd( DEBUG_USART_CLK, ENABLE );
 
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure );
 
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure );
 
    USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
    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_Init( DEBUG_USARTx, &USART_InitStructure );
    USART_Cmd( DEBUG_USARTx, ENABLE );
		
		USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}


void Uart_Send_DMA_Config(void)
{
	DMA_InitTypeDef DMA_InitStructure;

	/* Enable DMA clock */
	/* Configure DMA Stream */
	
	RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1,  ENABLE);
 
	DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t) DMA_Send_Buffer; // 存储要发送的数据的地址
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(DEBUG_USARTx->DR);	 // 向串口寄存器搬运数据
	DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralDST;
	
	DMA_InitStructure.DMA_BufferSize         = buffer_size;								 // 内存大小
	DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;			 // 内存地址自增
	DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;  // 外设地址禁止自增
	DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;
	DMA_InitStructure.DMA_PeripheralDataSize = 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 );												 // USART1->TX对应DMA1通道4
	DMA_ClearFlag( DMA1_FLAG_GL4);
    DMA_Cmd(DMA1_Channel4 , ENABLE);


}



void print(char *fmt, ...){
	DMA_Cmd(DMA1_Channel4 , DISABLE);	// 先失能DMA通道4
	va_list args;	// 记录输入的参数
    uint16_t length; // 用于记录字符串长度
	va_start(args, fmt); // 对字符串进行转换
	length = vsnprintf(DMA_Send_Buffer, sizeof(DMA_Send_Buffer) - 1, fmt, args); // 开始对DMA_Send_Buffer赋值,并返回字符串长度
	if(length > buffer_size-1) length = buffer_size-1; // 防止超出数组界限
	DMA_SetCurrDataCounter(DMA1_Channel4, length); // 设置要发送数据的长度
	DMA_Cmd(DMA1_Channel4 , ENABLE);	// 使能DMA通道4,开始搬运数据
}

        以上便是使用DMA实现的print函数,可以实现与printf函数相同的功能,并且笔者亲自测试了性能(确实能够解放CPU),也解决了上述高频发送数据导致的软定时器不准确问题。当然代码以及注释都是笔者个人的一点理解,有不对的地方欢迎大家评论区批评指正。

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用STM32CubeMX配置DMA实现串口通信中的printf功能。下面是一个示例代码,可以将printf输出通过DMA发送串口: 1. 首先,确保你已经在STM32CubeMX中启用了相关的USART和DMA功能,并正确配置了串口通信的参数。 2. 在生成的代码中找到USART相关的初始化函数,一般位于`usart.c`文件中。在该函数中,找到`HAL_UART_MspInit()`函数实现。 3. 在`HAL_UART_MspInit()`函数的开头添加以下代码,用于初始化DMA传输相关的资源: ```c /* DMA interrupt init */ /* DMA1_Channel4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); ``` 4. 接下来,在`HAL_UART_MspInit()`函数中添加以下代码,用于配置DMA传输: ```c /* Enable the DMA transfer for transmit */ __HAL_RCC_DMA1_CLK_ENABLE(); hdma_usart2_tx.Instance = DMA1_Channel4; hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode = DMA_NORMAL; hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) { Error_Handler(); } /* Associate the initialized DMA handle to the UART handle */ __HAL_LINKDMA(huart, hdmatx, hdma_usart2_tx); /* Enable the DMA transfer complete interrupt */ __HAL_UART_ENABLE_IT(huart, UART_IT_TC); ``` 注意:上述代码中,使用的是USART2和DMA1_Channel4,你需要根据实际情况进行调整。 5. 在生成的代码中找到`USART_Transmit()`函数,一般位于`usart.c`文件中。在该函数中添加以下代码,用于启动DMA传输: ```c /* Set the DMA transfer complete callback */ huart->hdmatx->XferCpltCallback = UART_DMA_TransmitCplt; /* Start the DMA transfer */ HAL_DMA_Start_IT(huart->hdmatx, (uint32_t)str, strlen(str)); ``` 6. 最后,在生成的代码中找到`USART_IRQHandler()`函数,一般位于`stm32f4xx_it.c`文件中。在该函数中添加以下代码,用于处理DMA传输完成的中断: ```c /* USART2 DMA Transmit Complete Interrupt */ void DMA1_Channel4_IRQHandler(void) { /* Check the DMA interrupt flag */ if (__HAL_DMA_GET_IT_SOURCE(huart2.hdmatx, DMA_IT_TC) != RESET) { /* Clear the DMA interrupt flag */ __HAL_DMA_CLEAR_FLAG(huart2.hdmatx, DMA_IT_TC); /* Disable the DMA transfer complete interrupt */ __HAL_DMA_DISABLE_IT(huart2.hdmatx, DMA_IT_TC); /* Call the DMA transmit complete callback function */ HAL_UART_TxCpltCallback(&huart2); } } ``` 完成以上步骤后,你就可以在代码中使用标准的`printf`函数,输出的内容会通过DMA传输到串口中。 希望这对你有帮助!如有更多问题,请继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值