单片机用DMA实现不阻塞发送的printf函数

单片机用DMA实现不阻塞发送的printf函数(DMA,乒乓缓冲)


前言

在单片机里面通常为了调试方便,会把printf函数的输出重定向到串口中,具体的方法就是改写fputc函数或者_write函数(不同单片机有所不同),使其用串口来发送字符。而在我使用的单片机(沁恒微ch32v303)中,实现如下:

__attribute__((used)) int _write(int fd, char *buf, int size)
{
    int i;
    for(i = 0; i < size; i++)
    {
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
        USART_SendData(USART1, *buf++);
    }
    return size;
}

这种实现方法虽然功能没问题,但是存在一些缺点:

  1. 发送是阻塞的。会占用cpu,并且波特率越低,占用时间越长。

那么有没有方法解决这个问题呢?

众所周知,DMA的传输是不占用CPU的,但是他有个特点就是:

  1. 每次DMA传输都是要指定数据长度。

而我们printf函数每次发送的都是一个变长的数据。
那么只要我们实现了DMA的变长数据发送,我们就能够使用DMA来实现无阻塞的printf函数

接下来一步一步实现这个功能。

功能构思

  1. 首先我们需要定义一个DMA发送函数(printfDMA_Send),这个函数的主要功能就是:启动DMA传送,把指定的发送buff的指定长度数据传输到串口发送数据寄存器。
  2. 重新实现一个自己的printf函数,该函数的主要功能就是往上述的发送buff中填入数据。
    最终使用的效果大致如下:
//其他代码
printfDMA("test dma printf\r\n"); //往发送buff填入数据
//其他代码
printfDMA("test num printf:%d", 123) //网发送buff填入数据
//其他代码

/* 放到业务代码的最后 */
printfDMA_Send() //把填入发送buff的数据一次性通过DMA发送到串口数据寄存器

具体实现

DMA外设初始化配置

void ch32v30x_module_InitDMA(ch32v30x_module *self)
{
#ifndef HARDWARE_DISABLE
    DMA_InitTypeDef DMA_InitStructure = {0};
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    DMA_DeInit(DMA1_Channel2);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&USART3->DATAR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)(DebugBuff[0]);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = UART_TX_BUFF_SIZE;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel2, ENABLE);

    USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
#endif
}

定义用于DMA传输的发送数据buff

#define UART_TX_BUFF_SIZE 80
char DebugBuff[2][UART_TX_BUFF_SIZE] = {0};
char DebugIndex = 0;
char DebugTmpBuff[UART_TX_BUFF_SIZE] = {0};

UART_TX_BUFF_SIZE:这里定义了发送数据buff的长度,这个决定了一次每次DMA传输前一共能填入的字符数量,数据量越大该值越大。又或者使用小一点的数据长度,但调用DBprintfDMA_Send()的次数频繁一点。
char DebugBuff[2][UART_TX_BUFF_SIZE]:定义了一个二维数组用作乒乓缓冲,“DebugIndex”是指示当前使用的缓冲,每次发送完就切换另一个缓冲buff。使用乒乓缓冲的目的是:当DMA传输的过程中,会不断读取数据缓冲区,此时如果我们有新的数据来了如果仍填在这个缓冲区,那就会覆盖掉还未发送出去的旧数据。因此所谓“乒乓缓冲”的意义就是:一个缓冲区作为发送缓冲区,另一个缓冲区作为新数据输入的缓冲器。当一次发送完成后,两者交换,从而是的发送和新数据输入不会冲突
char DebugTmpBuff[UART_TX_BUFF_SIZE]:用于支持格式化打印的临时buff

printfDMA函数实现

#define printfDMA(...)                                                       \
    sprintf(DebugTmpBuff, __VA_ARGS__); \
    if (strlen(DebugBuff[DebugIndex]) + strlen(DebugTmpBuff) <= UART_TX_BUFF_SIZE - 1) \
    {                                                                                 \
        strcat(DebugBuff[DebugIndex], DebugTmpBuff);                                   \
    }

主要功能:把格式化字符填入到当前DebugIndex指示的缓冲区,并做数据长度检查
加入一个临时缓冲区DebugTmpBuff的目的是为了使用sprintf做格式化数据转换

printfDMA_Send函数实现

#define printfDMA_Send()                                 \
    DMA_Cmd(DMA1_Channel2, DISABLE);                     /* 关闭DMA通道 */\
    DMA1_Channel2->MADDR = (u32)DebugBuff[DebugIndex];   /* DMA源地址设置 */\
    DMA1_Channel2->CNTR = strlen(DebugBuff[DebugIndex]); /* DMA发送数据长度设置 */\
    DMA_Cmd(DMA1_Channel2, ENABLE);                      /* 启动DMA通道 */\
    DebugIndex = (DebugIndex + 1) & 0x1;                 /* 对2取余,切换缓冲区 */\
    DebugBuff[DebugIndex][0] = '\0';                     /* 清空新缓冲区。字符串首字符设为0,strlen函数就会当成长度为0的字符串 */

主要功能:把当前DebugIndex指示的乒乓缓冲区中的数据通过DMA发送出去。

效果

在M4内核带FPU的144M的mcu上做测试(stm32F4、沁恒微ch32v303等)做测试,printfDMA_Send()占用3us左右。printfDMA()函数占用的时间主要取决于要打印的字符长度(不在取决于波特率)。整体时间占用比文中开头的阻塞式打印快了很多,适合在任务周期比较短,但是要求打印大量信息的场合中使用。

待改进

1. printfDMA()中的sprintf()函数执行效率低。
如果在不需要格式化打印,只需打印纯字符串的情况下,去掉sprintf效率会提高很多。目前仍未想到有其他更好的方法可以替代sprintf()的功能。
2. 占用空间比较大
需要提前开辟一段buff来支持DMA传输,对于RAM资源比较紧张的场合并不适用

### 回答1: STM32是一款微控制器,而Printf是一种C语言的函数,用于将格式化的数据输出到标准输出设备。而DMA是直接内存访问,是一种数据传输技术。 在STM32中,使用Printf函数输出信息一般会占用大量的CPU时间,影响系统的实时性。因此,使用DMA技术可以大大减少CPU的占用率,提高系统的效率。 当通过DMA发送数据时,可以使用STM32的USART或UART模块进行数据传输。使用DMA技术,可以将需要打印的信息存储在一个缓冲区中,然后通过DMA模块将数据发送到USART或UART模块中,进行输出。这样可以让CPU更多的时间去处理其他的任务,提高系统效率。 总之,使用DMA技术可以解决Printf函数在输出信息时占用CPU时间过多的问题,提高系统效率。 ### 回答2: STM32Printf DMA是两个不同的概念,其中STM32是一款微控制器,而Printf DMA是一种数据传输技术。 STM32是一种由ST公司推出的高性能微控制器,其可广泛应用于各种嵌入式系统中。它拥有多种外设串行通信接口,可方便地与其他设备进行通信。另外,STM32还具有丰富的定时器功能,可广泛应用于各种实时控制场景。 Printf DMA是一种数据传输技术,其可将数据缓冲区中的数据通过DMA控制器传输到外设中,从而达到高效数据传输的目的。其主要优点是可以减少CPU的处理负担,加快数据传输速度,同时也减少了内存带宽的使用。 在STM32中,使用Printf DMA可以大大提高串口输出数据的效率。使用Printf DMA的方式是将数据先存储在一个缓冲区中,然后通过DMA控制器将数据传输到串口外设中。具体实现方式可以参考相关资料。 综上所述,STM32是一款高性能微控制器,而Printf DMA是一种数据传输技术,通过将其应用于STM32中,可以大大提升串口数据传输效率,减少CPU的处理负担,加快数据传输速度,提高系统可靠性和稳定性。 ### 回答3: STM32是一款非常流行的单片机,它可以用来实现各种各样的功能,包括打印输出。而在STM32中,打印输出通常会使用printf函数实现。由于printf函数需要向串口发送数据,因此在发送过程中可能会产生一些延迟,影响程序的实时性和响应性。 为了解决这个问题,可以使用DMA(直接存储器访问)技术来实现printf函数。通过DMA,可以将要发送的数据一次性传输到串口的缓冲区中,而不需要等待数据一个一个地发送,从而提高了效率和实时性。 具体来说,实现STM32 DMA printf的方法如下: 1. 配置USART或UART串口 2. 配置DMA传输通道 3. 打开DMA传输通道 4. 在程序中使用printf函数 5. 等待DMA传输完成 需要注意的是,在使用DMA进行printf输出时,需要将printf的输出定向到串口,否则数据无法发送到串口,达不到预期的效果。 总的来说,STM32 DMA printf技术可以有效地提高程序的实时性和响应性,特别是在发送大量数据时,使用DMA可以显著地减少延迟。因此,在实际应用中,可以根据需要选择是否使用DMA实现printf函数
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值