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 驱动在中断上下文中调用,因此它运行在中断环境中。
解决方案:
将数据处理从中断上下文转移到线程上下文,避免在中断中使用不可重入或可能阻塞的函数(如互斥量、信号量获取等)。
正确做法:使用工作线程或专用处理线程
方法一:使用 rt_sem + 独立线程处理(推荐)
- 在中断回调中只做最少量操作:复制数据到缓冲区、释放信号量。
- 创建一个低优先级线程来读取缓冲区并处理数据。
修改示例:
// 定义一个邮箱或使用信号量通知线程
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);
总结
错误:在中断回调中使用 rt_mutex_take。
正确:中断中只做快速数据拷贝 + 发信号量;用独立线程处理耗时操作。
建议:所有涉及阻塞、同步原语(mutex、take)的操作都应移出中断上下文。
这样可解决“Function [rt_mutex_take] shall not be used in ISR”断言错误,并提升系统稳定性。

