TinyUSB事件
2026-08-06
本文链接为:http://www.usbzh.com/article/detail-1784.html ,欢迎转载,转载请附上本文链接。
事件结构体
typedef struct TU_ATTR_ALIGNED(4) {
uint8_t rhport;//root hub port
uint8_t event_id;
union {
// BUS RESET
struct {
tusb_speed_t speed;
} bus_reset;
// SOF
struct {
uint32_t frame_count;
}sof;
// SETUP_RECEIVED
tusb_control_request_t setup_received;
// XFER_COMPLETE
struct {
uint8_t ep_addr;
uint8_t result;
uint32_t len;
}xfer_complete;
// FUNC_CALL
struct {
void (*func) (void*);
void* param;
}func_call;
};
} dcd_event_t;
根据共用体可以知,事件类型分为以下几种:
typedef enum {
DCD_EVENT_INVALID = 0, // 0
DCD_EVENT_BUS_RESET, // 1
DCD_EVENT_UNPLUGGED, // 2
DCD_EVENT_SOF, // 3
DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support
DCD_EVENT_RESUME, // 5
DCD_EVENT_SETUP_RECEIVED, // 6
DCD_EVENT_XFER_COMPLETE, // 7
USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function
DCD_EVENT_COUNT
} dcd_eventid_t;
事件的发送
所以事件的发送有以下:
// helper to send bus signal event
TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr) {
dcd_event_t event;
event.rhport = rhport;
event.event_id = eid;
dcd_event_handler(&event, in_isr);
}
// helper to send bus reset event
TU_ATTR_ALWAYS_INLINE static inline void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr) {
dcd_event_t event;
event.rhport = rhport;
event.event_id = DCD_EVENT_BUS_RESET;
event.bus_reset.speed = speed;
dcd_event_handler(&event, in_isr);
}
// helper to send setup received
TU_ATTR_ALWAYS_INLINE static inline void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr) {
dcd_event_t event;
event.rhport = rhport;
event.event_id = DCD_EVENT_SETUP_RECEIVED;
memcpy(&event.setup_received, setup, sizeof(tusb_control_request_t));
dcd_event_handler(&event, in_isr);
}
// helper to send transfer complete event
TU_ATTR_ALWAYS_INLINE static inline void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr) {
dcd_event_t event;
event.rhport = rhport;
event.event_id = DCD_EVENT_XFER_COMPLETE;
event.xfer_complete.ep_addr = ep_addr;
event.xfer_complete.len = xferred_bytes;
event.xfer_complete.result = result;
dcd_event_handler(&event, in_isr);
}
TU_ATTR_ALWAYS_INLINE static inline void dcd_event_sof(uint8_t rhport, uint32_t frame_count, bool in_isr) {
dcd_event_t event;
event.rhport = rhport;
event.event_id = DCD_EVENT_SOF;
event.sof.frame_count = frame_count;
dcd_event_handler(&event, in_isr);
}
dcd_event_handlerb函数执行的是将事件精细化处理,并回调一些类驱动相关的回调,并
TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) {
bool send = false;
switch (event->event_id) {
case DCD_EVENT_UNPLUGGED:
_usbd_dev.connected = 0;
_usbd_dev.addressed = 0;
_usbd_dev.cfg_num = 0;
_usbd_dev.suspended = 0;
send = true;
break;
case DCD_EVENT_SUSPEND:
// NOTE: When plugging/unplugging device, the D+/D- state are unstable and
// can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ).
// In addition, some MCUs such as SAMD or boards that haven no VBUS detection cannot distinguish
// suspended vs disconnected. We will skip handling SUSPEND/RESUME event if not currently connected
if (_usbd_dev.connected) {
_usbd_dev.suspended = 1;
send = true;
}
break;
case DCD_EVENT_RESUME:
// skip event if not connected (especially required for SAMD)
if (_usbd_dev.connected) {
_usbd_dev.suspended = 0;
send = true;
}
break;
case DCD_EVENT_SOF:
// SOF driver handler in ISR context
for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) {
usbd_class_driver_t const* driver = get_driver(i);
if (driver && driver->sof) {
driver->sof(event->rhport, event->sof.frame_count);
}
}
// Some MCUs after running dcd_remote_wakeup() does not have way to detect the end of remote wakeup
// which last 1-15 ms. DCD can use SOF as a clear indicator that bus is back to operational
if (_usbd_dev.suspended) {
_usbd_dev.suspended = 0;
dcd_event_t const event_resume = {.rhport = event->rhport, .event_id = DCD_EVENT_RESUME};
queue_event(&event_resume, in_isr);
}
if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) {
dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF, .sof.frame_count = event->sof.frame_count};
queue_event(&event_sof, in_isr);
}
break;
case DCD_EVENT_SETUP_RECEIVED:
_usbd_queued_setup++;
TU_LOG_USBD(" event setup %d\r\n", _usbd_queued_setup);
send = true;
break;
default:
send = true;
break;
}
if (send) {
queue_event(event, in_isr);
}
}
queue_event中tud_event_hook_cb是一个空函数,如果需要,可以做一些其它的操作。
使用osal_queue_send事件放入FIFO中
TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, bool in_isr) {
TU_ASSERT(osal_queue_send(_usbd_q, event, in_isr));
tud_event_hook_cb(event->rhport, event->event_id, in_isr);
return true;
}
osal_queue_send很简单,就是写FIFO.一个有意思的参数就是in_isr,在中断中就不用加锁了,不在中断中是需要加锁同步的。
TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const* data, bool in_isr) {
if (!in_isr) {
_osal_q_lock(qhdl);
}
bool success = tu_fifo_write(&qhdl->ff, data);
if (!in_isr) {
_osal_q_unlock(qhdl);
}
return success;
}
事件的处理
事件的处理是在一个tud_task任务中,这是单片机main函数中的死循环
while (1) {
tud_task(); // tinyusb device task
}
调用tud_task_ext
void tud_task (void) {
tud_task_ext(UINT32_MAX, false);
}
就是从FIFO中取出事件,然后依次处理。
void tud_task_ext(uint32_t timeout_ms, bool in_isr) {
(void) in_isr; // not implemented yet
// Skip if stack is not initialized
if (!tud_inited()) return;
// Loop until there is no more events in the queue
while (1) {
dcd_event_t event;
if (!osal_queue_receive(_usbd_q, &event, timeout_ms)) return;
#if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL
if (event.event_id == DCD_EVENT_SETUP_RECEIVED) TU_LOG_USBD("\r\n"); // extra line for setup
TU_LOG_USBD("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED");
#endif
switch (event.event_id) {
case DCD_EVENT_BUS_RESET:
TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]);
usbd_reset(event.rhport);
_usbd_dev.speed = event.bus_reset.speed;
break;
case DCD_EVENT_UNPLUGGED:
TU_LOG_USBD("\r\n");
usbd_reset(event.rhport);
tud_umount_cb();
break;
case DCD_EVENT_SETUP_RECEIVED:
TU_LOG_USBD("SETUP received %d\r\n", _usbd_queued_setup);
TU_ASSERT(_usbd_queued_setup > 0,);
_usbd_queued_setup--;
TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8);
if (_usbd_queued_setup) {
TU_LOG_USBD(" Skipped since there is other SETUP in queue\r\n");
break;
}
// Mark as connected after receiving 1st setup packet.
// But it is easier to set it every time instead of wasting time to check then set
_usbd_dev.connected = 1;
// mark both in & out control as free
_usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0;
_usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0;
_usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0;
_usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0;
// Process control request
if (!process_control_request(event.rhport, &event.setup_received)) {
TU_LOG_USBD(" Stall EP0\r\n");
// Failed -> stall both control endpoint IN and OUT
dcd_edpt_stall(event.rhport, 0);
dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK);
}
break;
case DCD_EVENT_XFER_COMPLETE: {
// Invoke the class callback associated with the endpoint address
uint8_t const ep_addr = event.xfer_complete.ep_addr;
uint8_t const epnum = tu_edpt_number(ep_addr);
uint8_t const ep_dir = tu_edpt_dir(ep_addr);
TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len);
_usbd_dev.ep_status[epnum][ep_dir].busy = 0;
_usbd_dev.ep_status[epnum][ep_dir].claimed = 0;
if (0 == epnum) {
usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result,event.xfer_complete.len);
}
else
{
usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]);
TU_ASSERT(driver,);
TU_LOG_USBD(" %s xfer callback\r\n", driver->name);
driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len);//hidd_xfer_cb
}
break;
}
case DCD_EVENT_SUSPEND:
// NOTE: When plugging/unplugging device, the D+/D- state are unstable and
// can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ), which result in a series of event
// e.g suspend -> resume -> unplug/plug. Skip suspend/resume if not connected
if (_usbd_dev.connected) {
TU_LOG_USBD(": Remote Wakeup = %u\r\n", _usbd_dev.remote_wakeup_en);
tud_suspend_cb(_usbd_dev.remote_wakeup_en);
} else {
TU_LOG_USBD(" Skipped\r\n");
}
break;
case DCD_EVENT_RESUME:
if (_usbd_dev.connected) {
TU_LOG_USBD("\r\n");
tud_resume_cb();
} else {
TU_LOG_USBD(" Skipped\r\n");
}
break;
case USBD_EVENT_FUNC_CALL:
TU_LOG_USBD("\r\n");
if (event.func_call.func) event.func_call.func(event.func_call.param);
break;
case DCD_EVENT_SOF:
if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) {
TU_LOG_USBD("\r\n");
tud_sof_cb(event.sof.frame_count);
}
break;
default:
TU_BREAKPOINT();
break;
}
#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO
// return if there is no more events, for application to run other background
if (osal_queue_empty(_usbd_q)) return;
#endif
}
}
本文链接为:http://www.usbzh.com/article/detail-1784.html ,欢迎转载,转载请附上本文链接。
TinyUSB





