项目场景:

        使用RT-Thread操作系统的串口设备 DMA 接收回环使用例程;


问题描述

        使用RT-Thread操作系统的串口设备 DMA 接收使用例程时,串口仅能实现发送功能,而无法接收显示,多次接收后在shell终端触发警告:

msh >[W/UART] Warning: There is no enough buffer for saving data, please increase the RT_SERIAL_RB_BUFSZ option.
图1        串口助手

         串口助手能够接收到开发板初始发送的 "hello RT-Thread!"

        但是串口助手发送给开发板的字符串没有回传;

        连续多次发送后,shell命令端发出警告:

图2        命令终端

原因分析:

        这个警告表示用于存储串口接收数据的缓冲区(ring buffer)大小不够。当使用串口接收数据并且开启了某些接收机制(如 DMA 接收)时,如果接收数据的速度较快或者数据量较大,现有的缓冲区大小RT_SERIAL_RB_BUFSZ可能无法容纳所有接收到的数据,从而导致这个警告。


解决方案:

        通过询问 AI ,找到以下方法能够解决该问题:

🔅优化数据接收处理速度

  • 如果数据接收处理速度过慢,可能会导致缓冲区中的数据来不及被处理而溢出。可以尝试优化接收处理函数,使其能够更快速地处理接收到的数据。例如,减少在接收处理函数中的复杂计算、延时操作等。
  • 可以采用中断方式及时处理接收的数据。在串口接收中断服务程序中,只进行必要的数据搬运等操作,将主要的数据处理工作放在中断外部的线程中进行,以提高接收效率。

👆👆👆上面是 AI 的回答👆👆👆

        基于此,发现原例程代码在串口回调函数中,使用的消息队列花费时间过长,导致缓冲区中的数据来不及被处理而溢出;

         🔴原例程代码:

引用自UART设备

/*
 * 程序清单:这是一个串口设备 DMA 接收使用例程
 * 例程导出了 uart_dma_sample 命令到控制终端
 * 命令调用格式:uart_dma_sample uart3
 * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
 * 程序功能:通过串口输出字符串"hello RT-Thread!",并通过串口输出接收到的数据,然后打印接收到的数据。
*/

#include <rtthread.h>

#define SAMPLE_UART_NAME       "uart3"      /* 串口设备名称 */

/* 串口接收消息结构*/
struct rx_msg
{
    rt_device_t dev;
    rt_size_t size;
};
/* 串口设备句柄 */
static rt_device_t serial;
/* 消息队列控制块 */
static struct rt_messagequeue rx_mq;

/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    struct rx_msg msg;
    rt_err_t result;
    msg.dev = dev;
    msg.size = size;

    result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
    if ( result == -RT_EFULL)
    {
        /* 消息队列满 */
        rt_kprintf("message queue full!\n");
    }
    return result;
}

static void serial_thread_entry(void *parameter)
{
    struct rx_msg msg;
    rt_err_t result;
    rt_uint32_t rx_length;
    static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];

    while (1)
    {
        rt_memset(&msg, 0, sizeof(msg));
        /* 从消息队列中读取消息*/
        result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
        if (result > 0)
        {
            /* 从串口读取数据*/
            rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
            rx_buffer[rx_length] = '\0';
            /* 通过串口设备 serial 输出读取到的消息 */
            rt_device_write(serial, 0, rx_buffer, rx_length);
            /* 打印数据 */
            rt_kprintf("%s\n",rx_buffer);
        }
    }
}

static int uart_dma_sample(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];
    static char msg_pool[256];
    char str[] = "hello RT-Thread!\r\n";

    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    }

    /* 查找串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }

    /* 初始化消息队列 */
    rt_mq_init(&rx_mq, "rx_mq",
               msg_pool,                 /* 存放消息的缓冲区 */
               sizeof(struct rx_msg),    /* 一条消息的最大长度 */
               sizeof(msg_pool),         /* 存放消息的缓冲区大小 */
               RT_IPC_FLAG_FIFO);        /* 如果有多个线程等待,按照先来先得到的方法分配消息 */

    /* 以 DMA 接收及轮询发送方式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_input);
    /* 发送字符串 */
    rt_device_write(serial, 0, str, (sizeof(str) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }

    return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample);

        由于消息队列花费时间过长,这里考虑修改为信号量,通过信号量替代原本消息队列的逻辑,最终实现串口 DMA 接收回环;

        修改代码如下:

/*
 * 程序清单:这是一个串口设备 DMA 接收使用例程
 * 例程导出了 uart_dma_sample 命令到控制终端
 * 命令调用格式:uart_dma_sample uart3
 * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
 * 程序功能:通过串口输出字符串"hello RT-Thread!",并通过串口输出接收到的数据,然后打印接收到的数据。
*/

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#define DBG_LVL         DBG_LOG
#include <rtdbg.h>
#define PIN_LED_B              GET_PIN(F, 11)      // PF11 :  LED_B        --> LED
#define PIN_LED_R              GET_PIN(F, 12)      // PF12 :  LED_R        --> LED
#define SAMPLE_UART_NAME       "uart3"      /* 串口设备名称 */

/* 串口接收消息结构*/
struct rx_msg
{
    rt_device_t dev;
    rt_size_t size;
};
/* 串口设备句柄 */
static rt_device_t serial;

rt_uint32_t rx_length;
static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
/* 信号量 */
static struct rt_semaphore rx_sem;
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
    rt_err_t result = RT_EOK;
    rx_length = rt_device_read(dev, 0, rx_buffer, size);
    rt_sem_release(&rx_sem);
    return result;
}

static void serial_thread_entry(void *parameter)
{
    while (1)
    {
        rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
        rt_device_write(serial, 0, rx_buffer, rx_length);
    }
}

static int uart_dma_sample(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX];
    static char msg_pool[256];
    char str[] = "hello RT-Thread!\r\n";
    rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
    else
    {
        rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
    }

    /* 查找串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }
    /* 初始化信号量*/
    rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
    /* 以 DMA 接收及轮询发送方式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
    /* 设置接收回调函数 */
    rt_device_set_rx_indicate(serial, uart_input);
    /* 发送字符串 */
    rt_device_write(serial, 0, str, (sizeof(str) - 1));

    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 0, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }

    return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample);
图3        最终实现

总结:

        在串口设备使用 DMA 接收时,串口回调函数中不能使用复杂的逻辑来操作;

        原例程中使用的消息队列逻辑花费时间过长,导致缓冲区中的数据来不及被处理而溢出;经测试,使用信号量代替消息队列的逻辑,能够解决该问题。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐