/* ============================================================================= * applog.c - UART 디버그 로그 (RPi setup_logger() 대응) * * USART3(PD8 TX / PD9 RX, APP_LOG_UART_BAUD) 로 printf 스타일 * 라인 로그를 출력한다. 라인은 다음 형식으로 찍힌다: * * [ 12345 ms] INFO 메시지... * * 직렬화: * FreeRTOS 스케줄러 동작 중이면 뮤텍스로 라인 단위 직렬화한다. * 부팅 초기(스케줄러 시작 전, 또는 ISR 문맥)에는 뮤텍스 없이 그대로 출력한다. * * USART3 HAL 핸들과 MspInit(핀/클럭) 은 이 파일이 소유한다. * ===========================================================================*/ #include "applog.h" #include "app_config.h" #include "stm32f4xx_hal.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" #include #include #include /* ── 설정 상수 ───────────────────────────────────────────────────────────── */ /* 한 줄 로그 최대 길이(접두부 포함). 스택 버퍼 크기. */ #define LOG_LINE_MAX 160 /* HAL_UART_Transmit 블로킹 타임아웃(ms). */ #define LOG_TX_TIMEOUT_MS 100u /* 뮤텍스 획득 대기(ms). 초과 시 직렬화 포기하고 그대로 출력(로그는 best-effort). */ #define LOG_MUTEX_WAIT_MS 50u /* ── USART3 HAL 핸들 (이 모듈 소유) ───────────────────────────────────────── */ static UART_HandleTypeDef huart_log; /* ── 직렬화용 뮤텍스 + 초기화 플래그 ─────────────────────────────────────── */ static SemaphoreHandle_t s_logMutex = NULL; static volatile int s_inited = 0; /* ============================================================================= * USART3 MspInit — PD8(TX)/PD9(RX), AF7, 클럭 게이팅 * HAL_UART_Init() 내부에서 HAL_UART_MspInit() 약함수 형태로 호출된다. * (여러 UART 를 쓰는 프로젝트라면 instance 분기. 여기선 USART3 만 처리.) * ===========================================================================*/ void HAL_UART_MspInit(UART_HandleTypeDef *uartHandle) { GPIO_InitTypeDef g = {0}; if (uartHandle->Instance != USART3) { return; /* 다른 UART 는 해당 모듈이 처리 */ } __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_USART3_CLK_ENABLE(); /* PD8=USART3_TX, PD9=USART3_RX, AF7. * (PA2/PA3 는 LAN8720 RMII 의 ETH_MDIO/미사용과 충돌하므로 사용 불가 → * 로그 UART 는 USART3 PD8/PD9 로 배치한다. HARDWARE.md 핀맵 참조.) */ g.Pin = GPIO_PIN_8 | GPIO_PIN_9; g.Mode = GPIO_MODE_AF_PP; g.Pull = GPIO_PULLUP; g.Speed = GPIO_SPEED_FREQ_VERY_HIGH; g.Alternate = GPIO_AF7_USART3; HAL_GPIO_Init(GPIOD, &g); } /* ============================================================================= * applog_init — USART3 초기화 + 로그 뮤텍스 생성 * ===========================================================================*/ void applog_init(void) { huart_log.Instance = USART3; huart_log.Init.BaudRate = APP_LOG_UART_BAUD; huart_log.Init.WordLength = UART_WORDLENGTH_8B; huart_log.Init.StopBits = UART_STOPBITS_1; huart_log.Init.Parity = UART_PARITY_NONE; huart_log.Init.Mode = UART_MODE_TX_RX; huart_log.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart_log.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart_log) != HAL_OK) { /* UART 를 못 켜면 로그는 무음 처리(디바이스 동작 자체는 계속). */ s_inited = 0; return; } /* 뮤텍스 생성 시도. 실패해도(메모리 부족 등) 로그는 비직렬화로 동작. */ s_logMutex = xSemaphoreCreateMutex(); s_inited = 1; } /* ── 스케줄러가 돌고 있고 ISR 문맥이 아닐 때만 뮤텍스를 쓴다 ──────────────── */ static int log_can_use_mutex(void) { if (s_logMutex == NULL) { return 0; } if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { return 0; /* 스케줄러 시작 전: 뮤텍스 블로킹 불가 */ } if (__get_IPSR() != 0u) { return 0; /* ISR 문맥: 블로킹 뮤텍스 호출 금지 */ } return 1; } /* ── 원시 바이트 전송(블로킹) ────────────────────────────────────────────── */ static void log_tx(const char *buf, uint16_t len) { if (len == 0u) { return; } (void)HAL_UART_Transmit(&huart_log, (uint8_t *)buf, len, LOG_TX_TIMEOUT_MS); } /* ============================================================================= * applog — printf 스타일 라인 로그 * 형식: "[%8lu ms] %-5s \r\n" * ===========================================================================*/ void applog(const char *level, const char *fmt, ...) { char line[LOG_LINE_MAX]; int n; int pos = 0; va_list ap; int locked = 0; if (!s_inited) { return; /* UART 미초기화: 무음 */ } if (level == NULL) { level = "?"; } /* 업타임 스탬프(ms). 스케줄러 전/후 모두 HAL_GetTick() 으로 일관 — TIM6 기반. */ n = snprintf(line, sizeof(line), "[%8lu ms] %-5s ", (unsigned long)HAL_GetTick(), level); if (n < 0) { return; } pos = (n < (int)sizeof(line)) ? n : (int)sizeof(line) - 1; /* 사용자 메시지 */ va_start(ap, fmt); n = vsnprintf(line + pos, sizeof(line) - (size_t)pos, fmt, ap); va_end(ap); if (n > 0) { pos += n; if (pos > (int)sizeof(line) - 1) { pos = (int)sizeof(line) - 1; /* vsnprintf 가 잘라낸 경우 */ } } /* 줄바꿈(\r\n). 공간이 모자라면 마지막 2바이트를 덮어쓴다. */ if (pos > (int)sizeof(line) - 3) { pos = (int)sizeof(line) - 3; } line[pos++] = '\r'; line[pos++] = '\n'; /* 직렬화 후 전송 */ if (log_can_use_mutex()) { if (xSemaphoreTake(s_logMutex, pdMS_TO_TICKS(LOG_MUTEX_WAIT_MS)) == pdTRUE) { locked = 1; } } log_tx(line, (uint16_t)pos); if (locked) { (void)xSemaphoreGive(s_logMutex); } }