STM32串口printf通过DMA打印(含实测代码)

由于printf映射到串口后,打印耗时较久,特别是波特率低又有较频繁的打印时,严重影响主程序执行效率。
STM32串口具有DMA功能,可以直接从RAM搬运数据到USART,解放了CPU。因此只要实现printf打印到RAM,再从RAM用DMA搬运到UASRT即可。
为实现这种方法,需要构造一个缓存区。具体代码如下(已在SMT32F103C8T6实测验证):


//*****************自定义printf函数,通过dma实现串口1打印输出******************//
#define PRINTF_BUFFSIZE 50

typedef struct{
	volatile uint8_t ms_counter;
	uint8_t falg[6];//1:对应数组有数据需要发送;0:对应数组不需要发送
	uint8_t dma_data[PRINTF_BUFFSIZE];//DMA发送地址	
	uint8_t data[6][PRINTF_BUFFSIZE];//定义6*50缓存区,6个50字节数组,根据打印内容长短,缓存大小可调整
	
}Buff;

Buff txbuff;

//先打印到缓存里
void my_printf_dma(const char *fmt, ...)
{
	static uint8_t i = 0;	
    va_list args;
    va_start(args, fmt);
	memset(&txbuff.data[i][0], 0, PRINTF_BUFFSIZE);
    vsnprintf((char*)&txbuff.data[i][0], PRINTF_BUFFSIZE, fmt, args);
	txbuff.falg[i] = 1;
    va_end(args);
	i++;
	if(i>=6)
	{
		i = 0;		
	}
}
//按序查找有效的需要DMA发送打印的数组
int8_t my_find_data_id(void)
{
	static uint8_t i = 0;
	uint8_t tmp = i;	
    int16_t res;
//	printf("tmp: %d\r\n",tmp);
	for(;i<6;i++)
	{
		if(txbuff.falg[i]==1)
        {
         //   printf("txbuff.falg[%d]: %d\r\n",i,txbuff.falg[i]);
			break;
        }
	}
	if(i>=6)//刚才找到末尾也未找到需要发送的数组,从头开始找
	{
		i = 0;
		for(;i<tmp;i++)
		{
			if(txbuff.falg[i]==1)
				break;
		}
		if(i>=tmp)//还是未找到
		{
            res = -1;
		}
		else//找到了
		{
            res = i;      
		}
	}
	else
	{ 
          res = i;      
	}
    i++;
    if(i>=6)
    {
         i=0;
    }
    return res;
}


void my_buff_2_usart(void)
{
	if(txbuff.ms_counter <2)//每隔2ms检查一下有没有需要打印的内容
	{
		return;	
	}

	txbuff.ms_counter = 0;
	
	if(HAL_UART_GetState(&huart1) != HAL_UART_STATE_READY)
	{
		return;//串口还未打印完
	}	
		
	int8_t i = my_find_data_id();
	if(i>=0)
	{
		memset( &txbuff.dma_data[0], 0, PRINTF_BUFFSIZE);
		memcpy( &txbuff.dma_data[0], &txbuff.data[i][0],strlen((const char*)(&txbuff.data[i][0])));
		HAL_UART_Transmit_DMA(&huart1, &txbuff.dma_data[0], strlen((const char*)&txbuff.dma_data[0]));
		txbuff.falg[i]=0;	
	}
}

//放到ms定时器中断服务函数里
void my_printf_time_service(void)
{
	txbuff.ms_counter++;
}
<think>好的,我现在需要帮助用户了解如何在STM32中使用DMA进行printf操作。首先,我得回忆一下用户提供的引用内容,特别是关于DMA配置的部分。引用[2]中提到,DMA可以从USART2的DR寄存器搬运数据到数组RxBuffer,这样CPU不需要参与数据传输。而引用[3]则提到DMA自动处理数据采样,不需要主动操作,只需从变量地址调取数据。 用户的问题是如何将DMAprintf结合使用。常规的printf通常使用轮询或中断方式发送数据,但DMA可以更高效地处理数据传输,减少CPU负担。我需要考虑如何重定向printf到USART,并利用DMA传输。 首先,需要确认STM32的USART配置,是否支持DMA传输。根据引用[2],USART2与DMA配合使用时,DMA的外设地址设置为USART2->DR,内存地址则是目标缓冲区。同样,发送数据的话,应该将内存中的数据通过DMA传输到USART的DR寄存器。 接下来,重定向printf函数通常需要重写_write或fputc函数,将输出指向USART的发送函数。但这里需要结合DMA,所以可能需要修改发送函数,使其使用DMA传输而不是轮询或中断。 然后,考虑如何管理缓冲区。当调用printf时,数据会被写入缓冲区,然后触发DMA传输。需要注意的是,DMA传输是异步的,所以需要确保前一次传输完成后再启动新的传输,或者使用双缓冲区机制避免数据覆盖。 另外,引用[3]中提到DMA自动搬运数据,用户只需从变量地址读取。但这里是发送数据,所以需要将数据从内存(如数组)搬运到USART的发送寄存器。DMA的配置方向应该设置为从内存到外设,即DMA_DIR_PeripheralDST。 可能还需要处理传输完成中断,以便在DMA传输完成后进行后续操作,比如释放缓冲区或通知系统可以发送下一个数据包。 最后,需要提供一个代码示例,展示如何配置USART、DMA,并重定向printf。例如,初始化USART和DMA通道,重写fputc函数,使用DMA发送数据,并处理传输完成中断。 需要检查是否存在潜在问题,比如缓冲区溢出、DMA传输未完成时再次调用printf导致的数据冲突,以及如何确保所有字符都被正确发送。此外,代码示例中的配置参数需要根据具体STM32型号和使用的USART进行调整,比如DMA通道、USART时钟使能等。</think>### STM32中使用DMA实现printf操作的实现步骤 #### 1. 基本原理 通过DMA将内存中的字符串数据直接传输到USART外设的发送寄存器(DR),替代传统的轮询或中断方式。这种方式能显著降低CPU占用率,特别适合高频率、大数据量的串口通信场景[^2][^3]。 #### 2. 核心配置步骤 ```c // 配置USART2 USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_Mode = USART_Mode_Tx; // 仅开启发送模式 USART_Init(USART2, &USART_InitStructure); // 配置DMA通道 DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR; // 目标地址为串口数据寄存器 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer; // 源地址为内存缓冲区 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设方向 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; // 缓冲区大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式 DMA_Init(DMA1_Channel7, &DMA_InitStructure); // 根据实际硬件选择通道 ``` #### 3. printf重定向实现 ```c // 定义发送缓冲区和状态标志 #define TX_BUFFER_SIZE 128 volatile uint8_t dma_tx_complete = 1; char tx_buffer[TX_BUFFER_SIZE]; // 重写fputc函数 int fputc(int ch, FILE *f) { static uint16_t index = 0; if(dma_tx_complete) { // 等待上次传输完成 tx_buffer[index++] = ch; if(ch == '\n' || index >= TX_BUFFER_SIZE) { DMA_Cmd(DMA1_Channel7, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel7, index); DMA_Cmd(DMA1_Channel7, ENABLE); USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); // 开启DMA请求 index = 0; dma_tx_complete = 0; } } return ch; } // DMA传输完成中断处理 void DMA1_Channel7_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC7)) { DMA_ClearITPendingBit(DMA1_IT_TC7); dma_tx_complete = 1; // 设置传输完成标志 } } ``` #### 4. 关键优化点 - **双缓冲机制**:建议使用`tx_buffer[2][TX_BUFFER_SIZE]`交替切换缓冲区,避免数据覆盖 - **超时检测**:添加Watchdog定时器监控传输超时 - **流量控制**:配合硬件流控(RTS/CTS)防止数据丢失 - **内存对齐**:使用`__attribute__((aligned(4)))`优化DMA访问效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ydgd118

您的鼓励是我最大的动力!谢赏!

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

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

打赏作者

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

抵扣说明:

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

余额充值