/* Mirror application printf calls to plain-text USB CDC without waiting * for the USB host in producers. UART still uses the SDK's original printf. */ #include #include #include #include "ql_rtos.h" #include "ql_uart.h" #include "field_log.h" /* This module is also compiled with -Dprintf=field_log_printf. */ #undef printf extern int printf(const char *format, ...); #define LOG_RECORD_BYTES 512 #define LOG_QUEUE_DEPTH 16 struct log_record { unsigned int length; char text[LOG_RECORD_BYTES]; }; static ql_queue_t usb_queue; static ql_task_t usb_task; static volatile int ready; static unsigned long dropped; static void note_drop(void) { ql_rtos_task_sched_lock(); ++dropped; ql_rtos_task_sched_unlock(); } int field_log_printf(const char *format, ...) { struct log_record record; va_list args; int n, ret; va_start(args, format); n = vsnprintf(record.text, sizeof(record.text), format, args); va_end(args); if (n < 0) return n; if (n >= (int)sizeof(record.text)) { const char suffix[] = " [TRUNCATED]\r\n"; size_t cut = sizeof(record.text) - sizeof(suffix); /* Keep the UTF-8 prefix valid if a multi-byte character crosses cut. */ while (cut && ((unsigned char)record.text[cut] & 0xc0) == 0x80) --cut; memcpy(record.text + cut, suffix, sizeof(suffix)); } record.length = (unsigned int)strlen(record.text); ret = printf("%s", record.text); if (ready && record.length && ql_rtos_queue_release(usb_queue, sizeof(record), (u8 *)&record, QL_NO_WAIT) != 0) note_drop(); return ret; } /* SDK variants return zero on success or a positive byte count. */ static int usb_write_all(const char *text, int length) { int offset = 0; while (offset < length) { int n = ql_uart_write(QL_USB_CDC_PORT, (unsigned char *)text + offset, length - offset); if (n == 0) return 0; if (n < 0 || n > length - offset) return -1; offset += n; } return 0; } static void usb_worker(void *arg) { struct log_record record; int opened = 0; unsigned long last_notice = 0, reported = 0; (void)arg; for (;;) { if (!opened) { int open_result = ql_uart_open(QL_USB_CDC_PORT, QL_UART_BAUD_115200, QL_FC_NONE); if (open_result == QL_UART_SUCCESS || open_result == QL_UART_ALREAD_OPEN) { const char banner[] = "\r\n[USB日志] 板板现场日志 CDC 已开启;UTF-8 / 115200 8N1;短按原报警键开始网络诊断\r\n"; opened = usb_write_all(banner, sizeof(banner) - 1) == 0; if (!opened) ql_uart_close(QL_USB_CDC_PORT); } if (!opened) { ql_rtos_task_sleep_ms(1000); continue; } } if (ql_rtos_queue_wait(usb_queue, (u8 *)&record, sizeof(record), 1000) == 0) { if (usb_write_all(record.text, record.length) != 0) { note_drop(); ql_uart_close(QL_USB_CDC_PORT); opened = 0; ql_rtos_task_sleep_ms(1000); } } /* Beacons identify the correct serial interface even when app is idle. * Drop totals are never reset: concurrent producers cannot lose them. */ if (opened && (u32)(ql_rtos_get_systicks() - last_notice) >= 1000) { char status[160]; unsigned long total; ql_rtos_task_sched_lock(); total = dropped; ql_rtos_task_sched_unlock(); snprintf(status, sizeof(status), "\r\n[USB日志] 在线;累计丢弃=%lu 条%s\r\n", total, total != reported ? ";USB拥塞或未连接,记录可能不完整" : ""); if (usb_write_all(status, (int)strlen(status)) != 0) { ql_uart_close(QL_USB_CDC_PORT); opened = 0; } else reported = total; last_notice = ql_rtos_get_systicks(); } } } int field_log_init(void) { int ret; if (ready) return 0; ret = ql_rtos_queue_create(&usb_queue, sizeof(struct log_record), LOG_QUEUE_DEPTH); if (ret != 0) { printf("[USB日志] 队列创建失败=%d;仅保留UART日志\r\n", ret); return ret; } ret = ql_rtos_task_create(&usb_task, 4096, 130, "usb_log", usb_worker, NULL); if (ret != 0) { ql_rtos_queue_delete(usb_queue); usb_queue = NULL; printf("[USB日志] 转发任务创建失败=%d;仅保留UART日志\r\n", ret); return ret; } ready = 1; field_log_printf("[USB日志] 应用日志已同步到USB CDC;内核/基带日志不在此转发范围\r\n"); return 0; }