/** * @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). UART frontend lives in app_cli.c, BLE frame * frontend in app/ble/app_ble_proto.c. * * @note Concurrency: the output callback and the shared TX buffer are single * instances. The UART frontend (CliTask) and the BLE frontend (BleTask) * are expected to execute commands rarely and never concurrently; the * BLE frontend sets/restores the callback around each cli_exec_line() * and the UART frontend forces the default (NULL) before dispatch. */ #include "cli_core.h" #include "app_version.h" #include "bsp_usart.h" #include "bsp_led.h" #include "app_tasks.h" #include "app_ota.h" #include "dfu_layout.h" #include #include #include #include #include "boot_crc32.h" #include "FreeRTOS.h" #include "task.h" #define CLI_TX_BUF_SIZE 96 #define CLI_MAX_ARGS 8 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 UART DMA output */ /* ------------------------------------------------------------------ */ /* Output */ /* ------------------------------------------------------------------ */ /** * @brief Default output: send a string through USART1 DMA TX. * @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("N32WB031 FreeRTOS + UART(DMA) + LED + CLI demo\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 "log [on|off]": show or set the active log printing switch. */ static void CmdLog(int argc, char* argv[]) { if (argc < 2) { cli_printf("log is %s\r\n", AppTasks_GetLogEnabled() ? "on" : "off"); return; } if (strcmp(argv[1], "on") == 0) { AppTasks_SetLogEnabled(1); } else if (strcmp(argv[1], "off") == 0) { AppTasks_SetLogEnabled(0); } else { cli_write("usage: log [on|off]\r\n"); return; } cli_printf("log set to %s\r\n", AppTasks_GetLogEnabled() ? "on" : "off"); } /** * @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) { bsp_led_on(port, pin); } else if (strcmp(argv[2], "off") == 0) { bsp_led_off(port, pin); } else if (strcmp(argv[2], "toggle") == 0) { bsp_led_toggle(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 "bankinfo": show the running bank and the bootsetting record. */ static void CmdBankInfo(int argc, char* argv[]) { caiic_bootsetting_t bs; uint32_t self = app_ota_current_bank_base(); (void)argc; (void)argv; cli_printf("running bank: %s (base 0x%08lX)\r\n", (self == CAIIC_APP1_BASE) ? "APP1" : (self == CAIIC_APP2_BASE) ? "APP2" : "UNKNOWN", (unsigned long)self); memcpy(&bs, (const void*)CAIIC_BOOTSETTING_ADDR, sizeof(bs)); if (bs.magic != CAIIC_BOOT_MAGIC) { cli_write("bootsetting: invalid (blank or magic mismatch)\r\n"); return; } cli_printf("bootsetting: active_bank=%u, struct_crc %s\r\n", (unsigned int)bs.active_bank, (bs.crc32 == caiic_crc32((const uint8_t*)CAIIC_BOOTSETTING_ADDR, (uint32_t)offsetof(caiic_bootsetting_t, crc32))) ? "ok" : "BAD"); cli_printf("bank1: start=0x%08lX size=%lu crc32=0x%08lX version=0x%06lX\r\n", (unsigned long)bs.bank1.start_address, (unsigned long)bs.bank1.size, (unsigned long)bs.bank1.crc32, (unsigned long)bs.bank1.version); cli_printf("bank2: start=0x%08lX size=%lu crc32=0x%08lX version=0x%06lX\r\n", (unsigned long)bs.bank2.start_address, (unsigned long)bs.bank2.size, (unsigned long)bs.bank2.crc32, (unsigned long)bs.bank2.version); } /* Command table; add new commands here */ static const CliCmd_t s_cmds[] = { {"help", "list all commands", CmdHelp}, {"version", "show firmware version", CmdVersion}, {"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo}, {"log", "log [on|off]: active printing switch (default off)", CmdLog}, {"led", "led <1|2> : control board LED", CmdLed}, {"bankinfo", "show running bank and bootsetting record", CmdBankInfo}, }; #define CLI_CMD_COUNT (sizeof(s_cmds) / sizeof(s_cmds[0])) /** * @brief "help": list all registered commands. */ static void CmdHelp(int argc, char* argv[]) { uint32_t i; (void)argc; (void)argv; 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, Ctrl+Z = log off\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; }