如何利用DMA在STM32中实现串口发送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),也解决了上述高频发送数据导致的软定时器不准确问题。当然代码以及注释都是笔者个人的一点理解,有不对的地方欢迎大家评论区批评指正。

作者:火热心 阳

物联沃分享整理
物联沃-IOTWORD物联网 » 如何利用DMA在STM32中实现串口发送printf函数

发表回复