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