cherry/cdc_acm_uart 死机

SDK(main)/example/cherry_usb/cdc_acm_uart 通信会导致死机。

[I/TOUCH] Regist touch screen driver, probe=1202627d
call par CFG1(3313)
fc 9, xtal 2000, pll 2047
call par CFG1(3313)
fc 9, xtal 2000, pll 2046
[I/main] USB-UART Bridge starting…
[I/usb_uart] UART initialized successfully
[I/usb_uart] USB-UART bridge initialized
[I/main] USB-UART Bridge ready
msh />
[22:39:02.393]收←◆[I/usb_uart] USB Configured
[I/usb_uart] Set baud rate: 1000000

[22:39:10.552]收←◆[I/usb_uart] Set baud rate: 115200
[I/usb_uart] Set baud rate: 115200

[22:39:13.325]收←◆Function[rt_mutex_take] shall not be used in ISR
Assertion failed at function:rt_mutex_take, line number:659 ,(0)
Previous ISR enable 1

/* USB CDC ACM Bulk OUT callback - called when USB receives data */

void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
if (nbytes > 0)
{
/* Buffer received data */
rt_mutex_take(&buffer_mutex, RT_WAITING_FOREVER);
rt_size_t written = rt_ringbuffer_put(&usb_to_uart_rb, usb_read_buffer, nbytes);
rt_mutex_release(&buffer_mutex);

    if (written < nbytes)
    {
        LOG_W("USB RX buffer overflow: %u bytes lost", nbytes - written);
    }

    /* Signal thread to process data */
    rt_sem_release(&usb_rx_sem);
}

/* Start next read */
usbd_ep_start_read(busid, CDC_OUT_EP, usb_read_buffer, sizeof(usb_read_buffer));

}

应该是这个函数中使用了互斥锁(mutex)?这个函数是在中断上下文中执行的吗?该如何修改以避免在中断中使用互斥锁?


分析与建议:

是的,问题出在 usbd_cdc_acm_bulk_out 这个回调函数中调用了 rt_mutex_take(&buffer_mutex, RT_WAITING_FOREVER);。从日志提示:

Function[rt_mutex_take] shall not be used in ISR

说明当前是在中断服务例程(ISR) 中调用了不能用于中断的函数 rt_mutex_take。在实时操作系统(如RT-Thread)中,rt_mutex_take不允许在中断上下文中使用的,因为它可能会引起阻塞(即使你传了 RT_WAITING_FOREVER),而中断上下文不允许调度或阻塞。

原因:

usbd_cdc_acm_bulk_out 是 USB 接收数据完成后的中断回调函数,通常由底层 USB 驱动在中断上下文中调用,因此它运行在中断环境中。

解决方案:

将数据处理从中断上下文转移到线程上下文,避免在中断中使用不可重入或可能阻塞的函数(如互斥量、信号量获取等)。

:white_check_mark: 正确做法:使用工作线程或专用处理线程

方法一:使用 rt_sem + 独立线程处理(推荐)
  1. 在中断回调中只做最少量操作:复制数据到缓冲区、释放信号量。
  2. 创建一个低优先级线程来读取缓冲区并处理数据。
修改示例:
// 定义一个邮箱或使用信号量通知线程
static rt_sem_t usb_rx_sem;
static rt_uint8_t *g_usb_read_buffer_copy;  // 预先分配缓存用于传递数据
static rt_uint32_t g_nbytes;
static rt_bool_t data_ready = RT_FALSE;
static rt_mutex_t buffer_mutex; // 保护共享数据

// USB 接收回调(运行在中断上下文)
void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    if (nbytes > 0)
    {
        // 尝试快速拷贝数据(不要拿 mutex,改用临界区或确保原子性)
        rt_base_t level = rt_hw_interrupt_disable();
        if (!data_ready && nbytes <= sizeof(usb_read_buffer)) // 假设 g_usb_read_buffer_copy 足够大
        {
            rt_memcpy(g_usb_read_buffer_copy, usb_read_buffer, nbytes);
            g_nbytes = nbytes;
            data_ready = RT_TRUE;
            rt_hw_interrupt_enable(level);
            rt_sem_release(usb_rx_sem); // 通知线程处理
        }
        else
        {
            rt_hw_interrupt_enable(level);
            LOG_W("USB RX dropped or busy");
        }
    }

    // 启动下一次读取
    usbd_ep_start_read(busid, CDC_OUT_EP, usb_read_buffer, sizeof(usb_read_buffer));
}

// 处理线程
void usb_uart_process_thread(void *parameter)
{
    while (1)
    {
        rt_sem_take(usb_rx_sem, RT_WAITING_FOREVER);

        if (data_ready)
        {
            rt_base_t level = rt_hw_interrupt_disable();
            rt_uint32_t local_nbytes = g_nbytes;
            rt_hw_interrupt_enable(level);

            // 现在在**线程上下文**中,可以安全使用 mutex
            rt_mutex_take(&buffer_mutex, RT_WAITING_FOREVER);
            rt_size_t written = rt_ringbuffer_put(&usb_to_uart_rb, g_usb_read_buffer_copy, local_nbytes);
            rt_mutex_release(&buffer_mutex);

            if (written < local_nbytes)
            {
                LOG_W("USB RX buffer overflow: %u bytes lost", local_nbytes - written);
            }

            // 标记为空闲
            level = rt_hw_interrupt_disable();
            data_ready = RT_FALSE;
            rt_hw_interrupt_enable(level);
        }
    }
}
初始化时创建线程和资源:
// 初始化部分
buffer_mutex = rt_mutex_create("buf_mtx", RT_IPC_FLAG_PRIO);
usb_rx_sem = rt_sem_create("usb_rx_sem", 0, RT_IPC_FLAG_PRIO);
g_usb_read_buffer_copy = rt_malloc(sizeof(usb_read_buffer));
// 创建处理线程
rt_thread_t tid = rt_thread_create("usb_proc", usb_uart_process_thread, RT_NULL, 1024, 10, 10);
if (tid) rt_thread_startup(tid);

总结

  • :cross_mark: 错误:在中断回调中使用 rt_mutex_take
  • :white_check_mark: 正确:中断中只做快速数据拷贝 + 发信号量;用独立线程处理耗时操作。
  • :wrench: 建议:所有涉及阻塞、同步原语(mutex、take)的操作都应移出中断上下文。

这样可解决“Function [rt_mutex_take] shall not be used in ISR”断言错误,并提升系统稳定性。

您这边是使用的什么板子,是直接编译烧录的这个例程么,有没有做什么改动?

官方的开发板。直接编译使用。

是52的么,或者芯片的型号是什么?

对SF32LB52 sf32lb52-lcd_n16r8

可以参考以下代码进行修改