/* USB-Serial-JTAG 链路的 ESP 胶水(A13)。**只进 IDF 目标**,分帧逻辑在 ulink.c(宿主机可测)。 ---- 这根线归谁 ---------------------------------------------------------- sdkconfig 里**关掉了副控制台**(`CONFIG_ESP_CONSOLE_SECONDARY_NONE`): IDF 的副控制台会绕开驱动直接往 USB FIFO 写 stdout,和这里的写入互不同步, 一条回应中间插进半行 printf 就被拆坏了(CRC 能兜住,但那是每条都可能坏)。 现在这根线上的字节**只有两个来源**,都经同一把互斥锁、同一个 `ulink_tx_t`: · `esp_log` 钩子:每条日志复制一份到 USB(UART0 / J33 照旧拿全量); · 命令回应:执行器任务经 `cmd_queue` 的回调写。 `printf()` 与 panic 输出只去 UART0。代价:USB 上看不到 panic 回溯 —— 复位原因会在下一次启动日志里报,回溯要接 J33。 ---- 两个写超时 ---------------------------------------------------------- 上位机没开串口时 USB 发不出去,`usb_serial_jtag_write_bytes()` 会等到超时。 · 日志:**0 等待**,写不下就丢(UART 上还有一份)。日志钩子在**任何任务**里被调, 包括 20 ms 节拍的 `t_safety`,等一下就是把巡检拖慢一下; · 回应:等 `ULINK_ESP_FRAME_WAIT_MS`。回应在执行器任务里写,等太久键会松得晚。 拿锁同理:日志拿不到锁就不等(丢 USB 那份),回应等一小会儿。 */ #include #include #include #include "cmd_queue.h" #include "driver/usb_serial_jtag.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "ulink.h" #include "ulink_esp.h" static const char *TAG = "ulink"; #define ULINK_ESP_TX_BUF 4096 /* 装得下两条满配 status(≈1 KB)+ 一堆日志 */ #define ULINK_ESP_RX_BUF 1024 #define ULINK_ESP_FRAME_WAIT_MS 20 #define ULINK_ESP_LOG_LINE 256 static SemaphoreHandle_t s_mtx; static ulink_tx_t s_tx; static ulink_rx_t s_rx; static vprintf_like_t s_prev_vprintf; static bool s_up; static TickType_t s_wait; /* 本次写允许等多久:由调用者在持锁时设 */ static size_t usj_sink(void *ctx, const char *data, size_t len) { (void)ctx; if (!usb_serial_jtag_is_connected()) { return 0; /* 没人听:一字节都不写(写进缓冲的会在下次连上时吐出一段陈年日志) */ } const int n = usb_serial_jtag_write_bytes(data, len, s_wait); return n > 0 ? (size_t)n : 0u; } static int log_vprintf(const char *fmt, va_list ap) { if (s_up && xSemaphoreTake(s_mtx, 0) == pdTRUE) { static char line[ULINK_ESP_LOG_LINE]; /* 锁内用,静态即可,不占调用者的栈 */ va_list ap2; va_copy(ap2, ap); const int n = vsnprintf(line, sizeof(line), fmt, ap2); va_end(ap2); if (n > 0) { s_wait = 0; ulink_tx_log(&s_tx, line, (size_t)n < sizeof(line) ? (size_t)n : sizeof(line) - 1); } xSemaphoreGive(s_mtx); } return s_prev_vprintf(fmt, ap); /* UART0 照旧 */ } static void send_frame(const char *json, size_t len) { if (!s_up || xSemaphoreTake(s_mtx, pdMS_TO_TICKS(ULINK_ESP_FRAME_WAIT_MS)) != pdTRUE) { return; /* 上位机那一侧表现为超时,按同一个 seq 重发(判重兜住) */ } s_wait = pdMS_TO_TICKS(ULINK_ESP_FRAME_WAIT_MS); (void)ulink_tx_frame(&s_tx, json, len); xSemaphoreGive(s_mtx); } /* cmd_queue 的回调:在**执行器任务**里被调(cmd_queue.h)。 */ static void reply_usb(void *ctx, const char *line, size_t len) { (void)ctx; send_frame(line, len); } static void link_error(const char *payload, const char *err) { char out[96]; const size_t n = ulink_link_error(payload, err, out, sizeof(out)); if (n > 0) { send_frame(out, n); } } /* ulink_rx_feed 的回调:在**链路任务**里被调。 */ static void on_line(void *ctx, ulink_rx_kind_t kind, const char *payload) { (void)ctx; if (kind != ULINK_RX_CMD) { link_error(payload, ulink_rx_err_str(kind)); return; } if (!cmd_queue_submit(CMD_LINK_USB, payload, reply_usb, NULL)) { link_error(payload, "queue_full"); } } hal_err_t ulink_esp_init(void) { s_mtx = xSemaphoreCreateMutex(); if (s_mtx == NULL) { return HAL_ERR_STATE; } usb_serial_jtag_driver_config_t cfg = { .tx_buffer_size = ULINK_ESP_TX_BUF, .rx_buffer_size = ULINK_ESP_RX_BUF, }; const esp_err_t e = usb_serial_jtag_driver_install(&cfg); if (e != ESP_OK) { ESP_LOGE(TAG, "USB-Serial-JTAG 驱动装不上:%s —— USB 命令链路不可用", esp_err_to_name(e)); return HAL_ERR_BUS; } ulink_tx_init(&s_tx, usj_sink, NULL); ulink_rx_init(&s_rx); s_up = true; s_prev_vprintf = esp_log_set_vprintf(log_vprintf); ESP_LOGI(TAG, "USB 命令链路就绪:请求 %s[*CRC],回应 %s*CRC", ULINK_CMD_PREFIX, ULINK_RSP_PREFIX); return HAL_OK; } void ulink_esp_poll(uint32_t wait_ms) { if (!s_up) { vTaskDelay(pdMS_TO_TICKS(wait_ms)); return; } uint8_t buf[64]; const int n = usb_serial_jtag_read_bytes(buf, sizeof(buf), pdMS_TO_TICKS(wait_ms)); if (n > 0) { ulink_rx_feed(&s_rx, buf, (size_t)n, on_line, NULL); } } void ulink_esp_stats(ulink_esp_stats_t *st) { st->rx_cmds = s_rx.cmds; st->rx_ignored = s_rx.ignored; st->rx_errors = s_rx.too_long + s_rx.bad_crc + s_rx.bad_bytes; st->tx_frames = s_tx.frames; st->tx_short = s_tx.short_writes; }