개발할 때마다 디버거 연결하기 귀찮다. 간단한 명령으로 상태 보고 설정할 수 있으면 좋겠다.
UART CLI 만들었다.
CLI 구조
typedef struct {
const char *cmd;
void (*handler)(int argc, char **argv);
const char *help;
} CLI_Command_t;
const CLI_Command_t commands[] = {
{"help", CLI_Help, "Show commands"},
{"status", CLI_Status, "Show BMS status"},
{"cells", CLI_Cells, "Show cell voltages"},
{"temp", CLI_Temp, "Show temperatures"},
{"log", CLI_Log, "Show/clear logs"},
{"bal", CLI_Balance, "Balance control"},
{"reset", CLI_Reset, "Reset BMS"},
{NULL, NULL, NULL}
};
명령 파싱
void CLI_Process(char *line) {
char *argv[8];
int argc = 0;
// 공백으로 분리
char *token = strtok(line, " ");
while (token && argc < 8) {
argv[argc++] = token;
token = strtok(NULL, " ");
}
if (argc == 0) return;
// 명령 찾기
for (int i = 0; commands[i].cmd; i++) {
if (strcmp(argv[0], commands[i].cmd) == 0) {
commands[i].handler(argc, argv);
return;
}
}
printf("Unknown command: %s\n", argv[0]);
}
status 명령
void CLI_Status(int argc, char **argv) {
printf("=== BMS Status ===\n");
printf("State: %s\n", state_names[g_bms.state]);
printf("Pack: %.1fV, %.1fA\n",
g_bms.pack_voltage_mv / 1000.0f,
g_bms.pack_current_ma / 1000.0f);
printf("SOC: %d%%\n", g_bms.soc);
printf("Temp: %d~%d C\n", g_bms.min_temp / 10, g_bms.max_temp / 10);
printf("Cells: %dmV ~ %dmV (delta %dmV)\n",
g_bms.min_cell_mv, g_bms.max_cell_mv,
g_bms.max_cell_mv - g_bms.min_cell_mv);
}
출력:
=== BMS Status ===
State: NORMAL
Pack: 72.3V, 15.2A
SOC: 85%
Temp: 28~32 C
Cells: 3312mV ~ 3348mV (delta 36mV)
cells 명령
void CLI_Cells(int argc, char **argv) {
printf("=== Cell Voltages ===\n");
for (int i = 0; i < 24; i++) {
char bal = g_balancing[i] ? '*' : ' ';
printf("C%02d: %4dmV %c", i + 1, g_cells_mv[i], bal);
if ((i + 1) % 4 == 0) printf("\n");
}
}
출력:
=== Cell Voltages ===
C01: 3312mV C02: 3320mV C03: 3315mV C04: 3348mV *
C05: 3318mV C06: 3325mV C07: 3330mV C08: 3342mV *
...
* 표시가 밸런싱 중인 셀.
bal 명령
수동 밸런싱 제어:
void CLI_Balance(int argc, char **argv) {
if (argc < 2) {
printf("Usage: bal <on|off|auto> [cell]\n");
return;
}
if (strcmp(argv[1], "on") == 0 && argc >= 3) {
int cell = atoi(argv[2]);
if (cell >= 1 && cell <= 24) {
g_balancing[cell - 1] = true;
printf("Cell %d balance ON\n", cell);
}
}
else if (strcmp(argv[1], "off") == 0) {
for (int i = 0; i < 24; i++) g_balancing[i] = false;
printf("All balance OFF\n");
}
else if (strcmp(argv[1], "auto") == 0) {
g_balance_mode = BALANCE_AUTO;
printf("Auto balance mode\n");
}
}
시리얼 수신
인터럽트로 한 줄씩 받기:
char rx_buf[64];
int rx_idx = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
char c = rx_char;
if (c == '\r' || c == '\n') {
rx_buf[rx_idx] = '\0';
if (rx_idx > 0) {
CLI_Process(rx_buf);
}
rx_idx = 0;
printf("> "); // 프롬프트
} else {
if (rx_idx < sizeof(rx_buf) - 1) {
rx_buf[rx_idx++] = c;
}
}
HAL_UART_Receive_IT(huart, &rx_char, 1);
}
CAN 진단
UDS(Unified Diagnostic Services) 간략 버전:
void CAN_DiagHandler(uint8_t *data) {
uint8_t service = data[0];
switch (service) {
case 0x22: // Read Data By ID
CAN_DiagReadData(data[1], data[2]);
break;
case 0x2E: // Write Data By ID
CAN_DiagWriteData(data[1], data[2], &data[3]);
break;
case 0x19: // Read DTC
CAN_DiagReadDTC();
break;
case 0x14: // Clear DTC
CAN_DiagClearDTC();
break;
}
}
정리
- UART CLI: 개발/디버깅용
- 명령: status, cells, temp, log, bal, reset
- CAN 진단: UDS 기반
현장 가서 노트북 연결하고 status 치면 바로 상태 파악.
Part 6 통신 & 진단편 끝.
다음은 고급 기능.