

1. STM32 Nucelo-64 보드 구매
- 2026년을 맞아, 본격적으로 STM32 개발 공부를 시작하기 위해 보드를 구매하였다.
- STM32 MCU에 대해서는 작년도 부터 분석하였으므로 설명은 생략한다.
- 개발을 앞써 기본적인 STM32 개발환경과 용어에 대해서 이해하고 정리하고자 한다.
- Arduino IDE를 사용해도 가능하지만, 그렇게 되면 개발 공부의 의미가 없으로 정식 IDE 개발 툴인 STM32 Cube IDE를 사용한다.
2. STM32 소프트웨어 (펌웨어) 개발 관련 툴
① STM32 Cube MX




- STM32 계열의 MCU를 어떻게 쓸 것인가 정의하는 그래픽 UI 설정(Configuration) 툴
- MCU의 GPIO 핀 용도 설정 (IN, OUT, SPI, I2C, ADC, TIM 등)
- 미들웨어 설정 (FreeRTOS, USB, LWIP 등)
- 초기화 코드 (StartUp Code)를 자동으로 생성해준다.
② STM32 Cube IDE

- Eclipse 기반 통합 IDE
- Cube MX가 포함되어 있음
- C/C++ 코드 작성
- 빌드 & 컴파일
- 디버깅 (BreakPoint, Register, Memory, RTOS veiw)
- ST Link 연동
- STM32 전용 올인원 개발 IDE
③ STM32 Cube MCU Packages

STM32에서 공식적으로 제공하는 MCU 개발 소프트웨어 패키지이다.
여기서 중요한 것은 MCU 마다 제공되는 패키지가 다르다는 것이다.
(F1 계열, F4계열, G4계열 등......)
- Driver (HAL, LL)
- 미들웨어 (Free RTOS, USB Device / Host, TCP/IP, FATFS, TOUCH, GRAPHICS 등)
- 예제코드 포함
개발을 위한 최소한의 기본 제공 API 라이브러리 패키지라고 보면 될 듯하다.
3. STM32 개발 전용 용어
① ST-Link


- STM사에서 만든 공식 디버거/프로그래머 (하드웨어를 의미함)
- PC ↔ ST-Link ↔ STM32 구조로 연결되어 펌웨어 다운로드, 시리얼 등을 지원한다.
- SWD와 JTAG 인터페이스를 사용한다.
- ST-Link가 없으면 사실상 실무에서는 개발이 불가능하다.
- Nucelo-64 보드에 기본적으로 내장 탑재 되어 있어 위의 별도의 디버거, 프로그래머 없이 ST-Link를 사용할 수 있다.
- 당연하게도 공식 정품이 있는가 하면, 중국에서 만든 기성품들도 많이 있다...
② SWD (Serial Wire Debug)

- SWD 인터페이스 Arm Cortex-M 계열 MCU 개발 시에 많이 사용되는 인터페이스
- SWDIO, SWCLK 2개의 신호선으로 제공됨
- MCU 내부에 디버그 전용 하드웨어 블록이 있다. (DP, AP)
- 타이밍 기반의 통신 프로토콜이며 수 MHz ~ 수십 MHz 클럭을 사용한다. (동기식 통신)
- JTAG보다 더 적은 신호선을 사용하기 때문에 더 많이 쓰인다.
3. STM32 펌웨어 빌드해보기
① STM32 Cube IDE 설치
https://www.st.com/en/development-tools/stm32cubeide.html
STM32CubeIDE | Software - STMicroelectronics
STM32CubeIDE is an all-in-one multi-OS development tool, which is part of the STM32Cube software ecosystem.
www.st.com
- 로그인 이후 설치 가능함
- 계정 만들시 비밀번호 규약이 조금 까다로우니 참고.
② 터미널 통신 프로그램 (Tera Term) 설치


https://github.com/TeraTermProject/teraterm/releases
Releases · TeraTermProject/teraterm
Contribute to TeraTermProject/teraterm development by creating an account on GitHub.
github.com
- STM32 Cube IDE 개발환경에서는 Arduino IDE 개발환경 처럼 시리얼 터미널을 지원하지 않는다.
- 그렇기 때문에 별도의 시리얼 통신(UART) 터미널을 통해 MCU 데이터 로그를 확인해야하는데, 가장 많이 사용하는 터미널 프로그램 중 대표적으로 Tera Term이 있다.
- 시리얼 디버깅이 왜 중요한지는 생략하겠다.
- ST-Link (가상 COM Port) 뿐만 아니라 물리적 인터페이스만 있다면 RS232, RS485 등 다양한 통신도 지원하니 참고.
③ Project 생성

- 익숙치 않은 이클립스 기반 환경.... 뭔가 Arduino IDE 보다 무거운 느낌이 든다. (실행이 더 오래걸림)
- STM32 Project 생성 후 보드를 선택한다. (Nucelo - 64)


- 이클립스 환경은 WrokSpace 용어에 익숙해질 필요가 있다. 말 그대로 작업공간이며 프로젝트 폴더 경로를 의미한다.
- 지금은 완전 첫걸음이지만, 나중에 MCU 프로젝트가 많아지면 WrokSpace 관리에 유의해야한다.
- Targeted Language는 프로그래밍 언어를 C 기반으로 할지, C++ 기반으로 할지 설정하는 기능이다.
- 지금은 C++ 사용하는 것은 의미가 없어보인다. 근본 중의 근본 C로 일단 선택한다.
- Targeted Binary Type은 결과물의 형태를 의미하는데 MCU에서 실행하는 경우 Execuable을 사용한다.
- Static Library는 라이브러리 개발자가 사용하는 기능인데 지금은 잘 모르니 디폴트 Execuable을 체크한다.
- 마지막으로 Targeted Project Type 인데 지금은 당연히 STM32 Cube 기반이다.
- Empty는 아무것도 존재하지 않은 상태로 펌웨어 개발을 시작하는 형태이다. 효율성으로 따지면 잘 사용하지 않는다. 물론 HAL 부터 코드를 작성해서 자신만의 최적화를 원하는
(변태)개발자도 간혹 있긴하다. - Target Reference는 지금 선택한 타켓 MCU나 보드가 뭔지 표시될 뿐이고, Firmware Package Name and Version의 경우 해당 MCU의 공식 패키지 버전을 의미한다. 보통은 최신 안정 버전 그대로 사용한다.
- Repository Location은 패키지를 저장하는 로컬 경로이다. IDE가 알아서 필요한 패키지를 찾아서 쓴다.
- Code Generator Options에서 Add necessary library files as reference…는 라이브러리를 프로젝트 폴더에 복사하지 않고 Repository에 있는 파일을 참조만 하는 형태이다. 이렇게 되면 라이브러리를 일일이 내부 프로젝트 폴더에 포함 시키지 않아도 된다. (프로젝트 폴더가 가벼워 진다.)
- Copy all used libraries into the project folder는 관련 라이브러리를 모두 복사해서 현재 프로젝트 폴더에 포함시켜버린다. (폴더 용량이 커진다...)
- Copy only the necessary library files 기능은 진짜 필요한 것만 프로젝트 폴더로 복사해서 사용한다. 초보자는 가장 무난한 선택이니 해당 기능을 체크한다.


- 보드의 내부 LED와 버튼을 사용할 것인지 묻는다. OK 체크한다.
- USB 또한 시리얼 통신 (터미널 프로그램)을 사용할 것이다.

- 라이센스 동의를 묻는다. 동의하면 된다.
- 라이센스는 대충 "비상업적/상업적 둘다 사용 가능하지만 STM사 책임 없음" 내용이다.
④ MCU 설정 (Configuration) : Cube MX

- 드디어 Cube MX 화면이다. 이 화면만은 STM32 개발만의 시그니처 예술 이라고 본다.ㅎㅎ
- 해당 MCU의 노란색 핀들은 SWD 인터페이스로 사용되는 핀이니 절대 건들지 않기로 하자

- 디지털 출력으로 사용할 핀을 2개정도 설정해보자. (PB5, PA10)
⑤ 프로젝트 회로 조립 (디지털 출력 LED 2개)


- 나에게 시간이 많았다면, 데이터 시트에서 IO 입출력 임피던스까지 고려하였겠지만 시간이 없으므로 (벌써 자정을 넘김..) 저항기는 가장 무난한 200옴으로 선정하였다.
⑥ 프로젝트 펌웨어 개발 (코드 작성)

- 우선 코드 작성에 앞써 프로젝트 구조에 대해서 분석해보자
- core/ 경로는 소스 파일이 저장된다. main.c 에는 While 과 User Code 영역이 있고 gpio.c에는 CubeMX가 만든 GPIO 초기화 소스 코드가 있다.
- 참고로 ioc는 Arduino의 ino와 같은 프로젝트 내용이 저장되는 확장자명이다. (Cube MX 설계도 파일이 같이 저장)
- 전체적인 프로젝트 폴더 구조는 다음과 같다.
Hello_STM32/
├─ Core/
│ ├─ Inc/
│ │ ├─ main.h
│ │ ├─ gpio.h
│ │ └─ stm32c0xx_it.h
│ └─ Src/
│ ├─ main.c
│ ├─ gpio.c
│ └─ stm32c0xx_it.c
│
├─ Drivers/
│ ├─ CMSIS/
│ └─ STM32C0xx_HAL_Driver/
│
├─ Middlewares/ (있을 수도, 없을 수도)
│
├─ Startup/
│ └─ startup_stm32c031c6tx.s
│
├─ Hello_STM32.ioc
└─ STM32C031C6Tx_FLASH.ld
- Core 경로에서 내가 짠 코드가 돌아가는 구조이니 우선 main.c 를 작성한다.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
printf("=== STM32 START ===\r\n");
printf("PB5 / PA10 LED Test Begin\r\n");
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET);
printf("PB5 ON, PA10 OFF\r\n");
HAL_Delay(1000);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET);
printf("PBB OFF, PA10 ON\r\n");
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LD2_Pin|GPIO_PIN_10, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : LD2_Pin PA10 */
GPIO_InitStruct.Pin = LD2_Pin|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

- 해당 아이콘 버튼을 눌러 프로젝트 펌웨어를 MCU에 빌드한다.
STMicroelectronics ST-LINK GDB server. Version 7.10.0
Copyright (c) 2025, STMicroelectronics. All rights reserved.
Starting server with the following options:
Persistent Mode : Disabled
Logging Level : 1
Listen Port Number : 61234
Status Refresh Delay : 15s
Verbose Mode : Disabled
SWD Debug : Enabled
InitWhile : Enabled
Waiting for debugger connection...
Debugger connected
Waiting for debugger connection...
Debugger connected
Waiting for debugger connection...
-------------------------------------------------------------------
STM32CubeProgrammer v2.19.0
-------------------------------------------------------------------
Log output file: C:\Users\HJW\AppData\Local\Temp\STM32CubeProgrammer_a18696.log
ST-LINK SN : 066CFF525650657287241117
ST-LINK FW : V2J46M31
Board : NUCLEO-F103RB
Voltage : 3.25V
SWD freq : 4000 KHz
Connect mode: Under Reset
Reset mode : Hardware reset
Device ID : 0x410
Revision ID : Rev X
Device name : STM32F101/F102/F103 Medium-density
Flash size : 128 KBytes
Device type : MCU
Device CPU : Cortex-M3
BL Version : --
Opening and parsing file: ST-LINK_GDB_server_a18696.srec
Memory Programming ...
File : ST-LINK_GDB_server_a18696.srec
Size : 9.00 KB
Address : 0x08000000
Erasing memory corresponding to sector 0:
Erasing internal memory sectors [0 8]
Download in Progress:
File download complete
Time elapsed during download operation: 00:00:00.638
Verifying ...
Download verified successfully
Shutting down...
Exit.
- 보드가 ST Link를 통해 자동 인식 되었으며, 보드 LED가 깜빡이며 펌웨어가 플래시된다.
- 참고로 해당 코드는 1초간견으로 PB5와 PA10이 번갈아 Output 되며, Serial 포트를 통해 터미널에 로그를 남기는 코드이다.
⑦ 결과 확인




https://www.youtube.com/shorts/2i8IY9dAwUg
- 상당히 낯선 이클립스 기반의 개발환경이어서 다소 서툴지만, 결국 펌웨어를 MCU에 빌드하는데 성공하였다.
- 다음에 시간이 될때 보드와 MCU의 데이터시트를 좀 더 살펴보아야 겠다.
'Project > STM32' 카테고리의 다른 글
| STM32 NUCLEO-64 디지털 I/O 구현 (0) | 2026.01.08 |
|---|