evan.liu 8a7cebcf77 V1.00.26: UART baud 115200 -> 460800
- firmware BSP_USART_BAUDRATE 460800 (divider error +0.03%); uartrst/
  uartinfo follow the macro
- RX DMA ring 2KB -> 3KB: at 460800 2KB only covers 44ms, less than the
  ~45ms flash-erase interrupt-off window; stack top now 0x2000BDE8
  (guard warns <1KB from the 0x2000C000 cliff, by design)
- tools: ble_ota_update.py UartTransport and uart_cap.py default baud
- Verified: stream OTA 55832B in 2.6s @23kB/s (one lost frame recovered
  by go-back-N), PASS after reboot; text CLI fine at 460800
- docs: ble_protocol.md, design spec, AGENTS.md, dev log section 48
2026-09-05 10:01:23 +08:00

441 lines
13 KiB
C

/**
* @file cli_core.c
* @brief CLI core implementation: command table + handlers + line execution.
* Transport-agnostic: all output goes through the current cli_out_fn
* (default: USART1 DMA TX). The UART frontend lives in app_cli.c;
* the BLE frame frontend (app_ble_proto.c) switches the output to
* framed CLI_RSP responses around cli_exec_line().
*/
#include "cli_core.h"
#include "app_version.h"
#include "bsp_usart.h"
#include "app_gpio.h"
#include "app_info.h"
#include "app_ble_proto.h"
#include "app_params.h"
#include "app_bootset.h"
#include "app_cli.h"
#include "n32wb03x.h" /* SystemTrimValueGet (temp debug) */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "FreeRTOS.h"
#include "task.h"
#define CLI_TX_BUF_SIZE 96
#define CLI_MAX_ARGS 8
#define CLI_RESET_DELAY_MS 200u /* let the reply drain before NVIC_SystemReset */
typedef void (*CliCmdHandler_t)(int argc, char* argv[]);
typedef struct
{
const char* name;
const char* help;
CliCmdHandler_t handler;
} CliCmd_t;
static char s_txBuf[CLI_TX_BUF_SIZE];
static cli_out_fn s_out = NULL; /* NULL = default dual output */
/* ------------------------------------------------------------------ */
/* Output */
/* ------------------------------------------------------------------ */
/**
* @brief Default output: USART1 DMA TX. BLE-originated commands temporarily
* switch the output to framed CLI_RSP (proto_cli_out) via
* cli_set_output() around cli_exec_line().
* @note Strings must contain explicit "\r\n" (no auto CR like fputc).
*/
static void cli_out_uart(const char* str)
{
bsp_usart_write_dma((const uint8_t*)str, (uint16_t)strlen(str));
}
void cli_set_output(cli_out_fn out)
{
s_out = out;
}
void cli_write(const char* str)
{
if (s_out != NULL)
{
s_out(str);
}
else
{
cli_out_uart(str);
}
}
void cli_printf(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
vsnprintf(s_txBuf, CLI_TX_BUF_SIZE, fmt, args);
va_end(args);
s_txBuf[CLI_TX_BUF_SIZE - 1] = '\0';
cli_write(s_txBuf);
}
/* ------------------------------------------------------------------ */
/* Built-in commands */
/* ------------------------------------------------------------------ */
static void CmdHelp(int argc, char* argv[]);
/**
* @brief "version": show firmware and clock info.
*/
static void CmdVersion(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_printf("firmware version: %s\r\n", APP_FW_VERSION);
cli_write("mcm-ddc-ble: BLE(rdtss) + FreeRTOS + UART(DMA) + CLI\r\n");
cli_printf("SystemCoreClock = %lu Hz\r\n", (unsigned long)SystemCoreClock);
}
/**
* @brief "sysinfo": show FreeRTOS runtime info (uptime, tasks, heap,
* per-task state/priority/stack high water mark).
*/
static void CmdSysInfo(int argc, char* argv[])
{
static char s_taskList[384]; /* vTaskList output buffer */
uint32_t ticks = (uint32_t)xTaskGetTickCount();
(void)argc;
(void)argv;
cli_printf("uptime: %lu ticks (%lu s)\r\n",
(unsigned long)ticks, (unsigned long)(ticks / configTICK_RATE_HZ));
cli_printf("tasks: %u\r\n", (unsigned int)uxTaskGetNumberOfTasks());
cli_printf("heap: total %u B, free %u B, min ever free %u B\r\n",
(unsigned int)configTOTAL_HEAP_SIZE,
(unsigned int)xPortGetFreeHeapSize(),
(unsigned int)xPortGetMinimumEverFreeHeapSize());
/* vTaskList lines already end with "\r\n"; stack column is in words */
cli_write("name state prio stack(w) num\r\n");
s_taskList[0] = '\0';
vTaskList(s_taskList);
cli_write(s_taskList);
}
/**
* @brief "led <1|2> <on|off|toggle>": control a board LED.
*/
static void CmdLed(int argc, char* argv[])
{
GPIO_Module* port;
uint16_t pin;
if (argc < 3)
{
cli_write("usage: led <1|2> <on|off|toggle>\r\n");
return;
}
if (argv[1][0] == '1' && argv[1][1] == '\0')
{
port = LED1_PORT;
pin = LED1_PIN;
}
else if (argv[1][0] == '2' && argv[1][1] == '\0')
{
port = LED2_PORT;
pin = LED2_PIN;
}
else
{
cli_write("usage: led <1|2> <on|off|toggle>\r\n");
return;
}
if (strcmp(argv[2], "on") == 0)
{
LedOn(port, pin);
}
else if (strcmp(argv[2], "off") == 0)
{
LedOff(port, pin);
}
else if (strcmp(argv[2], "toggle") == 0)
{
LedBlink(port, pin);
}
else
{
cli_write("usage: led <1|2> <on|off|toggle>\r\n");
return;
}
cli_printf("led %s %s done\r\n", argv[1], argv[2]);
}
/**
* @brief "devinfo": show the same items the BLE INFO_QUERY(all) reports.
*/
static void CmdDevInfo(int argc, char* argv[])
{
int16_t temp = app_info_chip_temp_c10();
uint16_t fan = app_fan_get_rpm();
(void)argc;
(void)argv;
cli_printf("firmware: %s (0x%08lX)\r\n", APP_FW_VERSION,
(unsigned long)APP_FW_VERSION_NUM);
cli_printf("cur bank: APP%u\r\n", (unsigned int)app_info_cur_bank());
if (temp == 0x7FFF)
{
cli_write("chip temp: ADC read timeout\r\n");
}
else
{
trim_stored_t *p_trim = SystemTrimValueGet();
cli_printf("chip temp: %d.%d C (adc=%u trim=%lu)\r\n",
(int)(temp / 10), (int)(temp % 10),
(unsigned int)app_info_last_temp_raw(),
(p_trim != NULL) ? (unsigned long)p_trim->rc_adc_ts_25c : 0ul);
}
cli_printf("vdd: %u mV\r\n", (unsigned int)app_info_vdd_mv());
if (fan == APP_FAN_RPM_INVALID)
{
cli_write("fan rpm: n/a (no tachometer)\r\n");
}
else
{
cli_printf("fan rpm: %u\r\n", (unsigned int)fan);
}
cli_printf("uptime: %lu s\r\n",
(unsigned long)((uint32_t)xTaskGetTickCount() / configTICK_RATE_HZ));
cli_printf("free heap: %u B\r\n", (unsigned int)xPortGetFreeHeapSize());
}
/**
* @brief "blelog [on|off]": BLE traffic log switch (connect/disconnect
* events + hex dump of every RX/TX protocol frame on the UART).
*/
static void CmdBleLog(int argc, char* argv[])
{
if (argc >= 2)
{
if (strcmp(argv[1], "on") == 0)
{
app_ble_proto_log_set(1);
}
else if (strcmp(argv[1], "off") == 0)
{
app_ble_proto_log_set(0);
}
else
{
cli_write("usage: blelog [on|off]\r\n");
return;
}
}
cli_printf("ble log: %s\r\n", app_ble_proto_log_get() ? "on" : "off");
}
/**
* @brief "reset": system reset (reboot into the active bank).
*/
static void CmdReset(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("resetting...\r\n");
vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS));
NVIC_SystemReset();
}
/**
* @brief "factory": restore the APP_DATA params to factory defaults,
* save them to flash, then reboot. The bootsetting record is not
* touched (it selects the boot bank, it is not user configuration).
*/
static void CmdFactory(int argc, char* argv[])
{
(void)argc;
(void)argv;
if (app_params_restore_defaults() == 0)
{
cli_write("factory defaults restored, resetting...\r\n");
}
else
{
cli_write("factory: flash write failed, resetting anyway...\r\n");
}
vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS));
NVIC_SystemReset();
}
/**
* @brief "uartrst": re-initialize USART1 at the default baud rate and
* flush the RX queue (recovery from a wedged/misconfigured UART).
*/
static void CmdUartRst(int argc, char* argv[])
{
(void)argc;
(void)argv;
bsp_usart_set_baud(BSP_USART_BAUDRATE);
cli_printf("usart reset: %lu 8N1, rx queue flushed\r\n",
(unsigned long)BSP_USART_BAUDRATE);
}
/**
* @brief "uartinfo": show the USART1 configuration.
*/
static void CmdUartInfo(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("usart1: TX=PB6 RX=PB7 (AF4)\r\n");
cli_printf("baud: %lu, 8 data bits, no parity, 1 stop bit\r\n",
(unsigned long)BSP_USART_BAUDRATE);
cli_write("flow control: none; tx: DMA CH1 (polled); rx: RXDNE irq queue\r\n");
}
/**
* @brief "ota": switch the UART frontend into OTA binary frame mode
* (0xCA OTA frames on the raw stream, framed RSP on TX).
* Handshake: the "[ota] binary mode ON" marker line is the go-ahead -
* the host must wait for it before sending frames. Exits: OTA_ABORT
* frame (immediate), 3 s idle timeout (fallback), or device reset
* after a successful OTA_END. The BLE OTA channel (...e0005) is
* always available and does not need this command.
*/
static void CmdOta(int argc, char* argv[])
{
(void)argc;
(void)argv;
cli_write("[ota] binary mode ON - send 0xCA OTA frames now\r\n"
"(ble_protocol.md section 6.6; OTA_ABORT or 3s idle exits)\r\n");
app_cli_ota_mode_enter();
}
/* Command table; add new commands here */
static const CliCmd_t s_cmds[] = {
{"help", "help [cmd]: list commands / show one command's help", CmdHelp},
{"version", "show firmware version", CmdVersion},
{"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo},
{"devinfo", "show device info (temp/vdd/fan, same as BLE INFO_QUERY all)", CmdDevInfo},
{"blelog", "blelog [on|off]: BLE traffic log (events + frame hex dump)", CmdBleLog},
{"led", "led <1|2> <on|off|toggle>: control board LED", CmdLed},
{"appget", "appget [json]: show app params (led/pwm)", AppParams_CmdAppget},
{"appset", "appset <led|pwm> <val> | appset json {\"led\":500}", AppParams_CmdAppset},
{"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump},
{"bsset", "bsset <field> <val>: write bootsetting field (careful!)", AppBootset_CmdBsset},
{"appsw", "appsw [1|2]: switch boot bank (crc verified) + reboot", AppBootset_CmdAppswitch},
{"ota", "ota: UART enters OTA binary frame mode (see help ota)", CmdOta},
{"reset", "reset: system reset (reboot)", CmdReset},
{"factory", "factory: restore params to defaults and reboot", CmdFactory},
{"uartrst", "uartrst: re-init USART1 (460800 8N1), flush rx queue", CmdUartRst},
{"uartinfo", "uartinfo: show USART1 pins/baud/format", CmdUartInfo},
};
#define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0]))
/**
* @brief "help [cmd]": without an argument, list all commands with their
* one-line help (the full table exceeds 512B and relies on the
* chunked BLE read of the CLI characteristic); with an argument,
* show that command's detailed help only.
*/
static void CmdHelp(int argc, char* argv[])
{
uint32_t i;
if (argc >= 2)
{
for (i = 0; i < CLI_CMD_COUNT; i++)
{
if (strcmp(argv[1], s_cmds[i].name) == 0)
{
cli_printf(" %-8s %s\r\n", s_cmds[i].name, s_cmds[i].help);
return;
}
}
cli_printf("unknown command: %s (try 'help')\r\n", argv[1]);
return;
}
cli_write("commands:\r\n");
for (i = 0; i < CLI_CMD_COUNT; i++)
{
cli_printf(" %-8s %s\r\n", s_cmds[i].name, s_cmds[i].help);
}
cli_write("keys: TAB = complete, UP/DOWN = history\r\n");
}
uint32_t cli_cmd_count(void)
{
return CLI_CMD_COUNT;
}
const char* cli_cmd_name(uint32_t idx)
{
return (idx < CLI_CMD_COUNT) ? s_cmds[idx].name : NULL;
}
/* ------------------------------------------------------------------ */
/* Line parsing / dispatch */
/* ------------------------------------------------------------------ */
int cli_exec_line(char* line)
{
char* argv[CLI_MAX_ARGS];
int argc = 0;
char* p = line;
uint32_t i;
/* Split on spaces/tabs */
while (*p != '\0' && argc < CLI_MAX_ARGS)
{
while (*p == ' ' || *p == '\t')
{
p++;
}
if (*p == '\0')
{
break;
}
argv[argc++] = p;
while (*p != '\0' && *p != ' ' && *p != '\t')
{
p++;
}
if (*p != '\0')
{
*p++ = '\0';
}
}
if (argc == 0)
{
return 0;
}
for (i = 0; i < CLI_CMD_COUNT; i++)
{
if (strcmp(argv[0], s_cmds[i].name) == 0)
{
s_cmds[i].handler(argc, argv);
return 0;
}
}
cli_printf("unknown command: %s (try 'help')\r\n", argv[0]);
return 1;
}