文件
phonerail/firmware/main/ulink_esp.c
T
zhou yang和Claude Opus 5.5 a32fa06c43 固件:A12 命令处理器 + A13 USB 分帧 + A14 WiFi/MQTT(含 C1 配网)
- A12 单一命令处理器(cmd*.c):一行 JSON 进出、seq 全局判重(32 条按值)、
  限幅 / 空槽一律拒 / 拒绝记事件;命令号与错误码冻结(进 flash)。新增 power 命令,
  fan 改为只抬不压的下限(t_thermal_set_floor)。rail_set_mv 注明不拿锁,调用方包 xact。
- A13 USB-Serial-JTAG 分帧(ulink*.c):@C / @R 前缀 + CRC-16,日志行首 @ 垫空格,
  任意切块可切;关掉 USB 副控制台(它绕开驱动写 FIFO)。PC 端客户端 tools/prlink.py。
- A14 + C1(netcfg / mlink / cmd_net):topic phonerail/<mac>/{cmd,rsp,tele,state},
  mqtts(evse-ca 编进固件)、1 s 遥测、遗嘱离线;wifi / mqtt 配置只收 USB、存 NVS,
  net 永不回显密码。
- 协议定稿写进需求文档 §10.8(A12 / A13 / A14 三节);N7 撤销改 D7(机架管理前后端)。

宿主机 23 个 ctest / 400 例全绿;板 B 已烧 0.1.0-a14,经 WiFi → 公网 → 8883 TLS 真连网验过。
固件 0xE2220 / 1 MB 分区只剩 12%,C2 前要重排分区表。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 12:33:18 +08:00

156 行
5.6 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#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<json>[*CRC],回应 %s<json>*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;
}