본문 바로가기
MCU/STM32

STM32CubeMX에서 아두이노 스타일 사용자 코드 구조 만들기

by linuxgo 2025. 9. 14.

STM32CubeMX는 STM32 마이크로컨트롤러용 프로젝트를 쉽게 생성할 수 있는 강력한 도구입니다.
하지만 기본적으로 CubeMX가 생성하는 코드는 main.c 안에 모든 초기화 코드와 주변장치 핸들러가 몰려 있어 프로젝트가 커질수록 관리가 어려워집니다.

아두이노처럼 setup()loop() 구조를 적용하면, 사용자 코드를 CubeMX 자동 생성 코드와 분리하여 깔끔하게 관리할 수 있습니다.

이번 글에서는 CubeMX에서 아두이노 스타일 코드 구조를 만들고, 전역 변수와 USART 핸들러를 안전하게 사용하는 방법까지 소개합니다.

1. 기본 CubeMX 구조

CubeMX로 USART 하나를 활성화하고 프로젝트를 생성하면, main.c는 대략 다음과 같이 구성됩니다:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  while (1)
  {
    // 사용자 코드 작성
  }
}

사용자 코드를 while(1) 안에 직접 작성하면, CubeMX가 다시 코드를 생성할 때 기존 코드와 충돌이 발생할 수 있습니다.
이를 해결하기 위해, 사용자 전용 파일을 만들어 setup()loop() 함수를 정의하면, main.c는 초기화와 함수 호출만 담당하게 만들 수 있습니다.

2. 아두이노 스타일 코드 구조

프로젝트 구조 예시

Core/
 ├── Inc/
 │    └── user_app.h    ← 사용자 헤더 (전역 변수 extern 선언)
 ├── Src/
 │    ├── user_app.c    ← 사용자 코드 (전역 변수 정의, setup()/loop() 함수)
 │    └── main.c        ← CubeMX 자동 생성 (setup()/loop() 호출만)

user_app.h

#ifndef __USER_APP_H
#define __USER_APP_H

#include "main.h"

// 사용자 전역 변수 extern 선언
extern int counter;
extern float sensor_value;

// 사용자 함수 선언
void setup();
void loop();

#endif

user_app.c

#include "user_app.h"

// 사용자 전역 변수 정의
int counter = 0;
float sensor_value = 0.0f;

void setup() {
    counter = 0;
    sensor_value = 0.0f;

    char msg[] = "System Initialized!\r\n";
    HAL_UART_Transmit(&huart1, (uint8_t*)msg, sizeof(msg)-1, HAL_MAX_DELAY);
}

void loop() {
    counter++;
    sensor_value = counter * 0.1f;

    char buf[50];
    int len = sprintf(buf, "Counter=%d, Sensor=%.2f\r\n", counter, sensor_value);
    HAL_UART_Transmit(&huart1, (uint8_t*)buf, len, HAL_MAX_DELAY);

    HAL_Delay(1000); // 1초마다 반복
}

main.c

#include "user_app.h"

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  setup(); // setup 호출

  while (1)
  {
    loop(); // loop 호출
  }
}

장점

  • main.c는 CubeMX가 다시 생성해도 안전
  • 사용자 코드는 user_app.c/h 안에서만 관리
  • Arduino 스타일 setup() / loop() 패턴 적용 가능

3. 전역 변수 관리 (extern)

여러 C 파일에서 같은 변수를 사용하려면 extern 키워드를 활용합니다.

  • 정의(Definition): 실제 메모리 공간 생성 → .c 파일에서 한 번만 선언
  • 선언(Declaration): 다른 파일에서 참조할 수 있도록 이름만 알림 → 헤더(.h)에서 extern 사용
// user_app.c
int counter = 0;    // 정의

// user_app.h
extern int counter; // 선언

// main.c
#include "user_app.h"
if (counter % 10 == 0) {
    // counter 사용 가능
}

 

4. USART 핸들러(huart1) 사용법

USART 핸들러는 CubeMX 옵션에 따라 정의 위치가 달라집니다.

4.1 Default Mode

  • main.c → UART_HandleTypeDef huart1; 정의
  • main.h → extern UART_HandleTypeDef huart1; 선언
  • 사용자 코드 → #include "main.h"

4.2 Separate Files Mode

  • usart.c → UART_HandleTypeDef huart1; 정의
  • usart.h → extern UART_HandleTypeDef huart1; 선언
  • 사용자 코드 → #include "usart.h"
CubeMX 옵션 정의 위치 extern 선언 위치 사용자 코드에서 include
Default Mode main.c main.h #include "main.h"
Separate Files Mode usart.c usart.h #include "usart.h"

5. CubeMX 아두이노 스타일 사용 장점

  • 코드 관리가 쉽고, main.c를 건드리지 않아도 됨
  • 전역 변수, UART, GPIO 등 사용자 자원을 한 곳에서 관리 가능
  • Arduino 경험이 있는 사용자에게 친숙한 개발 패턴 적용 가능
  • CubeMX 자동 생성 코드와 충돌 최소화