임베디드 시스템 심층 디버깅: FreeRTOS를 위한 SEGGER RTT 및 SystemView 활용
임베디드 시스템의 효과적인 디버깅과 성능 분석은 마이크로컨트롤러 개발의 핵심 요소입니다. SEGGER RTT(Real-Time Transfer)와 SystemView 기술은 이러한 작업을 위한 강력한 도구를 제공하여, 개발자가 타겟 장치의 작동을 멈추지 않고도 FreeRTOS와 같은 실시간 운영체제(RTOS)의 상세한 디버그 정보를 얻고 심층 모니터링을 수행할 수 있도록 돕습니다. 이 글에서는 임베디드 소프트웨어의 복잡한 동작 패턴을 진단하는 데 매우 중요한 RTT Viewer를 이용한 로그 출력과 SystemView를 이용한 RTOS 이벤트 시각화 설정의 작동 원리 및 실제 적용 방법을 살펴보겠습니다.
임베디드 시스템 디버깅의 기본과 SEGGER RTT의 역할
임베디드 시스템을 디버깅하려면 타겟 장치의 동작에 미치는 영향을 최소화하는 특수 접근 방식이 필요합니다. 브레이크포인트를 사용한 JTAG/SWD 디버깅과 같은 전통적인 방식은 원치 않는 지연을 유발할 수 있습니다. 바로 이 지점에서 DAP(Debug Access Port), ITM(Instrumentation Trace Macrocell), RTT(Real-Time Transfer)와 같은 메커니즘이 해결책으로 등장합니다.
- DAP (Debug Access Port): 마이크로컨트롤러의 버스와 코어에 디버깅을 위한 접근을 제공하는 하드웨어 블록입니다.
- ITM (Instrumentation Trace Macrocell): Cortex-M 코어(M3부터)에 탑재된 특수 블록으로, 최소한의 시간 오버헤드로 고속 디버그 메시지 출력을 위해 설계되었습니다.
- RTT (Real-Time Transfer): SEGGER의 독점 기술로, 마이크로컨트롤러 RAM의 순환 버퍼를 사용하여 호스트 컴퓨터와 양방향 데이터 교환을 합니다. RTT의 주요 장점은 비침해성(non-intrusiveness)입니다. 마이크로컨트롤러의 CPU가 멈추지 않으며, 디버그 정보가 매우 빠른 속도로 전송되어 실시간 로그 출력(
printf)에 이상적입니다.
프로젝트에 RTT를 통합하려면 SEGGER RTT 라이브러리 소스 파일을 추가해야 합니다. 이 과정은 Keil uVision이든 다른 IDE든 특정 통합 개발 환경(IDE)에 얽매이지 않습니다. 컴파일 및 실행 후, J-Link 디버거는 마이크로컨트롤러 RAM에서 RTT 제어 블록(CB) 구조를 자동으로 검색합니다. 이 블록에는 식별자, 채널 수, 읽기 및 쓰기를 위한 순환 버퍼 설명이 포함되어 데이터 교환을 위한 진입점을 제공합니다. 대부분의 경우 검색은 자동화되지만, 수동 주소 구성도 가능합니다.
RTT 연결 및 사용 예시:
헤더 파일 포함:
#include <stdio.h>
#include "SEGGER_RTT.h"
유형별 메시지 래퍼 (선택 사항, 편의를 위해):
#define LOG_INFO(msg, ...) { SEGGER_RTT_TerminalOut(0, "INFO: "); SEGGER_RTT_printf(0, msg, ##__VA_ARGS__); }
#define LOG_ERR(msg, ...) { SEGGER_RTT_TerminalOut(0, "ERROR: "); SEGGER_RTT_printf(0, msg, ##__VA_ARGS__); }
void LOG_FLOAT(const char* prefix, float value, const char* suffix) {
char buf[64];
snprintf(buf, sizeof(buf), "%s%.2f%s", prefix, value, suffix);
SEGGER_RTT_TerminalOut(0, buf);
}
제어 블록 초기화 및 로그 출력:
SEGGER_RTT_Init();
// ...
LOG_INFO("Central Heating: %s\r\n", Boiler.isCentralHeatingActive(response) ? "on" : "off");
LOG_ERR("Error: OpenTherm is not initialized");
J-Link 드라이버 패키지에 포함된 RTT Viewer 유틸리티를 사용하면 이러한 로그를 볼 수 있습니다. 이 유틸리티는 다양한 탭(터미널)으로 정보를 출력하고, 여러 RTT 채널을 사용하여 더욱 고급 사용자 지정 데이터 전송을 지원합니다. RTT의 장점으로는 추가 마이크로컨트롤러 핀(예: SWO)을 사용하지 않고 SWD를 통해 printf를 출력할 수 있는 기능, Cortex-M0/M0+와의 호환성, 쉬운 설정, 그리고 다른 방법에 비해 매우 빠른 작동 속도가 있습니다.
SystemView: 심층 실시간 RTOS 성능 분석
SystemView는 RTT를 전송 계층으로 사용하여 임베디드 시스템 작동을 실시간으로 분석하고 시각화하는 강력한 SEGGER 도구입니다. 이를 통해 개발자는 태스크 전환, 인터럽트 처리 및 기타 시스템 이벤트를 추적하여 FreeRTOS 동작을 더 깊이 이해할 수 있습니다. SystemView 설정은 RTT보다 복잡하며 몇 가지 주요 단계를 포함합니다:
- 소스 파일 포함: 필요한 SystemView 파일을 프로젝트에 추가합니다.
- FreeRTOS 설정: SystemView는 커널의 주요 지점(예: 태스크 생성 또는 컨텍스트 전환 중)에 위치한 FreeRTOS 트레이스 매크로를 가로채는 방식으로 작동합니다.
FreeRTOSConfig.h파일에서 이 메커니즘을 활성화하려면 다음을 수행해야 합니다:
* #define configUSE_TRACE_FACILITY 1로 설정합니다.
* FreeRTOS V10+의 경우, #define INCLUDE_xTaskGetIdleTaskHandle 1을 추가합니다.
* FreeRTOSConfig.h 파일의 맨 끝에 SEGGER_SYSVIEW_FreeRTOS.h를 포함합니다.
트레이스 매크로 예시:
```c
#define traceTASK_SWITCHED_IN() SEGGER_SYSVIEW_OnTaskStartExec((U32)pxCurrentTCB)
#define traceTASK_SWITCHED_OUT() SEGGER_SYSVIEW_OnTaskStopExec()
#define traceISR_ENTER() SEGGER_SYSVIEW_RecordEnterISR()
// etc.
```
- 정확한 타이밍을 위한 DWT_CYCCNT 사용: Cortex-M3/M4 코어에는 32비트 사이클 카운터(CYCCNT)가 있는 내장 DWT(Data Watchpoint and Trace) 모듈이 포함되어 있습니다. SystemView는 이를 고정밀 시간 참조로 사용합니다.
SEGGER_SYSVIEW_Conf()함수에서 이 카운터를 활성화해야 합니다:
```c
void SEGGER_SYSVIEW_Conf(void) {
// ---- Setup DWT (for Cortex-M3/M4) ----
#define DEMCR ((volatile U32)0xE000EDFCu)
#define DWT_CTRL ((volatile U32)0xE0001000u)
#define DWT_CYCCNT ((volatile U32)0xE0001004u)
#define DEMCR_TRCENA (1u << 24)
#define DWT_CTRL_CYCCNTENA (1u << 0)
DEMCR |= DEMCR_TRCENA; // Enable access to TRCENA
DWT_CYCCNT = 0; // Clear counter
DWT_CTRL |= DWT_CTRL_CYCCNTENA; // Start DWT count
SEGGER_SYSVIEW_Init(SYSVIEW_TIMESTAMP_FREQ, SYSVIEW_CPU_FREQ,
&SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc);
SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE);
}
```
또한, SYSVIEW_RAM_BASE 매크로가 마이크로컨트롤러의 RAM 주소와 일치하는지 확인하십시오.
- 인터럽트 서비스 루틴(ISR) 처리: SystemView가 FreeRTOS 매크로를 통해 가로채는 태스크 전환과 달리, 인터럽트는 명시적인 호출이 필요합니다. 인터럽트 핸들러에
SEGGER_SYSVIEW_RecordEnterISR()및SEGGER_SYSVIEW_RecordExitISR()함수를 추가해야 합니다:
```c
void EINT15_10_IRQHandler()
{
SEGGER_SYSVIEW_RecordEnterISR();
if(EINT->IPEND & EINT_IPEND_11) {
// Your code
}
SEGGER_SYSVIEW_RecordExitISR();
}
```
- 인터럽트 이름 지정: SystemView에서 명확성을 위해
_cbSendSystemDesc()함수(SEGGER_SYSVIEW_Config_FreeRTOS.c에 있음)를 확장하여 인터럽트 이름을 도구에 알려야 합니다:
```c
static void _cbSendSystemDesc(void) {
SEGGER_SYSVIEW_SendSysDesc("N="SYSVIEW_APP_NAME",D="SYSVIEW_DEVICE_NAME",O=FreeRTOS");
SEGGER_SYSVIEW_SendSysDesc("I#15=SysTickIRQ");
SEGGER_SYSVIEW_SendSysDesc("I#46=ModbusIRQ");
//...
}
```
- 인터럽트 ID 및 타임스탬프 가져오기 함수: 올바른 SystemView 작동에 필요한 두 가지 함수를
main(또는 다른 편리한 위치)에 추가합니다:
```c
// A function to get the number of the current active interrupt (ISR)
U32 SEGGER_SYSVIEW_X_GetInterruptId(void) {
// In the Cortex-M core, the interrupt number is stored in the lower 9 bits of the IPSR register.
return (((volatile U32)(0xE000ED04u)) & 0x1FFu);
}
// In Cortex-M3/M4, we use the DWT_CYCCNT core clock counter register
U32 SEGGER_SYSVIEW_X_GetTimestamp(void) {
return ((volatile U32)(0xE0001004u));
}
```
- 최종 초기화:
main함수에서 하드웨어 및 주변 장치를 초기화한 후,SEGGER_SYSVIEW_Conf()를 호출하고, 이어서 태스크 스케줄러vTaskStartScheduler()를 시작합니다:
```c
InitPhy();
SEGGER_SYSVIEW_Conf(); // Segger SystemView Initialization
CreateDeviceTasks();
vTaskStartScheduler(); // Start the real time scheduler
```
성공적인 설정 후, SystemView 유틸리티는 시스템 동작에 대한 상세한 시각화를 제공합니다. SystemView의 가장 가치 있는 장점 중 하나는 타겟 마이크로컨트롤러가 디버그 모드가 아닐 때도 작동할 수 있다는 점입니다. 이를 통해 실제 작동 조건에서 모니터링이 가능하며, 진단이 필요할 때 J-Link를 연결하여 복잡하고 시간에 의존적인 오류를 식별하는 과정을 크게 단순화합니다.
통합 및 도구 선택: RTT Viewer vs. SystemView
SEGGER RTT Viewer와 SystemView는 상호 보완적인 도구로, 각각 임베디드 소프트웨어 개발의 특정 작업을 처리합니다. RTT Viewer는 텍스트 로그 및 디버그 정보 출력에 이상적이며, 기존 printf를 대체하면서 훨씬 높은 성능과 비침해성을 제공합니다. 반면 SystemView는 컨텍스트 전환, 태스크 실행, 인터럽트 처리와 같은 FreeRTOS 이벤트를 그래픽으로 표현하여 분석 수준을 한 단계 높여줍니다. 이는 성능 최적화 및 스케줄링 문제 식별에 매우 중요합니다.
SEGGER_RTT_printf와 SEGGER_SYSVIEW_Print(SystemView에서 텍스트 메시지에 사용되는 경우) 중 어떤 것을 선택할지는 상황에 따라 다릅니다. SEGGER_RTT_printf는 일반적인 로그에 사용되는 반면, SystemView 함수는 분석 타임라인에 통합되어야 하는 이벤트에 사용됩니다. SystemView는 트레이스 데이터 전송을 위해 자체 내부 RTT 채널을 사용하며, 이는 일반적으로 직접적인 사용자 상호 작용을 위한 것이 아니라는 점에 유의해야 합니다. SystemView는 RTOS 없이도 작동할 수 있지만, 많은 기능이 운영체제 훅(hook)에 의존하므로 기능이 심각하게 제한될 것입니다.
SystemView에서 타임스탬프를 위해 SysTick 대신 DWT_CYCCNT를 사용하는 데에는 여러 요인이 있습니다. DWT_CYCCNT는 가능한 최고의 정확도를 제공하며 RTOS에서 사용하는 시스템 타이머 설정과 독립적인 전용 하드웨어 프로세서 사이클 카운터입니다. 이는 SystemView의 타임스탬프가 가능한 한 신뢰할 수 있으며 운영체제 자체의 작동으로 인한 왜곡에 영향을 받지 않도록 보장합니다.
RTOS 트레이싱 및 분석을 위한 다른 도구들도 존재합니다. 예를 들어 Percepio의 Tracealyzer는 임베디드 시스템 동작을 시각화하고 분석하는 풍부한 기능을 제공합니다. 하지만 SEGGER RTT와 SystemView는 J-Link 생태계와의 깊은 통합과 높은 성능 덕분에 두각을 나타내며, Cortex-M 마이크로컨트롤러를 사용하는 개발자들에게 선호되는 솔루션입니다.
핵심 요약:
- SEGGER RTT는 RAM의 순환 버퍼를 통해 고속의 비침해적인 디버그 로그(
printf) 출력을 제공합니다. - SystemView는 RTT를 전송 수단으로 사용하여 FreeRTOS를 실시간으로 시각화하고 분석하는 강력한 도구입니다.
- 정확한 타임라인을 위해 SystemView는 하드웨어 DWT_CYCCNT 카운터(Cortex-M3/M4)에 의존합니다.
- 두 도구 모두 CPU를 멈추지 않고 시스템 모니터링을 가능하게 하여 복잡한 문제 진단을 크게 단순화합니다.
- SystemView는 필요에 따라 J-Link를 연결하여 디버그 모드가 아닌 상태에서도 시스템 동작 분석을 가능하게 합니다.
— Editorial Team
아직 댓글이 없습니다.