/** * @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 "app_display.h" #include "n32wb03x.h" /* SystemTrimValueGet (temp debug) */ #include #include #include #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> ": 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> \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> \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> \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 "hang": wedge the CLI task in a busy loop so the idle hook stops * feeding the IWDG - watchdog fire test (device resets in ~4s and * the next banner shows "Last reset: IWDG watchdog!"). */ static void CmdHang(int argc, char* argv[]) { (void)argc; (void)argv; cli_write("hanging now - watchdog should reset in ~4s ...\r\n"); vTaskDelay(pdMS_TO_TICKS(CLI_RESET_DELAY_MS)); for (;;) { /* busy: idle task starves, IWDG unfed */ } } /** * @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: DMA CH2 ring (3KB, idle irq)\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(); } /** * @brief Parse a "pxN" pin name (e.g. "pb0", "PA3", "PB13") into a GPIO port * and pin mask. @return 1 on success, 0 on bad name/out-of-range pin. */ static int pinset_parse(const char* name, GPIO_Module** port, uint16_t* mask) { const char* p; int pin = 0; if (name[0] != 'p' && name[0] != 'P') { return 0; } if (name[1] == 'a' || name[1] == 'A') { *port = GPIOA; } else if (name[1] == 'b' || name[1] == 'B') { *port = GPIOB; } else { return 0; } p = &name[2]; if (*p < '0' || *p > '9') { return 0; } while (*p >= '0' && *p <= '9') { pin = pin * 10 + (*p - '0'); if (pin > 15) { return 0; } p++; } if (*p != '\0') { return 0; } if (*port == GPIOA && pin > 6) /* PA7 not bonded on this chip */ { return 0; } if (*port == GPIOB && pin > 13) /* PB14/PB15 not bonded */ { return 0; } *mask = (uint16_t)(1u << pin); return 1; } /** * @brief "pinset [0|1]": set a GPIO output high/low, or read its level. * e.g. "pinset pb0 1", "pinset pa0 0", "pinset pb0" (read level). */ static void CmdPinSet(int argc, char* argv[]) { GPIO_Module* port = NULL; uint16_t mask = 0; GPIO_InitType s; if (argc < 2) { cli_write("usage: pinset [0|1] (e.g. pinset pb0 1)\r\n"); return; } if (!pinset_parse(argv[1], &port, &mask)) { cli_printf("bad pin: %s (use pa0..pa6 / pb0..pb13)\r\n", argv[1]); return; } if (port == GPIOA) { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE); } else { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE); } if (argc >= 3) { /* reserved-pin guard: warn before reconfiguring SWD / console pins */ if ((port == GPIOA && (mask & (GPIO_PIN_4 | GPIO_PIN_5)) != 0u) || (port == GPIOB && (mask & (GPIO_PIN_6 | GPIO_PIN_7)) != 0u)) { cli_write("warning: SWD/console pin - reconfigure may break the link\r\n"); } GPIO_InitStruct(&s); s.Pin = mask; s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); if (argv[2][0] == '1' && argv[2][1] == '\0') { GPIO_SetBits(port, mask); cli_printf("%s = 1\r\n", argv[1]); } else if (argv[2][0] == '0' && argv[2][1] == '\0') { GPIO_ResetBits(port, mask); cli_printf("%s = 0\r\n", argv[1]); } else { cli_write("usage: pinset [0|1] (0=low, 1=high)\r\n"); } } else { /* read: the input data register reflects the actual pin level */ cli_printf("%s = %u\r\n", argv[1], (unsigned)(GPIO_ReadInputDataBit(port, mask) ? 1u : 0u)); } } /** * @brief Parse a decimal floating-point number ("36.5", "-1.2", "88"). * @return 1 on success, 0 on bad input. */ static int cli_parse_float(const char* s, float* out) { float v = 0.0f; float frac = 0.1f; int neg = 0; int any = 0; if (*s == '-') { neg = 1; s++; } else if (*s == '+') { s++; } while (*s >= '0' && *s <= '9') { v = v * 10.0f + (float)(*s - '0'); s++; any = 1; } if (*s == '.') { s++; while (*s >= '0' && *s <= '9') { v += frac * (float)(*s - '0'); frac *= 0.1f; s++; any = 1; } } if (!any || *s != '\0') { return 0; } *out = neg ? -v : v; return 1; } /** * @brief Parse a hexadecimal integer ("ff", "0xFF", "10"). * @return 1 on success, 0 on bad input. */ static int cli_parse_hex(const char* s, uint32_t* out) { uint32_t v = 0u; int any = 0; if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { s += 2; } for (;;) { uint32_t d; char c = *s; if (c >= '0' && c <= '9') { d = (uint32_t)(c - '0'); } else if (c >= 'a' && c <= 'f') { d = (uint32_t)(c - 'a' + 10); } else if (c >= 'A' && c <= 'F') { d = (uint32_t)(c - 'A' + 10); } else { break; } v = (v << 4) | d; any = 1; s++; } if (!any || *s != '\0') { return 0; } *out = v; return 1; } /** * @brief "disp [f] [hex]": set what the 7-seg display shows. * Default: decimal with 1 decimal place, Celsius (degF indicator off). * "f" turns on the degF indicator; "hex" displays the integer in hex. */ static void CmdDisp(int argc, char* argv[]) { uint8_t type = DISP_TYPE_CELSIUS; uint8_t base = DISP_BASE_DECIMAL; float value = 0.0f; uint32_t hexval = 0u; int i; if (argc < 2) { cli_write("usage: disp [f] [hex] (e.g. disp 36.5, disp ff hex)\r\n"); return; } /* scan mode flags first (so the value is parsed in the right base) */ for (i = 2; i < argc; i++) { if (strcmp(argv[i], "f") == 0) { type = DISP_TYPE_FAHRENHEIT; } else if (strcmp(argv[i], "hex") == 0 || strcmp(argv[i], "16") == 0) { base = DISP_BASE_HEX; } else { cli_printf("bad mode: %s (use f / hex)\r\n", argv[i]); return; } } if (base == DISP_BASE_HEX) { if (!cli_parse_hex(argv[1], &hexval)) { cli_printf("bad hex value: %s\r\n", argv[1]); return; } value = (float)hexval; } else if (!cli_parse_float(argv[1], &value)) { cli_printf("bad value: %s\r\n", argv[1]); return; } display_set(value, type, base); if (base == DISP_BASE_HEX) { cli_printf("display = 0x%lX %c\r\n", (unsigned long)hexval, (type == DISP_TYPE_FAHRENHEIT) ? 'F' : 'C'); } else { int scaled = (int)(value * 10.0f + (value < 0.0f ? -0.5f : 0.5f)); int ip = scaled / 10; int fp = scaled - ip * 10; if (fp < 0) { fp = -fp; } cli_printf("display = %d.%d %c\r\n", ip, fp, (type == DISP_TYPE_FAHRENHEIT) ? 'F' : 'C'); } } /** * @brief Parse a display pin selector "pin1".."pin7" (or bare "1".."7"). * @return 1 on success, 0 on bad input. */ static int cli_parse_disp_pin(const char* s, uint8_t* pin_no) { if (s[0] == 'p' || s[0] == 'P') { if ((s[1] != 'i' && s[1] != 'I') || (s[2] != 'n' && s[2] != 'N')) { return 0; } s += 3; } if (s[0] < '1' || s[0] > '7' || s[1] != '\0') { return 0; } *pin_no = (uint8_t)(s[0] - '0'); return 1; } /** * @brief Format a GPIO port + pin mask as a pin name ("PA6"/"PB0"/"PB11"). */ static void cli_pin_str(GPIO_Module* port, uint16_t pin, char* out) { char pc = (port == GPIOA) ? 'A' : 'B'; uint8_t n = 0u; while ((pin & 1u) == 0u) { pin >>= 1; n++; } if (n >= 10u) { out[0] = 'P'; out[1] = pc; out[2] = '1'; out[3] = (char)('0' + (n - 10u)); out[4] = '\0'; } else { out[0] = 'P'; out[1] = pc; out[2] = (char)('0' + n); out[3] = '\0'; } } /** * @brief "dispauto [on|off]": enable/disable the automatic display scan. * Turning it off releases all 7 display pins to high-impedance so * "dispset" can drive them manually. */ static void CmdDispAuto(int argc, char* argv[]) { if (argc >= 2) { if (strcmp(argv[1], "on") == 0) { display_auto_set(1u); } else if (strcmp(argv[1], "off") == 0) { display_auto_set(0u); } else { cli_write("usage: dispauto [on|off]\r\n"); return; } } cli_printf("auto display: %s\r\n", display_auto_get() ? "on" : "off"); } /** * @brief Drive one display pin (1..7) to 0 / 1 / high-Z. * @return 1 on success, 0 on bad value, -1 on bad pin number. */ static int cli_disp_set_pin(uint8_t pin_no, char val) { GPIO_Module* port; uint16_t pin; GPIO_InitType s; if (!display_pin_map(pin_no, &port, &pin)) { return -1; } /* Make sure the port clock is on before touching the pin (defensive; * display_task also enables both clocks at boot). */ if (port == GPIOA) { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE); } else { RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOB, ENABLE); } GPIO_InitStruct(&s); s.Pin = pin; if (val == '1') { s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); GPIO_SetBits(port, pin); } else if (val == '0') { s.GPIO_Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitPeripheral(port, &s); GPIO_ResetBits(port, pin); } else if (val == 'z' || val == 'Z') { s.GPIO_Mode = GPIO_MODE_INPUT; s.GPIO_Pull = GPIO_NO_PULL; GPIO_InitPeripheral(port, &s); } else { return 0; } return 1; } /** * @brief "dispset [0|1|z]": drive display pin(s) manually. * pin1..pin7 map to PB0/PB1/PA0/PA1/PB11/PB13/PA6; "all" hits all 7. * "z" = high-Z. No value reads one pin's level. Use after "dispauto off". */ static void CmdDispSet(int argc, char* argv[]) { if (argc < 2) { cli_write("usage: dispset [0|1|z]\r\n"); return; } /* "all": apply the value to all 7 display pins */ if (strcmp(argv[1], "all") == 0 || strcmp(argv[1], "ALL") == 0) { uint8_t p; if (argc < 3 || argv[2][1] != '\0') { cli_write("usage: dispset all [0|1|z]\r\n"); return; } if (display_auto_get()) { cli_write("warning: auto display is ON - use 'dispauto off' first\r\n"); } for (p = 1u; p <= 7u; p++) { if (cli_disp_set_pin(p, argv[2][0]) != 1) { cli_write("usage: dispset all [0|1|z]\r\n"); return; } } cli_printf("all 7 display pins = %s\r\n", (argv[2][0] == 'z' || argv[2][0] == 'Z') ? "high-Z" : argv[2]); return; } /* single pin */ { GPIO_Module* port; uint16_t pin; uint8_t pin_no; char name[8]; if (!cli_parse_disp_pin(argv[1], &pin_no) || !display_pin_map(pin_no, &port, &pin)) { cli_printf("bad pin: %s (use pin1..pin7 or all)\r\n", argv[1]); return; } cli_pin_str(port, pin, name); if (argc >= 3) { if (display_auto_get()) { cli_write("warning: auto display is ON - use 'dispauto off' first\r\n"); } if (argv[2][1] == '\0' && cli_disp_set_pin(pin_no, argv[2][0]) == 1) { if (argv[2][0] == 'z' || argv[2][0] == 'Z') { cli_printf("pin%u (%s) = high-Z\r\n", (unsigned)pin_no, name); } else { cli_printf("pin%u (%s) = %c\r\n", (unsigned)pin_no, name, argv[2][0]); } } else { cli_write("usage: dispset [0|1|z]\r\n"); } } else { cli_printf("pin%u (%s) = %u\r\n", (unsigned)pin_no, name, (unsigned)(GPIO_ReadInputDataBit(port, pin) ? 1u : 0u)); } } } /** * @brief "disptest": automated display self-test. Cycles the display through * digits 0..9, "88888" (all segments), hex "ABCDE", a degF value and * "-123.4" so every digit/segment can be verified visually. Restores * the previous auto-scan state when finished. */ static void CmdDispTest(int argc, char* argv[]) { static const float s_digits[10] = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }; uint8_t prev_auto = display_auto_get(); uint32_t i; (void)argc; (void)argv; /* the test drives values through display_set(), so auto must be on */ display_auto_set(1u); cli_write("display self-test: 0..9 -> 88888 -> ABCDE -> 72.0F -> -123.4\r\n"); for (i = 0; i < 10u; i++) { display_set(s_digits[i], DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_printf(" %lu.0\r\n", (unsigned long)i); vTaskDelay(pdMS_TO_TICKS(500)); } display_set(8888.8f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" 88888 (all segments)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); display_set((float)0xABCDEu, DISP_TYPE_CELSIUS, DISP_BASE_HEX); cli_write(" ABCDE (hex)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); display_set(72.0f, DISP_TYPE_FAHRENHEIT, DISP_BASE_DECIMAL); cli_write(" 72.0 F (degF indicator on)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); display_set(-123.4f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); cli_write(" -123.4 (minus sign)\r\n"); vTaskDelay(pdMS_TO_TICKS(1200)); display_set(0.0f, DISP_TYPE_CELSIUS, DISP_BASE_DECIMAL); display_auto_set(prev_auto); cli_write("display self-test done\r\n"); } /* 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> : control board LED", CmdLed}, {"appget", "appget [json]: show app params (led/pwm)", AppParams_CmdAppget}, {"appset", "appset | appset json {\"led\":500}", AppParams_CmdAppset}, {"bsdump", "bsdump: show bootsetting record + crc check", AppBootset_CmdBsdump}, {"bsset", "bsset : 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}, {"pinset", "pinset [0|1]: set/read GPIO level (pa0-6/pb0-13)", CmdPinSet}, {"disp", "disp [f] [hex]: set 7-seg display value", CmdDisp}, {"dispauto", "dispauto [on|off]: enable/disable auto display scan", CmdDispAuto}, {"dispset", "dispset [0|1|z]: drive display pin(s)", CmdDispSet}, {"disptest", "disptest: run display self-test (0-9/all-seg/hex/degF/minus)", CmdDispTest}, {"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}, {"hang", "hang: wedge the CLI task (IWDG watchdog fire test)", CmdHang}, }; #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("mode: APP\r\n"); 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; }