- disptest: 自动循环 0.0~9.0 -> 88888(全段) -> ABCDE(hex) -> 72.0F -> -123.4 - 测试期间强制开自动扫描, 结束恢复原 auto 状态并回 0.0 - 复核段位公式/管脚表/DIG6 指示段/解码逻辑, 无其它缺陷(除已修 GPIOA 时钟) - 仅 APP 改动, Boot 不动 (仍 V1.00.07) - docs/开发日志.md: 新增 §64
1011 lines
27 KiB
C
1011 lines
27 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 "app_display.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 "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 <pxN> [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 <pxN> [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 <pxN> [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 <value> [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 <value> [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 <pin1-7|all> [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 <pin1-7|all> [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 <pin1-7> [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> <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},
|
|
{"pinset", "pinset <pxN> [0|1]: set/read GPIO level (pa0-6/pb0-13)", CmdPinSet},
|
|
{"disp", "disp <value> [f] [hex]: set 7-seg display value", CmdDisp},
|
|
{"dispauto", "dispauto [on|off]: enable/disable auto display scan", CmdDispAuto},
|
|
{"dispset", "dispset <pin1-7|all> [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;
|
|
}
|