CubeIDE의 디버깅 기능을 사용하면 실시간으로 특정 변수의 값을 관찰할 수 있다. 하지만 이 방법은 코드 한줄한줄 단계별로 실행해야 하므로 변화되는 연속적인 값을 봐야 하는등 시간적 값의 변화 관찰에는 적합하지 않다.
이런 상황에서는 보통 UART를 이용해 값을 전송해서 확인하는 방법을 사용 할 수 있는데 이번장에서는 UART를 사용하지 않고 CubeIDE의 출력창에 printf를 사용해 문자열을 그대로 표시하는 방법을 설명하고자 한다.
1. 설정
SWV(Serial Wire Viewer)/ITM(Instrumentaion Trace Macrocell) 은 위에 기술한대로 printf등의 문자열 출력 함수로 CubeIDE의 콘솔화면에 문자를 표시하는 기능이다.
단, 이 기능은 ARM Cortex-M3/M4/M7 에서만 지원되므로 다른 MCU를 사용한다면 사용할 수 없다.
그리고 SWO핀의 상태를 확인해야 하는데 Nucleo보드를 사용하고 있다면 보드 매뉴얼 29p의 Solder bridges에 기술된 대로 SB110(SWO)의 상태를 먼저 확인해야 한다.
좌측에서 보는 바와 같이 Nucleo보드 뒷면의 위쪽 ST-Link부분에 위치해 있으므로 연결이 되어 있는지 먼저 확인해 보기 바란다.
디폴트 값은 ON이므로 별도 Customize를 하지 않았다면 그대로 사용하면 될 것이다.
다음으로 CubeIDE의 설정을 하나 해야 한다.
아래 그림과 같이 메뉴의 "Run-Debug Configuration" 을 실행해서 그림과 같이 Debugger탭의 Serial Wire Viewer(SWV)를 찾아 Enable란을 체크해 준다. 그리고 그 아래 Core Clock(MHz) 부분에 현재 설정된 HCLK 클럭 속도를 MHz단위로 적어주면 되는데 이 값은 그 다음 화면에서와 같이 CubeMX의 Clock Configuration화면에서 찾을 수 있다.(디폴트로 이 값이 설정되어 있을 것이다.)
2. 코드 작성
설정이 완료되었다면 출력을 위한 코딩을 해주어야 한다.
우선 Core/Src/syscall.c 파일을 열어보자.
이 파일에는 파일입출력, 표준 입출력등의 System Call 루틴들이 포함되어 있다. OS가 있는 시스템인 경우 OS가 System Call에 대한 처리를 해주겠지만 OS가 없을 경우 이와 같이 직접 구현된 System Call을 사용한다.
(참고로 malloc등 메모리에 관련된 System Call은 sysmem.c에 구현되어 있다.)
여기 정의된 함수중 __io_putchar() 함수를 재정의 해야 하는데 extern으로 선언되어 있고 실제 소스는 확인이 안된다. 하지만 이 함수의 경우 weak형태이므로 우리가 이 함수를 재정의하면 우리가 작성한 함수로 대체할 수 있다.
이 함수는 표준출력으로 문자를 보내는 기능을 하는데 printf()등의 함수내에서 이 함수를 호출하도록 되어 있기 때문에 이 함수를 바꿔주면 printf의 출력을 우리가 원하는 방향으로 제어가 가능하다.
따라서 main.c에 아래와 같이 함수를 재정의하자.
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
int __io_putchar(int ch)
{
// Write character to ITM ch.0
ITM_SendChar(ch);
return(ch);
}
/* USER CODE END 0 */
ITM_SendChar()를 통해 기본적인 출력을 CubeIDE의 SWV ITM Console창으로 보낸다는 코드이다.
그리고 메인 while()문 안에 다음과 같이 코딩한 뒤 F11키를 눌러 디버깅 모드로 실행을 해보자.
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
printf("TEST\r\n");
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
잠시뒤 main()의 HAL_Init()부분에서 실행이 멈춰있을 것이다.
이 상태에서 메뉴의 "Window-Show View-SWV-SWV ITM Data Console"을 선택해 창을 추가한다.
추가된 창을 표시한 뒤 오른쪽 상단의 첫번째 아이콘(트레이스 설정)을 클릭하면 아래와 같은 창이 뜨는데 여기서 0번 Console에 체크한 뒤 OK를 눌러 창을 닫으면 SWV ITM Data Console창에 "포트 0"탭이 하나 추가된다.
이 상태에서 오른쪽 위의 두번째 아이콘 녹화버튼(빨간색 동그라미)을 클릭한 뒤 F8키를 눌러 프로그램의 실행을 재개해보자.
그러면 아래와 같이 우리가 코딩한 문자열이 Console화면에 표시될 것이다.
만약에 표시가 되지 않는다면 녹화 버튼을 한번 더 눌러줘야 하는데 실행중에는 이 버튼이 활성화가 되지 않는다.
다음과 같이 소스의 printf에 커서를 위치시키고 Ctrl+Shift+B를 눌러 이 위치에 브레이크 포인트를 걸면 디버거는 바로 이 부분에서 실행을 일시중지할 것이다.
일시중지 상태에서는 다시 녹화버튼이 활성화 되므로 다시 버튼을 한번 누르고 printf에 걸린 브레이크 포인트는 다시 Ctrl+Shift+B를 눌러 해제한 뒤 F8을 눌러 실행을 재개하면 표시가 될 것이다.
3. SWV 데이터 트레이스 타임라인 그래프
특정 변수의 값이 변화하는 걸 그래프로 확인하는 방법도 있다.
먼저 코딩을 다음과 같이 하자.
/* USER CODE BEGIN 0 */
int __io_putchar(int ch)
{
// Write character to ITM ch.0
ITM_SendChar(ch);
return(ch);
}
int gnValue = 0; // 기존 코드에 전역변수 하나 추가
/* USER CODE END 0 */
int main(void)
{
...
int nLocal = 50; // main함수안에 로컬변수 추가
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
//printf("TEST\r\n");
if(gnValue++ > 100)
gnValue = 0;
if(nLocal-- <= 0)
nLocal = 50;
HAL_Delay(50);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
이렇게 코딩하고 F11을 눌러 디버그 모드로 실행을 하자. 마찬가지로 HAL_Init()에 일시정지가 되어 있는 상태에서 메뉴의 "Window-Show View-SWV-SWV 데이터 트레이스 타임라인 그래프"를 선택해 해당 화면을 추가한다.
그리고 이 화면의 오른쪽 상단의 "트레이스 설정" 아이콘을 클릭해 이전과 같이 설정창을 연다.
그리고 이 창에서 아래와 같이 gnValue, nLocal변수를 지정해주고 활성화 체크한 뒤 OK버튼을 누른다.
소스에서 F8을 눌러 실행을 재개하면 지정한 변수에 대해 아래와 같이 값이 그래프로 표시될 것이다. 만약 안나온다면 위에서와 같이 브레이크 포인트를 잡은 뒤 일시정지 상태에서 녹화버튼을 누르고 실행을 재개하면 된다.
4. C++ 프로젝트에서 SWV 사용하기
만약 프로젝트 생성시 Targeted Language를 C로 선언해서 생성했다면 위의 방법대로 하면 문제없이 실행이 된다. 하지만 C++로 선언했다면 위의 방식으로는 동작을 하지 않을 것이다.
정확한 원인은 알 수 없지만 일단 C++프로젝트에서 사용할 수 있는 방법만 알아보자.
일단 C++에서는 printf에서 __io_putchar()를 호출하지 않는듯 보인다. 이 함수를 재정의해도 이 함수가 호출되지 않는다.
따라서 디버깅용 printf()함수를 별도로 만들어 이 함수를 사용해서 SWV Console화면으로 출력을 보낼 수 있다.
아래와 같이 코딩하자.
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
/* USER CODE END Includes */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void vprint(const char *fmt, va_list argp)
{
char string[200] = {0,};
if(0 < vsprintf(string,fmt,argp))
{
int nLen = (int)strlen(string);
for(int i = 0; i < nLen; i++)
ITM_SendChar(string[i]);
}
}
void my_printf(const char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vprint(fmt, argp);
va_end(argp);
}
/* USER CODE END 0 */
int main(void)
{
...
while(1)
{
my_printf("TEST : %d\r\n", 10);
HAL_Delay(500);
}
}
이후 방법은 C와 동일하므로 테스트 해보자.
'임베디드 > STM32' 카테고리의 다른 글
13. [STM32] Interrupt-Register (0) | 2024.09.26 |
---|---|
12. [STM32] Interrupt 개요 (0) | 2024.09.26 |
10. [STM32] GPIO예제(feat. Open Drain) (0) | 2024.09.23 |
9. [STM32] Bit-banding (0) | 2024.09.10 |
8. [STM32] GPIO - Peripheral Register 2 (0) | 2024.08.13 |