这段代码是 CH32V 系列微控制器的 USB 设备中断处理函数(USBD_IRQHandler
),主要负责处理 USB 传输事件、设置请求和连接状态变化。以下是详细解析:
一、函数概述
- 功能:处理 USB 设备端的各种中断事件,包括传输完成、设置包接收和连接状态变化。
- 核心逻辑:
- 读取中断标志寄存器(
INT_FG
)判断中断类型。 - 根据不同中断类型执行相应处理:
- 传输完成(
USBHS_TRANSFER_FLAG
):处理 IN/OUT 端点数据传输。 - 设置请求(
USBHS_SETUP_FLAG
):处理控制端点的设置包。 - 连接检测(
USBHS_DETECT_FLAG
):处理 USB 连接 / 断开事件。
- 传输完成(
- 读取中断标志寄存器(
二、传输完成中断处理(USBHS_TRANSFER_FLAG
)
1. 端点索引和令牌类型解析
ep_idx = (USBHS_DEVICE->INT_ST) & MASK_UIS_ENDP;
token = (((USBHS_DEVICE->INT_ST) & MASK_UIS_TOKEN) >> 4) & 0x03;
ep_idx
:获取发生中断的端点索引(0-15)。token
:获取传输令牌类型(PID_IN/PID_OUT)。
2. IN 令牌处理(主机读取数据)
控制端点(EP0)
-
处理控制传输的数据阶段:
if (g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len > g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps) { // 数据未传输完,继续发送下一包 g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len -= g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps; ep0_tx_data_toggle ^= 1; // 切换数据Toggle位 } else { // 数据传输完成,处理状态阶段 g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len; g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len = 0; ep0_tx_data_toggle = true; }
-
特殊处理:
- 地址设置:若
g_ch32_usbhs_udc.dev_addr > 0
,则设置新的设备地址(USB 枚举流程)。 - 状态阶段处理:若为控制传输的状态阶段,重新配置 EP0 接收下一个设置包。
- 地址设置:若
非控制端点(EP1-EPn)
- 处理批量 / 中断 / 同步传输:
if (g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len > g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps) { // 更新缓冲区指针,准备发送下一包 g_ch32_usbhs_udc.in_ep[ep_idx].xfer_buf += g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps; epx_tx_data_toggle[ep_idx - 1] ^= 1; // 切换Toggle位 // 配置DMA和端点控制寄存器 USB_SET_TX_LEN(ep_idx, write_count); USB_SET_TX_DMA(ep_idx, (uint32_t)g_ch32_usbhs_udc.in_ep[ep_idx].xfer_buf); USB_SET_TX_CTRL(ep_idx, tmp | USBHS_EP_T_RES_ACK); } else { // 传输完成,触发回调 g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len; g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len = 0; usbd_event_ep_in_complete_handler(ep_idx | 0x80, g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len); }
3. OUT 令牌处理(主机发送数据)
控制端点(EP0)
-
处理控制传输的接收数据:
read_count = USBHS_DEVICE->RX_LEN; g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len += read_count; g_ch32_usbhs_udc.out_ep[ep_idx].xfer_len -= read_count; usbd_event_ep_out_complete_handler(0x00, g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len);
-
特殊处理:
- 若接收长度为 0(状态阶段),重新配置 EP0 接收下一个设置包。
非控制端点(EP1-EPn)
-
处理批量 / 中断 / 同步传输的接收:
if (USBHS_DEVICE->INT_ST & USBHS_DEV_UIS_TOG_OK) { read_count = USBHS_DEVICE->RX_LEN; g_ch32_usbhs_udc.out_ep[ep_idx].xfer_buf += read_count; if ((read_count < g_ch32_usbhs_udc.out_ep[ep_idx].ep_mps) || (g_ch32_usbhs_udc.out_ep[ep_idx].xfer_len == 0)) { // 接收完成或不足一包,触发回调 usbd_event_ep_out_complete_handler(ep_idx, g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len); } else { // 继续接收下一包 USB_SET_RX_DMA(ep_idx, (uint32_t)g_ch32_usbhs_udc.out_ep[ep_idx].xfer_buf); USB_SET_RX_CTRL(ep_idx, USBHS_EP_R_RES_ACK); } }
三、设置请求中断处理(USBHS_SETUP_FLAG
)
usbd_event_ep0_setup_complete_handler((uint8_t *)&g_ch32_usbhs_udc.setup);
- 当接收到设置包(SETUP 事务)时,触发回调函数处理 USB 请求(如获取描述符、设置地址等)。
- 典型 USB 枚举流程的起点。
四、连接检测中断处理(USBHS_DETECT_FLAG
)
USBHS_DEVICE->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
USBHS_DEVICE->UEP0_TX_LEN = 0;
USBHS_DEVICE->UEP0_TX_CTRL = USBHS_EP_T_RES_NAK;
-
初始化 USB 端点配置:
- 启用控制端点(EP0)的发送和接收。
- 重置数据 Toggle 位(
ep0_tx_data_toggle
/ep0_rx_data_toggle
)。
-
初始化非控制端点(EP1-EPn):
- 禁用所有非控制端点(设置为 NAK 状态)。
- 重置 Toggle 位数组(
epx_tx_data_toggle
)。
-
触发复位回调:
memset(&g_ch32_usbhs_udc, 0, sizeof(struct ch32_usbhs_udc)); usbd_event_reset_handler();
- 清除 USB 设备控制结构体,触发复位事件回调。
五、关键机制
- Toggle 位管理:
- 通过
ep0_tx_data_toggle
、ep0_rx_data_toggle
和epx_tx_data_toggle
数组管理数据 Toggle 位,确保数据正确传输。 - Toggle 位用于同步发送方和接收方的数据状态,防止重复或丢失数据。
- 通过
- DMA 配置:
- 通过
USB_SET_TX_DMA
和USB_SET_RX_DMA
配置端点的 DMA 缓冲区地址,实现高效数据传输。
- 通过
- 事件回调:
- 通过
usbd_event_xxx_handler
系列函数通知上层应用处理 USB 事件,如传输完成、设置请求等。
- 通过
六、总结
该中断处理函数实现了 USB 设备端的核心功能:
- 端点管理:控制数据在主机和设备间的双向传输。
- 枚举流程:处理 USB 连接、地址设置和描述符获取。
- 错误处理:通过 NAK 响应和 Toggle 位管理确保数据可靠性。
- 状态机设计:遵循 USB 协议规范,实现控制传输的三个阶段(设置、数据、状态)。
通过这套机制,CH32V 微控制器能够作为 USB 设备与主机通信,支持各种 USB 类(如 CDC、HID、MSC 等)的实现。
void USBD_IRQHandler(void)
{
uint32_t ep_idx, token, write_count, read_count;
uint8_t intflag = 0;
intflag = USBHS_DEVICE->INT_FG;
if (intflag & USBHS_TRANSFER_FLAG) {
ep_idx = (USBHS_DEVICE->INT_ST) & MASK_UIS_ENDP;
token = (((USBHS_DEVICE->INT_ST) & MASK_UIS_TOKEN) >> 4) & 0x03;
if (token == PID_IN) {
if (ep_idx == 0x00) {
if (g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len > g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps) {
g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len -= g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps;
g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps;
ep0_tx_data_toggle ^= 1;
} else {
g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len;
g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len = 0;
ep0_tx_data_toggle = true;
}
usbd_event_ep_in_complete_handler(ep_idx | 0x80, g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len);
if (g_ch32_usbhs_udc.dev_addr > 0) {
USBHS_DEVICE->DEV_AD = g_ch32_usbhs_udc.dev_addr & 0xff;
g_ch32_usbhs_udc.dev_addr = 0;
}
if (g_ch32_usbhs_udc.setup.wLength && ((g_ch32_usbhs_udc.setup.bmRequestType & USB_REQUEST_DIR_MASK) == USB_REQUEST_DIR_OUT)) {
/* In status, start reading setup */
USBHS_DEVICE->UEP0_DMA = (uint32_t)&g_ch32_usbhs_udc.setup;
USBHS_DEVICE->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
ep0_tx_data_toggle = true;
} else if (g_ch32_usbhs_udc.setup.wLength == 0) {
/* In status, start reading setup */
USBHS_DEVICE->UEP0_DMA = (uint32_t)&g_ch32_usbhs_udc.setup;
USBHS_DEVICE->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
ep0_tx_data_toggle = true;
}
} else {
USB_SET_TX_CTRL(ep_idx, (USB_GET_TX_CTRL(ep_idx) & ~(USBHS_EP_T_RES_MASK | USBHS_EP_T_TOG_MASK)) | USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0);
if (g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len > g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps) {
g_ch32_usbhs_udc.in_ep[ep_idx].xfer_buf += g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps;
g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len -= g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps;
g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps;
epx_tx_data_toggle[ep_idx - 1] ^= 1;
write_count = MIN(g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len, g_ch32_usbhs_udc.in_ep[ep_idx].ep_mps);
USB_SET_TX_LEN(ep_idx, write_count);
USB_SET_TX_DMA(ep_idx, (uint32_t)g_ch32_usbhs_udc.in_ep[ep_idx].xfer_buf);
uint32_t tmp = USB_GET_TX_CTRL(ep_idx);
tmp &= ~(USBHS_EP_T_RES_MASK | USBHS_EP_T_TOG_MASK);
tmp |= USBHS_EP_T_RES_ACK;
tmp |= (epx_tx_data_toggle[ep_idx - 1] ? USBHS_EP_T_TOG_1 : USBHS_EP_T_TOG_0);
USB_SET_TX_CTRL(ep_idx, tmp);
} else {
g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len += g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len;
g_ch32_usbhs_udc.in_ep[ep_idx].xfer_len = 0;
epx_tx_data_toggle[ep_idx - 1] ^= 1;
usbd_event_ep_in_complete_handler(ep_idx | 0x80, g_ch32_usbhs_udc.in_ep[ep_idx].actual_xfer_len);
}
}
} else if (token == PID_OUT) {
if (ep_idx == 0x00) {
read_count = USBHS_DEVICE->RX_LEN;
g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len += read_count;
g_ch32_usbhs_udc.out_ep[ep_idx].xfer_len -= read_count;
usbd_event_ep_out_complete_handler(0x00, g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len);
if (read_count == 0) {
/* Out status, start reading setup */
USBHS_DEVICE->UEP0_DMA = (uint32_t)&g_ch32_usbhs_udc.setup;
USBHS_DEVICE->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
ep0_rx_data_toggle = true;
ep0_tx_data_toggle = true;
} else {
ep0_rx_data_toggle ^= 1;
}
} else {
if (USBHS_DEVICE->INT_ST & USBHS_DEV_UIS_TOG_OK) {
USB_SET_RX_CTRL(ep_idx, (USB_GET_RX_CTRL(ep_idx) & ~USBHS_EP_R_RES_MASK) | USBHS_EP_R_RES_NAK);
read_count = USBHS_DEVICE->RX_LEN;
g_ch32_usbhs_udc.out_ep[ep_idx].xfer_buf += read_count;
g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len += read_count;
g_ch32_usbhs_udc.out_ep[ep_idx].xfer_len -= read_count;
if ((read_count < g_ch32_usbhs_udc.out_ep[ep_idx].ep_mps) || (g_ch32_usbhs_udc.out_ep[ep_idx].xfer_len == 0)) {
usbd_event_ep_out_complete_handler(ep_idx, g_ch32_usbhs_udc.out_ep[ep_idx].actual_xfer_len);
} else {
USB_SET_RX_DMA(ep_idx, (uint32_t)g_ch32_usbhs_udc.out_ep[ep_idx].xfer_buf);
USB_SET_RX_CTRL(ep_idx, (USB_GET_RX_CTRL(ep_idx) & ~USBHS_EP_R_RES_MASK) | USBHS_EP_R_RES_ACK);
}
}
}
}
USBHS_DEVICE->INT_FG = USBHS_TRANSFER_FLAG;
} else if (intflag & USBHS_SETUP_FLAG) {
usbd_event_ep0_setup_complete_handler((uint8_t *)&g_ch32_usbhs_udc.setup);
USBHS_DEVICE->INT_FG = USBHS_SETUP_FLAG;
} else if (intflag & USBHS_DETECT_FLAG) {
USBHS_DEVICE->ENDP_CONFIG = USBHS_EP0_T_EN | USBHS_EP0_R_EN;
USBHS_DEVICE->UEP0_TX_LEN = 0;
USBHS_DEVICE->UEP0_TX_CTRL = USBHS_EP_T_RES_NAK;
ep0_tx_data_toggle = true;
ep0_rx_data_toggle = true;
for (uint8_t ep_idx = 1; ep_idx < USB_NUM_BIDIR_ENDPOINTS; ep_idx++) {
USB_SET_TX_LEN(ep_idx, 0);
USB_SET_TX_CTRL(ep_idx, USBHS_EP_T_AUTOTOG | USBHS_EP_T_RES_NAK); // autotog does not work
USB_SET_RX_CTRL(ep_idx, USBHS_EP_R_AUTOTOG | USBHS_EP_R_RES_NAK);
epx_tx_data_toggle[ep_idx - 1] = false;
}
memset(&g_ch32_usbhs_udc, 0, sizeof(struct ch32_usbhs_udc));
usbd_event_reset_handler();
USBHS_DEVICE->UEP0_DMA = (uint32_t)&g_ch32_usbhs_udc.setup;
USBHS_DEVICE->UEP0_RX_CTRL = USBHS_EP_R_RES_ACK;
USBHS_DEVICE->INT_FG = USBHS_DETECT_FLAG;
}
}